aspera-cli 4.14.0 → 4.16.0

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