aspera-cli 4.7.0 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -0
  3. data/README.md +844 -861
  4. data/bin/ascli +20 -1
  5. data/bin/asession +37 -34
  6. data/docs/test_env.conf +11 -3
  7. data/examples/aoc.rb +13 -12
  8. data/examples/dascli +26 -0
  9. data/examples/faspex4.rb +34 -29
  10. data/examples/transfer.rb +30 -29
  11. data/lib/aspera/aoc.rb +151 -143
  12. data/lib/aspera/ascmd.rb +56 -45
  13. data/lib/aspera/ats_api.rb +6 -5
  14. data/lib/aspera/cli/basic_auth_plugin.rb +18 -16
  15. data/lib/aspera/cli/extended_value.rb +32 -30
  16. data/lib/aspera/cli/formater.rb +103 -111
  17. data/lib/aspera/cli/info.rb +2 -1
  18. data/lib/aspera/cli/listener/line_dump.rb +1 -0
  19. data/lib/aspera/cli/listener/logger.rb +1 -0
  20. data/lib/aspera/cli/listener/progress.rb +13 -12
  21. data/lib/aspera/cli/listener/progress_multi.rb +21 -20
  22. data/lib/aspera/cli/main.rb +106 -89
  23. data/lib/aspera/cli/manager.rb +96 -85
  24. data/lib/aspera/cli/plugin.rb +50 -32
  25. data/lib/aspera/cli/plugins/alee.rb +6 -5
  26. data/lib/aspera/cli/plugins/aoc.rb +521 -426
  27. data/lib/aspera/cli/plugins/ats.rb +84 -83
  28. data/lib/aspera/cli/plugins/bss.rb +30 -27
  29. data/lib/aspera/cli/plugins/config.rb +483 -397
  30. data/lib/aspera/cli/plugins/console.rb +17 -15
  31. data/lib/aspera/cli/plugins/cos.rb +26 -35
  32. data/lib/aspera/cli/plugins/faspex.rb +201 -168
  33. data/lib/aspera/cli/plugins/faspex5.rb +109 -74
  34. data/lib/aspera/cli/plugins/node.rb +378 -189
  35. data/lib/aspera/cli/plugins/orchestrator.rb +71 -65
  36. data/lib/aspera/cli/plugins/preview.rb +131 -122
  37. data/lib/aspera/cli/plugins/server.rb +94 -93
  38. data/lib/aspera/cli/plugins/shares.rb +42 -28
  39. data/lib/aspera/cli/plugins/sync.rb +15 -14
  40. data/lib/aspera/cli/transfer_agent.rb +56 -52
  41. data/lib/aspera/cli/version.rb +2 -1
  42. data/lib/aspera/colors.rb +29 -28
  43. data/lib/aspera/command_line_builder.rb +50 -43
  44. data/lib/aspera/cos_node.rb +64 -38
  45. data/lib/aspera/data_repository.rb +1 -0
  46. data/lib/aspera/environment.rb +18 -8
  47. data/lib/aspera/fasp/agent_base.rb +26 -23
  48. data/lib/aspera/fasp/agent_connect.rb +35 -30
  49. data/lib/aspera/fasp/agent_direct.rb +68 -60
  50. data/lib/aspera/fasp/agent_httpgw.rb +71 -64
  51. data/lib/aspera/fasp/agent_node.rb +24 -23
  52. data/lib/aspera/fasp/agent_trsdk.rb +19 -20
  53. data/lib/aspera/fasp/error.rb +2 -1
  54. data/lib/aspera/fasp/error_info.rb +79 -68
  55. data/lib/aspera/fasp/installation.rb +122 -114
  56. data/lib/aspera/fasp/listener.rb +1 -0
  57. data/lib/aspera/fasp/parameters.rb +44 -41
  58. data/lib/aspera/fasp/resume_policy.rb +14 -11
  59. data/lib/aspera/fasp/transfer_spec.rb +6 -5
  60. data/lib/aspera/fasp/uri.rb +25 -24
  61. data/lib/aspera/faspex_gw.rb +83 -72
  62. data/lib/aspera/hash_ext.rb +10 -12
  63. data/lib/aspera/id_generator.rb +8 -7
  64. data/lib/aspera/keychain/encrypted_hash.rb +60 -45
  65. data/lib/aspera/keychain/macos_security.rb +26 -24
  66. data/lib/aspera/log.rb +34 -38
  67. data/lib/aspera/nagios.rb +14 -13
  68. data/lib/aspera/node.rb +19 -19
  69. data/lib/aspera/oauth.rb +121 -101
  70. data/lib/aspera/open_application.rb +6 -5
  71. data/lib/aspera/persistency_action_once.rb +9 -8
  72. data/lib/aspera/persistency_folder.rb +10 -9
  73. data/lib/aspera/preview/file_types.rb +261 -266
  74. data/lib/aspera/preview/generator.rb +74 -73
  75. data/lib/aspera/preview/image_error.png +0 -0
  76. data/lib/aspera/preview/options.rb +7 -6
  77. data/lib/aspera/preview/utils.rb +30 -33
  78. data/lib/aspera/preview/video_error.png +0 -0
  79. data/lib/aspera/proxy_auto_config.rb +25 -23
  80. data/lib/aspera/rest.rb +73 -74
  81. data/lib/aspera/rest_call_error.rb +1 -0
  82. data/lib/aspera/rest_error_analyzer.rb +11 -9
  83. data/lib/aspera/rest_errors_aspera.rb +5 -4
  84. data/lib/aspera/secret_hider.rb +68 -0
  85. data/lib/aspera/ssh.rb +12 -10
  86. data/lib/aspera/sync.rb +49 -47
  87. data/lib/aspera/temp_file_manager.rb +7 -5
  88. data/lib/aspera/timer_limiter.rb +9 -8
  89. data/lib/aspera/uri_reader.rb +11 -14
  90. data/lib/aspera/web_auth.rb +17 -15
  91. data.tar.gz.sig +0 -0
  92. metadata +117 -34
  93. metadata.gz.sig +2 -0
  94. data/bin/dascli +0 -13
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'singleton'
3
4
  require 'aspera/log'
4
5
 
@@ -7,22 +8,22 @@ module Aspera
7
8
  # implements a simple resume policy
8
9
  class ResumePolicy
9
10
  # list of supported parameters and default values
10
- DEFAULTS={
11
- iter_max: 7,
12
- sleep_initial: 2,
13
- sleep_factor: 2,
14
- sleep_max: 60
15
- }
11
+ DEFAULTS = {
12
+ iter_max: 7,
13
+ sleep_initial: 2,
14
+ sleep_factor: 2,
15
+ sleep_max: 60
16
+ }.freeze
16
17
 
17
18
  # @param params see DEFAULTS
18
19
  def initialize(params=nil)
19
- @parameters=DEFAULTS.clone
20
+ @parameters = DEFAULTS.dup
20
21
  if !params.nil?
21
22
  raise "expecting Hash (or nil), but have #{params.class}" unless params.is_a?(Hash)
22
23
  params.each do |k,v|
23
24
  raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map(&:to_s).join(',')}" unless DEFAULTS.has_key?(k)
24
25
  raise "#{k} must be Integer" unless v.is_a?(Integer)
25
- @parameters[k]=v
26
+ @parameters[k] = v
26
27
  end
27
28
  end
28
29
  Log.log.debug("resume params=#{@parameters}")
@@ -30,7 +31,8 @@ module Aspera
30
31
 
31
32
  # calls block a number of times (resumes) until success or limit reached
32
33
  # this is re-entrant, one resumer can handle multiple transfers in //
33
- def process(&block)
34
+ def execute_with_resume
35
+ raise 'block manndatory' unless block_given?
34
36
  # maximum of retry
35
37
  remaining_resumes = @parameters[:iter_max]
36
38
  sleep_seconds = @parameters[:sleep_initial]
@@ -39,7 +41,8 @@ module Aspera
39
41
  loop do
40
42
  Log.log.debug('transfer starting');
41
43
  begin
42
- block.call
44
+ # call provided block
45
+ yield
43
46
  break
44
47
  rescue Fasp::Error => e
45
48
  Log.log.warn("An error occured: #{e.message}");
@@ -57,7 +60,7 @@ module Aspera
57
60
  end
58
61
 
59
62
  # take this retry in account
60
- remaining_resumes-=1
63
+ remaining_resumes -= 1
61
64
  Log.log.warn("resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})");
62
65
 
63
66
  # wait a bit before retrying, maybe network condition will be better
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'aspera/fasp/parameters'
3
4
 
4
5
  module Aspera
@@ -6,14 +7,14 @@ module Aspera
6
7
  # parameters for Transfer Spec
7
8
  class TransferSpec
8
9
  # default transfer username for access key based transfers
9
- ACCESS_KEY_TRANSFER_USER='xfer'
10
- SSH_PORT=33_001
11
- UDP_PORT=33_001
12
- AK_TSPEC_BASE={
10
+ ACCESS_KEY_TRANSFER_USER = 'xfer'
11
+ SSH_PORT = 33_001
12
+ UDP_PORT = 33_001
13
+ AK_TSPEC_BASE = {
13
14
  'remote_user' => ACCESS_KEY_TRANSFER_USER,
14
15
  'ssh_port' => SSH_PORT,
15
16
  'fasp_port' => UDP_PORT
16
- }
17
+ }.freeze
17
18
  # define constants for enums of parameters: <paramater>_<enum>, e.g. CIPHER_AES_128
18
19
  Aspera::Fasp::Parameters.description.each do |k,v|
19
20
  next unless v[:enum].is_a?(Array)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'aspera/log'
3
4
  require 'aspera/command_line_builder'
4
5
 
@@ -7,38 +8,38 @@ module Aspera
7
8
  # translates a "faspe:" URI (used in Faspex) into transfer spec hash
8
9
  class Uri
9
10
  def initialize(fasplink)
10
- @fasp_uri=URI.parse(fasplink.gsub(' ','%20'))
11
+ @fasp_uri = URI.parse(fasplink.gsub(' ','%20'))
11
12
  # TODO: check scheme is faspe
12
13
  end
13
14
 
14
15
  def transfer_spec
15
- result_ts={}
16
- result_ts['remote_host']=@fasp_uri.host
17
- result_ts['remote_user']=@fasp_uri.user
18
- result_ts['ssh_port']=@fasp_uri.port
19
- result_ts['paths']=[{'source'=>URI.decode_www_form_component(@fasp_uri.path)}]
16
+ result_ts = {}
17
+ result_ts['remote_host'] = @fasp_uri.host
18
+ result_ts['remote_user'] = @fasp_uri.user
19
+ result_ts['ssh_port'] = @fasp_uri.port
20
+ result_ts['paths'] = [{'source' => URI.decode_www_form_component(@fasp_uri.path)}]
20
21
  # faspex does not encode trailing base64 encoded tags, fix that
21
- fixed_query = @fasp_uri.query.gsub(/(=+)$/){|x|'%3D'*x.length}
22
+ fixed_query = @fasp_uri.query.gsub(/(=+)$/){|x|'%3D' * x.length}
22
23
 
23
24
  URI.decode_www_form(fixed_query).each do |i|
24
- name=i[0]
25
- value=i[1]
25
+ name = i[0]
26
+ value = i[1]
26
27
  case name
27
- when 'cookie' then result_ts['cookie']=value
28
- when 'token' then result_ts['token']=value
29
- when 'sshfp' then result_ts['sshfp']=value
30
- when 'policy' then result_ts['rate_policy']=value
31
- when 'httpport' then result_ts['http_fallback_port']=value.to_i
32
- when 'targetrate' then result_ts['target_rate_kbps']=value.to_i
33
- when 'minrate' then result_ts['min_rate_kbps']=value.to_i
34
- when 'port' then result_ts['fasp_port']=value.to_i
35
- when 'bwcap' then result_ts['target_rate_cap_kbps']=value.to_i
36
- when 'enc' then result_ts['cipher']=value.gsub(/^aes/,'aes-').gsub(/cfb$/,'-cfb').gsub(/gcm$/,'-gcm').gsub(/--/,'-')
37
- when 'tags64' then result_ts['tags']=JSON.parse(Base64.strict_decode64(value))
38
- when 'createpath' then result_ts['create_dir']=CommandLineBuilder.yes_to_true(value)
39
- when 'fallback' then result_ts['http_fallback']=CommandLineBuilder.yes_to_true(value)
40
- when 'lockpolicy' then result_ts['lock_rate_policy']=CommandLineBuilder.yes_to_true(value)
41
- when 'lockminrate' then result_ts['lock_min_rate']=CommandLineBuilder.yes_to_true(value)
28
+ when 'cookie' then result_ts['cookie'] = value
29
+ when 'token' then result_ts['token'] = value
30
+ when 'sshfp' then result_ts['sshfp'] = value
31
+ when 'policy' then result_ts['rate_policy'] = value
32
+ when 'httpport' then result_ts['http_fallback_port'] = value.to_i
33
+ when 'targetrate' then result_ts['target_rate_kbps'] = value.to_i
34
+ when 'minrate' then result_ts['min_rate_kbps'] = value.to_i
35
+ when 'port' then result_ts['fasp_port'] = value.to_i
36
+ when 'bwcap' then result_ts['target_rate_cap_kbps'] = value.to_i
37
+ when 'enc' then result_ts['cipher'] = value.gsub(/^aes/,'aes-').gsub(/cfb$/,'-cfb').gsub(/gcm$/,'-gcm').gsub(/--/,'-')
38
+ when 'tags64' then result_ts['tags'] = JSON.parse(Base64.strict_decode64(value))
39
+ when 'createpath' then result_ts['create_dir'] = CommandLineBuilder.yes_to_true(value)
40
+ when 'fallback' then result_ts['http_fallback'] = CommandLineBuilder.yes_to_true(value)
41
+ when 'lockpolicy' then result_ts['lock_rate_policy'] = CommandLineBuilder.yes_to_true(value)
42
+ when 'lockminrate' then result_ts['lock_min_rate'] = CommandLineBuilder.yes_to_true(value)
42
43
  when 'auth' then Log.log.debug("ignoring auth #{name}=#{value}") # TODO: translate into transfer spec ? yes/no
43
44
  when 'v' then Log.log.debug("ignoring v #{name}=#{value}") # TODO: translate into transfer spec ? 2
44
45
  when 'protect' then Log.log.debug("ignoring protect #{name}=#{value}") # TODO: translate into transfer spec ?
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'aspera/log'
3
4
  require 'aspera/aoc'
4
5
  require 'aspera/fasp/transfer_spec'
@@ -13,14 +14,14 @@ module Aspera
13
14
  # this class answers the Faspex /send API and creates a package on Aspera on Cloud
14
15
  class FaspexGW
15
16
  class FxGwServlet < WEBrick::HTTPServlet::AbstractServlet
16
- def initialize(_server,a_aoc_api_user,a_workspace_id)
17
+ def initialize(_server, a_aoc_api_user, a_workspace_id)
17
18
  super
18
- @aoc_api_user=a_aoc_api_user
19
- @aoc_workspace_id=a_workspace_id
19
+ @aoc_api_user = a_aoc_api_user
20
+ @aoc_workspace_id = a_workspace_id
20
21
  end
21
22
 
22
23
  # parameters from user to Faspex API call
23
- #{"delivery":{"use_encryption_at_rest":false,"note":"note","sources":[{"paths":["file1"]}],"title":"my title","recipients":["email1"],"send_upload_result":true}}
24
+ # {"delivery":{"use_encryption_at_rest":false,"note":"note","sources":[{"paths":["file1"]}],"title":"my title","recipients":["email1"],"send_upload_result":true}}
24
25
  # {
25
26
  # "delivery"=>{
26
27
  # "use_encryption_at_rest"=>false,
@@ -33,127 +34,137 @@ module Aspera
33
34
  # }
34
35
  def process_faspex_send(request, response)
35
36
  raise 'no payload' if request.body.nil?
36
- faspex_pkg_parameters=JSON.parse(request.body)
37
- faspex_pkg_delivery=faspex_pkg_parameters['delivery']
37
+
38
+ faspex_pkg_parameters = JSON.parse(request.body)
39
+ faspex_pkg_delivery = faspex_pkg_parameters['delivery']
38
40
  Log.log.debug("faspex pkg create parameters=#{faspex_pkg_parameters}")
39
41
 
40
42
  # get recipient ids
41
- files_pkg_recipients=[]
43
+ files_pkg_recipients = []
42
44
  faspex_pkg_delivery['recipients'].each do |recipient_email|
43
- user_lookup=@aoc_api_user.read('contacts',{'current_workspace_id'=>@aoc_workspace_id,'q'=>recipient_email})[:data]
44
- raise StandardError,"no such unique user: #{recipient_email} / #{user_lookup}" unless !user_lookup.nil? && user_lookup.length.eql?(1)
45
- recipient_user_info=user_lookup.first
46
- files_pkg_recipients.push({'id'=>recipient_user_info['source_id'],'type'=>recipient_user_info['source_type']})
45
+ user_lookup = @aoc_api_user.read('contacts',
46
+ { 'current_workspace_id' => @aoc_workspace_id, 'q' => recipient_email })[:data]
47
+ raise StandardError,
48
+ "no such unique user: #{recipient_email} / #{user_lookup}" unless !user_lookup.nil? && user_lookup.length.eql?(1)
49
+
50
+ recipient_user_info = user_lookup.first
51
+ files_pkg_recipients.push({
52
+ 'id' => recipient_user_info['source_id'],
53
+ 'type' => recipient_user_info['source_type']
54
+ })
47
55
  end
48
56
 
49
57
  # create a new package with one file
50
- the_package=@aoc_api_user.create('packages',{
51
- 'file_names' =>faspex_pkg_delivery['sources'][0]['paths'],
52
- 'name' =>faspex_pkg_delivery['title'],
53
- 'note' =>faspex_pkg_delivery['note'],
54
- 'recipients' =>files_pkg_recipients,
55
- 'workspace_id'=>@aoc_workspace_id})[:data]
58
+ the_package = @aoc_api_user.create('packages', {
59
+ 'file_names' => faspex_pkg_delivery['sources'][0]['paths'],
60
+ 'name' => faspex_pkg_delivery['title'],
61
+ 'note' => faspex_pkg_delivery['note'],
62
+ 'recipients' => files_pkg_recipients,
63
+ 'workspace_id' => @aoc_workspace_id
64
+ })[:data]
56
65
 
57
66
  # get node information for the node on which package must be created
58
- node_info=@aoc_api_user.read("nodes/#{the_package['node_id']}")[:data]
67
+ node_info = @aoc_api_user.read("nodes/#{the_package['node_id']}")[:data]
59
68
 
60
69
  # get transfer token (for node)
61
- node_auth_bearer_token=@aoc_api_user.oauth_token(scope: AoC.node_scope(node_info['access_key'],AoC::SCOPE_NODE_USER))
70
+ node_auth_bearer_token = @aoc_api_user.oauth_token(scope: AoC.node_scope(node_info['access_key'],
71
+ AoC::SCOPE_NODE_USER))
62
72
 
63
73
  # tell Files what to expect in package: 1 transfer (can also be done after transfer)
64
- @aoc_api_user.update("packages/#{the_package['id']}",{'sent'=>true,'transfers_expected'=>1})
74
+ @aoc_api_user.update("packages/#{the_package['id']}", { 'sent' => true, 'transfers_expected' => 1 })
65
75
 
66
76
  # to return an error:
67
77
  # response.status=400
68
78
  # return 'ERROR HERE'
69
79
 
70
80
  # TODO: check about xfer_*
71
- ts_tags={
81
+ ts_tags = {
72
82
  'aspera' => {
73
- 'files' => { 'package_id' => the_package['id'], 'package_operation' => 'upload' },
74
- 'node' => { 'access_key' => node_info['access_key'], 'file_id' => the_package['contents_file_id'] },
75
- 'xfer_id' => SecureRandom.uuid,
76
- 'xfer_retry' => 3600 } }
83
+ 'files' => { 'package_id' => the_package['id'], 'package_operation' => 'upload' },
84
+ 'node' => { 'access_key' => node_info['access_key'], 'file_id' => the_package['contents_file_id'] },
85
+ 'xfer_id' => SecureRandom.uuid,
86
+ 'xfer_retry' => 3600
87
+ }
88
+ }
77
89
  # this transfer spec is for transfer to AoC
78
- faspex_transfer_spec={
79
- 'direction' => 'send',
80
- 'remote_host' => node_info['host'],
81
- 'remote_user' => Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER,
82
- 'ssh_port' => Fasp::TransferSpec::SSH_PORT,
83
- 'fasp_port' => Fasp::TransferSpec::UDP_PORT,
84
- 'tags' => ts_tags,
85
- 'token' => node_auth_bearer_token,
86
- 'paths' => [{'destination' => '/'}],
87
- 'cookie' => 'unused',
88
- 'create_dir' => true,
89
- 'rate_policy' => 'fair',
90
- 'rate_policy_allowed' => 'fixed',
91
- 'min_rate_cap_kbps' => nil,
92
- 'min_rate_kbps' => 0,
90
+ faspex_transfer_spec = {
91
+ 'direction' => 'send',
92
+ 'remote_host' => node_info['host'],
93
+ 'remote_user' => Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER,
94
+ 'ssh_port' => Fasp::TransferSpec::SSH_PORT,
95
+ 'fasp_port' => Fasp::TransferSpec::UDP_PORT,
96
+ 'tags' => ts_tags,
97
+ 'token' => node_auth_bearer_token,
98
+ 'paths' => [{ 'destination' => '/' }],
99
+ 'cookie' => 'unused',
100
+ 'create_dir' => true,
101
+ 'rate_policy' => 'fair',
102
+ 'rate_policy_allowed' => 'fixed',
103
+ 'min_rate_cap_kbps' => nil,
104
+ 'min_rate_kbps' => 0,
93
105
  'target_rate_percentage' => nil,
94
- 'lock_target_rate' => nil,
95
- 'fasp_url' => 'unused',
96
- 'lock_min_rate' => true,
97
- 'lock_rate_policy' => true,
98
- 'source_root' => '',
99
- 'content_protection' => nil,
100
- 'target_rate_cap_kbps' => 20_000, # TODO: is this value useful ?
101
- 'target_rate_kbps' => 10_000, # TODO: get from where?
102
- 'cipher' => 'aes-128',
103
- 'cipher_allowed' => nil,
104
- 'http_fallback' => false,
105
- 'http_fallback_port' => nil,
106
- 'https_fallback_port' => nil,
107
- 'destination_root' => '/'
106
+ 'lock_target_rate' => nil,
107
+ 'fasp_url' => 'unused',
108
+ 'lock_min_rate' => true,
109
+ 'lock_rate_policy' => true,
110
+ 'source_root' => '',
111
+ 'content_protection' => nil,
112
+ 'target_rate_cap_kbps' => 20_000, # TODO: is this value useful ?
113
+ 'target_rate_kbps' => 10_000, # TODO: get from where?
114
+ 'cipher' => 'aes-128',
115
+ 'cipher_allowed' => nil,
116
+ 'http_fallback' => false,
117
+ 'http_fallback_port' => nil,
118
+ 'https_fallback_port' => nil,
119
+ 'destination_root' => '/'
108
120
  }
109
121
  # but we place it in a Faspex package creation response
110
- faspex_package_create_result={
111
- 'links' => {'status' => 'unused'},
122
+ faspex_package_create_result = {
123
+ 'links' => { 'status' => 'unused' },
112
124
  'xfer_sessions' => [faspex_transfer_spec]
113
125
  }
114
126
  Log.log.info("faspex_package_create_result=#{faspex_package_create_result}")
115
- response.status=200
127
+ response.status = 200
116
128
  response.content_type = 'application/json'
117
- response.body=JSON.generate(faspex_package_create_result)
129
+ response.body = JSON.generate(faspex_package_create_result)
118
130
  end
119
131
 
120
- def do_GET(request, response) # rubocop:disable Naming/MethodName
132
+ def do_GET(request, response)
121
133
  case request.path
122
134
  when '/aspera/faspex/send'
123
135
  process_faspex_send(request, response)
124
136
  else
125
- response.status=400
126
- return 'ERROR HERE'
137
+ response.status = 400
138
+ 'ERROR HERE'
127
139
  end
128
140
  end
129
141
  end # FxGwServlet
130
142
 
131
143
  class NewUserServlet < WEBrick::HTTPServlet::AbstractServlet
132
- def do_GET(request, response) # rubocop:disable Naming/MethodName
144
+ def do_GET(request, response)
133
145
  case request.path
134
146
  when '/newuser'
135
- response.status=200
147
+ response.status = 200
136
148
  response.content_type = 'text/html'
137
- response.body='<html><body>hello world</body></html>'
149
+ response.body = '<html><body>hello world</body></html>'
138
150
  else
139
151
  raise "unsupported path: [#{request.path}]"
140
152
  end
141
153
  end
142
154
  end
143
155
 
144
- def initialize(a_aoc_api_user,a_workspace_id)
156
+ def initialize(a_aoc_api_user, a_workspace_id)
145
157
  webrick_options = {
146
- Port: 9443,
147
- Logger: Log.log,
148
- SSLEnable: true,
149
- SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
150
- SSLCertName: [['CN',WEBrick::Utils.getservername]]
158
+ Port: 9443,
159
+ Logger: Log.log,
160
+ SSLEnable: true,
161
+ SSLCertName: [['CN', WEBrick::Utils.getservername]]
151
162
  }
152
163
  Log.log.info("Server started on port #{webrick_options[:Port]}")
153
164
  @server = WEBrick::HTTPServer.new(webrick_options)
154
- @server.mount('/aspera/faspex', FxGwServlet,a_aoc_api_user,a_workspace_id)
165
+ @server.mount('/aspera/faspex', FxGwServlet, a_aoc_api_user, a_workspace_id)
155
166
  @server.mount('/newuser', NewUserServlet)
156
- trap('INT') {@server.shutdown}
167
+ trap('INT') { @server.shutdown }
157
168
  end
158
169
 
159
170
  def start_server
@@ -1,22 +1,12 @@
1
1
  # frozen_string_literal: true
2
- # for older rubies
3
- unless Hash.method_defined?(:dig)
4
- class Hash
5
- def dig(*path)
6
- path.inject(self) do |location, key|
7
- location.respond_to?(:keys) ? location[key] : nil
8
- end
9
- end
10
- end
11
- end
12
2
 
13
3
  class ::Hash
14
4
  def deep_merge(second)
15
- merge(second){|_key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge(v2) : v2}
5
+ merge(second){|_key,v1,v2|Hash === v1 && Hash === v2 ? v1.deep_merge(v2) : v2}
16
6
  end
17
7
 
18
8
  def deep_merge!(second)
19
- merge!(second){|_key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge!(v2) : v2}
9
+ merge!(second){|_key,v1,v2|Hash === v1 && Hash === v2 ? v1.deep_merge!(v2) : v2}
20
10
  end
21
11
  end
22
12
 
@@ -27,3 +17,11 @@ unless Hash.method_defined?(:symbolize_keys)
27
17
  end
28
18
  end
29
19
  end
20
+
21
+ unless Hash.method_defined?(:stringify_keys)
22
+ class Hash
23
+ def stringify_keys
24
+ return each_with_object({}){|(k,v),memo| memo[k.to_s] = v }
25
+ end
26
+ end
27
+ end
@@ -1,23 +1,24 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'uri'
3
4
 
4
5
  module Aspera
5
6
  class IdGenerator
6
- ID_SEPARATOR='_'
7
- WINDOWS_PROTECTED_CHAR=%r{[/:"<>\\*?]}
8
- PROTECTED_CHAR_REPLACE='_'
7
+ ID_SEPARATOR = '_'
8
+ WINDOWS_PROTECTED_CHAR = %r{[/:"<>\\*?]}.freeze
9
+ PROTECTED_CHAR_REPLACE = '_'
9
10
  private_constant :ID_SEPARATOR,:PROTECTED_CHAR_REPLACE,:WINDOWS_PROTECTED_CHAR
10
11
  def self.from_list(object_id)
11
12
  if object_id.is_a?(Array)
12
- object_id=object_id.reject(&:nil?).map do |i|
13
+ object_id = object_id.compact.map do |i|
13
14
  i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
14
15
  end.join(ID_SEPARATOR)
15
16
  end
16
17
  raise 'id must be a String' unless object_id.is_a?(String)
17
18
  return object_id.
18
- gsub(WINDOWS_PROTECTED_CHAR,PROTECTED_CHAR_REPLACE). # remove windows forbidden chars
19
- gsub('.',PROTECTED_CHAR_REPLACE). # keep dot for extension only (nicer)
20
- downcase
19
+ gsub(WINDOWS_PROTECTED_CHAR,PROTECTED_CHAR_REPLACE). # remove windows forbidden chars
20
+ gsub('.',PROTECTED_CHAR_REPLACE). # keep dot for extension only (nicer)
21
+ downcase
21
22
  end
22
23
  end
23
24
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'aspera/hash_ext'
2
4
  require 'openssl'
3
5
 
4
6
  module Aspera
5
7
  module Keychain
6
8
  class SimpleCipher
7
9
  def initialize(key)
8
- @key=Digest::SHA1.hexdigest(key)[0..23]
10
+ @key = Digest::SHA1.hexdigest(key+('*'*23))[0..23]
9
11
  @cipher = OpenSSL::Cipher.new('DES-EDE3-CBC')
10
12
  end
11
13
 
@@ -26,92 +28,105 @@ module Aspera
26
28
 
27
29
  # Manage secrets in a simple Hash
28
30
  class EncryptedHash
29
- SEPARATOR='%'
31
+ SEPARATOR = '%'
32
+ ACCEPTED_KEYS = %i[username url secret description].freeze
30
33
  private_constant :SEPARATOR
34
+ attr_reader :legacy_detected
31
35
  def initialize(values)
32
36
  raise 'values shall be Hash' unless values.is_a?(Hash)
33
- @all_secrets=values
37
+ @all_secrets = values
38
+ end
39
+
40
+ def identifier(options)
41
+ return options[:username] if options[:url].to_s.empty?
42
+ %i[url username].map{|s|options[s]}.join(SEPARATOR)
34
43
  end
35
44
 
36
45
  def set(options)
37
46
  raise 'options shall be Hash' unless options.is_a?(Hash)
38
- unsupported=options.keys-[:username,:url,:secret,:description]
47
+ unsupported = options.keys - ACCEPTED_KEYS
39
48
  raise "unsupported options: #{unsupported}" unless unsupported.empty?
40
- username=options[:username]
49
+ username = options[:username]
41
50
  raise 'options shall have username' if username.nil?
42
- url=options[:url]
51
+ url = options[:url]
43
52
  raise 'options shall have username' if url.nil?
44
- secret=options[:secret]
53
+ secret = options[:secret]
45
54
  raise 'options shall have secret' if secret.nil?
46
- key=[url,username].join(SEPARATOR)
55
+ key = identifier(options)
47
56
  raise "secret #{key} already exist, delete first" if @all_secrets.has_key?(key)
48
- obj={username: username, url: url, secret: SimpleCipher.new(key).encrypt(secret)}
49
- obj[:description]=options[:description] if options.has_key?(:description)
50
- @all_secrets[key]=obj
57
+ obj = {username: username, url: url, secret: SimpleCipher.new(key).encrypt(secret)}
58
+ obj[:description] = options[:description] if options.has_key?(:description)
59
+ @all_secrets[key] = obj.stringify_keys
51
60
  nil
52
61
  end
53
62
 
54
63
  def list
55
- result=[]
56
- @all_secrets.each do |k,v|
57
- case v
58
- when String
59
- o={username: k, url: '', description: ''}
60
- when Hash
61
- o=v.clone
62
- o.delete(:secret)
63
- o[:description]||=''
64
- else raise 'error'
65
- end
66
- o[:description]=v[:description] if v.is_a?(Hash) && v[:description].is_a?(String)
67
- result.push(o)
64
+ result = []
65
+ legacy_detected=false
66
+ @all_secrets.each do |name,value|
67
+ normal = # normalized version
68
+ case value
69
+ when String
70
+ legacy_detected=true
71
+ {username: name, url: '', secret: value}
72
+ when Hash then value.symbolize_keys
73
+ else raise 'error secret must be String (legacy) or Hash (new)'
74
+ end
75
+ normal[:description] = '' unless normal.has_key?(:description)
76
+ extraneous_keys=normal.keys - ACCEPTED_KEYS
77
+ Log.log.error("wrongs keys in secret hash: #{extraneous_keys.map(&:to_s).join(',')}") unless extraneous_keys.empty?
78
+ result.push(normal)
68
79
  end
80
+ Log.log.warn('Legacy vault format detected in config file, please refer to documentation to convert to new format.') if legacy_detected
69
81
  return result
70
82
  end
71
83
 
72
84
  def delete(options)
73
85
  raise 'options shall be Hash' unless options.is_a?(Hash)
74
- unsupported=options.keys-[:username,:url]
86
+ unsupported = options.keys - %i[username url]
75
87
  raise "unsupported options: #{unsupported}" unless unsupported.empty?
76
- username=options[:username]
88
+ username = options[:username]
77
89
  raise 'options shall have username' if username.nil?
78
- url=options[:url]
79
- key=nil
90
+ url = options[:url]
91
+ key = nil
80
92
  if !url.nil?
81
- extk=[url,username].join(SEPARATOR)
82
- key=extk if @all_secrets.has_key?(extk)
93
+ extk = identifier(options)
94
+ key = extk if @all_secrets.has_key?(extk)
83
95
  end
84
96
  # backward compatibility: TODO: remove in future ? (make url mandatory ?)
85
- key=username if key.nil? && @all_secrets.has_key?(username)
97
+ key = username if key.nil? && @all_secrets.has_key?(username)
86
98
  raise 'no such secret' if key.nil?
87
99
  @all_secrets.delete(key)
88
100
  end
89
101
 
90
102
  def get(options)
91
103
  raise 'options shall be Hash' unless options.is_a?(Hash)
92
- unsupported=options.keys-[:username,:url]
104
+ unsupported = options.keys - %i[username url]
93
105
  raise "unsupported options: #{unsupported}" unless unsupported.empty?
94
- username=options[:username]
106
+ username = options[:username]
95
107
  raise 'options shall have username' if username.nil?
96
- url=options[:url]
97
- val=nil
108
+ url = options[:url]
109
+ info = nil
98
110
  if !url.nil?
99
- val=@all_secrets[[url,username].join(SEPARATOR)]
111
+ info = @all_secrets[identifier(options)]
100
112
  end
101
113
  # backward compatibility: TODO: remove in future ? (make url mandatory ?)
102
- if val.nil?
103
- val=@all_secrets[username]
114
+ if info.nil?
115
+ info = @all_secrets[username]
104
116
  end
105
- result=options.clone
106
- case val
117
+ result = options.clone
118
+ case info
107
119
  when NilClass
108
- raise 'no such secret'
120
+ raise "no such secret: #{options[:url]} #{username}"
109
121
  when String
110
- result.merge!({secret: val, description: ''})
122
+ result[:secret] = info
123
+ result[:description] = ''
111
124
  when Hash
112
- key=[url,username].join(SEPARATOR)
113
- plain=SimpleCipher.new(key).decrypt(val[:secret])
114
- result.merge!({secret: plain, description: val[:description]})
125
+ info=info.symbolize_keys
126
+ key = identifier(options)
127
+ plain = SimpleCipher.new(key).decrypt(info[:secret])
128
+ result[:secret] = plain
129
+ result[:description] = info[:description]
115
130
  else raise 'error'
116
131
  end
117
132
  return result