aspera-cli 4.6.0 → 4.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +427 -300
  3. data/bin/ascli +2 -1
  4. data/bin/asession +1 -0
  5. data/docs/test_env.conf +2 -0
  6. data/examples/aoc.rb +4 -3
  7. data/examples/faspex4.rb +21 -19
  8. data/examples/proxy.pac +1 -1
  9. data/examples/transfer.rb +15 -15
  10. data/lib/aspera/aoc.rb +135 -124
  11. data/lib/aspera/ascmd.rb +85 -75
  12. data/lib/aspera/ats_api.rb +11 -10
  13. data/lib/aspera/cli/basic_auth_plugin.rb +13 -14
  14. data/lib/aspera/cli/extended_value.rb +42 -33
  15. data/lib/aspera/cli/formater.rb +138 -111
  16. data/lib/aspera/cli/info.rb +17 -0
  17. data/lib/aspera/cli/listener/line_dump.rb +3 -2
  18. data/lib/aspera/cli/listener/logger.rb +2 -1
  19. data/lib/aspera/cli/listener/progress.rb +16 -18
  20. data/lib/aspera/cli/listener/progress_multi.rb +13 -16
  21. data/lib/aspera/cli/main.rb +122 -130
  22. data/lib/aspera/cli/manager.rb +146 -154
  23. data/lib/aspera/cli/plugin.rb +38 -34
  24. data/lib/aspera/cli/plugins/alee.rb +6 -6
  25. data/lib/aspera/cli/plugins/aoc.rb +273 -276
  26. data/lib/aspera/cli/plugins/ats.rb +82 -76
  27. data/lib/aspera/cli/plugins/bss.rb +14 -16
  28. data/lib/aspera/cli/plugins/config.rb +350 -306
  29. data/lib/aspera/cli/plugins/console.rb +23 -19
  30. data/lib/aspera/cli/plugins/cos.rb +18 -18
  31. data/lib/aspera/cli/plugins/faspex.rb +180 -159
  32. data/lib/aspera/cli/plugins/faspex5.rb +64 -54
  33. data/lib/aspera/cli/plugins/node.rb +147 -140
  34. data/lib/aspera/cli/plugins/orchestrator.rb +68 -66
  35. data/lib/aspera/cli/plugins/preview.rb +92 -96
  36. data/lib/aspera/cli/plugins/server.rb +79 -75
  37. data/lib/aspera/cli/plugins/shares.rb +23 -24
  38. data/lib/aspera/cli/plugins/sync.rb +20 -22
  39. data/lib/aspera/cli/transfer_agent.rb +40 -39
  40. data/lib/aspera/cli/version.rb +2 -1
  41. data/lib/aspera/colors.rb +35 -27
  42. data/lib/aspera/command_line_builder.rb +48 -34
  43. data/lib/aspera/cos_node.rb +29 -21
  44. data/lib/aspera/data_repository.rb +3 -2
  45. data/lib/aspera/environment.rb +50 -45
  46. data/lib/aspera/fasp/agent_base.rb +22 -20
  47. data/lib/aspera/fasp/agent_connect.rb +13 -11
  48. data/lib/aspera/fasp/agent_direct.rb +48 -59
  49. data/lib/aspera/fasp/agent_httpgw.rb +33 -39
  50. data/lib/aspera/fasp/agent_node.rb +15 -13
  51. data/lib/aspera/fasp/agent_trsdk.rb +12 -14
  52. data/lib/aspera/fasp/error.rb +2 -1
  53. data/lib/aspera/fasp/error_info.rb +68 -52
  54. data/lib/aspera/fasp/installation.rb +106 -94
  55. data/lib/aspera/fasp/listener.rb +1 -0
  56. data/lib/aspera/fasp/parameters.rb +83 -92
  57. data/lib/aspera/fasp/parameters.yaml +305 -249
  58. data/lib/aspera/fasp/resume_policy.rb +11 -14
  59. data/lib/aspera/fasp/transfer_spec.rb +26 -0
  60. data/lib/aspera/fasp/uri.rb +22 -21
  61. data/lib/aspera/faspex_gw.rb +55 -90
  62. data/lib/aspera/hash_ext.rb +4 -3
  63. data/lib/aspera/id_generator.rb +8 -7
  64. data/lib/aspera/keychain/encrypted_hash.rb +17 -16
  65. data/lib/aspera/keychain/macos_security.rb +6 -10
  66. data/lib/aspera/log.rb +25 -20
  67. data/lib/aspera/nagios.rb +13 -12
  68. data/lib/aspera/node.rb +30 -22
  69. data/lib/aspera/oauth.rb +175 -226
  70. data/lib/aspera/open_application.rb +4 -3
  71. data/lib/aspera/persistency_action_once.rb +6 -6
  72. data/lib/aspera/persistency_folder.rb +5 -9
  73. data/lib/aspera/preview/file_types.rb +6 -5
  74. data/lib/aspera/preview/generator.rb +25 -24
  75. data/lib/aspera/preview/options.rb +16 -14
  76. data/lib/aspera/preview/utils.rb +98 -98
  77. data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
  78. data/lib/aspera/proxy_auto_config.rb +111 -20
  79. data/lib/aspera/rest.rb +115 -113
  80. data/lib/aspera/rest_call_error.rb +2 -2
  81. data/lib/aspera/rest_error_analyzer.rb +23 -25
  82. data/lib/aspera/rest_errors_aspera.rb +15 -14
  83. data/lib/aspera/ssh.rb +12 -10
  84. data/lib/aspera/sync.rb +42 -41
  85. data/lib/aspera/temp_file_manager.rb +18 -14
  86. data/lib/aspera/timer_limiter.rb +2 -1
  87. data/lib/aspera/uri_reader.rb +7 -5
  88. data/lib/aspera/web_auth.rb +79 -76
  89. metadata +64 -21
  90. data/docs/Makefile +0 -65
  91. data/docs/README.erb.md +0 -4424
  92. data/docs/README.md +0 -13
  93. data/docs/diagrams.txt +0 -49
  94. data/docs/doc_tools.rb +0 -58
  95. data/lib/aspera/cli/plugins/shares2.rb +0 -114
  96. data/lib/aspera/fasp/default.rb +0 -17
data/lib/aspera/oauth.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'aspera/open_application'
2
3
  require 'aspera/web_auth'
3
4
  require 'aspera/id_generator'
@@ -11,14 +12,17 @@ module Aspera
11
12
  # call get_authorization() to get a token.
12
13
  # bearer tokens are kept in memory and also in a file cache for later re-use
13
14
  # if a token is expired (api returns 4xx), call again get_authorization({refresh: true})
15
+ # https://tools.ietf.org/html/rfc6749
14
16
  class Oauth
15
- # used for code exchange
16
- DEFAULT_PATH_AUTHORIZE='authorize'
17
- # to generate token
18
- DEFAULT_PATH_TOKEN='token'
19
- # field with token in result
20
- DEFAULT_TOKEN_FIELD='access_token'
21
- private
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
21
+ }.freeze
22
+
23
+ # OAuth methods supported by default
24
+ STD_AUTH_TYPES=[:web, :jwt].freeze
25
+
22
26
  # remove 5 minutes to account for time offset (TODO: configurable?)
23
27
  JWT_NOTBEFORE_OFFSET_SEC=300
24
28
  # one hour validity (TODO: configurable?)
@@ -27,184 +31,237 @@ module Aspera
27
31
  TOKEN_CACHE_EXPIRY_SEC=1800
28
32
  # a prefix for persistency of tokens (garbage collect)
29
33
  PERSIST_CATEGORY_TOKEN='token'
30
- ONE_HOUR_AS_DAY_FRACTION=Rational(1,24)
34
+ TOKEN_EXPIRATION_GUARD_SEC=120
31
35
 
32
- private_constant :JWT_NOTBEFORE_OFFSET_SEC,:JWT_EXPIRY_OFFSET_SEC,:PERSIST_CATEGORY_TOKEN,:TOKEN_CACHE_EXPIRY_SEC,:ONE_HOUR_AS_DAY_FRACTION
33
- class << self
34
- # OAuth methods supported
35
- def auth_types
36
- [ :body_userpass, :header_userpass, :web, :jwt, :url_token, :ibm_apikey ]
37
- end
36
+ private_constant :JWT_NOTBEFORE_OFFSET_SEC,:JWT_EXPIRY_OFFSET_SEC,:TOKEN_CACHE_EXPIRY_SEC,:PERSIST_CATEGORY_TOKEN,:TOKEN_EXPIRATION_GUARD_SEC
38
37
 
38
+ # persistency manager
39
+ @persist=nil
40
+ # token creation methods
41
+ @handlers={}
42
+ # 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
+ ]
53
+
54
+ class << self
39
55
  def persist_mgr=(manager)
40
56
  @persist=manager
57
+ # cleanup expired tokens
58
+ @persist.garbage_collect(PERSIST_CATEGORY_TOKEN,TOKEN_CACHE_EXPIRY_SEC)
41
59
  end
42
60
 
43
61
  def persist_mgr
44
62
  if @persist.nil?
45
- Log.log.warn('Not using persistency (use Aspera::Oauth.persist_mgr=Aspera::PersistencyFolder.new)')
63
+ Log.log.debug('Not using persistency') # (use Aspera::Oauth.persist_mgr=Aspera::PersistencyFolder.new)
46
64
  # create NULL persistency class
47
65
  @persist=Class.new do
48
- def get(x);nil;end;def delete(x);nil;end;def put(x,y);nil;end;def garbage_collect(x,y);nil;end
66
+ 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
49
67
  end.new
50
68
  end
51
69
  return @persist
52
70
  end
53
71
 
72
+ # delete all existing tokens
54
73
  def flush_tokens
55
74
  persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN,nil)
56
75
  end
57
76
 
77
+ # register a bearer token decoder, mainly to inspect expiry date
58
78
  def register_decoder(method)
59
79
  @decoders||=[]
60
80
  @decoders.push(method)
61
81
  end
62
82
 
83
+ # decode token using all registered decoders
63
84
  def decode_token(token)
64
- Log.log.debug(">>>> #{token} : #{@decoders.length}")
65
85
  @decoders.each do |decoder|
66
86
  result=decoder.call(token) rescue nil
67
87
  return result unless result.nil?
68
88
  end
69
89
  return nil
70
90
  end
71
- end
72
91
 
73
- # seems to be quite standard token encoding (RFC?)
74
- self.register_decoder lambda { |token| parts=token.split('.'); raise "not aoc token" unless parts.length.eql?(3); JSON.parse(Base64.decode64(parts[1]))}
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
96
+ end
75
97
 
76
- # for supported parameters, look in the code for @params
77
- # parameters are provided all with oauth_ prefix :
78
- # :base_url
79
- # :client_id
80
- # :client_secret
81
- # :redirect_uri
82
- # :jwt_audience
83
- # :jwt_private_key_obj
84
- # :jwt_subject
85
- # :path_authorize (default: DEFAULT_PATH_AUTHORIZE)
86
- # :path_token (default: DEFAULT_PATH_TOKEN)
87
- # :scope (optional)
88
- # :grant (one of returned by self.auth_types)
89
- # :url_token
90
- # :user_name
91
- # :user_pass
92
- # :token_type
93
- def initialize(auth_params)
94
- Log.log.debug("auth=#{auth_params}")
95
- @params=auth_params.clone
96
- # default values
97
- # name of field to take as token from result of call to /token
98
- @params[:token_field]||=DEFAULT_TOKEN_FIELD
99
- # default endpoint for /token
100
- @params[:path_token]||=DEFAULT_PATH_TOKEN
101
- # default endpoint for /authorize
102
- @params[:path_authorize]||=DEFAULT_PATH_AUTHORIZE
103
- rest_params={base_url: @params[:base_url]}
104
- if @params.has_key?(:client_id)
105
- rest_params.merge!({auth: {
106
- type: :basic,
107
- username: @params[:client_id],
108
- password: @params[:client_secret]
109
- }})
98
+ # @return one of the registered creators for the given create type
99
+ def token_creator(id)
100
+ raise "token create type unknown: #{id}/#{id.class}" unless @handlers.has_key?(id)
101
+ @handlers[id]
110
102
  end
111
- @token_auth_api=Rest.new(rest_params)
112
- if @params.has_key?(:redirect_uri)
113
- uri=URI.parse(@params[:redirect_uri])
114
- raise "redirect_uri scheme must be http" unless uri.scheme.start_with?('http')
115
- raise "redirect_uri must have a port" if uri.port.nil?
116
- # we could check that host is localhost or local address
103
+
104
+ # list of identifiers foundn in creation parameters that can be used to uniquely identify the token
105
+ def id_elements
106
+ return @id_elements
117
107
  end
118
- # cleanup expired tokens
119
- self.class.persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN,TOKEN_CACHE_EXPIRY_SEC)
120
- end
108
+ end # self
109
+
110
+ # 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]))}
121
112
 
122
- # open the login page, wait for code and check_code, then return code
123
- def goto_page_and_get_code(login_page_url,check_code)
113
+ # generic token creation, parameters are provided in :generic
114
+ register_token_creator :generic,lambda { |oauth|
115
+ return oauth.create_token(oauth.params[:generic])
116
+ }
117
+
118
+ # Authentication using Web browser
119
+ 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}))
122
+ # here, we need a human to authorize on a web page
124
123
  Log.log.info("login_page_url=#{login_page_url}".bg_red.gray)
125
124
  # start a web server to receive request code
126
- webserver=WebAuth.new(@params[:redirect_uri])
125
+ webserver=WebAuth.new(oauth.params[:web][:redirect_uri])
127
126
  # start browser on login page
128
127
  OpenApplication.instance.uri(login_page_url)
129
128
  # wait for code in request
130
- request_params=webserver.get_request
131
- Log.log.error("state does not match") if !check_code.eql?(request_params['state'])
132
- code=request_params['code']
133
- return code
134
- end
129
+ received_params=webserver.received_request
130
+ raise 'state does not match' if !callback_verif.eql?(received_params['state'])
131
+ # 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))
133
+ }
135
134
 
136
- def create_token(rest_params)
137
- return @token_auth_api.call({
138
- operation: 'POST',
139
- subpath: @params[:path_token],
140
- headers: {'Accept'=>'application/json'}}.merge(rest_params))
135
+ # Authentication using private key
136
+ register_token_creator :jwt,lambda { |oauth|
137
+ # https://tools.ietf.org/html/rfc7523
138
+ # https://tools.ietf.org/html/rfc7519
139
+ require 'jwt'
140
+ seconds_since_epoch=Time.new.to_i
141
+ Log.log.info("seconds=#{seconds_since_epoch}")
142
+ raise 'missing JWT payload' unless oauth.params[:jwt][:payload].is_a?(Hash)
143
+ jwt_payload = {
144
+ exp: seconds_since_epoch+JWT_EXPIRY_OFFSET_SEC, # expiration time
145
+ nbf: seconds_since_epoch-JWT_NOTBEFORE_OFFSET_SEC, # not before
146
+ iat: seconds_since_epoch, # issued at
147
+ jti: SecureRandom.uuid # JWT id
148
+ }.merge(oauth.params[:jwt][:payload])
149
+ Log.log.debug("JWT jwt_payload=[#{jwt_payload}]")
150
+ rsa_private=oauth.params[:jwt][:private_key_obj] # type: OpenSSL::PKey::RSA
151
+ Log.log.debug("private=[#{rsa_private}]")
152
+ assertion = JWT.encode(jwt_payload, rsa_private, 'RS256', oauth.params[:jwt][:headers]||{})
153
+ 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}))
155
+ }
156
+
157
+ attr_reader :params, :token_auth_api
158
+
159
+ private
160
+
161
+ # [M]=mandatory [D]=has default value [0]=accept nil
162
+ # :base_url [M] URL of authentication API
163
+ # :auth
164
+ # :crtype [M] :generic, :web, :jwt, custom
165
+ # :client_id [0]
166
+ # :client_secret [0]
167
+ # :scope [0]
168
+ # :path_token [D] API end point to create a token
169
+ # :token_field [D] field in result that contains the token
170
+ # :jwt:private_key_obj [M] for type :jwt
171
+ # :jwt:payload [M] for type :jwt
172
+ # :jwt:headers [0] for type :jwt
173
+ # :web:redirect_uri [M] for type :web
174
+ # :web:path_authorize [D] for type :web
175
+ # :generic [M] for type :generic
176
+ def initialize(a_params)
177
+ Log.log.debug("auth=#{a_params}")
178
+ # 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)
183
+ raise 'redirect_uri must have a port' if uri.port.nil?
184
+ # TODO: we could check that host is localhost or local address
185
+ 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)
141
189
  end
142
190
 
143
191
  # @return unique identifier of token
144
- def token_cache_id(api_scope)
145
- oauth_uri=URI.parse(@params[:base_url])
146
- parts=[PERSIST_CATEGORY_TOKEN,api_scope,oauth_uri.host,oauth_uri.path]
147
- # add some of the parameters that uniquely define the token
148
- [:grant,:jwt_subject,:user_name,:url_token,:api_key].inject(parts){|p,i|p.push(@params[i])}
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
149
201
  return IdGenerator.from_list(parts)
150
202
  end
151
203
 
152
204
  public
153
205
 
154
- # used to change parameter, such as scope
155
- attr_reader :params
206
+ # helper method to create token as per RFC
207
+ def create_token(www_params)
208
+ return @token_auth_api.call({
209
+ operation: 'POST',
210
+ subpath: @params[:path_token],
211
+ headers: {'Accept'=>'application/json'},
212
+ www_body_params: www_params})
213
+ end
156
214
 
157
- # @param options : :scope and :refresh
158
- def get_authorization(options={})
159
- # api scope can be overriden to get auth for other scope
160
- api_scope=options[:scope] || @params[:scope]
161
- # as it is optional in many place: create struct
162
- p_scope={}
163
- p_scope[:scope] = api_scope unless api_scope.nil?
164
- p_client_id_and_scope=p_scope.clone
165
- p_client_id_and_scope[:client_id] = @params[:client_id] if @params.has_key?(:client_id)
166
- use_refresh_token=options[:refresh]
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?
220
+ return call_params
221
+ end
167
222
 
168
- # generate token identifier to use with cache
169
- token_id=token_cache_id(api_scope)
223
+ # Oauth v2 token generation
224
+ # @param use_refresh_token set to true to force refresh or re-generation (if previous failed)
225
+ def get_authorization(use_refresh_token: false)
226
+ # generate token unique identifier for persistency (memory/disk cache)
227
+ token_id=token_cache_id
170
228
 
171
229
  # get token_data from cache (or nil), token_data is what is returned by /token
172
230
  token_data=self.class.persist_mgr.get(token_id)
173
231
  token_data=JSON.parse(token_data) unless token_data.nil?
174
- # Optional optimization: check if node token is expired, then force refresh
175
- # in case the transfer agent cannot refresh himself
176
- # else, anyway, faspmanager is equipped with refresh code
177
- if !token_data.nil?
178
- # TODO: use @params[:token_field] ?
179
- decoded_node_token = self.class.decode_token(token_data['access_token'])
180
- Log.dump('decoded_node_token',decoded_node_token) unless decoded_node_token.nil?
181
- if decoded_node_token.is_a?(Hash) and decoded_node_token['expires_at'].is_a?(String)
182
- expires_at=DateTime.parse(decoded_node_token['expires_at'])
183
- # Time.at(decoded_node_token['exp'])
184
- # does it seem expired, with one hour of security
185
- use_refresh_token=true if DateTime.now > (expires_at-ONE_HOUR_AS_DAY_FRACTION)
232
+ # Optional optimization: check if node token is expired basd on decoded content then force refresh if close enough
233
+ # might help in case the transfer agent cannot refresh himself
234
+ # `direct` agent is equipped with refresh code
235
+ if !use_refresh_token && !token_data.nil?
236
+ decoded_token = self.class.decode_token(token_data[@params[:token_field]])
237
+ Log.dump('decoded_token',decoded_token) unless decoded_token.nil?
238
+ 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
243
+ # 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
245
+ Log.log.debug("Expiration: #{expires_at_sec} / #{use_refresh_token}")
186
246
  end
187
247
  end
188
248
 
189
249
  # an API was already called, but failed, we need to regenerate or refresh
190
250
  if use_refresh_token
191
- if token_data.is_a?(Hash) and token_data.has_key?('refresh_token')
251
+ if token_data.is_a?(Hash) && token_data.has_key?('refresh_token')
192
252
  # save possible refresh token, before deleting the cache
193
253
  refresh_token=token_data['refresh_token']
194
254
  end
195
- # delete caches
255
+ # delete cache
196
256
  self.class.persist_mgr.delete(token_id)
197
257
  token_data=nil
198
258
  # lets try the existing refresh token
199
259
  if !refresh_token.nil?
200
260
  Log.log.info("refresh=[#{refresh_token}]".bg_green)
201
261
  # try to refresh
202
- # note: admin token has no refresh, and lives by default 1800secs
203
- # Note: scope is mandatory in Files, and we can either provide basic auth, or client_Secret in data
204
- resp=create_token(www_body_params: p_client_id_and_scope.merge({
205
- grant_type: 'refresh_token',
206
- refresh_token: refresh_token}))
207
- if resp[:http].code.start_with?('2') then
262
+ # 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}))
264
+ if resp[:http].code.start_with?('2')
208
265
  # save only if success
209
266
  json_data=resp[:http].body
210
267
  token_data=JSON.parse(json_data)
@@ -215,116 +272,9 @@ module Aspera
215
272
  end
216
273
  end
217
274
 
218
- # no cache
219
- if token_data.nil? then
220
- resp=nil
221
- case @params[:grant]
222
- when :web
223
- # AoC Web based Auth
224
- check_code=SecureRandom.uuid
225
- auth_params=p_client_id_and_scope.merge({
226
- response_type: 'code',
227
- redirect_uri: @params[:redirect_uri],
228
- state: check_code
229
- })
230
- auth_params[:client_secret]=@params[:client_secret] if @params.has_key?(:client_secret)
231
- login_page_url=Rest.build_uri("#{@params[:base_url]}/#{@params[:path_authorize]}",auth_params)
232
- # here, we need a human to authorize on a web page
233
- code=goto_page_and_get_code(login_page_url,check_code)
234
- # exchange code for token
235
- resp=create_token(www_body_params: p_client_id_and_scope.merge({
236
- grant_type: 'authorization_code',
237
- code: code,
238
- redirect_uri: @params[:redirect_uri]
239
- }))
240
- when :jwt
241
- # https://tools.ietf.org/html/rfc7519
242
- # https://tools.ietf.org/html/rfc7523
243
- require 'jwt'
244
- seconds_since_epoch=Time.new.to_i
245
- Log.log.info("seconds=#{seconds_since_epoch}")
246
-
247
- payload = {
248
- iss: @params[:client_id], # issuer
249
- sub: @params[:jwt_subject], # subject
250
- aud: @params[:jwt_audience], # audience
251
- nbf: seconds_since_epoch-JWT_NOTBEFORE_OFFSET_SEC, # not before
252
- exp: seconds_since_epoch+JWT_EXPIRY_OFFSET_SEC # expiration
253
- }
254
- # Hum.. compliant ? TODO: remove when Faspex5 API is clarified
255
- if @params.has_key?(:f5_username)
256
- payload[:jti] = SecureRandom.uuid # JWT id
257
- payload[:iat] = seconds_since_epoch # issued at
258
- payload.delete(:nbf) # not used in f5
259
- p_scope[:redirect_uri]="https://127.0.0.1:5000/token" # used ?
260
- p_scope[:state]=SecureRandom.uuid
261
- p_scope[:client_id]=@params[:client_id]
262
- @token_auth_api.params[:auth]={type: :basic,username: @params[:f5_username], password: @params[:f5_password]}
263
- end
264
-
265
- # non standard, only for global ids
266
- payload.merge!(@params[:jwt_add]) if @params.has_key?(:jwt_add)
267
- Log.log.debug("JWT payload=[#{payload}]")
268
-
269
- rsa_private=@params[:jwt_private_key_obj] # type: OpenSSL::PKey::RSA
270
- Log.log.debug("private=[#{rsa_private}]")
271
-
272
- assertion = JWT.encode(payload, rsa_private, 'RS256', @params[:jwt_headers]||{})
273
- Log.log.debug("assertion=[#{assertion}]")
274
-
275
- resp=create_token(www_body_params: p_scope.merge({
276
- grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
277
- assertion: assertion
278
- }))
279
- when :url_token
280
- # AoC Public Link
281
- params={url_token: @params[:url_token]}
282
- params[:password]=@params[:password] if @params.has_key?(:password)
283
- resp=create_token({
284
- json_params: params,
285
- url_params: p_scope.merge({grant_type: 'url_token'})
286
- })
287
- when :ibm_apikey
288
- # ATS
289
- resp=create_token(www_body_params: {
290
- grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
291
- response_type: 'cloud_iam',
292
- apikey: @params[:api_key]
293
- })
294
- when :delegated_refresh
295
- # COS
296
- resp=create_token(www_body_params: {
297
- grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
298
- response_type: 'delegated_refresh_token',
299
- apikey: @params[:api_key],
300
- receiver_client_ids: 'aspera_ats'
301
- })
302
- when :header_userpass
303
- # used in Faspex apiv4 and shares2
304
- resp=create_token(
305
- json_params: p_client_id_and_scope.merge({grant_type: 'password'}), #:www_body_params also works
306
- auth: {
307
- type: :basic,
308
- username: @params[:user_name],
309
- password: @params[:user_pass]}
310
- )
311
- when :body_userpass
312
- # legacy, not used
313
- resp=create_token(www_body_params: p_client_id_and_scope.merge({
314
- grant_type: 'password',
315
- username: @params[:user_name],
316
- password: @params[:user_pass]
317
- }))
318
- when :body_data
319
- # used in Faspex apiv5
320
- resp=create_token({
321
- auth: {type: :none},
322
- json_params: @params[:userpass_body],
323
- })
324
- else
325
- raise "auth grant type unknown: #{@params[:grant]}"
326
- end
327
- # TODO: test return code ?
275
+ # no cache, nor refresh: generate a token
276
+ if token_data.nil?
277
+ resp=self.class.token_creator(@params[:crtype]).call(self)
328
278
  json_data=resp[:http].body
329
279
  token_data=JSON.parse(json_data)
330
280
  self.class.persist_mgr.put(token_id,json_data)
@@ -333,6 +283,5 @@ module Aspera
333
283
  # ok we shall have a token here
334
284
  return 'Bearer '+token_data[@params[:token_field]]
335
285
  end
336
-
337
286
  end # OAuth
338
287
  end # Aspera
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'aspera/log'
2
3
  require 'aspera/environment'
3
4
  require 'rbconfig'
@@ -10,12 +11,12 @@ module Aspera
10
11
  class OpenApplication
11
12
  include Singleton
12
13
  # User Interfaces
13
- def self.user_interfaces; [ :text, :graphical ]; end
14
+ def self.user_interfaces; [:text, :graphical]; end
14
15
 
15
16
  def self.default_gui_mode
16
17
  return :graphical if [Aspera::Environment::OS_WINDOWS,Aspera::Environment::OS_X].include?(Aspera::Environment.os)
17
18
  # unix family
18
- return :graphical if ENV.has_key?("DISPLAY") and !ENV["DISPLAY"].empty?
19
+ return :graphical if ENV.has_key?('DISPLAY') && !ENV['DISPLAY'].empty?
19
20
  return :text
20
21
  end
21
22
 
@@ -27,7 +28,7 @@ module Aspera
27
28
  when Aspera::Environment::OS_WINDOWS
28
29
  return system('start explorer "'+uri.to_s+'"')
29
30
  when Aspera::Environment::OS_LINUX
30
- return system("xdg-open '#{uri.to_s}'")
31
+ return system("xdg-open '#{uri}'")
31
32
  else
32
33
  raise "no graphical open method for #{Aspera::Environment.os}"
33
34
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'json'
2
3
  require 'aspera/log'
3
4
 
@@ -13,11 +14,11 @@ module Aspera
13
14
  # @param :merge Optional merge data from file to current data
14
15
  def initialize(options)
15
16
  Log.log.debug("persistency: #{options}")
16
- raise "options shall be Hash" unless options.is_a?(Hash)
17
- raise "mandatory :manager" if options[:manager].nil?
18
- raise "mandatory :data" if options[:data].nil?
19
- raise "mandatory :id (String)" unless options[:id].is_a?(String)
20
- raise "mandatory 1 element in :id" unless options[:id].length >= 1
17
+ raise 'options shall be Hash' unless options.is_a?(Hash)
18
+ raise 'mandatory :manager' if options[:manager].nil?
19
+ raise 'mandatory :data' if options[:data].nil?
20
+ raise 'mandatory :id (String)' unless options[:id].is_a?(String)
21
+ raise 'mandatory 1 element in :id' unless options[:id].length >= 1
21
22
  @manager=options[:manager]
22
23
  @persisted_object=options[:data]
23
24
  @object_id=options[:id]
@@ -41,6 +42,5 @@ module Aspera
41
42
  def data
42
43
  return @persisted_object
43
44
  end
44
-
45
45
  end
46
46
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'fileutils'
2
3
  require 'aspera/log'
3
4
 
@@ -10,10 +11,6 @@ module Aspera
10
11
  private_constant :FILE_SUFFIX
11
12
  def initialize(folder)
12
13
  @cache={}
13
- set_folder(folder)
14
- end
15
-
16
- def set_folder(folder)
17
14
  @folder=folder
18
15
  Log.log.debug("persistency folder: #{@folder}")
19
16
  end
@@ -22,12 +19,12 @@ module Aspera
22
19
  def get(object_id)
23
20
  Log.log.debug("persistency get: #{object_id}")
24
21
  if @cache.has_key?(object_id)
25
- Log.log.debug("got from memory cache")
22
+ Log.log.debug('got from memory cache')
26
23
  else
27
24
  persist_filepath=id_to_filepath(object_id)
28
25
  Log.log.debug("persistency = #{persist_filepath}")
29
26
  if File.exist?(persist_filepath)
30
- Log.log.debug("got from file cache")
27
+ Log.log.debug('got from file cache')
31
28
  @cache[object_id]=File.read(persist_filepath)
32
29
  end
33
30
  end
@@ -35,7 +32,7 @@ module Aspera
35
32
  end
36
33
 
37
34
  def put(object_id,value)
38
- raise "value: only String supported" unless value.is_a?(String)
35
+ raise 'value: only String supported' unless value.is_a?(String)
39
36
  persist_filepath=id_to_filepath(object_id)
40
37
  Log.log.debug("persistency saving: #{persist_filepath}")
41
38
  File.write(persist_filepath,value)
@@ -66,11 +63,10 @@ module Aspera
66
63
 
67
64
  # @param object_id String or Array
68
65
  def id_to_filepath(object_id)
69
- raise "object_id: only String supported" unless object_id.is_a?(String)
66
+ raise 'object_id: only String supported' unless object_id.is_a?(String)
70
67
  FileUtils.mkdir_p(@folder)
71
68
  return File.join(@folder,"#{object_id}#{FILE_SUFFIX}")
72
69
  #.gsub(/[^a-z]+/,FILE_FIELD_SEPARATOR)
73
70
  end
74
-
75
71
  end # PersistencyFolder
76
72
  end # Aspera
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'aspera/log'
2
3
  require 'singleton'
3
4
 
@@ -305,15 +306,15 @@ module Aspera
305
306
  def conversion_type(filepath,mimetype)
306
307
  Log.log.debug("conversion_type(#{filepath},m=#{mimetype},t=#{@use_mimemagic})")
307
308
  # 1- get type from provided mime type, using local mapping
308
- conv_type=SUPPORTED_MIME_TYPES[mimetype] if ! mimetype.nil?
309
+ conv_type=SUPPORTED_MIME_TYPES[mimetype] if !mimetype.nil?
309
310
  # 2- else, from computed mime type (if available)
310
- if conv_type.nil? and @use_mimemagic
311
+ if conv_type.nil? && @use_mimemagic
311
312
  detected_mime=mime_from_file(filepath)
312
- if ! detected_mime.nil?
313
+ if !detected_mime.nil?
313
314
  conv_type=SUPPORTED_MIME_TYPES[detected_mime]
314
- if ! mimetype.nil?
315
+ if !mimetype.nil?
315
316
  if mimetype.eql?(detected_mime)
316
- Log.log.debug("matching mime type per magic number")
317
+ Log.log.debug('matching mime type per magic number')
317
318
  else
318
319
  # note: detected can be nil
319
320
  Log.log.debug("non matching mime types: node=[#{mimetype}], magic=[#{detected_mime}]")