aspera-cli 4.4.0 → 4.7.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2095 -1503
  3. data/bin/ascli +2 -1
  4. data/bin/asession +4 -5
  5. data/docs/test_env.conf +3 -0
  6. data/examples/aoc.rb +4 -3
  7. data/examples/faspex4.rb +25 -25
  8. data/examples/proxy.pac +1 -1
  9. data/examples/transfer.rb +17 -17
  10. data/lib/aspera/aoc.rb +238 -185
  11. data/lib/aspera/ascmd.rb +93 -83
  12. data/lib/aspera/ats_api.rb +11 -10
  13. data/lib/aspera/cli/basic_auth_plugin.rb +13 -14
  14. data/lib/aspera/cli/extended_value.rb +42 -33
  15. data/lib/aspera/cli/formater.rb +142 -108
  16. data/lib/aspera/cli/info.rb +17 -0
  17. data/lib/aspera/cli/listener/line_dump.rb +3 -2
  18. data/lib/aspera/cli/listener/logger.rb +2 -1
  19. data/lib/aspera/cli/listener/progress.rb +16 -18
  20. data/lib/aspera/cli/listener/progress_multi.rb +18 -21
  21. data/lib/aspera/cli/main.rb +173 -149
  22. data/lib/aspera/cli/manager.rb +163 -168
  23. data/lib/aspera/cli/plugin.rb +43 -31
  24. data/lib/aspera/cli/plugins/alee.rb +6 -6
  25. data/lib/aspera/cli/plugins/aoc.rb +405 -370
  26. data/lib/aspera/cli/plugins/ats.rb +86 -79
  27. data/lib/aspera/cli/plugins/bss.rb +14 -16
  28. data/lib/aspera/cli/plugins/config.rb +580 -362
  29. data/lib/aspera/cli/plugins/console.rb +23 -19
  30. data/lib/aspera/cli/plugins/cos.rb +18 -18
  31. data/lib/aspera/cli/plugins/faspex.rb +201 -158
  32. data/lib/aspera/cli/plugins/faspex5.rb +80 -57
  33. data/lib/aspera/cli/plugins/node.rb +183 -166
  34. data/lib/aspera/cli/plugins/orchestrator.rb +71 -67
  35. data/lib/aspera/cli/plugins/preview.rb +92 -96
  36. data/lib/aspera/cli/plugins/server.rb +79 -75
  37. data/lib/aspera/cli/plugins/shares.rb +35 -19
  38. data/lib/aspera/cli/plugins/sync.rb +20 -22
  39. data/lib/aspera/cli/transfer_agent.rb +76 -113
  40. data/lib/aspera/cli/version.rb +2 -1
  41. data/lib/aspera/colors.rb +35 -27
  42. data/lib/aspera/command_line_builder.rb +48 -34
  43. data/lib/aspera/cos_node.rb +29 -21
  44. data/lib/aspera/data_repository.rb +3 -2
  45. data/lib/aspera/environment.rb +50 -45
  46. data/lib/aspera/fasp/{manager.rb → agent_base.rb} +28 -25
  47. data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +52 -43
  48. data/lib/aspera/fasp/{local.rb → agent_direct.rb} +58 -72
  49. data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +37 -43
  50. data/lib/aspera/fasp/{node.rb → agent_node.rb} +35 -16
  51. data/lib/aspera/fasp/agent_trsdk.rb +104 -0
  52. data/lib/aspera/fasp/error.rb +2 -1
  53. data/lib/aspera/fasp/error_info.rb +68 -52
  54. data/lib/aspera/fasp/installation.rb +152 -124
  55. data/lib/aspera/fasp/listener.rb +1 -0
  56. data/lib/aspera/fasp/parameters.rb +87 -92
  57. data/lib/aspera/fasp/parameters.yaml +305 -249
  58. data/lib/aspera/fasp/resume_policy.rb +11 -14
  59. data/lib/aspera/fasp/transfer_spec.rb +26 -0
  60. data/lib/aspera/fasp/uri.rb +22 -21
  61. data/lib/aspera/faspex_gw.rb +55 -89
  62. data/lib/aspera/hash_ext.rb +4 -3
  63. data/lib/aspera/id_generator.rb +8 -7
  64. data/lib/aspera/keychain/encrypted_hash.rb +121 -0
  65. data/lib/aspera/keychain/macos_security.rb +90 -0
  66. data/lib/aspera/log.rb +55 -37
  67. data/lib/aspera/nagios.rb +13 -12
  68. data/lib/aspera/node.rb +30 -25
  69. data/lib/aspera/oauth.rb +175 -226
  70. data/lib/aspera/open_application.rb +4 -3
  71. data/lib/aspera/persistency_action_once.rb +6 -6
  72. data/lib/aspera/persistency_folder.rb +5 -9
  73. data/lib/aspera/preview/file_types.rb +6 -5
  74. data/lib/aspera/preview/generator.rb +25 -24
  75. data/lib/aspera/preview/options.rb +16 -14
  76. data/lib/aspera/preview/utils.rb +98 -98
  77. data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
  78. data/lib/aspera/proxy_auto_config.rb +111 -20
  79. data/lib/aspera/rest.rb +154 -135
  80. data/lib/aspera/rest_call_error.rb +2 -2
  81. data/lib/aspera/rest_error_analyzer.rb +23 -25
  82. data/lib/aspera/rest_errors_aspera.rb +15 -14
  83. data/lib/aspera/ssh.rb +12 -10
  84. data/lib/aspera/sync.rb +42 -41
  85. data/lib/aspera/temp_file_manager.rb +18 -14
  86. data/lib/aspera/timer_limiter.rb +2 -1
  87. data/lib/aspera/uri_reader.rb +7 -5
  88. data/lib/aspera/web_auth.rb +79 -76
  89. metadata +116 -29
  90. data/docs/Makefile +0 -66
  91. data/docs/README.erb.md +0 -3973
  92. data/docs/README.md +0 -13
  93. data/docs/diagrams.txt +0 -49
  94. data/docs/doc_tools.rb +0 -58
  95. data/lib/aspera/api_detector.rb +0 -60
  96. data/lib/aspera/cli/plugins/shares2.rb +0 -114
  97. data/lib/aspera/secrets.rb +0 -20
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'singleton'
2
3
  require 'aspera/log'
3
4
 
@@ -5,13 +6,12 @@ module Aspera
5
6
  module Fasp
6
7
  # implements a simple resume policy
7
8
  class ResumePolicy
8
-
9
9
  # list of supported parameters and default values
10
10
  DEFAULTS={
11
- :iter_max => 7,
12
- :sleep_initial => 2,
13
- :sleep_factor => 2,
14
- :sleep_max => 60
11
+ iter_max: 7,
12
+ sleep_initial: 2,
13
+ sleep_factor: 2,
14
+ sleep_max: 60
15
15
  }
16
16
 
17
17
  # @param params see DEFAULTS
@@ -20,12 +20,9 @@ module Aspera
20
20
  if !params.nil?
21
21
  raise "expecting Hash (or nil), but have #{params.class}" unless params.is_a?(Hash)
22
22
  params.each do |k,v|
23
- if DEFAULTS.has_key?(k)
24
- raise "#{k} must be Integer" unless v.is_a?(Integer)
25
- @parameters[k]=v
26
- else
27
- raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map{|i|i.to_s}.join(",")}"
28
- end
23
+ raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map(&:to_s).join(',')}" unless DEFAULTS.has_key?(k)
24
+ raise "#{k} must be Integer" unless v.is_a?(Integer)
25
+ @parameters[k]=v
29
26
  end
30
27
  end
31
28
  Log.log.debug("resume params=#{@parameters}")
@@ -45,9 +42,9 @@ module Aspera
45
42
  block.call
46
43
  break
47
44
  rescue Fasp::Error => e
48
- Log.log.warn("An error occured: #{e.message}" );
45
+ Log.log.warn("An error occured: #{e.message}");
49
46
  # failure in ascp
50
- if e.retryable? then
47
+ if e.retryable?
51
48
  # exit if we exceed the max number of retry
52
49
  raise Fasp::Error,'Maximum number of retry reached' if remaining_resumes <= 0
53
50
  else
@@ -61,7 +58,7 @@ module Aspera
61
58
 
62
59
  # take this retry in account
63
60
  remaining_resumes-=1
64
- Log.log.warn( "resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})" );
61
+ Log.log.warn("resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})");
65
62
 
66
63
  # wait a bit before retrying, maybe network condition will be better
67
64
  sleep(sleep_seconds)
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ require 'aspera/fasp/parameters'
3
+
4
+ module Aspera
5
+ module Fasp
6
+ # parameters for Transfer Spec
7
+ class TransferSpec
8
+ # 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={
13
+ 'remote_user' => ACCESS_KEY_TRANSFER_USER,
14
+ 'ssh_port' => SSH_PORT,
15
+ 'fasp_port' => UDP_PORT
16
+ }
17
+ # define constants for enums of parameters: <paramater>_<enum>, e.g. CIPHER_AES_128
18
+ Aspera::Fasp::Parameters.description.each do |k,v|
19
+ next unless v[:enum].is_a?(Array)
20
+ v[:enum].each do |enum|
21
+ TransferSpec.const_set("#{k.to_s.upcase}_#{enum.upcase.gsub(/[^A-Z0-9]/,'_')}", enum.freeze)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'aspera/log'
2
3
  require 'aspera/command_line_builder'
3
4
 
@@ -15,33 +16,33 @@ module Aspera
15
16
  result_ts['remote_host']=@fasp_uri.host
16
17
  result_ts['remote_user']=@fasp_uri.user
17
18
  result_ts['ssh_port']=@fasp_uri.port
18
- result_ts['paths']=[{"source"=>URI.decode_www_form_component(@fasp_uri.path)}]
19
+ result_ts['paths']=[{'source'=>URI.decode_www_form_component(@fasp_uri.path)}]
19
20
  # faspex does not encode trailing base64 encoded tags, fix that
20
21
  fixed_query = @fasp_uri.query.gsub(/(=+)$/){|x|'%3D'*x.length}
21
22
 
22
- URI::decode_www_form(fixed_query).each do |i|
23
+ URI.decode_www_form(fixed_query).each do |i|
23
24
  name=i[0]
24
25
  value=i[1]
25
26
  case name
26
- when 'cookie'; result_ts['cookie']=value
27
- when 'token'; result_ts['token']=value
28
- when 'policy'; result_ts['rate_policy']=value
29
- when 'httpport'; result_ts['http_fallback_port']=value.to_i
30
- when 'targetrate'; result_ts['target_rate_kbps']=value.to_i
31
- when 'minrate'; result_ts['min_rate_kbps']=value.to_i
32
- when 'port'; result_ts['fasp_port']=value.to_i
33
- when 'enc'; result_ts['cipher']=value.gsub('-','') # aes-128 -> aes128
34
- when 'tags64'; result_ts['tags']=JSON.parse(Base64.strict_decode64(value))
35
- when 'bwcap'; result_ts['target_rate_cap_kbps']=value.to_i
36
- when 'createpath'; result_ts['create_dir']=CommandLineBuilder.yes_to_true(value)
37
- when 'fallback'; result_ts['http_fallback']=CommandLineBuilder.yes_to_true(value)
38
- when 'lockpolicy'; result_ts['lock_rate_policy']=CommandLineBuilder.yes_to_true(value)
39
- when 'lockminrate'; result_ts['lock_min_rate']=CommandLineBuilder.yes_to_true(value)
40
- when 'sshfp'; result_ts['sshfp']=value
41
- when 'auth'; Log.log.debug("ignoring #{name}=#{value}") # TODO: translate into transfer spec ? yes/no
42
- when 'v'; Log.log.debug("ignoring #{name}=#{value}") # TODO: translate into transfer spec ? 2
43
- when 'protect'; Log.log.debug("ignoring #{name}=#{value}") # TODO: translate into transfer spec ?
44
- else Log.log.error("non managed URI value: #{name} = #{value}")
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)
42
+ when 'auth' then Log.log.debug("ignoring auth #{name}=#{value}") # TODO: translate into transfer spec ? yes/no
43
+ when 'v' then Log.log.debug("ignoring v #{name}=#{value}") # TODO: translate into transfer spec ? 2
44
+ when 'protect' then Log.log.debug("ignoring protect #{name}=#{value}") # TODO: translate into transfer spec ?
45
+ else Log.log.warn("URI parameter ignored: #{name} = #{value}")
45
46
  end
46
47
  end
47
48
  return result_ts
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
1
2
  require 'aspera/log'
2
3
  require 'aspera/aoc'
3
- require 'aspera/node'
4
+ require 'aspera/fasp/transfer_spec'
4
5
  require 'aspera/cli/main'
5
6
  require 'webrick'
6
7
  require 'webrick/https'
@@ -12,7 +13,8 @@ module Aspera
12
13
  # this class answers the Faspex /send API and creates a package on Aspera on Cloud
13
14
  class FaspexGW
14
15
  class FxGwServlet < WEBrick::HTTPServlet::AbstractServlet
15
- def initialize(server,a_aoc_api_user,a_workspace_id)
16
+ def initialize(_server,a_aoc_api_user,a_workspace_id)
17
+ super
16
18
  @aoc_api_user=a_aoc_api_user
17
19
  @aoc_workspace_id=a_workspace_id
18
20
  end
@@ -30,27 +32,27 @@ module Aspera
30
32
  # }
31
33
  # }
32
34
  def process_faspex_send(request, response)
33
- raise "no payload" if request.body.nil?
35
+ raise 'no payload' if request.body.nil?
34
36
  faspex_pkg_parameters=JSON.parse(request.body)
35
37
  faspex_pkg_delivery=faspex_pkg_parameters['delivery']
36
- Log.log.debug "faspex pkg create parameters=#{faspex_pkg_parameters}"
38
+ Log.log.debug("faspex pkg create parameters=#{faspex_pkg_parameters}")
37
39
 
38
40
  # get recipient ids
39
41
  files_pkg_recipients=[]
40
42
  faspex_pkg_delivery['recipients'].each do |recipient_email|
41
- user_lookup=@aoc_api_user.read("contacts",{'current_workspace_id'=>@aoc_workspace_id,'q'=>recipient_email})[:data]
42
- raise StandardError,"no such unique user: #{recipient_email} / #{user_lookup}" unless !user_lookup.nil? and user_lookup.length == 1
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)
43
45
  recipient_user_info=user_lookup.first
44
- files_pkg_recipients.push({"id"=>recipient_user_info['source_id'],"type"=>recipient_user_info['source_type']})
46
+ files_pkg_recipients.push({'id'=>recipient_user_info['source_id'],'type'=>recipient_user_info['source_type']})
45
47
  end
46
48
 
47
49
  # create a new package with one file
48
- the_package=@aoc_api_user.create("packages",{
49
- "file_names"=>faspex_pkg_delivery['sources'][0]['paths'],
50
- "name"=>faspex_pkg_delivery['title'],
51
- "note"=>faspex_pkg_delivery['note'],
52
- "recipients"=>files_pkg_recipients,
53
- "workspace_id"=>@aoc_workspace_id})[:data]
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]
54
56
 
55
57
  # get node information for the node on which package must be created
56
58
  node_info=@aoc_api_user.read("nodes/#{the_package['node_id']}")[:data]
@@ -59,50 +61,50 @@ module Aspera
59
61
  node_auth_bearer_token=@aoc_api_user.oauth_token(scope: AoC.node_scope(node_info['access_key'],AoC::SCOPE_NODE_USER))
60
62
 
61
63
  # tell Files what to expect in package: 1 transfer (can also be done after transfer)
62
- @aoc_api_user.update("packages/#{the_package['id']}",{"sent"=>true,"transfers_expected"=>1})
64
+ @aoc_api_user.update("packages/#{the_package['id']}",{'sent'=>true,'transfers_expected'=>1})
65
+
66
+ # to return an error:
67
+ # response.status=400
68
+ # return 'ERROR HERE'
63
69
 
64
- if false
65
- response.status=400
66
- return "ERROR HERE"
67
- end
68
70
  # TODO: check about xfer_*
69
71
  ts_tags={
70
- "aspera" => {
71
- "files" => { "package_id" => the_package['id'], "package_operation" => "upload" },
72
- "node" => { "access_key" => node_info['access_key'], "file_id" => the_package['contents_file_id'] },
73
- "xfer_id" => SecureRandom.uuid,
74
- "xfer_retry" => 3600 } }
72
+ '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 } }
75
77
  # this transfer spec is for transfer to AoC
76
78
  faspex_transfer_spec={
77
79
  'direction' => 'send',
78
80
  'remote_host' => node_info['host'],
79
- 'remote_user' => Node::ACCESS_KEY_TRANSFER_USER,
80
- 'ssh_port' => Node::SSH_PORT_DEFAULT,
81
- 'fasp_port' => Node::UDP_PORT_DEFAULT
81
+ 'remote_user' => Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER,
82
+ 'ssh_port' => Fasp::TransferSpec::SSH_PORT,
83
+ 'fasp_port' => Fasp::TransferSpec::UDP_PORT,
82
84
  'tags' => ts_tags,
83
85
  'token' => node_auth_bearer_token,
84
86
  'paths' => [{'destination' => '/'}],
85
87
  'cookie' => 'unused',
86
88
  'create_dir' => true,
87
89
  'rate_policy' => 'fair',
88
- 'rate_policy_allowed' => 'fixed',
89
- 'min_rate_cap_kbps' => nil,
90
- 'min_rate_kbps' => 0,
90
+ 'rate_policy_allowed' => 'fixed',
91
+ 'min_rate_cap_kbps' => nil,
92
+ 'min_rate_kbps' => 0,
91
93
  'target_rate_percentage' => nil,
92
- 'lock_target_rate' => nil,
93
- 'fasp_url' => 'unused',
94
- 'lock_min_rate' => true,
95
- 'lock_rate_policy' => true,
96
- 'source_root' => '',
97
- 'content_protection' => nil,
98
- 'target_rate_cap_kbps' => 20000, # TODO
99
- 'target_rate_kbps' => 10000, # TODO
100
- 'cipher' => 'aes-128',
101
- 'cipher_allowed' => nil,
102
- 'http_fallback' => false,
103
- 'http_fallback_port' => nil,
104
- 'https_fallback_port' => nil,
105
- 'destination_root' => '/'
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
108
  }
107
109
  # but we place it in a Faspex package creation response
108
110
  faspex_package_create_result={
@@ -111,28 +113,27 @@ module Aspera
111
113
  }
112
114
  Log.log.info("faspex_package_create_result=#{faspex_package_create_result}")
113
115
  response.status=200
114
- response.content_type = "application/json"
116
+ response.content_type = 'application/json'
115
117
  response.body=JSON.generate(faspex_package_create_result)
116
118
  end
117
119
 
118
- def do_GET (request, response)
120
+ def do_GET(request, response) # rubocop:disable Naming/MethodName
119
121
  case request.path
120
122
  when '/aspera/faspex/send'
121
123
  process_faspex_send(request, response)
122
124
  else
123
125
  response.status=400
124
- return "ERROR HERE"
125
- raise "unsupported path: #{request.path}"
126
+ return 'ERROR HERE'
126
127
  end
127
128
  end
128
129
  end # FxGwServlet
129
130
 
130
131
  class NewUserServlet < WEBrick::HTTPServlet::AbstractServlet
131
- def do_GET (request, response)
132
+ def do_GET(request, response) # rubocop:disable Naming/MethodName
132
133
  case request.path
133
134
  when '/newuser'
134
135
  response.status=200
135
- response.content_type = "text/html"
136
+ response.content_type = 'text/html'
136
137
  response.body='<html><body>hello world</body></html>'
137
138
  else
138
139
  raise "unsupported path: [#{request.path}]"
@@ -140,49 +141,14 @@ module Aspera
140
141
  end
141
142
  end
142
143
 
143
- def fill_self_signed_cert(options)
144
- key = OpenSSL::PKey::RSA.new(4096)
145
- cert = OpenSSL::X509::Certificate.new
146
- cert.subject = cert.issuer = OpenSSL::X509::Name.parse("/C=FR/O=Test/OU=Test/CN=Test")
147
- cert.not_before = Time.now
148
- cert.not_after = Time.now + 365 * 24 * 60 * 60
149
- cert.public_key = key.public_key
150
- cert.serial = 0x0
151
- cert.version = 2
152
- ef = OpenSSL::X509::ExtensionFactory.new
153
- ef.issuer_certificate = cert
154
- ef.subject_certificate = cert
155
- cert.extensions = [
156
- ef.create_extension("basicConstraints","CA:TRUE", true),
157
- ef.create_extension("subjectKeyIdentifier", "hash"),
158
- # ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
159
- ]
160
- cert.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always,issuer:always"))
161
- cert.sign(key, OpenSSL::Digest::SHA256.new)
162
- options[:SSLPrivateKey] = key
163
- options[:SSLCertificate] = cert
164
- end
165
-
166
144
  def initialize(a_aoc_api_user,a_workspace_id)
167
145
  webrick_options = {
168
- :app => FaspexGW,
169
- :Port => 9443,
170
- :Logger => Log.log,
171
- #:DocumentRoot => Cli::Main.gem_root,
172
- :SSLEnable => true,
173
- :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
146
+ Port: 9443,
147
+ Logger: Log.log,
148
+ SSLEnable: true,
149
+ SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
150
+ SSLCertName: [['CN',WEBrick::Utils.getservername]]
174
151
  }
175
- case 2
176
- when 0
177
- # generate self signed cert
178
- webrick_options[:SSLCertName] = [ [ 'CN',WEBrick::Utils::getservername ] ]
179
- Log.log.error(">>>#{webrick_options[:SSLCertName]}")
180
- when 1
181
- fill_self_signed_cert(webrick_options)
182
- when 2
183
- webrick_options[:SSLPrivateKey] =OpenSSL::PKey::RSA.new(File.read('/Users/laurent/workspace/Tools/certificate/myserver.key'))
184
- webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read('/Users/laurent/workspace/Tools/certificate/myserver.crt'))
185
- end
186
152
  Log.log.info("Server started on port #{webrick_options[:Port]}")
187
153
  @server = WEBrick::HTTPServer.new(webrick_options)
188
154
  @server.mount('/aspera/faspex', FxGwServlet,a_aoc_api_user,a_workspace_id)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # for older rubies
2
3
  unless Hash.method_defined?(:dig)
3
4
  class Hash
@@ -11,18 +12,18 @@ end
11
12
 
12
13
  class ::Hash
13
14
  def deep_merge(second)
14
- self.merge(second){|key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge(v2) : v2}
15
+ merge(second){|_key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge(v2) : v2}
15
16
  end
16
17
 
17
18
  def deep_merge!(second)
18
- self.merge!(second){|key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge!(v2) : v2}
19
+ merge!(second){|_key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge!(v2) : v2}
19
20
  end
20
21
  end
21
22
 
22
23
  unless Hash.method_defined?(:symbolize_keys)
23
24
  class Hash
24
25
  def symbolize_keys
25
- return self.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
26
+ return each_with_object({}){|(k,v),memo| memo[k.to_sym] = v; }
26
27
  end
27
28
  end
28
29
  end
@@ -1,22 +1,23 @@
1
+ # frozen_string_literal: true
1
2
  require 'uri'
2
3
 
3
4
  module Aspera
4
5
  class IdGenerator
5
6
  ID_SEPARATOR='_'
6
- WINDOWS_PROTECTED_CHAR=%r{[/:"<>\\\*\?]}
7
+ WINDOWS_PROTECTED_CHAR=%r{[/:"<>\\*?]}
7
8
  PROTECTED_CHAR_REPLACE='_'
8
9
  private_constant :ID_SEPARATOR,:PROTECTED_CHAR_REPLACE,:WINDOWS_PROTECTED_CHAR
9
10
  def self.from_list(object_id)
10
11
  if object_id.is_a?(Array)
11
- object_id=object_id.select{|i|!i.nil?}.map do |i|
12
- (i.is_a?(String) and i.start_with?('https://')) ? URI.parse(i).host : i.to_s
12
+ object_id=object_id.reject(&:nil?).map do |i|
13
+ i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
13
14
  end.join(ID_SEPARATOR)
14
15
  end
15
- raise "id must be a String" unless object_id.is_a?(String)
16
+ raise 'id must be a String' unless object_id.is_a?(String)
16
17
  return object_id.
17
- gsub(WINDOWS_PROTECTED_CHAR,PROTECTED_CHAR_REPLACE). # remove windows forbidden chars
18
- gsub('.',PROTECTED_CHAR_REPLACE). # keep dot for extension only (nicer)
19
- downcase
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
20
21
  end
21
22
  end
22
23
  end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+ require 'openssl'
3
+
4
+ module Aspera
5
+ module Keychain
6
+ class SimpleCipher
7
+ def initialize(key)
8
+ @key=Digest::SHA1.hexdigest(key)[0..23]
9
+ @cipher = OpenSSL::Cipher.new('DES-EDE3-CBC')
10
+ end
11
+
12
+ def encrypt(value)
13
+ @cipher.encrypt
14
+ @cipher.key = @key
15
+ s = @cipher.update(value) + @cipher.final
16
+ s.unpack1('H*')
17
+ end
18
+
19
+ def decrypt(value)
20
+ @cipher.decrypt
21
+ @cipher.key = @key
22
+ s = [value].pack('H*').unpack('C*').pack('c*')
23
+ @cipher.update(s) + @cipher.final
24
+ end
25
+ end
26
+
27
+ # Manage secrets in a simple Hash
28
+ class EncryptedHash
29
+ SEPARATOR='%'
30
+ private_constant :SEPARATOR
31
+ def initialize(values)
32
+ raise 'values shall be Hash' unless values.is_a?(Hash)
33
+ @all_secrets=values
34
+ end
35
+
36
+ def set(options)
37
+ raise 'options shall be Hash' unless options.is_a?(Hash)
38
+ unsupported=options.keys-[:username,:url,:secret,:description]
39
+ raise "unsupported options: #{unsupported}" unless unsupported.empty?
40
+ username=options[:username]
41
+ raise 'options shall have username' if username.nil?
42
+ url=options[:url]
43
+ raise 'options shall have username' if url.nil?
44
+ secret=options[:secret]
45
+ raise 'options shall have secret' if secret.nil?
46
+ key=[url,username].join(SEPARATOR)
47
+ 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
51
+ nil
52
+ end
53
+
54
+ 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)
68
+ end
69
+ return result
70
+ end
71
+
72
+ def delete(options)
73
+ raise 'options shall be Hash' unless options.is_a?(Hash)
74
+ unsupported=options.keys-[:username,:url]
75
+ raise "unsupported options: #{unsupported}" unless unsupported.empty?
76
+ username=options[:username]
77
+ raise 'options shall have username' if username.nil?
78
+ url=options[:url]
79
+ key=nil
80
+ if !url.nil?
81
+ extk=[url,username].join(SEPARATOR)
82
+ key=extk if @all_secrets.has_key?(extk)
83
+ end
84
+ # backward compatibility: TODO: remove in future ? (make url mandatory ?)
85
+ key=username if key.nil? && @all_secrets.has_key?(username)
86
+ raise 'no such secret' if key.nil?
87
+ @all_secrets.delete(key)
88
+ end
89
+
90
+ def get(options)
91
+ raise 'options shall be Hash' unless options.is_a?(Hash)
92
+ unsupported=options.keys-[:username,:url]
93
+ raise "unsupported options: #{unsupported}" unless unsupported.empty?
94
+ username=options[:username]
95
+ raise 'options shall have username' if username.nil?
96
+ url=options[:url]
97
+ val=nil
98
+ if !url.nil?
99
+ val=@all_secrets[[url,username].join(SEPARATOR)]
100
+ end
101
+ # backward compatibility: TODO: remove in future ? (make url mandatory ?)
102
+ if val.nil?
103
+ val=@all_secrets[username]
104
+ end
105
+ result=options.clone
106
+ case val
107
+ when NilClass
108
+ raise 'no such secret'
109
+ when String
110
+ result.merge!({secret: val, description: ''})
111
+ when Hash
112
+ key=[url,username].join(SEPARATOR)
113
+ plain=SimpleCipher.new(key).decrypt(val[:secret])
114
+ result.merge!({secret: plain, description: val[:description]})
115
+ else raise 'error'
116
+ end
117
+ return result
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+ require 'security'
3
+
4
+ # enhance the gem to support other keychains
5
+ module Security
6
+ class Keychain
7
+ class << self
8
+ def by_name(name)
9
+ keychains_from_output('security list-keychains').select{|kc|kc.filename.end_with?("/#{name}.keychain-db")}.first
10
+ end
11
+ end
12
+ end
13
+
14
+ class Password
15
+ class << self
16
+ # add some login to original method
17
+ alias orig_flags_for_options flags_for_options
18
+ def flags_for_options(options = {})
19
+ keychain=options.delete(:keychain)
20
+ url=options.delete(:url)
21
+ if !url.nil?
22
+ uri=URI.parse(url)
23
+ raise 'only https' unless uri.scheme.eql?('https')
24
+ options[:r]='htps'
25
+ raise 'host required in URL' if uri.host.nil?
26
+ options[:s]=uri.host
27
+ options[:p]=uri.path unless ['','/'].include?(uri.path)
28
+ options[:P]=uri.port unless uri.port.eql?(443) && !url.include?(':443/')
29
+ end
30
+ flags=[orig_flags_for_options(options)]
31
+ flags.push(keychain.filename) unless keychain.nil?
32
+ flags.join(' ')
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ module Aspera
39
+ module Keychain
40
+ # keychain based on macOS keychain, using `security` cmmand line
41
+ class MacosSecurity
42
+ def initialize(name=nil)
43
+ @keychain=name.nil? ? Security::Keychain.default_keychain : Security::Keychain.by_name(name)
44
+ raise "no such keychain #{name}" if @keychain.nil?
45
+ end
46
+
47
+ def set(options)
48
+ raise 'options shall be Hash' unless options.is_a?(Hash)
49
+ unsupported=options.keys-[:username,:url,:secret,:description]
50
+ raise "unsupported options: #{unsupported}" unless unsupported.empty?
51
+ username=options[:username]
52
+ raise 'options shall have username' if username.nil?
53
+ url=options[:url]
54
+ raise 'options shall have url' if url.nil?
55
+ secret=options[:secret]
56
+ raise 'options shall have secret' if secret.nil?
57
+ raise 'set not implemented'
58
+ end
59
+
60
+ def get(options)
61
+ raise 'options shall be Hash' unless options.is_a?(Hash)
62
+ unsupported=options.keys-[:username,:url]
63
+ raise "unsupported options: #{unsupported}" unless unsupported.empty?
64
+ username=options[:username]
65
+ raise 'options shall have username' if username.nil?
66
+ url=options[:url]
67
+ raise 'options shall have url' if url.nil?
68
+ info=Security::InternetPassword.find(keychain: @keychain, url: url, account: username)
69
+ raise 'not found' if info.nil?
70
+ result=options.clone
71
+ result.merge!({secret: info.password, description: info.attributes['icmt']})
72
+ return result
73
+ end
74
+
75
+ def list
76
+ raise 'list not implemented'
77
+ end
78
+
79
+ def delete(options)
80
+ raise 'options shall be Hash' unless options.is_a?(Hash)
81
+ unsupported=options.keys-[:username,:url]
82
+ raise "unsupported options: #{unsupported}" unless unsupported.empty?
83
+ username=options[:username]
84
+ raise 'options shall have username' if username.nil?
85
+ url=options[:url]
86
+ raise "delete not implemented #{url}"
87
+ end
88
+ end
89
+ end
90
+ end