aspera-cli 4.14.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 (104) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +29 -3
  4. data/CHANGELOG.md +300 -185
  5. data/CONTRIBUTING.md +74 -23
  6. data/README.md +2346 -1619
  7. data/bin/ascli +16 -25
  8. data/bin/asession +15 -15
  9. data/examples/dascli +2 -2
  10. data/examples/proxy.pac +1 -1
  11. data/lib/aspera/aoc.rb +216 -150
  12. data/lib/aspera/ascmd.rb +25 -18
  13. data/lib/aspera/assert.rb +45 -0
  14. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  15. data/lib/aspera/cli/error.rb +17 -0
  16. data/lib/aspera/cli/extended_value.rb +51 -16
  17. data/lib/aspera/cli/formatter.rb +276 -174
  18. data/lib/aspera/cli/hints.rb +81 -0
  19. data/lib/aspera/cli/main.rb +114 -147
  20. data/lib/aspera/cli/manager.rb +181 -136
  21. data/lib/aspera/cli/plugin.rb +82 -64
  22. data/lib/aspera/cli/plugins/alee.rb +0 -1
  23. data/lib/aspera/cli/plugins/aoc.rb +327 -331
  24. data/lib/aspera/cli/plugins/ats.rb +12 -8
  25. data/lib/aspera/cli/plugins/bss.rb +2 -2
  26. data/lib/aspera/cli/plugins/config.rb +575 -439
  27. data/lib/aspera/cli/plugins/console.rb +40 -0
  28. data/lib/aspera/cli/plugins/cos.rb +4 -5
  29. data/lib/aspera/cli/plugins/faspex.rb +111 -92
  30. data/lib/aspera/cli/plugins/faspex5.rb +245 -182
  31. data/lib/aspera/cli/plugins/node.rb +239 -160
  32. data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
  33. data/lib/aspera/cli/plugins/preview.rb +54 -38
  34. data/lib/aspera/cli/plugins/server.rb +63 -20
  35. data/lib/aspera/cli/plugins/shares.rb +64 -38
  36. data/lib/aspera/cli/sync_actions.rb +68 -0
  37. data/lib/aspera/cli/transfer_agent.rb +64 -67
  38. data/lib/aspera/cli/transfer_progress.rb +73 -0
  39. data/lib/aspera/cli/version.rb +1 -1
  40. data/lib/aspera/colors.rb +3 -1
  41. data/lib/aspera/command_line_builder.rb +27 -22
  42. data/lib/aspera/cos_node.rb +6 -4
  43. data/lib/aspera/coverage.rb +22 -0
  44. data/lib/aspera/data_repository.rb +33 -2
  45. data/lib/aspera/environment.rb +21 -8
  46. data/lib/aspera/fasp/agent_alpha.rb +116 -0
  47. data/lib/aspera/fasp/agent_base.rb +40 -76
  48. data/lib/aspera/fasp/agent_connect.rb +21 -22
  49. data/lib/aspera/fasp/agent_direct.rb +169 -179
  50. data/lib/aspera/fasp/agent_httpgw.rb +200 -195
  51. data/lib/aspera/fasp/agent_node.rb +43 -35
  52. data/lib/aspera/fasp/agent_trsdk.rb +124 -41
  53. data/lib/aspera/fasp/error_info.rb +2 -2
  54. data/lib/aspera/fasp/faux_file.rb +52 -0
  55. data/lib/aspera/fasp/installation.rb +89 -191
  56. data/lib/aspera/fasp/management.rb +249 -0
  57. data/lib/aspera/fasp/parameters.rb +86 -47
  58. data/lib/aspera/fasp/parameters.yaml +75 -8
  59. data/lib/aspera/fasp/products.rb +162 -0
  60. data/lib/aspera/fasp/resume_policy.rb +7 -5
  61. data/lib/aspera/fasp/sync.rb +273 -0
  62. data/lib/aspera/fasp/transfer_spec.rb +10 -8
  63. data/lib/aspera/fasp/uri.rb +6 -6
  64. data/lib/aspera/faspex_gw.rb +11 -8
  65. data/lib/aspera/faspex_postproc.rb +8 -7
  66. data/lib/aspera/hash_ext.rb +2 -2
  67. data/lib/aspera/id_generator.rb +3 -1
  68. data/lib/aspera/json_rpc.rb +51 -0
  69. data/lib/aspera/keychain/encrypted_hash.rb +46 -11
  70. data/lib/aspera/keychain/macos_security.rb +15 -13
  71. data/lib/aspera/line_logger.rb +23 -0
  72. data/lib/aspera/log.rb +61 -19
  73. data/lib/aspera/nagios.rb +7 -2
  74. data/lib/aspera/node.rb +105 -21
  75. data/lib/aspera/node_simulator.rb +214 -0
  76. data/lib/aspera/oauth.rb +57 -36
  77. data/lib/aspera/open_application.rb +4 -4
  78. data/lib/aspera/persistency_action_once.rb +13 -14
  79. data/lib/aspera/persistency_folder.rb +5 -4
  80. data/lib/aspera/preview/file_types.rb +56 -268
  81. data/lib/aspera/preview/generator.rb +28 -39
  82. data/lib/aspera/preview/options.rb +2 -0
  83. data/lib/aspera/preview/terminal.rb +36 -16
  84. data/lib/aspera/preview/utils.rb +23 -29
  85. data/lib/aspera/proxy_auto_config.rb +6 -3
  86. data/lib/aspera/rest.rb +127 -80
  87. data/lib/aspera/rest_call_error.rb +1 -1
  88. data/lib/aspera/rest_error_analyzer.rb +16 -14
  89. data/lib/aspera/rest_errors_aspera.rb +39 -34
  90. data/lib/aspera/secret_hider.rb +18 -17
  91. data/lib/aspera/ssh.rb +10 -5
  92. data/lib/aspera/temp_file_manager.rb +11 -4
  93. data/lib/aspera/web_auth.rb +10 -7
  94. data/lib/aspera/web_server_simple.rb +11 -5
  95. data.tar.gz.sig +0 -0
  96. metadata +108 -39
  97. metadata.gz.sig +0 -0
  98. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  99. data/lib/aspera/cli/listener/logger.rb +0 -22
  100. data/lib/aspera/cli/listener/progress.rb +0 -50
  101. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  102. data/lib/aspera/cli/plugins/sync.rb +0 -44
  103. data/lib/aspera/fasp/listener.rb +0 -13
  104. data/lib/aspera/sync.rb +0 -213
@@ -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'
@@ -24,18 +26,23 @@ module Aspera
24
26
  # OAuth methods supported by default
25
27
  STD_AUTH_TYPES = %i[web jwt].freeze
26
28
 
27
- # remove 5 minutes to account for time offset between client and server (TODO: configurable?)
28
- JWT_ACCEPTED_OFFSET_SEC = 300
29
- # one hour validity (TODO: configurable?)
30
- JWT_EXPIRY_OFFSET_SEC = 3600
31
- # tokens older than 30 minutes will be discarded from cache
32
- TOKEN_CACHE_EXPIRY_SEC = 1800
33
- # tokens valid for less than this duration will be regenerated
34
- TOKEN_EXPIRATION_GUARD_SEC = 120
29
+ @@globals = { # rubocop:disable Style/ClassVars
30
+ # remove 5 minutes to account for time offset between client and server (TODO: configurable?)
31
+ jwt_accepted_offset_sec: 300,
32
+ # one hour validity (TODO: configurable?)
33
+ jwt_expiry_offset_sec: 3600,
34
+ # tokens older than 30 minutes will be discarded from cache
35
+ token_cache_expiry_sec: 1800,
36
+ # tokens valid for less than this duration will be regenerated
37
+ token_expiration_guard_sec: 120
38
+ }
39
+
35
40
  # a prefix for persistency of tokens (simplify garbage collect)
36
41
  PERSIST_CATEGORY_TOKEN = 'token'
42
+ # prefix for bearer token when in header
43
+ BEARER_PREFIX = 'Bearer '
37
44
 
38
- private_constant :JWT_ACCEPTED_OFFSET_SEC, :JWT_EXPIRY_OFFSET_SEC, :TOKEN_CACHE_EXPIRY_SEC, :PERSIST_CATEGORY_TOKEN, :TOKEN_EXPIRATION_GUARD_SEC
45
+ private_constant :PERSIST_CATEGORY_TOKEN, :BEARER_PREFIX
39
46
 
40
47
  # persistency manager
41
48
  @persist = nil
@@ -45,10 +52,23 @@ module Aspera
45
52
  @id_handlers = {}
46
53
 
47
54
  class << self
55
+ def bearer_build(token)
56
+ return BEARER_PREFIX + token
57
+ end
58
+
59
+ def bearer_extract(token)
60
+ assert(bearer?(token)){'not a bearer token, wrong prefix'}
61
+ return token[BEARER_PREFIX.length..-1]
62
+ end
63
+
64
+ def bearer?(token)
65
+ return token.start_with?(BEARER_PREFIX)
66
+ end
67
+
48
68
  def persist_mgr=(manager)
49
69
  @persist = manager
50
70
  # cleanup expired tokens
51
- @persist.garbage_collect(PERSIST_CATEGORY_TOKEN, TOKEN_CACHE_EXPIRY_SEC)
71
+ @persist.garbage_collect(PERSIST_CATEGORY_TOKEN, @@globals[:token_cache_expiry_sec])
52
72
  end
53
73
 
54
74
  def persist_mgr
@@ -88,26 +108,28 @@ module Aspera
88
108
  # @param id_create called to generate unique id for token, for cache
89
109
  def register_token_creator(id, lambda_create, id_create)
90
110
  Log.log.debug{"registering token creator #{id}"}
91
- 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)
92
114
  @create_handlers[id] = lambda_create
93
115
  @id_handlers[id] = id_create
94
116
  end
95
117
 
96
118
  # @return one of the registered creators for the given create type
97
119
  def token_creator(id)
98
- 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})"}
99
121
  @create_handlers[id]
100
122
  end
101
123
 
102
124
  # list of identifiers found in creation parameters that can be used to uniquely identify the token
103
125
  def id_creator(id)
104
- 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}"}
105
127
  @id_handlers[id]
106
128
  end
107
129
  end # self
108
130
 
109
131
  # JSON Web Signature (JWS) compact serialization: https://datatracker.ietf.org/doc/html/rfc7515
110
- 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
111
133
 
112
134
  # generic token creation, parameters are provided in :generic
113
135
  register_token_creator :generic, lambda { |oauth|
@@ -134,7 +156,7 @@ module Aspera
134
156
  OpenApplication.instance.uri(login_page_url)
135
157
  # wait for code in request
136
158
  received_params = web_server.received_request
137
- raise 'wrong received state' unless random_state.eql?(received_params['state'])
159
+ assert(random_state.eql?(received_params['state'])){'wrong received state'}
138
160
  # exchange code for token
139
161
  return oauth.create_token(oauth.optional_scope_client_id(add_secret: true).merge(
140
162
  grant_type: 'authorization_code',
@@ -151,11 +173,11 @@ module Aspera
151
173
  require 'jwt'
152
174
  seconds_since_epoch = Time.new.to_i
153
175
  Log.log.info{"seconds=#{seconds_since_epoch}"}
154
- raise 'missing JWT payload' unless oauth.specific_parameters[:payload].is_a?(Hash)
176
+ assert(oauth.specific_parameters[:payload].is_a?(Hash)){'missing JWT payload'}
155
177
  jwt_payload = {
156
- exp: seconds_since_epoch + JWT_EXPIRY_OFFSET_SEC, # expiration time
157
- nbf: seconds_since_epoch - JWT_ACCEPTED_OFFSET_SEC, # not before
158
- iat: seconds_since_epoch - JWT_ACCEPTED_OFFSET_SEC + 1, # issued at (we tell a little in the past so that server always accepts)
178
+ exp: seconds_since_epoch + @@globals[:jwt_expiry_offset_sec], # expiration time
179
+ nbf: seconds_since_epoch - @@globals[:jwt_accepted_offset_sec], # not before
180
+ iat: seconds_since_epoch - @@globals[:jwt_accepted_offset_sec] + 1, # issued at (we tell a little in the past so that server always accepts)
159
181
  jti: SecureRandom.uuid # JWT id
160
182
  }.merge(oauth.specific_parameters[:payload])
161
183
  Log.log.debug{"JWT jwt_payload=[#{jwt_payload}]"}
@@ -173,14 +195,14 @@ module Aspera
173
195
  private
174
196
 
175
197
  # [M]=mandatory [D]=has default value [0]=accept nil
176
- # :base_url [M] URL of authentication API
198
+ # :base_url [M] URL of authentication API
177
199
  # :auth
178
- # :grant_method [M] :generic, :web, :jwt, custom
200
+ # :grant_method [M] :generic, :web, :jwt, [custom types]
179
201
  # :client_id [0]
180
202
  # :client_secret [0]
181
203
  # :scope [0]
182
- # :path_token [D] API end point to create a token
183
- # :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
184
206
  # :jwt:private_key_obj [M] for type :jwt
185
207
  # :jwt:payload [M] for type :jwt
186
208
  # :jwt:headers [0] for type :jwt
@@ -189,18 +211,16 @@ module Aspera
189
211
  # :generic [M] for type :generic
190
212
  def initialize(a_params)
191
213
  Log.log.debug{"auth=#{a_params}"}
192
- # replace default values
214
+ # set default values if not set in parameters common to all types
193
215
  @generic_parameters = DEFAULT_CREATE_PARAMS.deep_merge(a_params)
194
- # legacy
195
- @generic_parameters[:grant_method] ||= @generic_parameters.delete(:crtype) if @generic_parameters.key?(:crtype) # cspell: disable-line
196
216
  # check that type is known
197
217
  self.class.token_creator(@generic_parameters[:grant_method])
198
218
  # specific parameters for the creation type
199
219
  @specific_parameters = @generic_parameters[@generic_parameters[:grant_method]]
200
220
  if @generic_parameters[:grant_method].eql?(:web) && @specific_parameters.key?(:redirect_uri)
201
221
  uri = URI.parse(@specific_parameters[:redirect_uri])
202
- raise 'redirect_uri scheme must be http or https' unless %w[http https].include?(uri.scheme)
203
- 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'}
204
224
  # TODO: we could check that host is localhost or local address
205
225
  end
206
226
  rest_params = {
@@ -208,13 +228,14 @@ module Aspera
208
228
  redirect_max: 2
209
229
  }
210
230
  rest_params[:auth] = a_params[:auth] if a_params.key?(:auth)
231
+ # this is the OAuth API
211
232
  @api = Rest.new(rest_params)
212
- # if needed use from api
233
+ # if those are needed use from @api
213
234
  @generic_parameters.delete(:base_url)
214
235
  @generic_parameters.delete(:auth)
215
236
  @generic_parameters.delete(@generic_parameters[:grant_method])
216
- Log.dump(:generic_parameters, @generic_parameters)
217
- Log.dump(:specific_parameters, @specific_parameters)
237
+ Log.log.debug{Log.dump(:generic_parameters, @generic_parameters)}
238
+ Log.log.debug{Log.dump(:specific_parameters, @specific_parameters)}
218
239
  end
219
240
 
220
241
  public
@@ -248,7 +269,7 @@ module Aspera
248
269
  @generic_parameters[:grant_method],
249
270
  self.class.id_creator(@generic_parameters[:grant_method]).call(self), # array, so we flatten later
250
271
  @generic_parameters[:scope],
251
- @api.params.dig(%i[auth username])
272
+ @api.params.dig(*%i[auth username])
252
273
  ].flatten)
253
274
 
254
275
  # get token_data from cache (or nil), token_data is what is returned by /token
@@ -259,14 +280,14 @@ module Aspera
259
280
  # `direct` agent is equipped with refresh code
260
281
  if !use_refresh_token && !token_data.nil?
261
282
  decoded_token = self.class.decode_token(token_data[@generic_parameters[:token_field]])
262
- Log.dump('decoded_token', decoded_token) unless decoded_token.nil?
283
+ Log.log.debug{Log.dump('decoded_token', decoded_token)} unless decoded_token.nil?
263
284
  if decoded_token.is_a?(Hash)
264
285
  expires_at_sec =
265
286
  if decoded_token['expires_at'].is_a?(String) then DateTime.parse(decoded_token['expires_at']).to_time
266
287
  elsif decoded_token['exp'].is_a?(Integer) then Time.at(decoded_token['exp'])
267
288
  end
268
289
  # force refresh if we see a token too close from expiration
269
- use_refresh_token = true if expires_at_sec.is_a?(Time) && (expires_at_sec - Time.now) < TOKEN_EXPIRATION_GUARD_SEC
290
+ use_refresh_token = true if expires_at_sec.is_a?(Time) && (expires_at_sec - Time.now) < @@globals[:token_expiration_guard_sec]
270
291
  Log.log.debug{"Expiration: #{expires_at_sec} / #{use_refresh_token}"}
271
292
  end
272
293
  end
@@ -304,9 +325,9 @@ module Aspera
304
325
  token_data = JSON.parse(json_data)
305
326
  self.class.persist_mgr.put(token_id, json_data)
306
327
  end # if ! in_cache
307
- 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]}"}
308
329
  # ok we shall have a token here
309
- return 'Bearer ' + token_data[@generic_parameters[:token_field]]
330
+ return self.class.bearer_build(token_data[@generic_parameters[:token_field]])
310
331
  end
311
332
  end # OAuth
312
333
  end # Aspera
@@ -27,7 +27,7 @@ module Aspera
27
27
  def uri_graphical(uri)
28
28
  case Aspera::Environment.os
29
29
  when Aspera::Environment::OS_X then return system('open', uri.to_s)
30
- when Aspera::Environment::OS_WINDOWS then return system('start', 'explorer', '"' + uri.to_s + '"')
30
+ when Aspera::Environment::OS_WINDOWS then return system('start', 'explorer', %Q{"#{uri}"})
31
31
  when Aspera::Environment::OS_LINUX then return system('xdg-open', uri.to_s)
32
32
  else
33
33
  raise "no graphical open method for #{Aspera::Environment.os}"
@@ -38,7 +38,7 @@ module Aspera
38
38
  if ENV.key?('EDITOR')
39
39
  system(ENV['EDITOR'], file_path.to_s)
40
40
  elsif Aspera::Environment.os.eql?(Aspera::Environment::OS_WINDOWS)
41
- system('notepad.exe', '"' + file_path.to_s + '"')
41
+ system('notepad.exe', %Q{"#{file_path}"})
42
42
  else
43
43
  uri_graphical(file_path.to_s)
44
44
  end
@@ -59,9 +59,9 @@ module Aspera
59
59
  when :text
60
60
  case the_url.to_s
61
61
  when /^http/
62
- puts "USER ACTION: please enter this url in a browser:\n" + the_url.to_s.red + "\n"
62
+ puts "USER ACTION: please enter this url in a browser:\n#{the_url.to_s.red}\n"
63
63
  else
64
- puts "USER ACTION: open this:\n" + the_url.to_s.red + "\n"
64
+ puts "USER ACTION: open this:\n#{the_url.to_s.red}\n"
65
65
  end
66
66
  else
67
67
  raise StandardError, "unsupported url open method: #{@url_method}"
@@ -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,10 +35,10 @@ 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
- File.delete(persist_filepath) if File.exist?(persist_filepath)
41
+ FileUtils.rm_f(persist_filepath)
41
42
  File.write(persist_filepath, value)
42
43
  Environment.restrict_file_access(persist_filepath)
43
44
  @cache[object_id] = value
@@ -46,7 +47,7 @@ module Aspera
46
47
  def delete(object_id)
47
48
  persist_filepath = id_to_filepath(object_id)
48
49
  Log.log.debug{"persistency deleting: #{persist_filepath}"}
49
- File.delete(persist_filepath) if File.exist?(persist_filepath)
50
+ FileUtils.rm_f(persist_filepath)
50
51
  @cache.delete(object_id)
51
52
  end
52
53
 
@@ -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}")