aspera-cli 4.15.0 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
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}")