aspera-cli 4.7.0 → 4.9.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 (96) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/README.md +1267 -999
  4. data/bin/ascli +20 -1
  5. data/bin/asession +37 -34
  6. data/docs/test_env.conf +7 -3
  7. data/examples/aoc.rb +13 -12
  8. data/examples/dascli +23 -0
  9. data/examples/faspex4.rb +34 -29
  10. data/examples/{transfer.rb → node.rb} +31 -59
  11. data/examples/server.rb +93 -0
  12. data/lib/aspera/aoc.rb +153 -143
  13. data/lib/aspera/ascmd.rb +56 -45
  14. data/lib/aspera/ats_api.rb +9 -6
  15. data/lib/aspera/cli/basic_auth_plugin.rb +18 -16
  16. data/lib/aspera/cli/extended_value.rb +33 -30
  17. data/lib/aspera/cli/formater.rb +105 -111
  18. data/lib/aspera/cli/info.rb +3 -2
  19. data/lib/aspera/cli/listener/line_dump.rb +1 -0
  20. data/lib/aspera/cli/listener/logger.rb +1 -0
  21. data/lib/aspera/cli/listener/progress.rb +13 -12
  22. data/lib/aspera/cli/listener/progress_multi.rb +21 -20
  23. data/lib/aspera/cli/main.rb +110 -90
  24. data/lib/aspera/cli/manager.rb +99 -88
  25. data/lib/aspera/cli/plugin.rb +98 -39
  26. data/lib/aspera/cli/plugins/alee.rb +6 -5
  27. data/lib/aspera/cli/plugins/aoc.rb +581 -450
  28. data/lib/aspera/cli/plugins/ats.rb +84 -83
  29. data/lib/aspera/cli/plugins/bss.rb +30 -27
  30. data/lib/aspera/cli/plugins/config.rb +488 -397
  31. data/lib/aspera/cli/plugins/console.rb +17 -15
  32. data/lib/aspera/cli/plugins/cos.rb +26 -35
  33. data/lib/aspera/cli/plugins/faspex.rb +206 -172
  34. data/lib/aspera/cli/plugins/faspex5.rb +109 -74
  35. data/lib/aspera/cli/plugins/node.rb +379 -189
  36. data/lib/aspera/cli/plugins/orchestrator.rb +71 -65
  37. data/lib/aspera/cli/plugins/preview.rb +131 -122
  38. data/lib/aspera/cli/plugins/server.rb +50 -150
  39. data/lib/aspera/cli/plugins/shares.rb +61 -27
  40. data/lib/aspera/cli/plugins/sync.rb +15 -14
  41. data/lib/aspera/cli/transfer_agent.rb +75 -64
  42. data/lib/aspera/cli/version.rb +2 -1
  43. data/lib/aspera/colors.rb +29 -28
  44. data/lib/aspera/command_line_builder.rb +50 -43
  45. data/lib/aspera/cos_node.rb +64 -38
  46. data/lib/aspera/data_repository.rb +1 -0
  47. data/lib/aspera/environment.rb +33 -10
  48. data/lib/aspera/fasp/agent_base.rb +35 -30
  49. data/lib/aspera/fasp/agent_connect.rb +35 -30
  50. data/lib/aspera/fasp/agent_direct.rb +68 -60
  51. data/lib/aspera/fasp/agent_httpgw.rb +71 -64
  52. data/lib/aspera/fasp/agent_node.rb +24 -23
  53. data/lib/aspera/fasp/agent_trsdk.rb +19 -20
  54. data/lib/aspera/fasp/error.rb +2 -1
  55. data/lib/aspera/fasp/error_info.rb +79 -68
  56. data/lib/aspera/fasp/installation.rb +130 -126
  57. data/lib/aspera/fasp/listener.rb +1 -0
  58. data/lib/aspera/fasp/parameters.rb +71 -60
  59. data/lib/aspera/fasp/parameters.yaml +69 -17
  60. data/lib/aspera/fasp/resume_policy.rb +14 -11
  61. data/lib/aspera/fasp/transfer_spec.rb +6 -5
  62. data/lib/aspera/fasp/uri.rb +25 -24
  63. data/lib/aspera/faspex_gw.rb +83 -72
  64. data/lib/aspera/hash_ext.rb +23 -13
  65. data/lib/aspera/id_generator.rb +16 -13
  66. data/lib/aspera/keychain/encrypted_hash.rb +61 -46
  67. data/lib/aspera/keychain/macos_security.rb +26 -24
  68. data/lib/aspera/log.rb +35 -39
  69. data/lib/aspera/nagios.rb +36 -28
  70. data/lib/aspera/node.rb +19 -19
  71. data/lib/aspera/oauth.rb +120 -100
  72. data/lib/aspera/open_application.rb +25 -22
  73. data/lib/aspera/persistency_action_once.rb +9 -8
  74. data/lib/aspera/persistency_folder.rb +13 -9
  75. data/lib/aspera/preview/file_types.rb +261 -266
  76. data/lib/aspera/preview/generator.rb +74 -73
  77. data/lib/aspera/preview/image_error.png +0 -0
  78. data/lib/aspera/preview/options.rb +7 -6
  79. data/lib/aspera/preview/utils.rb +30 -33
  80. data/lib/aspera/preview/video_error.png +0 -0
  81. data/lib/aspera/proxy_auto_config.rb +27 -23
  82. data/lib/aspera/rest.rb +73 -74
  83. data/lib/aspera/rest_call_error.rb +1 -0
  84. data/lib/aspera/rest_error_analyzer.rb +23 -19
  85. data/lib/aspera/rest_errors_aspera.rb +43 -40
  86. data/lib/aspera/secret_hider.rb +74 -0
  87. data/lib/aspera/ssh.rb +13 -10
  88. data/lib/aspera/sync.rb +49 -47
  89. data/lib/aspera/temp_file_manager.rb +7 -5
  90. data/lib/aspera/timer_limiter.rb +9 -8
  91. data/lib/aspera/uri_reader.rb +17 -18
  92. data/lib/aspera/web_auth.rb +17 -15
  93. data.tar.gz.sig +5 -0
  94. metadata +119 -35
  95. metadata.gz.sig +0 -0
  96. data/bin/dascli +0 -13
data/lib/aspera/oauth.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'aspera/open_application'
3
4
  require 'aspera/web_auth'
4
5
  require 'aspera/id_generator'
@@ -14,46 +15,38 @@ module Aspera
14
15
  # if a token is expired (api returns 4xx), call again get_authorization({refresh: true})
15
16
  # https://tools.ietf.org/html/rfc6749
16
17
  class Oauth
17
- DEFAULT_CREATE_PARAMS={
18
- token_field: 'access_token', # field with token in result
19
- path_token: 'token', # default endpoint for /token to generate token
20
- web: {path_authorize: 'authorize'} # default endpoint for /authorize, used for code exchange
18
+ DEFAULT_CREATE_PARAMS = {
19
+ path_token: 'token', # default endpoint for /token to generate token
20
+ token_field: 'access_token', # field with token in result of call to path_token
21
+ web: {path_authorize: 'authorize'} # default endpoint for /authorize, used for code exchange
21
22
  }.freeze
22
23
 
23
24
  # OAuth methods supported by default
24
- STD_AUTH_TYPES=[:web, :jwt].freeze
25
+ STD_AUTH_TYPES = %i[web jwt].freeze
25
26
 
26
27
  # remove 5 minutes to account for time offset (TODO: configurable?)
27
- JWT_NOTBEFORE_OFFSET_SEC=300
28
+ JWT_NOTBEFORE_OFFSET_SEC = 300
28
29
  # one hour validity (TODO: configurable?)
29
- JWT_EXPIRY_OFFSET_SEC=3600
30
+ JWT_EXPIRY_OFFSET_SEC = 3600
30
31
  # tokens older than 30 minutes will be discarded from cache
31
- TOKEN_CACHE_EXPIRY_SEC=1800
32
- # a prefix for persistency of tokens (garbage collect)
33
- PERSIST_CATEGORY_TOKEN='token'
34
- TOKEN_EXPIRATION_GUARD_SEC=120
32
+ TOKEN_CACHE_EXPIRY_SEC = 1800
33
+ # tokens valid for less than this duration will be regenerated
34
+ TOKEN_EXPIRATION_GUARD_SEC = 120
35
+ # a prefix for persistency of tokens (simplify garbage collect)
36
+ PERSIST_CATEGORY_TOKEN = 'token'
35
37
 
36
38
  private_constant :JWT_NOTBEFORE_OFFSET_SEC,:JWT_EXPIRY_OFFSET_SEC,:TOKEN_CACHE_EXPIRY_SEC,:PERSIST_CATEGORY_TOKEN,:TOKEN_EXPIRATION_GUARD_SEC
37
39
 
38
40
  # persistency manager
39
- @persist=nil
41
+ @persist = nil
40
42
  # token creation methods
41
- @handlers={}
43
+ @create_handlers = {}
42
44
  # token unique identifiers from oauth parameters
43
- @id_elements=[
44
- [:crtype],
45
- [:generic,:grant_type],
46
- [:jwt,:payload,:sub],
47
- [:auth,:username],
48
- [:aoc_pub_link,:json,:url_token],
49
- [:generic,:apikey],
50
- [:scope],
51
- [:generic,:response_type]
52
- ]
45
+ @id_handlers = {}
53
46
 
54
47
  class << self
55
48
  def persist_mgr=(manager)
56
- @persist=manager
49
+ @persist = manager
57
50
  # cleanup expired tokens
58
51
  @persist.garbage_collect(PERSIST_CATEGORY_TOKEN,TOKEN_CACHE_EXPIRY_SEC)
59
52
  end
@@ -62,7 +55,7 @@ module Aspera
62
55
  if @persist.nil?
63
56
  Log.log.debug('Not using persistency') # (use Aspera::Oauth.persist_mgr=Aspera::PersistencyFolder.new)
64
57
  # create NULL persistency class
65
- @persist=Class.new do
58
+ @persist = Class.new do
66
59
  def get(_x);nil;end;def delete(_x);nil;end;def put(_x,_y);nil;end;def garbage_collect(_x,_y);nil;end # rubocop:disable Layout/EmptyLineBetweenDefs
67
60
  end.new
68
61
  end
@@ -76,60 +69,77 @@ module Aspera
76
69
 
77
70
  # register a bearer token decoder, mainly to inspect expiry date
78
71
  def register_decoder(method)
79
- @decoders||=[]
72
+ @decoders ||= []
80
73
  @decoders.push(method)
81
74
  end
82
75
 
83
76
  # decode token using all registered decoders
84
77
  def decode_token(token)
85
78
  @decoders.each do |decoder|
86
- result=decoder.call(token) rescue nil
79
+ result = decoder.call(token) rescue nil
87
80
  return result unless result.nil?
88
81
  end
89
82
  return nil
90
83
  end
91
84
 
92
- # register a token creation method, specify using field :crtype in constructor
93
- def register_token_creator(id, method)
94
- raise 'error' unless id.is_a?(Symbol) && method.is_a?(Proc)
95
- @handlers[id]=method
85
+ # register a token creation method
86
+ # @param id creation type from field :crtype in constructor
87
+ # @param lambda_create called to create token
88
+ # @param id_create called to generate unique id for token, for cache
89
+ def register_token_creator(id, lambda_create, id_create)
90
+ raise 'ERROR: requites Symbol and 2 lambdas' unless id.is_a?(Symbol) && lambda_create.is_a?(Proc) && id_create.is_a?(Proc)
91
+ @create_handlers[id] = lambda_create
92
+ @id_handlers[id] = id_create
96
93
  end
97
94
 
98
95
  # @return one of the registered creators for the given create type
99
96
  def token_creator(id)
100
- raise "token create type unknown: #{id}/#{id.class}" unless @handlers.has_key?(id)
101
- @handlers[id]
97
+ raise "token creator type unknown: #{id}/#{id.class}" unless @create_handlers.has_key?(id)
98
+ @create_handlers[id]
102
99
  end
103
100
 
104
101
  # list of identifiers foundn in creation parameters that can be used to uniquely identify the token
105
- def id_elements
106
- return @id_elements
102
+ def id_creator(id)
103
+ raise "id creator type unknown: #{id}/#{id.class}" unless @id_handlers.has_key?(id)
104
+ @id_handlers[id]
107
105
  end
108
106
  end # self
109
107
 
110
108
  # JSON Web Signature (JWS) compact serialization: https://datatracker.ietf.org/doc/html/rfc7515
111
- register_decoder lambda { |token| parts=token.split('.'); raise 'not aoc token' unless parts.length.eql?(3); JSON.parse(Base64.decode64(parts[1]))}
109
+ register_decoder lambda { |token| parts = token.split('.'); raise 'not aoc token' unless parts.length.eql?(3); JSON.parse(Base64.decode64(parts[1]))}
112
110
 
113
111
  # generic token creation, parameters are provided in :generic
114
112
  register_token_creator :generic,lambda { |oauth|
115
- return oauth.create_token(oauth.params[:generic])
113
+ return oauth.create_token(oauth.sparams)
114
+ },lambda { |oauth|
115
+ return [
116
+ oauth.sparams[:grant_type]&.split(':')&.last,
117
+ oauth.sparams[:apikey],
118
+ oauth.sparams[:response_type]
119
+ ]
116
120
  }
117
121
 
118
122
  # Authentication using Web browser
119
123
  register_token_creator :web,lambda { |oauth|
120
- callback_verif=SecureRandom.uuid # used to check later
121
- login_page_url=Rest.build_uri("#{oauth.params[:base_url]}/#{oauth.params[:web][:path_authorize]}",optional_scope_client_id({response_type: 'code', redirect_uri: oauth.params[:web][:redirect_uri], state: callback_verif}))
124
+ random_state = SecureRandom.uuid # used to check later
125
+ login_page_url = Rest.build_uri("#{oauth.api[:base_url]}/#{oauth.sparams[:path_authorize]}",
126
+ oauth.optional_scope_client_id.merge(response_type: 'code', redirect_uri: oauth.sparams[:redirect_uri], state: random_state))
122
127
  # here, we need a human to authorize on a web page
123
128
  Log.log.info("login_page_url=#{login_page_url}".bg_red.gray)
124
129
  # start a web server to receive request code
125
- webserver=WebAuth.new(oauth.params[:web][:redirect_uri])
130
+ webserver = WebAuth.new(oauth.sparams[:redirect_uri])
126
131
  # start browser on login page
127
132
  OpenApplication.instance.uri(login_page_url)
128
133
  # wait for code in request
129
- received_params=webserver.received_request
130
- raise 'state does not match' if !callback_verif.eql?(received_params['state'])
134
+ received_params = webserver.received_request
135
+ raise 'wrong received state' unless random_state.eql?(received_params['state'])
131
136
  # exchange code for token
132
- return oauth.create_token(oauth.optional_scope_client_id({grant_type: 'authorization_code', code: received_params['code'], redirect_uri: oauth.params[:web][:redirect_uri]},add_secret: true))
137
+ return oauth.create_token(oauth.optional_scope_client_id(add_secret: true).merge(
138
+ grant_type: 'authorization_code',
139
+ code: received_params['code'],
140
+ redirect_uri: oauth.sparams[:redirect_uri]))
141
+ },lambda { |_oauth|
142
+ return []
133
143
  }
134
144
 
135
145
  # Authentication using private key
@@ -137,24 +147,26 @@ module Aspera
137
147
  # https://tools.ietf.org/html/rfc7523
138
148
  # https://tools.ietf.org/html/rfc7519
139
149
  require 'jwt'
140
- seconds_since_epoch=Time.new.to_i
150
+ seconds_since_epoch = Time.new.to_i
141
151
  Log.log.info("seconds=#{seconds_since_epoch}")
142
- raise 'missing JWT payload' unless oauth.params[:jwt][:payload].is_a?(Hash)
152
+ raise 'missing JWT payload' unless oauth.sparams[:payload].is_a?(Hash)
143
153
  jwt_payload = {
144
- exp: seconds_since_epoch+JWT_EXPIRY_OFFSET_SEC, # expiration time
145
- nbf: seconds_since_epoch-JWT_NOTBEFORE_OFFSET_SEC, # not before
154
+ exp: seconds_since_epoch + JWT_EXPIRY_OFFSET_SEC, # expiration time
155
+ nbf: seconds_since_epoch - JWT_NOTBEFORE_OFFSET_SEC, # not before
146
156
  iat: seconds_since_epoch, # issued at
147
157
  jti: SecureRandom.uuid # JWT id
148
- }.merge(oauth.params[:jwt][:payload])
158
+ }.merge(oauth.sparams[:payload])
149
159
  Log.log.debug("JWT jwt_payload=[#{jwt_payload}]")
150
- rsa_private=oauth.params[:jwt][:private_key_obj] # type: OpenSSL::PKey::RSA
160
+ rsa_private = oauth.sparams[:private_key_obj] # type: OpenSSL::PKey::RSA
151
161
  Log.log.debug("private=[#{rsa_private}]")
152
- assertion = JWT.encode(jwt_payload, rsa_private, 'RS256', oauth.params[:jwt][:headers]||{})
162
+ assertion = JWT.encode(jwt_payload, rsa_private, 'RS256', oauth.sparams[:headers] || {})
153
163
  Log.log.debug("assertion=[#{assertion}]")
154
- return oauth.create_token(oauth.optional_scope_client_id({grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: assertion}))
164
+ return oauth.create_token(oauth.optional_scope_client_id.merge(grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: assertion))
165
+ },lambda { |oauth|
166
+ return [oauth.sparams.dig(:payload,:sub)]
155
167
  }
156
168
 
157
- attr_reader :params, :token_auth_api
169
+ attr_reader :gparams, :sparams, :api
158
170
 
159
171
  private
160
172
 
@@ -176,72 +188,80 @@ module Aspera
176
188
  def initialize(a_params)
177
189
  Log.log.debug("auth=#{a_params}")
178
190
  # replace default values
179
- @params=DEFAULT_CREATE_PARAMS.clone.deep_merge(a_params)
180
- if @params.has_key?(:redirect_uri)
181
- uri=URI.parse(@params[:web][:redirect_uri])
182
- raise 'redirect_uri scheme must be http or https' unless ['http','https'].include?(uri.scheme)
191
+ @gparams = DEFAULT_CREATE_PARAMS.deep_merge(a_params)
192
+ # check that type is known
193
+ self.class.token_creator(@gparams[:crtype])
194
+ # specific parameters for the creation type
195
+ @sparams=@gparams[@gparams[:crtype]]
196
+ if @gparams[:crtype].eql?(:web) && @sparams.has_key?(:redirect_uri)
197
+ uri = URI.parse(@sparams[:redirect_uri])
198
+ raise 'redirect_uri scheme must be http or https' unless %w[http https].include?(uri.scheme)
183
199
  raise 'redirect_uri must have a port' if uri.port.nil?
184
200
  # TODO: we could check that host is localhost or local address
185
201
  end
186
- rest_params={base_url: @params[:base_url]}
187
- rest_params[:auth]=a_params[:auth] if a_params.has_key?(:auth)
188
- @token_auth_api=Rest.new(rest_params)
189
- end
190
-
191
- # @return unique identifier of token
192
- # TODO: external handlers shall provide unique identifiers
193
- def token_cache_id
194
- parts=[PERSIST_CATEGORY_TOKEN,@params[:base_url]]
195
- # add some of the parameters that uniquely identify the token
196
- self.class.id_elements.each do |p|
197
- identifier=@params.dig(*p)
198
- identifier=identifier.split(':').last if identifier.is_a?(String) && p.last.eql?(:grant_type)
199
- parts.push(identifier) unless identifier.nil?
200
- end
201
- return IdGenerator.from_list(parts)
202
+ rest_params = {
203
+ base_url: @gparams[:base_url],
204
+ redirect_max: 2
205
+ }
206
+ rest_params[:auth] = a_params[:auth] if a_params.has_key?(:auth)
207
+ @api = Rest.new(rest_params)
208
+ # if needed use from api
209
+ @gparams.delete(:base_url)
210
+ @gparams.delete(:auth)
211
+ @gparams.delete(@gparams[:crtype])
212
+ Log.dump(:gparams,@gparams)
213
+ Log.dump(:sparams,@sparams)
202
214
  end
203
215
 
204
216
  public
205
217
 
206
218
  # helper method to create token as per RFC
207
219
  def create_token(www_params)
208
- return @token_auth_api.call({
220
+ return @api.call({
209
221
  operation: 'POST',
210
- subpath: @params[:path_token],
211
- headers: {'Accept'=>'application/json'},
222
+ subpath: @gparams[:path_token],
223
+ headers: {'Accept' => 'application/json'},
212
224
  www_body_params: www_params})
213
225
  end
214
226
 
215
- # helper method
216
- def optional_scope_client_id(call_params, add_secret: false)
217
- call_params[:scope] = @params[:scope] unless @params[:scope].nil?
218
- call_params[:client_id] = @params[:client_id] unless @params[:client_id].nil?
219
- call_params[:client_secret] = @params[:client_secret] if add_secret && !@params[:client_id].nil?
227
+ # @return Hash with optional general parameters
228
+ def optional_scope_client_id(add_secret: false)
229
+ call_params={}
230
+ call_params[:scope] = @gparams[:scope] unless @gparams[:scope].nil?
231
+ call_params[:client_id] = @gparams[:client_id] unless @gparams[:client_id].nil?
232
+ call_params[:client_secret] = @gparams[:client_secret] if add_secret && !@gparams[:client_id].nil?
220
233
  return call_params
221
234
  end
222
235
 
223
236
  # Oauth v2 token generation
224
237
  # @param use_refresh_token set to true to force refresh or re-generation (if previous failed)
225
- def get_authorization(use_refresh_token: false)
238
+ def get_authorization(use_refresh_token: false, use_cache: true)
226
239
  # generate token unique identifier for persistency (memory/disk cache)
227
- token_id=token_cache_id
240
+ token_id = IdGenerator.from_list([
241
+ PERSIST_CATEGORY_TOKEN,
242
+ @api.params[:base_url],
243
+ @gparams[:crtype],
244
+ self.class.id_creator(@gparams[:crtype]).call(self), # array, so we flatten later
245
+ @gparams[:scope],
246
+ @api.params.dig(%i[auth username])
247
+ ].flatten)
228
248
 
229
249
  # get token_data from cache (or nil), token_data is what is returned by /token
230
- token_data=self.class.persist_mgr.get(token_id)
231
- token_data=JSON.parse(token_data) unless token_data.nil?
250
+ token_data = self.class.persist_mgr.get(token_id) if use_cache
251
+ token_data = JSON.parse(token_data) unless token_data.nil?
232
252
  # Optional optimization: check if node token is expired basd on decoded content then force refresh if close enough
233
253
  # might help in case the transfer agent cannot refresh himself
234
254
  # `direct` agent is equipped with refresh code
235
255
  if !use_refresh_token && !token_data.nil?
236
- decoded_token = self.class.decode_token(token_data[@params[:token_field]])
256
+ decoded_token = self.class.decode_token(token_data[@gparams[:token_field]])
237
257
  Log.dump('decoded_token',decoded_token) unless decoded_token.nil?
238
258
  if decoded_token.is_a?(Hash)
239
- expires_at_sec=
240
- if decoded_token['expires_at'].is_a?(String) then DateTime.parse(decoded_token['expires_at']).to_time
241
- elsif decoded_token['exp'].is_a?(Integer) then Time.at(decoded_token['exp'])
242
- end
259
+ expires_at_sec =
260
+ if decoded_token['expires_at'].is_a?(String) then DateTime.parse(decoded_token['expires_at']).to_time
261
+ elsif decoded_token['exp'].is_a?(Integer) then Time.at(decoded_token['exp'])
262
+ end
243
263
  # force refresh if we see a token too close from expiration
244
- use_refresh_token=true if expires_at_sec.is_a?(Time) && (expires_at_sec-Time.now) < TOKEN_EXPIRATION_GUARD_SEC
264
+ use_refresh_token = true if expires_at_sec.is_a?(Time) && (expires_at_sec - Time.now) < TOKEN_EXPIRATION_GUARD_SEC
245
265
  Log.log.debug("Expiration: #{expires_at_sec} / #{use_refresh_token}")
246
266
  end
247
267
  end
@@ -250,21 +270,21 @@ module Aspera
250
270
  if use_refresh_token
251
271
  if token_data.is_a?(Hash) && token_data.has_key?('refresh_token')
252
272
  # save possible refresh token, before deleting the cache
253
- refresh_token=token_data['refresh_token']
273
+ refresh_token = token_data['refresh_token']
254
274
  end
255
275
  # delete cache
256
276
  self.class.persist_mgr.delete(token_id)
257
- token_data=nil
277
+ token_data = nil
258
278
  # lets try the existing refresh token
259
279
  if !refresh_token.nil?
260
280
  Log.log.info("refresh=[#{refresh_token}]".bg_green)
261
281
  # try to refresh
262
282
  # note: AoC admin token has no refresh, and lives by default 1800secs
263
- resp=create_token(optional_scope_client_id({grant_type: 'refresh_token',refresh_token: refresh_token}))
283
+ resp = create_token(optional_scope_client_id.merge(grant_type: 'refresh_token',refresh_token: refresh_token))
264
284
  if resp[:http].code.start_with?('2')
265
285
  # save only if success
266
- json_data=resp[:http].body
267
- token_data=JSON.parse(json_data)
286
+ json_data = resp[:http].body
287
+ token_data = JSON.parse(json_data)
268
288
  self.class.persist_mgr.put(token_id,json_data)
269
289
  else
270
290
  Log.log.debug("refresh failed: #{resp[:http].body}".bg_red)
@@ -274,14 +294,14 @@ module Aspera
274
294
 
275
295
  # no cache, nor refresh: generate a token
276
296
  if token_data.nil?
277
- resp=self.class.token_creator(@params[:crtype]).call(self)
278
- json_data=resp[:http].body
279
- token_data=JSON.parse(json_data)
297
+ resp = self.class.token_creator(@gparams[:crtype]).call(self)
298
+ json_data = resp[:http].body
299
+ token_data = JSON.parse(json_data)
280
300
  self.class.persist_mgr.put(token_id,json_data)
281
301
  end # if ! in_cache
282
- raise "API error: No such field in answer: #{@params[:token_field]}" unless token_data.has_key?(@params[:token_field])
302
+ raise "API error: No such field in answer: #{@gparams[:token_field]}" unless token_data.has_key?(@gparams[:token_field])
283
303
  # ok we shall have a token here
284
- return 'Bearer '+token_data[@params[:token_field]]
304
+ return 'Bearer ' + token_data[@gparams[:token_field]]
285
305
  end
286
306
  end # OAuth
287
307
  end # Aspera
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'aspera/log'
3
4
  require 'aspera/environment'
4
5
  require 'rbconfig'
@@ -10,34 +11,36 @@ module Aspera
10
11
  # if method is "graphical", then the URL will be opened with the default browser.
11
12
  class OpenApplication
12
13
  include Singleton
13
- # User Interfaces
14
- def self.user_interfaces; [:text, :graphical]; end
14
+ class << self
15
+ # User Interfaces
16
+ def user_interfaces; %i[text graphical]; end
15
17
 
16
- def self.default_gui_mode
17
- return :graphical if [Aspera::Environment::OS_WINDOWS,Aspera::Environment::OS_X].include?(Aspera::Environment.os)
18
- # unix family
19
- return :graphical if ENV.has_key?('DISPLAY') && !ENV['DISPLAY'].empty?
20
- return :text
21
- end
18
+ def default_gui_mode
19
+ return :graphical if [Aspera::Environment::OS_WINDOWS,Aspera::Environment::OS_X].include?(Aspera::Environment.os)
20
+ # unix family
21
+ return :graphical if ENV.has_key?('DISPLAY') && !ENV['DISPLAY'].empty?
22
+ return :text
23
+ end
22
24
 
23
- # command must be non blocking
24
- def self.uri_graphical(uri)
25
- case Aspera::Environment.os
26
- when Aspera::Environment::OS_X
27
- return system('open',uri.to_s)
28
- when Aspera::Environment::OS_WINDOWS
29
- return system('start explorer "'+uri.to_s+'"')
30
- when Aspera::Environment::OS_LINUX
31
- return system("xdg-open '#{uri}'")
32
- else
33
- raise "no graphical open method for #{Aspera::Environment.os}"
25
+ # command must be non blocking
26
+ def uri_graphical(uri)
27
+ case Aspera::Environment.os
28
+ when Aspera::Environment::OS_X
29
+ return system('open',uri.to_s)
30
+ when Aspera::Environment::OS_WINDOWS
31
+ return system('start explorer "' + uri.to_s + '"')
32
+ when Aspera::Environment::OS_LINUX
33
+ return system("xdg-open '#{uri}'")
34
+ else
35
+ raise "no graphical open method for #{Aspera::Environment.os}"
36
+ end
34
37
  end
35
38
  end
36
39
 
37
40
  attr_accessor :url_method
38
41
 
39
42
  def initialize
40
- @url_method=self.class.default_gui_mode
43
+ @url_method = self.class.default_gui_mode
41
44
  end
42
45
 
43
46
  # this is non blocking
@@ -48,9 +51,9 @@ module Aspera
48
51
  when :text
49
52
  case the_url.to_s
50
53
  when /^http/
51
- puts "USER ACTION: please enter this url in a browser:\n"+the_url.to_s.red()+"\n"
54
+ puts "USER ACTION: please enter this url in a browser:\n" + the_url.to_s.red + "\n"
52
55
  else
53
- puts "USER ACTION: open this:\n"+the_url.to_s.red()+"\n"
56
+ puts "USER ACTION: open this:\n" + the_url.to_s.red + "\n"
54
57
  end
55
58
  else
56
59
  raise StandardError,"unsupported url open method: #{@url_method}"
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'json'
3
4
  require 'aspera/log'
4
5
 
@@ -19,15 +20,15 @@ module Aspera
19
20
  raise 'mandatory :data' if options[:data].nil?
20
21
  raise 'mandatory :id (String)' unless options[:id].is_a?(String)
21
22
  raise 'mandatory 1 element in :id' unless options[:id].length >= 1
22
- @manager=options[:manager]
23
- @persisted_object=options[:data]
24
- @object_id=options[:id]
23
+ @manager = options[:manager]
24
+ @persisted_object = options[:data]
25
+ @object_id = options[:id]
25
26
  # by default , at save time, file is deleted if data is nil
26
- @delete_condition=options[:delete] || lambda{|d|d.empty?}
27
- @persist_format=options[:format] || lambda {|h| JSON.generate(h)}
28
- persist_parse=options[:parse] || lambda {|t| JSON.parse(t)}
29
- persist_merge=options[:merge] || lambda {|current,file| current.concat(file).uniq rescue current}
30
- value=@manager.get(@object_id)
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}
31
+ value = @manager.get(@object_id)
31
32
  persist_merge.call(@persisted_object,persist_parse.call(value)) unless value.nil?
32
33
  end
33
34
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'fileutils'
3
4
  require 'aspera/log'
4
5
 
@@ -7,11 +8,11 @@ require 'aspera/log'
7
8
  module Aspera
8
9
  # Persist data on file system
9
10
  class PersistencyFolder
10
- FILE_SUFFIX='.txt'
11
+ FILE_SUFFIX = '.txt'
11
12
  private_constant :FILE_SUFFIX
12
13
  def initialize(folder)
13
- @cache={}
14
- @folder=folder
14
+ @cache = {}
15
+ @folder = folder
15
16
  Log.log.debug("persistency folder: #{@folder}")
16
17
  end
17
18
 
@@ -21,11 +22,11 @@ module Aspera
21
22
  if @cache.has_key?(object_id)
22
23
  Log.log.debug('got from memory cache')
23
24
  else
24
- persist_filepath=id_to_filepath(object_id)
25
+ persist_filepath = id_to_filepath(object_id)
25
26
  Log.log.debug("persistency = #{persist_filepath}")
26
27
  if File.exist?(persist_filepath)
27
28
  Log.log.debug('got from file cache')
28
- @cache[object_id]=File.read(persist_filepath)
29
+ @cache[object_id] = File.read(persist_filepath)
29
30
  end
30
31
  end
31
32
  return @cache[object_id]
@@ -33,21 +34,23 @@ module Aspera
33
34
 
34
35
  def put(object_id,value)
35
36
  raise 'value: only String supported' unless value.is_a?(String)
36
- persist_filepath=id_to_filepath(object_id)
37
+ persist_filepath = id_to_filepath(object_id)
37
38
  Log.log.debug("persistency saving: #{persist_filepath}")
39
+ File.delete(persist_filepath) if File.exist?(persist_filepath)
38
40
  File.write(persist_filepath,value)
39
- @cache[object_id]=value
41
+ File.chmod(0400,persist_filepath)
42
+ @cache[object_id] = value
40
43
  end
41
44
 
42
45
  def delete(object_id)
43
- persist_filepath=id_to_filepath(object_id)
46
+ persist_filepath = id_to_filepath(object_id)
44
47
  Log.log.debug("persistency deleting: #{persist_filepath}")
45
48
  File.delete(persist_filepath) if File.exist?(persist_filepath)
46
49
  @cache.delete(object_id)
47
50
  end
48
51
 
49
52
  def garbage_collect(persist_category,max_age_seconds=nil)
50
- garbage_files=Dir[File.join(@folder,persist_category+'*'+FILE_SUFFIX)]
53
+ garbage_files = Dir[File.join(@folder,persist_category + '*' + FILE_SUFFIX)]
51
54
  if !max_age_seconds.nil?
52
55
  current_time = Time.now
53
56
  garbage_files.select! { |filepath| (current_time - File.stat(filepath).mtime).to_i > max_age_seconds}
@@ -65,6 +68,7 @@ module Aspera
65
68
  def id_to_filepath(object_id)
66
69
  raise 'object_id: only String supported' unless object_id.is_a?(String)
67
70
  FileUtils.mkdir_p(@folder)
71
+ File.chmod(0700,@folder)
68
72
  return File.join(@folder,"#{object_id}#{FILE_SUFFIX}")
69
73
  #.gsub(/[^a-z]+/,FILE_FIELD_SEPARATOR)
70
74
  end