aspera-cli 4.15.0 → 4.16.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 (80) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +292 -228
  5. data/CONTRIBUTING.md +69 -18
  6. data/README.md +1102 -952
  7. data/bin/ascli +13 -31
  8. data/bin/asession +3 -1
  9. data/examples/dascli +2 -2
  10. data/lib/aspera/aoc.rb +28 -33
  11. data/lib/aspera/ascmd.rb +3 -6
  12. data/lib/aspera/assert.rb +45 -0
  13. data/lib/aspera/cli/extended_value.rb +5 -5
  14. data/lib/aspera/cli/formatter.rb +26 -13
  15. data/lib/aspera/cli/hints.rb +4 -3
  16. data/lib/aspera/cli/main.rb +16 -3
  17. data/lib/aspera/cli/manager.rb +45 -36
  18. data/lib/aspera/cli/plugin.rb +20 -13
  19. data/lib/aspera/cli/plugins/aoc.rb +103 -73
  20. data/lib/aspera/cli/plugins/ats.rb +4 -3
  21. data/lib/aspera/cli/plugins/config.rb +114 -119
  22. data/lib/aspera/cli/plugins/cos.rb +2 -2
  23. data/lib/aspera/cli/plugins/faspex.rb +23 -19
  24. data/lib/aspera/cli/plugins/faspex5.rb +75 -43
  25. data/lib/aspera/cli/plugins/node.rb +28 -15
  26. data/lib/aspera/cli/plugins/orchestrator.rb +4 -2
  27. data/lib/aspera/cli/plugins/preview.rb +9 -7
  28. data/lib/aspera/cli/plugins/server.rb +6 -3
  29. data/lib/aspera/cli/plugins/shares.rb +30 -26
  30. data/lib/aspera/cli/sync_actions.rb +9 -9
  31. data/lib/aspera/cli/transfer_agent.rb +21 -14
  32. data/lib/aspera/cli/transfer_progress.rb +2 -3
  33. data/lib/aspera/cli/version.rb +1 -1
  34. data/lib/aspera/command_line_builder.rb +13 -11
  35. data/lib/aspera/cos_node.rb +3 -2
  36. data/lib/aspera/coverage.rb +22 -0
  37. data/lib/aspera/data_repository.rb +33 -2
  38. data/lib/aspera/environment.rb +4 -2
  39. data/lib/aspera/fasp/{agent_aspera.rb → agent_alpha.rb} +29 -39
  40. data/lib/aspera/fasp/agent_base.rb +17 -7
  41. data/lib/aspera/fasp/agent_direct.rb +88 -84
  42. data/lib/aspera/fasp/agent_httpgw.rb +4 -3
  43. data/lib/aspera/fasp/agent_node.rb +3 -2
  44. data/lib/aspera/fasp/agent_trsdk.rb +79 -37
  45. data/lib/aspera/fasp/installation.rb +51 -12
  46. data/lib/aspera/fasp/management.rb +11 -6
  47. data/lib/aspera/fasp/parameters.rb +53 -47
  48. data/lib/aspera/fasp/resume_policy.rb +7 -5
  49. data/lib/aspera/fasp/sync.rb +273 -0
  50. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  51. data/lib/aspera/fasp/uri.rb +2 -2
  52. data/lib/aspera/faspex_gw.rb +11 -8
  53. data/lib/aspera/faspex_postproc.rb +6 -5
  54. data/lib/aspera/id_generator.rb +3 -1
  55. data/lib/aspera/json_rpc.rb +10 -8
  56. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  57. data/lib/aspera/keychain/macos_security.rb +15 -13
  58. data/lib/aspera/log.rb +4 -3
  59. data/lib/aspera/nagios.rb +7 -2
  60. data/lib/aspera/node.rb +17 -16
  61. data/lib/aspera/node_simulator.rb +214 -0
  62. data/lib/aspera/oauth.rb +22 -19
  63. data/lib/aspera/persistency_action_once.rb +13 -14
  64. data/lib/aspera/persistency_folder.rb +3 -2
  65. data/lib/aspera/preview/file_types.rb +53 -267
  66. data/lib/aspera/preview/generator.rb +7 -5
  67. data/lib/aspera/preview/terminal.rb +14 -5
  68. data/lib/aspera/preview/utils.rb +8 -7
  69. data/lib/aspera/proxy_auto_config.rb +6 -3
  70. data/lib/aspera/rest.rb +29 -13
  71. data/lib/aspera/rest_error_analyzer.rb +1 -0
  72. data/lib/aspera/rest_errors_aspera.rb +2 -0
  73. data/lib/aspera/secret_hider.rb +5 -2
  74. data/lib/aspera/ssh.rb +10 -8
  75. data/lib/aspera/temp_file_manager.rb +1 -1
  76. data/lib/aspera/web_server_simple.rb +2 -1
  77. data.tar.gz.sig +0 -0
  78. metadata +96 -45
  79. metadata.gz.sig +0 -0
  80. data/lib/aspera/sync.rb +0 -219
data/lib/aspera/log.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'aspera/colors'
4
4
  require 'aspera/secret_hider'
5
5
  require 'aspera/environment'
6
+ require 'aspera/assert'
6
7
  require 'logger'
7
8
  require 'pp'
8
9
  require 'json'
@@ -39,6 +40,7 @@ class Logger
39
40
  end
40
41
  EOM
41
42
  end
43
+ # declare methods for all levels
42
44
  Logger::Severity.constants.each { |severity| make_methods(severity) }
43
45
  end
44
46
 
@@ -97,8 +99,7 @@ module Aspera
97
99
  Logger::Severity.constants.each do |name|
98
100
  return name.downcase.to_sym if @logger.level.eql?(Logger::Severity.const_get(name))
99
101
  end
100
- # should not happen
101
- raise "INTERNAL ERROR: unexpected level #{@logger.level}"
102
+ error_unexpected_value(@logger.level){'log level'}
102
103
  end
103
104
 
104
105
  # change underlying logger, but keep log level
@@ -123,7 +124,7 @@ module Aspera
123
124
  end
124
125
  @logger = Syslog::Logger.new(@program_name, Syslog::LOG_LOCAL2)
125
126
  else
126
- raise "unknown log type: #{new_log_type.class} #{new_log_type}"
127
+ raise "unknown log type: #{new_log_type}, use one of: #{LOG_TYPES.join(', ')}"
127
128
  end
128
129
  @logger.level = current_severity_integer
129
130
  @logger_type = new_log_type
data/lib/aspera/nagios.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/log'
4
+ require 'aspera/assert'
3
5
  require 'date'
4
6
 
5
7
  module Aspera
@@ -21,8 +23,11 @@ module Aspera
21
23
  class << self
22
24
  # process results of a analysis and display status and exit with code
23
25
  def process(data)
24
- raise 'INTERNAL ERROR, result must be list and not empty' unless data.is_a?(Array) && !data.empty?
25
- %w[status component message].each{|c|raise "INTERNAL ERROR, result must have #{c}" unless data.first.key?(c)}
26
+ assert_type(data, Array)
27
+ assert(!data.empty?){'data is empty'}
28
+ %w[status component message].each do |c|
29
+ assert(data.first.key?(c)){"result must have #{c}"}
30
+ end
26
31
  res_errors = data.reject{|s|s['status'].eql?('ok')}
27
32
  # keep only errors in case of problem, other ok are assumed so
28
33
  data = res_errors unless res_errors.empty?
data/lib/aspera/node.rb CHANGED
@@ -5,6 +5,7 @@ require 'aspera/fasp/transfer_spec'
5
5
  require 'aspera/rest'
6
6
  require 'aspera/oauth'
7
7
  require 'aspera/log'
8
+ require 'aspera/assert'
8
9
  require 'aspera/environment'
9
10
  require 'zlib'
10
11
  require 'base64'
@@ -50,7 +51,7 @@ module Aspera
50
51
  end
51
52
  return lambda{|f|File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
52
53
  when NilClass then return ->(_){true}
53
- else raise Cli::BadArgument, "Invalid match expression type: #{match_expression.class}"
54
+ else error_unexpected_value(match_expression.class.name, exception_class: Cli::BadArgument)
54
55
  end
55
56
  end
56
57
 
@@ -65,8 +66,8 @@ module Aspera
65
66
 
66
67
  def decode_scope(scope)
67
68
  items = scope.split(SCOPE_SEPARATOR, 2)
68
- raise "invalid scope: #{scope}" unless items.length.eql?(2)
69
- raise "invalid scope: #{scope}" unless items[0].start_with?(SCOPE_PREFIX)
69
+ assert(items.length.eql?(2)){"invalid scope: #{scope}"}
70
+ assert(items[0].start_with?(SCOPE_PREFIX)){"invalid scope: #{scope}"}
70
71
  return {access_key: items[0][SCOPE_PREFIX.length..-1], scope: items[1]}
71
72
  end
72
73
 
@@ -74,11 +75,11 @@ module Aspera
74
75
  # @param payload [String] JSON payload to be included in the token
75
76
  # @param private_key [OpenSSL::PKey::RSA] Private key to sign the token
76
77
  def bearer_token(access_key:, payload:, private_key:)
77
- raise 'payload shall be Hash' unless payload.is_a?(Hash)
78
- raise 'missing user_id' unless payload.key?('user_id')
79
- raise 'user_id must be a String' unless payload['user_id'].is_a?(String)
80
- raise 'user_id must not be empty' if payload['user_id'].empty?
81
- raise 'private_key shall be OpenSSL::PKey::RSA' unless private_key.is_a?(OpenSSL::PKey::RSA)
78
+ assert_type(payload, Hash)
79
+ assert(payload.key?('user_id'))
80
+ assert_type(payload['user_id'], String)
81
+ assert(!payload['user_id'].empty?)
82
+ assert_type(private_key, OpenSSL::PKey::RSA)
82
83
  # manage convenience parameters
83
84
  expiration_sec = payload['_validity'] || BEARER_TOKEN_VALIDITY_DEFAULT
84
85
  payload.delete('_validity')
@@ -104,7 +105,7 @@ module Aspera
104
105
  # if username is not provided, use the access key from the token
105
106
  if access_key.nil?
106
107
  access_key = Aspera::Node.decode_scope(Aspera::Node.decode_bearer_token(Oauth.bearer_extract(bearer_auth))['scope'])[:access_key]
107
- raise "internal error #{access_key}" if access_key.nil?
108
+ assert(!access_key.nil?)
108
109
  end
109
110
  return {
110
111
  Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => access_key,
@@ -131,10 +132,10 @@ module Aspera
131
132
  @add_tspec = add_tspec
132
133
  if !@app_info.nil?
133
134
  REQUIRED_APP_INFO_FIELDS.each do |field|
134
- raise "INTERNAL ERROR: app_info lacks field #{field}" unless @app_info.key?(field)
135
+ assert(@app_info.key?(field)){"app_info lacks field #{field}"}
135
136
  end
136
137
  REQUIRED_APP_API_METHODS.each do |method|
137
- raise "INTERNAL ERROR: #{@app_info[:api].class} lacks method #{method}" unless @app_info[:api].respond_to?(method)
138
+ assert(@app_info[:api].respond_to?(method)){"#{@app_info[:api].class} lacks method #{method}"}
138
139
  end
139
140
  end
140
141
  end
@@ -165,8 +166,8 @@ module Aspera
165
166
  # @param top_file_path [String] path of top folder (default = /)
166
167
  # @param block [Proc] processing method, arguments: entry, path, state
167
168
  def process_folder_tree(state:, top_file_id:, top_file_path: '/', &block)
168
- raise 'INTERNAL ERROR: top_file_path not set' if top_file_path.nil?
169
- raise 'INTERNAL ERROR: Missing block' unless block
169
+ assert(!top_file_path.nil?){'top_file_path not set'}
170
+ assert(block){'Missing block'}
170
171
  # start at top folder
171
172
  folders_to_explore = [{id: top_file_id, path: top_file_path}]
172
173
  Log.log.debug{Log.dump(:folders_to_explore, folders_to_explore)}
@@ -207,7 +208,7 @@ module Aspera
207
208
  # @param path [String] file path
208
209
  # @return [Hash] {.api,.file_id}
209
210
  def resolve_api_fid(top_file_id, path)
210
- raise 'file id shall be String' unless top_file_id.is_a?(String)
211
+ assert_type(top_file_id, String)
211
212
  process_last_link = path.end_with?(PATH_SEPARATOR)
212
213
  path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
213
214
  return {api: self, file_id: top_file_id} if path_elements.empty?
@@ -276,14 +277,14 @@ module Aspera
276
277
  case params[:auth][:type]
277
278
  when :basic
278
279
  ak_name = params[:auth][:username]
279
- raise 'ERROR: no secret in node object' unless params[:auth][:password]
280
+ assert(params[:auth][:password]){'no secret in node object'}
280
281
  ak_token = Rest.basic_token(params[:auth][:username], params[:auth][:password])
281
282
  when :oauth2
282
283
  ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
283
284
  # TODO: token_generation_lambda = lambda{|do_refresh|oauth_token(force_refresh: do_refresh)}
284
285
  # get bearer token, possibly use cache
285
286
  ak_token = oauth_token(force_refresh: false)
286
- else raise "Unsupported auth method for node gen4: #{params[:auth][:type]}"
287
+ else error_unexpected_value(params[:auth][:type])
287
288
  end
288
289
  transfer_spec = {
289
290
  'direction' => direction,
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/log'
4
+ require 'aspera/fasp/installation'
5
+ require 'aspera/fasp/agent_direct'
6
+ require 'webrick'
7
+ require 'json'
8
+
9
+ module Aspera
10
+ # this class answers the Faspex /send API and creates a package on Aspera on Cloud
11
+ class NodeSimulatorServlet < WEBrick::HTTPServlet::AbstractServlet
12
+ PATH_TRANSFERS = '/ops/transfers'
13
+ PATH_ONE_TRANSFER = %r{/ops/transfers/(.+)$}
14
+ # @param app_api [Aspera::AoC]
15
+ # @param app_context [String]
16
+ def initialize(server, credentials, transfer)
17
+ super(server)
18
+ @credentials = credentials
19
+ @xfer_manager = Aspera::Fasp::AgentDirect.new
20
+ end
21
+
22
+ def do_POST(request, response)
23
+ case request.path
24
+ when PATH_TRANSFERS
25
+ job_id = @xfer_manager.start_transfer(JSON.parse(request.body))
26
+ session = @xfer_manager.sessions_by_job(job_id).first
27
+ result = session[:ts].clone
28
+ result['id'] = job_id
29
+ set_json_response(response, result)
30
+ Log.log.debug{">>> transfer started: #{job_id}"}
31
+ else
32
+ set_json_response(response, [{error: 'Bad request'}], code: 400)
33
+ end
34
+ end
35
+
36
+ def do_GET(request, response)
37
+ case request.path
38
+ when '/info'
39
+ info = Aspera::Fasp::Installation.instance.ascp_info
40
+ set_json_response(response, {
41
+ application: 'node',
42
+ current_time: Time.now.utc.iso8601(0),
43
+ version: info['ascp_version'].gsub(/ .*$/, ''),
44
+ license_expiration_date: info['expiration_date'],
45
+ license_max_rate: info['maximum_bandwidth'],
46
+ os: %x(uname -srv).chomp,
47
+ aej_status: 'disconnected',
48
+ async_reporting: 'yes',
49
+ transfer_activity_reporting: 'yes',
50
+ transfer_user: 'xfer',
51
+ docroot: 'file:////data/aoc/eudemo-sedemo',
52
+ node_id: '2bbdcc39-f789-4d47-8163-6767fc14f421',
53
+ cluster_id: '6dae2844-d1a9-47a5-916d-9b3eac3ea466',
54
+ acls: [],
55
+ access_key_configuration_capabilities: {
56
+ transfer: %w[
57
+ cipher
58
+ policy
59
+ target_rate_cap_kbps
60
+ target_rate_kbps
61
+ preserve_timestamps
62
+ content_protection_secret
63
+ aggressiveness
64
+ token_encryption_key
65
+ byok_enabled
66
+ bandwidth_flow_network_rc_module
67
+ file_checksum_type],
68
+ server: %w[
69
+ activity_event_logging
70
+ activity_file_event_logging
71
+ recursive_counts
72
+ aej_logging
73
+ wss_enabled
74
+ activity_transfer_ignore_skipped_files
75
+ activity_files_max
76
+ access_key_credentials_encryption_type
77
+ discovery
78
+ auto_delete
79
+ allow
80
+ deny]
81
+ },
82
+ capabilities: [
83
+ {name: 'sync', value: true},
84
+ {name: 'watchfolder', value: true},
85
+ {name: 'symbolic_links', value: true},
86
+ {name: 'move_file', value: true},
87
+ {name: 'move_directory', value: true},
88
+ {name: 'filelock', value: false},
89
+ {name: 'ssh_fingerprint', value: false},
90
+ {name: 'aej_version', value: '1.0'},
91
+ {name: 'page', value: true},
92
+ {name: 'file_id_version', value: '2.0'},
93
+ {name: 'auto_delete', value: false}],
94
+ settings: [
95
+ {name: 'content_protection_required', value: false},
96
+ {name: 'content_protection_strong_pass_required', value: false},
97
+ {name: 'filelock_restriction', value: 'none'},
98
+ {name: 'ssh_fingerprint', value: nil},
99
+ {name: 'wss_enabled', value: false},
100
+ {name: 'wss_port', value: 443}
101
+ ]})
102
+ when PATH_TRANSFERS
103
+ result = @xfer_manager.sessions.map { |session| job_to_transfer(session) }
104
+ set_json_response(response, result)
105
+ when PATH_ONE_TRANSFER
106
+ job_id = request.path.match(PATH_ONE_TRANSFER)[1]
107
+ set_json_response(response, job_to_transfer(@xfer_manager.sessions_by_job(job_id).first))
108
+ else
109
+ set_json_response(response, [{error: 'Unknown request'}], code: 400)
110
+ end
111
+ end
112
+
113
+ def set_json_response(response, json, code: 200)
114
+ response.status = code
115
+ response['Content-Type'] = 'application/json'
116
+ response.body = json.to_json
117
+ Log.log.trace1{Log.dump('response', json)}
118
+ end
119
+
120
+ def job_to_transfer(job)
121
+ session = {
122
+ id: 'bafc72b8-366c-4501-8095-47208183d6b8',
123
+ client_node_id: '',
124
+ server_node_id: '2bbdcc39-f789-4d47-8163-6767fc14f421',
125
+ client_ip_address: '192.168.0.100',
126
+ server_ip_address: '5.10.114.4',
127
+ status: 'running',
128
+ retry_timeout: 3600,
129
+ retry_count: 0,
130
+ start_time_usec: 1701094040000000,
131
+ end_time_usec: nil,
132
+ elapsed_usec: 405312,
133
+ bytes_transferred: 26,
134
+ bytes_written: 26,
135
+ bytes_lost: 0,
136
+ files_completed: 1,
137
+ directories_completed: 0,
138
+ target_rate_kbps: 500000,
139
+ min_rate_kbps: 0,
140
+ calc_rate_kbps: 9900,
141
+ network_delay_usec: 40000,
142
+ avg_rate_kbps: 0.51,
143
+ error_code: 0,
144
+ error_desc: '',
145
+ source_statistics: {
146
+ args_scan_attempted: 1,
147
+ args_scan_completed: 1,
148
+ paths_scan_attempted: 1,
149
+ paths_scan_failed: 0,
150
+ paths_scan_skipped: 0,
151
+ paths_scan_excluded: 0,
152
+ dirs_scan_completed: 0,
153
+ files_scan_completed: 1,
154
+ dirs_xfer_attempted: 0,
155
+ dirs_xfer_fail: 0,
156
+ files_xfer_attempted: 1,
157
+ files_xfer_fail: 0,
158
+ files_xfer_noxfer: 0
159
+ },
160
+ precalc: {
161
+ enabled: true,
162
+ status: 'ready',
163
+ bytes_expected: 0,
164
+ directories_expected: 0,
165
+ files_expected: 0,
166
+ files_excluded: 0,
167
+ files_special: 0,
168
+ files_failed: 1
169
+ }}
170
+ return {
171
+ id: '609a667d-642e-4290-9312-b4d20d3c0159',
172
+ status: 'running',
173
+ start_spec: job[:ts],
174
+ sessions: [session],
175
+ bytes_transferred: 26,
176
+ bytes_written: 26,
177
+ bytes_lost: 0,
178
+ avg_rate_kbps: 0.51,
179
+ files_completed: 1,
180
+ files_skipped: 0,
181
+ directories_completed: 0,
182
+ start_time_usec: 1701094040000000,
183
+ end_time_usec: 1701094040405312,
184
+ elapsed_usec: 405312,
185
+ error_code: 0,
186
+ error_desc: '',
187
+ precalc: {
188
+ status: 'ready',
189
+ bytes_expected: 0,
190
+ files_expected: 0,
191
+ directories_expected: 0,
192
+ files_special: 0,
193
+ files_failed: 1
194
+ },
195
+ files: [{
196
+ id: 'd1b5c112-82b75425-860745fc-93851671-64541bdd',
197
+ path: '/workspaces/45071/packages/bYA_ilq73g.asp-package/contents/data_file.bin',
198
+ start_time_usec: 1701094040000000,
199
+ elapsed_usec: 105616,
200
+ end_time_usec: 1701094040001355,
201
+ status: 'completed',
202
+ error_code: 0,
203
+ error_desc: '',
204
+ size: 26,
205
+ type: 'file',
206
+ checksum_type: 'none',
207
+ checksum: nil,
208
+ start_byte: 0,
209
+ bytes_written: 26,
210
+ session_id: 'bafc72b8-366c-4501-8095-47208183d6b8'}]
211
+ }
212
+ end
213
+ end # NodeSimulatorServlet
214
+ end # Aspera
data/lib/aspera/oauth.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'aspera/open_application'
4
4
  require 'aspera/web_auth'
5
5
  require 'aspera/id_generator'
6
+ require 'aspera/log'
7
+ require 'aspera/assert'
6
8
  require 'base64'
7
9
  require 'date'
8
10
  require 'socket'
@@ -55,7 +57,7 @@ module Aspera
55
57
  end
56
58
 
57
59
  def bearer_extract(token)
58
- raise 'not a bearer token, wrong prefix' unless bearer?(token)
60
+ assert(bearer?(token)){'not a bearer token, wrong prefix'}
59
61
  return token[BEARER_PREFIX.length..-1]
60
62
  end
61
63
 
@@ -106,26 +108,28 @@ module Aspera
106
108
  # @param id_create called to generate unique id for token, for cache
107
109
  def register_token_creator(id, lambda_create, id_create)
108
110
  Log.log.debug{"registering token creator #{id}"}
109
- raise 'ERROR: requites Symbol and 2 lambdas' unless id.is_a?(Symbol) && lambda_create.is_a?(Proc) && id_create.is_a?(Proc)
111
+ assert_type(id, Symbol)
112
+ assert_type(lambda_create, Proc)
113
+ assert_type(id_create, Proc)
110
114
  @create_handlers[id] = lambda_create
111
115
  @id_handlers[id] = id_create
112
116
  end
113
117
 
114
118
  # @return one of the registered creators for the given create type
115
119
  def token_creator(id)
116
- raise "token grant method unknown: '#{id}' (#{id.class})" unless @create_handlers.key?(id)
120
+ assert(@create_handlers.key?(id)){"token grant method unknown: '#{id}' (#{id.class})"}
117
121
  @create_handlers[id]
118
122
  end
119
123
 
120
124
  # list of identifiers found in creation parameters that can be used to uniquely identify the token
121
125
  def id_creator(id)
122
- raise "id creator type unknown: #{id}/#{id.class}" unless @id_handlers.key?(id)
126
+ assert(@id_handlers.key?(id)){"id creator type unknown: #{id}/#{id.class}"}
123
127
  @id_handlers[id]
124
128
  end
125
129
  end # self
126
130
 
127
131
  # JSON Web Signature (JWS) compact serialization: https://datatracker.ietf.org/doc/html/rfc7515
128
- register_decoder lambda { |token| parts = token.split('.'); raise 'not aoc token' unless parts.length.eql?(3); JSON.parse(Base64.decode64(parts[1]))} # rubocop:disable Style/Semicolon, Layout/LineLength
132
+ register_decoder lambda { |token| parts = token.split('.'); assert(parts.length.eql?(3)){'not aoc token'}; JSON.parse(Base64.decode64(parts[1]))} # rubocop:disable Style/Semicolon, Layout/LineLength
129
133
 
130
134
  # generic token creation, parameters are provided in :generic
131
135
  register_token_creator :generic, lambda { |oauth|
@@ -152,7 +156,7 @@ module Aspera
152
156
  OpenApplication.instance.uri(login_page_url)
153
157
  # wait for code in request
154
158
  received_params = web_server.received_request
155
- raise 'wrong received state' unless random_state.eql?(received_params['state'])
159
+ assert(random_state.eql?(received_params['state'])){'wrong received state'}
156
160
  # exchange code for token
157
161
  return oauth.create_token(oauth.optional_scope_client_id(add_secret: true).merge(
158
162
  grant_type: 'authorization_code',
@@ -169,7 +173,7 @@ module Aspera
169
173
  require 'jwt'
170
174
  seconds_since_epoch = Time.new.to_i
171
175
  Log.log.info{"seconds=#{seconds_since_epoch}"}
172
- raise 'missing JWT payload' unless oauth.specific_parameters[:payload].is_a?(Hash)
176
+ assert(oauth.specific_parameters[:payload].is_a?(Hash)){'missing JWT payload'}
173
177
  jwt_payload = {
174
178
  exp: seconds_since_epoch + @@globals[:jwt_expiry_offset_sec], # expiration time
175
179
  nbf: seconds_since_epoch - @@globals[:jwt_accepted_offset_sec], # not before
@@ -191,14 +195,14 @@ module Aspera
191
195
  private
192
196
 
193
197
  # [M]=mandatory [D]=has default value [0]=accept nil
194
- # :base_url [M] URL of authentication API
198
+ # :base_url [M] URL of authentication API
195
199
  # :auth
196
- # :grant_method [M] :generic, :web, :jwt, custom
200
+ # :grant_method [M] :generic, :web, :jwt, [custom types]
197
201
  # :client_id [0]
198
202
  # :client_secret [0]
199
203
  # :scope [0]
200
- # :path_token [D] API end point to create a token
201
- # :token_field [D] field in result that contains the token
204
+ # :path_token [D] API end point to create a token
205
+ # :token_field [D] field in result that contains the token
202
206
  # :jwt:private_key_obj [M] for type :jwt
203
207
  # :jwt:payload [M] for type :jwt
204
208
  # :jwt:headers [0] for type :jwt
@@ -207,18 +211,16 @@ module Aspera
207
211
  # :generic [M] for type :generic
208
212
  def initialize(a_params)
209
213
  Log.log.debug{"auth=#{a_params}"}
210
- # replace default values
214
+ # set default values if not set in parameters common to all types
211
215
  @generic_parameters = DEFAULT_CREATE_PARAMS.deep_merge(a_params)
212
- # legacy
213
- @generic_parameters[:grant_method] ||= @generic_parameters.delete(:crtype) if @generic_parameters.key?(:crtype) # cspell: disable-line
214
216
  # check that type is known
215
217
  self.class.token_creator(@generic_parameters[:grant_method])
216
218
  # specific parameters for the creation type
217
219
  @specific_parameters = @generic_parameters[@generic_parameters[:grant_method]]
218
220
  if @generic_parameters[:grant_method].eql?(:web) && @specific_parameters.key?(:redirect_uri)
219
221
  uri = URI.parse(@specific_parameters[:redirect_uri])
220
- raise 'redirect_uri scheme must be http or https' unless %w[http https].include?(uri.scheme)
221
- raise 'redirect_uri must have a port' if uri.port.nil?
222
+ assert(%w[http https].include?(uri.scheme)){'redirect_uri scheme must be http or https'}
223
+ assert(!uri.port.nil?){'redirect_uri must have a port'}
222
224
  # TODO: we could check that host is localhost or local address
223
225
  end
224
226
  rest_params = {
@@ -226,8 +228,9 @@ module Aspera
226
228
  redirect_max: 2
227
229
  }
228
230
  rest_params[:auth] = a_params[:auth] if a_params.key?(:auth)
231
+ # this is the OAuth API
229
232
  @api = Rest.new(rest_params)
230
- # if needed use from api
233
+ # if those are needed use from @api
231
234
  @generic_parameters.delete(:base_url)
232
235
  @generic_parameters.delete(:auth)
233
236
  @generic_parameters.delete(@generic_parameters[:grant_method])
@@ -266,7 +269,7 @@ module Aspera
266
269
  @generic_parameters[:grant_method],
267
270
  self.class.id_creator(@generic_parameters[:grant_method]).call(self), # array, so we flatten later
268
271
  @generic_parameters[:scope],
269
- @api.params.dig(%i[auth username])
272
+ @api.params.dig(*%i[auth username])
270
273
  ].flatten)
271
274
 
272
275
  # get token_data from cache (or nil), token_data is what is returned by /token
@@ -322,7 +325,7 @@ module Aspera
322
325
  token_data = JSON.parse(json_data)
323
326
  self.class.persist_mgr.put(token_id, json_data)
324
327
  end # if ! in_cache
325
- raise "API error: No such field in answer: #{@generic_parameters[:token_field]}" unless token_data.key?(@generic_parameters[:token_field])
328
+ assert(token_data.key?(@generic_parameters[:token_field])){"API error: No such field in answer: #{@generic_parameters[:token_field]}"}
326
329
  # ok we shall have a token here
327
330
  return self.class.bearer_build(token_data[@generic_parameters[:token_field]])
328
331
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'json'
4
4
  require 'aspera/log'
5
+ require 'aspera/assert'
5
6
 
6
7
  module Aspera
7
8
  # Persist data on file system
@@ -13,21 +14,19 @@ module Aspera
13
14
  # @param :parse Optional parse method (default to JSON)
14
15
  # @param :format Optional dump method (default to JSON)
15
16
  # @param :merge Optional merge data from file to current data
16
- def initialize(options)
17
- Log.log.debug{"persistency: #{options}"}
18
- raise 'options shall be Hash' unless options.is_a?(Hash)
19
- raise 'mandatory :manager' if options[:manager].nil?
20
- raise 'mandatory :data' if options[:data].nil?
21
- raise 'mandatory :id (String)' unless options[:id].is_a?(String)
22
- raise 'mandatory 1 element in :id' unless options[:id].length >= 1
23
- @manager = options[:manager]
24
- @persisted_object = options[:data]
25
- @object_id = options[:id]
17
+ def initialize(manager:, data:, id:, delete: nil, parse: nil, format: nil, merge: nil)
18
+ assert(!manager.nil?)
19
+ assert(!data.nil?)
20
+ assert_type(id, String)
21
+ assert(!id.empty?)
22
+ @manager = manager
23
+ @persisted_object = data
24
+ @object_id = id
26
25
  # by default , at save time, file is deleted if data is nil
27
- @delete_condition = options[:delete] || lambda{|d|d.empty?}
28
- @persist_format = options[:format] || lambda {|h| JSON.generate(h)}
29
- persist_parse = options[:parse] || lambda {|t| JSON.parse(t)}
30
- persist_merge = options[:merge] || lambda {|current, file| current.concat(file).uniq rescue current}
26
+ @delete_condition = delete || lambda{|d|d.empty?}
27
+ @persist_format = format || lambda {|h| JSON.generate(h)}
28
+ persist_parse = parse || lambda {|t| JSON.parse(t)}
29
+ persist_merge = merge || lambda {|current, file| current.concat(file).uniq rescue current}
31
30
  value = @manager.get(@object_id)
32
31
  persist_merge.call(@persisted_object, persist_parse.call(value)) unless value.nil?
33
32
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'fileutils'
4
4
  require 'aspera/log'
5
+ require 'aspera/assert'
5
6
  require 'aspera/environment'
6
7
 
7
8
  # search: persistency_folder PersistencyFolder
@@ -34,7 +35,7 @@ module Aspera
34
35
  end
35
36
 
36
37
  def put(object_id, value)
37
- raise 'value: only String supported' unless value.is_a?(String)
38
+ assert_type(value, String)
38
39
  persist_filepath = id_to_filepath(object_id)
39
40
  Log.log.debug{"persistency saving: #{persist_filepath}"}
40
41
  FileUtils.rm_f(persist_filepath)
@@ -67,7 +68,7 @@ module Aspera
67
68
 
68
69
  # @param object_id String or Array
69
70
  def id_to_filepath(object_id)
70
- raise 'object_id: only String supported' unless object_id.is_a?(String)
71
+ assert_type(object_id, String)
71
72
  FileUtils.mkdir_p(@folder)
72
73
  Environment.restrict_file_access(@folder)
73
74
  return File.join(@folder, "#{object_id}#{FILE_SUFFIX}")