aspera-cli 4.11.0 → 4.12.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 (67) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +0 -1
  4. data/CHANGELOG.md +71 -52
  5. data/CONTRIBUTING.md +31 -6
  6. data/README.md +404 -259
  7. data/bin/asession +2 -2
  8. data/docs/test_env.conf +1 -0
  9. data/examples/aoc.rb +2 -2
  10. data/examples/dascli +11 -11
  11. data/examples/faspex4.rb +7 -7
  12. data/examples/node.rb +1 -1
  13. data/examples/proxy.pac +2 -2
  14. data/examples/server.rb +3 -3
  15. data/lib/aspera/aoc.rb +105 -40
  16. data/lib/aspera/cli/extended_value.rb +4 -4
  17. data/lib/aspera/cli/{formater.rb → formatter.rb} +7 -7
  18. data/lib/aspera/cli/listener/progress.rb +1 -1
  19. data/lib/aspera/cli/listener/progress_multi.rb +2 -2
  20. data/lib/aspera/cli/main.rb +18 -18
  21. data/lib/aspera/cli/manager.rb +5 -5
  22. data/lib/aspera/cli/plugin.rb +23 -20
  23. data/lib/aspera/cli/plugins/aoc.rb +75 -112
  24. data/lib/aspera/cli/plugins/ats.rb +6 -6
  25. data/lib/aspera/cli/plugins/config.rb +84 -83
  26. data/lib/aspera/cli/plugins/cos.rb +1 -1
  27. data/lib/aspera/cli/plugins/faspex.rb +38 -38
  28. data/lib/aspera/cli/plugins/faspex5.rb +187 -43
  29. data/lib/aspera/cli/plugins/node.rb +30 -37
  30. data/lib/aspera/cli/plugins/orchestrator.rb +7 -4
  31. data/lib/aspera/cli/plugins/preview.rb +10 -9
  32. data/lib/aspera/cli/plugins/server.rb +1 -1
  33. data/lib/aspera/cli/plugins/shares.rb +67 -43
  34. data/lib/aspera/cli/transfer_agent.rb +16 -16
  35. data/lib/aspera/cli/version.rb +2 -1
  36. data/lib/aspera/command_line_builder.rb +70 -66
  37. data/lib/aspera/cos_node.rb +9 -9
  38. data/lib/aspera/fasp/agent_base.rb +3 -1
  39. data/lib/aspera/fasp/agent_connect.rb +23 -23
  40. data/lib/aspera/fasp/agent_direct.rb +13 -14
  41. data/lib/aspera/fasp/agent_httpgw.rb +20 -19
  42. data/lib/aspera/fasp/agent_node.rb +13 -15
  43. data/lib/aspera/fasp/agent_trsdk.rb +1 -1
  44. data/lib/aspera/fasp/installation.rb +5 -5
  45. data/lib/aspera/fasp/listener.rb +1 -1
  46. data/lib/aspera/fasp/parameters.rb +49 -41
  47. data/lib/aspera/fasp/parameters.yaml +311 -212
  48. data/lib/aspera/fasp/resume_policy.rb +2 -2
  49. data/lib/aspera/fasp/transfer_spec.rb +0 -13
  50. data/lib/aspera/faspex_gw.rb +80 -161
  51. data/lib/aspera/faspex_postproc.rb +77 -0
  52. data/lib/aspera/log.rb +7 -7
  53. data/lib/aspera/nagios.rb +6 -6
  54. data/lib/aspera/node.rb +24 -19
  55. data/lib/aspera/oauth.rb +50 -47
  56. data/lib/aspera/proxy_auto_config.js +22 -22
  57. data/lib/aspera/proxy_auto_config.rb +3 -3
  58. data/lib/aspera/rest.rb +12 -10
  59. data/lib/aspera/rest_error_analyzer.rb +5 -5
  60. data/lib/aspera/secret_hider.rb +4 -3
  61. data/lib/aspera/ssh.rb +4 -4
  62. data/lib/aspera/sync.rb +37 -36
  63. data/lib/aspera/web_auth.rb +7 -59
  64. data/lib/aspera/web_server_simple.rb +76 -0
  65. data.tar.gz.sig +0 -0
  66. metadata +6 -4
  67. metadata.gz.sig +0 -0
data/lib/aspera/oauth.rb CHANGED
@@ -24,8 +24,8 @@ module Aspera
24
24
  # OAuth methods supported by default
25
25
  STD_AUTH_TYPES = %i[web jwt].freeze
26
26
 
27
- # remove 5 minutes to account for time offset (TODO: configurable?)
28
- JWT_NOTBEFORE_OFFSET_SEC = 300
27
+ # remove 5 minutes to account for time offset between client and server (TODO: configurable?)
28
+ JWT_ACCEPTED_OFFSET_SEC = 300
29
29
  # one hour validity (TODO: configurable?)
30
30
  JWT_EXPIRY_OFFSET_SEC = 3600
31
31
  # tokens older than 30 minutes will be discarded from cache
@@ -35,7 +35,7 @@ module Aspera
35
35
  # a prefix for persistency of tokens (simplify garbage collect)
36
36
  PERSIST_CATEGORY_TOKEN = 'token'
37
37
 
38
- private_constant :JWT_NOTBEFORE_OFFSET_SEC, :JWT_EXPIRY_OFFSET_SEC, :TOKEN_CACHE_EXPIRY_SEC, :PERSIST_CATEGORY_TOKEN, :TOKEN_EXPIRATION_GUARD_SEC
38
+ private_constant :JWT_ACCEPTED_OFFSET_SEC, :JWT_EXPIRY_OFFSET_SEC, :TOKEN_CACHE_EXPIRY_SEC, :PERSIST_CATEGORY_TOKEN, :TOKEN_EXPIRATION_GUARD_SEC
39
39
 
40
40
  # persistency manager
41
41
  @persist = nil
@@ -83,7 +83,7 @@ module Aspera
83
83
  end
84
84
 
85
85
  # register a token creation method
86
- # @param id creation type from field :crtype in constructor
86
+ # @param id creation type from field :grant_method in constructor
87
87
  # @param lambda_create called to create token
88
88
  # @param id_create called to generate unique id for token, for cache
89
89
  def register_token_creator(id, lambda_create, id_create)
@@ -94,11 +94,11 @@ module Aspera
94
94
 
95
95
  # @return one of the registered creators for the given create type
96
96
  def token_creator(id)
97
- raise "token creator type unknown: #{id}/#{id.class}" unless @create_handlers.key?(id)
97
+ raise "token grant method unknown: #{id}/#{id.class}" unless @create_handlers.key?(id)
98
98
  @create_handlers[id]
99
99
  end
100
100
 
101
- # list of identifiers foundn in creation parameters that can be used to uniquely identify the token
101
+ # list of identifiers found in creation parameters that can be used to uniquely identify the token
102
102
  def id_creator(id)
103
103
  raise "id creator type unknown: #{id}/#{id.class}" unless @id_handlers.key?(id)
104
104
  @id_handlers[id]
@@ -110,12 +110,12 @@ module Aspera
110
110
 
111
111
  # generic token creation, parameters are provided in :generic
112
112
  register_token_creator :generic, lambda { |oauth|
113
- return oauth.create_token(oauth.sparams)
113
+ return oauth.create_token(oauth.specific_parameters)
114
114
  }, lambda { |oauth|
115
115
  return [
116
- oauth.sparams[:grant_type]&.split(':')&.last,
117
- oauth.sparams[:apikey],
118
- oauth.sparams[:response_type]
116
+ oauth.specific_parameters[:grant_type]&.split(':')&.last,
117
+ oauth.specific_parameters[:apikey],
118
+ oauth.specific_parameters[:response_type]
119
119
  ]
120
120
  }
121
121
 
@@ -123,22 +123,22 @@ module Aspera
123
123
  register_token_creator :web, lambda { |oauth|
124
124
  random_state = SecureRandom.uuid # used to check later
125
125
  login_page_url = Rest.build_uri(
126
- "#{oauth.api.params[:base_url]}/#{oauth.sparams[:path_authorize]}",
127
- oauth.optional_scope_client_id.merge(response_type: 'code', redirect_uri: oauth.sparams[:redirect_uri], state: random_state))
126
+ "#{oauth.api.params[:base_url]}/#{oauth.specific_parameters[:path_authorize]}",
127
+ oauth.optional_scope_client_id.merge(response_type: 'code', redirect_uri: oauth.specific_parameters[:redirect_uri], state: random_state))
128
128
  # here, we need a human to authorize on a web page
129
129
  Log.log.info{"login_page_url=#{login_page_url}".bg_red.gray}
130
130
  # start a web server to receive request code
131
- webserver = WebAuth.new(oauth.sparams[:redirect_uri])
131
+ web_server = WebAuth.new(oauth.specific_parameters[:redirect_uri])
132
132
  # start browser on login page
133
133
  OpenApplication.instance.uri(login_page_url)
134
134
  # wait for code in request
135
- received_params = webserver.received_request
135
+ received_params = web_server.received_request
136
136
  raise 'wrong received state' unless random_state.eql?(received_params['state'])
137
137
  # exchange code for token
138
138
  return oauth.create_token(oauth.optional_scope_client_id(add_secret: true).merge(
139
139
  grant_type: 'authorization_code',
140
140
  code: received_params['code'],
141
- redirect_uri: oauth.sparams[:redirect_uri]))
141
+ redirect_uri: oauth.specific_parameters[:redirect_uri]))
142
142
  }, lambda { |_oauth|
143
143
  return []
144
144
  }
@@ -150,31 +150,31 @@ module Aspera
150
150
  require 'jwt'
151
151
  seconds_since_epoch = Time.new.to_i
152
152
  Log.log.info{"seconds=#{seconds_since_epoch}"}
153
- raise 'missing JWT payload' unless oauth.sparams[:payload].is_a?(Hash)
153
+ raise 'missing JWT payload' unless oauth.specific_parameters[:payload].is_a?(Hash)
154
154
  jwt_payload = {
155
155
  exp: seconds_since_epoch + JWT_EXPIRY_OFFSET_SEC, # expiration time
156
- nbf: seconds_since_epoch - JWT_NOTBEFORE_OFFSET_SEC, # not before
157
- iat: seconds_since_epoch, # issued at
156
+ nbf: seconds_since_epoch - JWT_ACCEPTED_OFFSET_SEC, # not before
157
+ iat: seconds_since_epoch - JWT_ACCEPTED_OFFSET_SEC + 1, # issued at (we tell a little in the past so that server always accepts)
158
158
  jti: SecureRandom.uuid # JWT id
159
- }.merge(oauth.sparams[:payload])
159
+ }.merge(oauth.specific_parameters[:payload])
160
160
  Log.log.debug{"JWT jwt_payload=[#{jwt_payload}]"}
161
- rsa_private = oauth.sparams[:private_key_obj] # type: OpenSSL::PKey::RSA
161
+ rsa_private = oauth.specific_parameters[:private_key_obj] # type: OpenSSL::PKey::RSA
162
162
  Log.log.debug{"private=[#{rsa_private}]"}
163
- assertion = JWT.encode(jwt_payload, rsa_private, 'RS256', oauth.sparams[:headers] || {})
163
+ assertion = JWT.encode(jwt_payload, rsa_private, 'RS256', oauth.specific_parameters[:headers] || {})
164
164
  Log.log.debug{"assertion=[#{assertion}]"}
165
165
  return oauth.create_token(oauth.optional_scope_client_id.merge(grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', assertion: assertion))
166
166
  }, lambda { |oauth|
167
- return [oauth.sparams.dig(:payload, :sub)]
167
+ return [oauth.specific_parameters.dig(:payload, :sub)]
168
168
  }
169
169
 
170
- attr_reader :gparams, :sparams, :api
170
+ attr_reader :generic_parameters, :specific_parameters, :api
171
171
 
172
172
  private
173
173
 
174
174
  # [M]=mandatory [D]=has default value [0]=accept nil
175
175
  # :base_url [M] URL of authentication API
176
176
  # :auth
177
- # :crtype [M] :generic, :web, :jwt, custom
177
+ # :grant_method [M] :generic, :web, :jwt, custom
178
178
  # :client_id [0]
179
179
  # :client_secret [0]
180
180
  # :scope [0]
@@ -189,38 +189,41 @@ module Aspera
189
189
  def initialize(a_params)
190
190
  Log.log.debug{"auth=#{a_params}"}
191
191
  # replace default values
192
- @gparams = DEFAULT_CREATE_PARAMS.deep_merge(a_params)
192
+ @generic_parameters = DEFAULT_CREATE_PARAMS.deep_merge(a_params)
193
+ # legacy
194
+ @generic_parameters[:grant_method] ||= @generic_parameters.delete(:crtype) if @generic_parameters.key?(:crtype)
193
195
  # check that type is known
194
- self.class.token_creator(@gparams[:crtype])
196
+ self.class.token_creator(@generic_parameters[:grant_method])
195
197
  # specific parameters for the creation type
196
- @sparams = @gparams[@gparams[:crtype]]
197
- if @gparams[:crtype].eql?(:web) && @sparams.key?(:redirect_uri)
198
- uri = URI.parse(@sparams[:redirect_uri])
198
+ @specific_parameters = @generic_parameters[@generic_parameters[:grant_method]]
199
+ if @generic_parameters[:grant_method].eql?(:web) && @specific_parameters.key?(:redirect_uri)
200
+ uri = URI.parse(@specific_parameters[:redirect_uri])
199
201
  raise 'redirect_uri scheme must be http or https' unless %w[http https].include?(uri.scheme)
200
202
  raise 'redirect_uri must have a port' if uri.port.nil?
201
203
  # TODO: we could check that host is localhost or local address
202
204
  end
203
205
  rest_params = {
204
- base_url: @gparams[:base_url],
206
+ base_url: @generic_parameters[:base_url],
205
207
  redirect_max: 2
206
208
  }
207
209
  rest_params[:auth] = a_params[:auth] if a_params.key?(:auth)
208
210
  @api = Rest.new(rest_params)
209
211
  # if needed use from api
210
- @gparams.delete(:base_url)
211
- @gparams.delete(:auth)
212
- @gparams.delete(@gparams[:crtype])
213
- Log.dump(:gparams, @gparams)
214
- Log.dump(:sparams, @sparams)
212
+ @generic_parameters.delete(:base_url)
213
+ @generic_parameters.delete(:auth)
214
+ @generic_parameters.delete(@generic_parameters[:grant_method])
215
+ Log.dump(:generic_parameters, @generic_parameters)
216
+ Log.dump(:specific_parameters, @specific_parameters)
215
217
  end
216
218
 
217
219
  public
218
220
 
219
221
  # helper method to create token as per RFC
220
222
  def create_token(www_params)
223
+ Log.log.debug{'Generating a new token'.bg_green}
221
224
  return @api.call({
222
225
  operation: 'POST',
223
- subpath: @gparams[:path_token],
226
+ subpath: @generic_parameters[:path_token],
224
227
  headers: {'Accept' => 'application/json'},
225
228
  www_body_params: www_params})
226
229
  end
@@ -228,9 +231,9 @@ module Aspera
228
231
  # @return Hash with optional general parameters
229
232
  def optional_scope_client_id(add_secret: false)
230
233
  call_params = {}
231
- call_params[:scope] = @gparams[:scope] unless @gparams[:scope].nil?
232
- call_params[:client_id] = @gparams[:client_id] unless @gparams[:client_id].nil?
233
- call_params[:client_secret] = @gparams[:client_secret] if add_secret && !@gparams[:client_id].nil?
234
+ call_params[:scope] = @generic_parameters[:scope] unless @generic_parameters[:scope].nil?
235
+ call_params[:client_id] = @generic_parameters[:client_id] unless @generic_parameters[:client_id].nil?
236
+ call_params[:client_secret] = @generic_parameters[:client_secret] if add_secret && !@generic_parameters[:client_id].nil?
234
237
  return call_params
235
238
  end
236
239
 
@@ -241,20 +244,20 @@ module Aspera
241
244
  token_id = IdGenerator.from_list([
242
245
  PERSIST_CATEGORY_TOKEN,
243
246
  @api.params[:base_url],
244
- @gparams[:crtype],
245
- self.class.id_creator(@gparams[:crtype]).call(self), # array, so we flatten later
246
- @gparams[:scope],
247
+ @generic_parameters[:grant_method],
248
+ self.class.id_creator(@generic_parameters[:grant_method]).call(self), # array, so we flatten later
249
+ @generic_parameters[:scope],
247
250
  @api.params.dig(%i[auth username])
248
251
  ].flatten)
249
252
 
250
253
  # get token_data from cache (or nil), token_data is what is returned by /token
251
254
  token_data = self.class.persist_mgr.get(token_id) if use_cache
252
255
  token_data = JSON.parse(token_data) unless token_data.nil?
253
- # Optional optimization: check if node token is expired basd on decoded content then force refresh if close enough
256
+ # Optional optimization: check if node token is expired based on decoded content then force refresh if close enough
254
257
  # might help in case the transfer agent cannot refresh himself
255
258
  # `direct` agent is equipped with refresh code
256
259
  if !use_refresh_token && !token_data.nil?
257
- decoded_token = self.class.decode_token(token_data[@gparams[:token_field]])
260
+ decoded_token = self.class.decode_token(token_data[@generic_parameters[:token_field]])
258
261
  Log.dump('decoded_token', decoded_token) unless decoded_token.nil?
259
262
  if decoded_token.is_a?(Hash)
260
263
  expires_at_sec =
@@ -295,14 +298,14 @@ module Aspera
295
298
 
296
299
  # no cache, nor refresh: generate a token
297
300
  if token_data.nil?
298
- resp = self.class.token_creator(@gparams[:crtype]).call(self)
301
+ resp = self.class.token_creator(@generic_parameters[:grant_method]).call(self)
299
302
  json_data = resp[:http].body
300
303
  token_data = JSON.parse(json_data)
301
304
  self.class.persist_mgr.put(token_id, json_data)
302
305
  end # if ! in_cache
303
- raise "API error: No such field in answer: #{@gparams[:token_field]}" unless token_data.key?(@gparams[:token_field])
306
+ raise "API error: No such field in answer: #{@generic_parameters[:token_field]}" unless token_data.key?(@generic_parameters[:token_field])
304
307
  # ok we shall have a token here
305
- return 'Bearer ' + token_data[@gparams[:token_field]]
308
+ return 'Bearer ' + token_data[@generic_parameters[:token_field]]
306
309
  end
307
310
  end # OAuth
308
311
  end # Aspera
@@ -1,4 +1,4 @@
1
- // inspired by PACSuppport.js, 2003-2004 by Apple Computer, Inc., all rights reserved
1
+ // inspired by POCSupport.js, 2003-2004 by Apple Computer, Inc., all rights reserved
2
2
  function isPlainHostName(host) {
3
3
  return (host.indexOf('.') == -1 ? true : false);
4
4
  }
@@ -10,14 +10,14 @@ function dnsDomainIs(host, domain) {
10
10
  return true;
11
11
  return false;
12
12
  }
13
- function localHostOrDomainIs(host, hostdom) {
13
+ function localHostOrDomainIs(host, host_domain) {
14
14
  var h1 = host.toLowerCase();
15
- var h2 = hostdom.toLowerCase();
15
+ var h2 = host_domain.toLowerCase();
16
16
  return ((h1 == h2) || (isPlainHostName(h1) & !isPlainHostName(h2))) ? true : false;
17
17
  }
18
18
  function isResolvable(host) {
19
19
  var ip = dnsResolve(host);
20
- return ((typeof ip == "string") && ip.length) ? true : false;
20
+ return ((typeof ip == 'string') && ip.length) ? true : false;
21
21
  }
22
22
  function isInNet(host, pattern, mask) {
23
23
  var ip = dnsResolve(host);
@@ -39,20 +39,20 @@ function dnsDomainLevels(host) {
39
39
  var parts = host.split('.');
40
40
  return parts.length - 1;
41
41
  }
42
- function shExpMatch(str, shexp) {
43
- if (typeof str != "string" || typeof shexp != "string")
42
+ function shExpMatch(str, shell_expr) {
43
+ if (typeof str != 'string' || typeof shell_expr != 'string')
44
44
  return false;
45
- if (shexp == "*")
45
+ if (shell_expr == '*')
46
46
  return true;
47
- if (str == "" && shexp == "")
47
+ if (str == '' && shell_expr == '')
48
48
  return true;
49
49
  str = str.toLowerCase();
50
- shexp = shexp.toLowerCase();
50
+ shell_expr = shell_expr.toLowerCase();
51
51
  var len = str.length;
52
- var pieces = shexp.split('*');
52
+ var pieces = shell_expr.split('*');
53
53
  var start = 0;
54
54
  for (i = 0; i < pieces.length; i++) {
55
- if (pieces[i] == "")
55
+ if (pieces[i] == '')
56
56
  continue;
57
57
  if (start > len)
58
58
  return false;
@@ -64,13 +64,13 @@ function shExpMatch(str, shexp) {
64
64
  len = str.length;
65
65
  }
66
66
  i--;
67
- if ((pieces[i] == "") || (str == ""))
67
+ if ((pieces[i] == '') || (str == ''))
68
68
  return true;
69
69
  return false;
70
70
  }
71
71
  function weekdayRange(wd1, wd2, gmt) {
72
72
  var today = new Date();
73
- var days = "SUNMONTUEWEDTHUFRISAT";
73
+ var days = 'SUNMONTUEWEDTHUFRISAT';
74
74
  wd1 = wd1.toUpperCase();
75
75
  if (wd2 == undefined)
76
76
  wd2 = wd1;
@@ -78,7 +78,7 @@ function weekdayRange(wd1, wd2, gmt) {
78
78
  wd2 = wd2.toUpperCase();
79
79
  var d1 = days.indexOf(wd1);
80
80
  var d2 = days.indexOf(wd2);
81
- if ((d2 == -1) && (wd2 == "GMT")) {
81
+ if ((d2 == -1) && (wd2 == 'GMT')) {
82
82
  gmt = wd2;
83
83
  d2 = d1;
84
84
  }
@@ -86,7 +86,7 @@ function weekdayRange(wd1, wd2, gmt) {
86
86
  return false;
87
87
  d1 = d1 / 3;
88
88
  d2 = d2 / 3;
89
- if (gmt == "GMT")
89
+ if (gmt == 'GMT')
90
90
  today = today.getUTCDay();
91
91
  else
92
92
  today = today.getDay();
@@ -100,11 +100,11 @@ function dateRange() {
100
100
  var today = new Date();
101
101
  var num = arguments.length;
102
102
  var gmt = arguments[num - 1];
103
- if (typeof gmt != "string")
103
+ if (typeof gmt != 'string')
104
104
  gmt = false;
105
105
  else {
106
106
  gmt = gmt.toUpperCase();
107
- if (gmt != "GMT")
107
+ if (gmt != 'GMT')
108
108
  gmt = false;
109
109
  else {
110
110
  gmt = true;
@@ -121,7 +121,7 @@ function dateRange() {
121
121
  var y2 = 0;
122
122
  for (i = 0; i < num; i++) {
123
123
  var arg = arguments[i];
124
- if (typeof arg == "number") {
124
+ if (typeof arg == 'number') {
125
125
  if (arg > 31) {
126
126
  if (!y1)
127
127
  y1 = arg;
@@ -137,8 +137,8 @@ function dateRange() {
137
137
  d2 = arg;
138
138
  else
139
139
  return false;
140
- } else if (typeof arg == "string") {
141
- var months = "JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC";
140
+ } else if (typeof arg == 'string') {
141
+ var months = 'JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC';
142
142
  arg = arg.toUpperCase();
143
143
  arg = months.indexOf(arg);
144
144
  if (arg == -1)
@@ -184,11 +184,11 @@ function timeRange() {
184
184
  var date2 = new Date();
185
185
  var num = arguments.length;
186
186
  var gmt = arguments[num - 1];
187
- if (typeof gmt != "string")
187
+ if (typeof gmt != 'string')
188
188
  gmt = false;
189
189
  else {
190
190
  gmt = gmt.toUpperCase();
191
- if (gmt != "GMT")
191
+ if (gmt != 'GMT')
192
192
  gmt = false;
193
193
  else {
194
194
  gmt = true;
@@ -11,14 +11,14 @@ module URI
11
11
  def register_proxy_finder
12
12
  raise 'mandatory block missing' unless Kernel.block_given?
13
13
  # overload the method in URI : call user's provided block and fallback to original method
14
- define_method(:find_proxy) {|envars=ENV| yield(to_s) || find_proxy_orig(envars)}
14
+ define_method(:find_proxy) {|env_vars=ENV| yield(to_s) || find_proxy_orig(env_vars)}
15
15
  end
16
16
  end
17
17
  end
18
18
  end
19
19
 
20
20
  module Aspera
21
- # Evaluate a proxy autoconfig script
21
+ # Evaluate a proxy auto config script
22
22
  class ProxyAutoConfig
23
23
  # template file is read once, it contains functions that can be used in a proxy autoconf script
24
24
  # it is similar to mozilla ascii_pac_utils.inc
@@ -70,7 +70,7 @@ END_OF_JAVASCRIPT
70
70
  end
71
71
 
72
72
  # execute proxy auto config script for the given URL : https://en.wikipedia.org/wiki/Proxy_auto-config
73
- # @return either nil, or a String formated following PAC standard
73
+ # @return either nil, or a String formatted following PAC standard
74
74
  def find_proxy_for_url(service_url)
75
75
  uri = URI.parse(service_url)
76
76
  simple_url = "#{uri.scheme}://#{uri.host}"
data/lib/aspera/rest.rb CHANGED
@@ -36,6 +36,8 @@ module Aspera
36
36
  proxy_pass: nil
37
37
  }
38
38
 
39
+ ARRAY_PARAMS = '[]'
40
+
39
41
  class << self
40
42
  # define accessors
41
43
  @@global.each_key do |p|
@@ -60,9 +62,9 @@ module Aspera
60
62
  orig.each do |k, v|
61
63
  case v
62
64
  when Array
63
- suffix = v.first.eql?('[]') ? v.shift : ''
65
+ suffix = v.first.eql?(ARRAY_PARAMS) ? v.shift : ''
64
66
  v.each do |e|
65
- params.push([k + suffix, e])
67
+ params.push([k.to_s + suffix, e])
66
68
  end
67
69
  else
68
70
  params.push([k, v])
@@ -140,7 +142,7 @@ module Aspera
140
142
  uri = self.class.build_uri("#{call_data[:base_url]}#{['', '/'].include?(call_data[:subpath]) ? '' : '/'}#{call_data[:subpath]}", call_data[:url_params])
141
143
  Log.log.debug{"URI=#{uri}"}
142
144
  begin
143
- # instanciate request object based on string name
145
+ # instantiate request object based on string name
144
146
  req = Net::HTTP.const_get(call_data[:operation].capitalize).new(uri)
145
147
  rescue NameError
146
148
  raise "unsupported operation : #{call_data[:operation]}"
@@ -198,7 +200,7 @@ module Aspera
198
200
  Log.log.debug{"accessing #{call_data[:subpath]}".red.bold.bg_green}
199
201
  call_data[:headers] ||= {}
200
202
  call_data[:headers]['User-Agent'] ||= self.class.user_agent
201
- # defaults from @params are overriden by call data
203
+ # defaults from @params are overridden by call data
202
204
  call_data = @params.deep_merge(call_data)
203
205
  case call_data[:auth][:type]
204
206
  when :none
@@ -279,7 +281,7 @@ module Aspera
279
281
  e = e_tok
280
282
  Log.log.error('refresh failed'.bg_red)
281
283
  # regenerate a brand new token
282
- req['Authorization'] = oauth_token(use_cache: false)
284
+ req['Authorization'] = oauth_token(force_refresh: true)
283
285
  end
284
286
  Log.log.debug{"using new token=#{call_data[:headers]['Authorization']}"}
285
287
  retry unless (oauth_tries -= 1).zero?
@@ -291,15 +293,15 @@ module Aspera
291
293
  new_url = e.response['location']
292
294
  new_url = "#{current_uri.scheme}:#{new_url}" unless new_url.start_with?('http')
293
295
  Log.log.info{"URL is moved: #{new_url}"}
294
- redir_uri = URI.parse(new_url)
296
+ redirection_uri = URI.parse(new_url)
295
297
  call_data[:base_url] = new_url
296
298
  call_data[:subpath] = ''
297
- if current_uri.host.eql?(redir_uri.host) && current_uri.port.eql?(redir_uri.port)
299
+ if current_uri.host.eql?(redirection_uri.host) && current_uri.port.eql?(redirection_uri.port)
298
300
  req = build_request(call_data)
299
301
  retry
300
302
  else
301
303
  # change host
302
- Log.log.info{"Redirect changes host: #{current_uri.host} -> #{redir_uri.host}"}
304
+ Log.log.info{"Redirect changes host: #{current_uri.host} -> #{redirection_uri.host}"}
303
305
  return self.class.new(call_data).call(call_data)
304
306
  end
305
307
  end
@@ -327,8 +329,8 @@ module Aspera
327
329
  return call({operation: 'PUT', subpath: subpath, headers: {'Accept' => 'application/json'}, json_params: params})
328
330
  end
329
331
 
330
- def delete(subpath)
331
- return call({operation: 'DELETE', subpath: subpath, headers: {'Accept' => 'application/json'}})
332
+ def delete(subpath, args=nil)
333
+ return call({operation: 'DELETE', subpath: subpath, headers: {'Accept' => 'application/json'}, url_params: args})
332
334
  end
333
335
 
334
336
  def cancel(subpath)
@@ -47,7 +47,7 @@ module Aspera
47
47
  raise RestCallError.new(call_context[:request], call_context[:response], call_context[:messages].join("\n")) unless call_context[:messages].empty?
48
48
  end
49
49
 
50
- # add a new error handler (done at application initialisation)
50
+ # add a new error handler (done at application initialization)
51
51
  # @param name : name of error handler (for logs)
52
52
  # @param block : processing of response: takes two parameters: name, call_context
53
53
  # name is the one provided here
@@ -69,7 +69,7 @@ module Aspera
69
69
  if call_context[:data].is_a?(Hash) && (!call_context[:response].code.start_with?('2') || always)
70
70
  msg_key = path.pop
71
71
  # dig and find sub entry corresponding to path in deep hash
72
- error_struct = path.inject(call_context[:data]) { |subhash, key| subhash.respond_to?(:keys) ? subhash[key] : nil }
72
+ error_struct = path.inject(call_context[:data]) { |sub_hash, key| sub_hash.respond_to?(:keys) ? sub_hash[key] : nil }
73
73
  if error_struct.is_a?(Hash) && error_struct[msg_key].is_a?(String)
74
74
  RestErrorAnalyzer.add_error(call_context, type, error_struct[msg_key])
75
75
  error_struct.each do |k, v|
@@ -89,10 +89,10 @@ module Aspera
89
89
  # @param msg one error message to add to list
90
90
  def add_error(call_context, type, msg)
91
91
  call_context[:messages].push(msg)
92
- logfile = instance.log_file
92
+ log_file = instance.log_file
93
93
  # log error for further analysis (file must exist to activate)
94
- return if logfile.nil? || !File.exist?(logfile)
95
- File.open(logfile, 'a+') do |f|
94
+ return if log_file.nil? || !File.exist?(log_file)
95
+ File.open(log_file, 'a+') do |f|
96
96
  f.write("\n=#{type}=====\n#{call_context[:request].method} #{call_context[:request].path}\n#{call_context[:response].code}\n"\
97
97
  "#{JSON.generate(call_context[:data])}\n#{call_context[:messages].join("\n")}")
98
98
  end
@@ -10,9 +10,9 @@ module Aspera
10
10
  # env vars for ascp with secrets
11
11
  ASCP_ENV_SECRETS = %w[ASPERA_SCP_PASS ASPERA_SCP_KEY ASPERA_SCP_FILEPASS ASPERA_PROXY_PASS ASPERA_SCP_TOKEN].freeze
12
12
  # keys in hash that contain secrets
13
- KEY_SECRETS = %w[password secret private_key passphrase].freeze
14
- ALL_SECRETS = [].concat(ASCP_ENV_SECRETS, KEY_SECRETS).freeze
15
- # regex that define namec captures :begin and :end
13
+ KEY_SECRETS = %w[password secret passphrase _key apikey crn token].freeze
14
+ ALL_SECRETS = [ASCP_ENV_SECRETS, KEY_SECRETS].flatten.freeze
15
+ # regex that define named captures :begin and :end
16
16
  REGEX_LOG_REPLACES = [
17
17
  # CLI manager get/set options
18
18
  /(?<begin>[sg]et (#{KEY_SECRETS.join('|')})=).*(?<end>)/,
@@ -70,6 +70,7 @@ module Aspera
70
70
  end
71
71
  end
72
72
  end
73
+ return obj
73
74
  end
74
75
  end
75
76
  end
data/lib/aspera/ssh.rb CHANGED
@@ -37,14 +37,14 @@ module Aspera
37
37
  channel.on_data{|_chan, data|response.push(data)}
38
38
  # prepare stderr processing, stderr if type = 1
39
39
  channel.on_extended_data do |_chan, _type, data|
40
- errormsg = "#{cmd}: [#{data.chomp}]"
40
+ error_message = "#{cmd}: [#{data.chomp}]"
41
41
  # Happens when windows user hasn't logged in and created home account.
42
42
  if data.include?('Could not chdir to home directory')
43
- errormsg += "\nHint: home not created in Windows?"
43
+ error_message += "\nHint: home not created in Windows?"
44
44
  end
45
- raise errormsg
45
+ raise error_message
46
46
  end
47
- # send commannd to SSH channel (execute)
47
+ # send command to SSH channel (execute)
48
48
  channel.send('cexe'.reverse, cmd){|_ch, _success|channel.send_data(input) unless input.nil?}
49
49
  end
50
50
  # wait for channel to finish (command exit)
data/lib/aspera/sync.rb CHANGED
@@ -10,48 +10,48 @@ module Aspera
10
10
  class Sync
11
11
  PARAMS_VX_INSTANCE =
12
12
  {
13
- 'alt_logdir' => { cltype: :opt_with_arg, accepted_types: :string},
14
- 'watchd' => { cltype: :opt_with_arg, accepted_types: :string},
15
- 'apply_local_docroot' => { cltype: :opt_without_arg},
16
- 'quiet' => { cltype: :opt_without_arg},
17
- 'ws_connect' => { cltype: :opt_without_arg}
13
+ 'alt_logdir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
14
+ 'watchd' => { cli: { type: :opt_with_arg}, accepted_types: :string},
15
+ 'apply_local_docroot' => { cli: { type: :opt_without_arg}},
16
+ 'quiet' => { cli: { type: :opt_without_arg}},
17
+ 'ws_connect' => { cli: { type: :opt_without_arg}}
18
18
  }.freeze
19
19
 
20
20
  # map sync session parameters to transfer spec: sync -> ts, true if same
21
21
  PARAMS_VX_SESSION =
22
22
  {
23
- 'name' => { cltype: :opt_with_arg, accepted_types: :string},
24
- 'local_dir' => { cltype: :opt_with_arg, accepted_types: :string},
25
- 'remote_dir' => { cltype: :opt_with_arg, accepted_types: :string},
26
- 'local_db_dir' => { cltype: :opt_with_arg, accepted_types: :string},
27
- 'remote_db_dir' => { cltype: :opt_with_arg, accepted_types: :string},
28
- 'host' => { cltype: :opt_with_arg, accepted_types: :string, ts: :remote_host},
29
- 'user' => { cltype: :opt_with_arg, accepted_types: :string, ts: :remote_user},
30
- 'private_key_paths' => { cltype: :opt_with_arg, accepted_types: :array, clswitch: '--private-key-path'},
31
- 'direction' => { cltype: :opt_with_arg, accepted_types: :string},
32
- 'checksum' => { cltype: :opt_with_arg, accepted_types: :string},
33
- 'tags' => { cltype: :opt_with_arg, accepted_types: :hash, ts: true,
34
- clswitch: '--tags64', clconvert: 'Aspera::Fasp::Parameters.clconv_json64'},
35
- 'tcp_port' => { cltype: :opt_with_arg, accepted_types: :int, ts: :ssh_port},
36
- 'rate_policy' => { cltype: :opt_with_arg, accepted_types: :string},
37
- 'target_rate' => { cltype: :opt_with_arg, accepted_types: :string},
38
- 'cooloff' => { cltype: :opt_with_arg, accepted_types: :int},
39
- 'pending_max' => { cltype: :opt_with_arg, accepted_types: :int},
40
- 'scan_intensity' => { cltype: :opt_with_arg, accepted_types: :string},
41
- 'cipher' => { cltype: :opt_with_arg, accepted_types: :string, ts: true},
42
- 'transfer_threads' => { cltype: :opt_with_arg, accepted_types: :int},
43
- 'preserve_time' => { cltype: :opt_without_arg, ts: :preserve_times},
44
- 'preserve_access_time' => { cltype: :opt_without_arg, ts: nil},
45
- 'preserve_modification_time' => { cltype: :opt_without_arg, ts: nil},
46
- 'preserve_uid' => { cltype: :opt_without_arg, ts: :preserve_file_owner_uid},
47
- 'preserve_gid' => { cltype: :opt_without_arg, ts: :preserve_file_owner_gid},
48
- 'create_dir' => { cltype: :opt_without_arg, ts: true},
49
- 'reset' => { cltype: :opt_without_arg},
23
+ 'name' => { cli: { type: :opt_with_arg}, accepted_types: :string},
24
+ 'local_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
25
+ 'remote_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
26
+ 'local_db_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
27
+ 'remote_db_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
28
+ 'host' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: :remote_host},
29
+ 'user' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: :remote_user},
30
+ 'private_key_paths' => { cli: { type: :opt_with_arg, switch: '--private-key-path'}, accepted_types: :array},
31
+ 'direction' => { cli: { type: :opt_with_arg}, accepted_types: :string},
32
+ 'checksum' => { cli: { type: :opt_with_arg}, accepted_types: :string},
33
+ 'tags' => { cli: { type: :opt_with_arg, switch: '--tags64', convert: 'Aspera::Fasp::Parameters.convert_json64'},
34
+ accepted_types: :hash, ts: true},
35
+ 'tcp_port' => { cli: { type: :opt_with_arg}, accepted_types: :int, ts: :ssh_port},
36
+ 'rate_policy' => { cli: { type: :opt_with_arg}, accepted_types: :string},
37
+ 'target_rate' => { cli: { type: :opt_with_arg}, accepted_types: :string},
38
+ 'cooloff' => { cli: { type: :opt_with_arg}, accepted_types: :int},
39
+ 'pending_max' => { cli: { type: :opt_with_arg}, accepted_types: :int},
40
+ 'scan_intensity' => { cli: { type: :opt_with_arg}, accepted_types: :string},
41
+ 'cipher' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: true},
42
+ 'transfer_threads' => { cli: { type: :opt_with_arg}, accepted_types: :int},
43
+ 'preserve_time' => { cli: { type: :opt_without_arg}, ts: :preserve_times},
44
+ 'preserve_access_time' => { cli: { type: :opt_without_arg}, ts: nil},
45
+ 'preserve_modification_time' => { cli: { type: :opt_without_arg}, ts: nil},
46
+ 'preserve_uid' => { cli: { type: :opt_without_arg}, ts: :preserve_file_owner_uid},
47
+ 'preserve_gid' => { cli: { type: :opt_without_arg}, ts: :preserve_file_owner_gid},
48
+ 'create_dir' => { cli: { type: :opt_without_arg}, ts: true},
49
+ 'reset' => { cli: { type: :opt_without_arg}},
50
50
  # NOTE: only one env var, but multiple sessions... could be a problem
51
- 'remote_password' => { cltype: :envvar, clvarname: 'ASPERA_SCP_PASS', ts: true},
52
- 'cookie' => { cltype: :envvar, clvarname: 'ASPERA_SCP_COOKIE', ts: true},
53
- 'token' => { cltype: :envvar, clvarname: 'ASPERA_SCP_TOKEN', ts: true},
54
- 'license' => { cltype: :envvar, clvarname: 'ASPERA_SCP_LICENSE'}
51
+ 'remote_password' => { cli: { type: :envvar, variable: 'ASPERA_SCP_PASS'}, ts: true},
52
+ 'cookie' => { cli: { type: :envvar, variable: 'ASPERA_SCP_COOKIE'}, ts: true},
53
+ 'token' => { cli: { type: :envvar, variable: 'ASPERA_SCP_TOKEN'}, ts: true},
54
+ 'license' => { cli: { type: :envvar, variable: 'ASPERA_SCP_LICENSE'}}
55
55
  }.freeze
56
56
 
57
57
  Aspera::CommandLineBuilder.normalize_description(PARAMS_VX_INSTANCE)
@@ -59,6 +59,7 @@ module Aspera
59
59
 
60
60
  PARAMS_VX_KEYS = %w[instance sessions].freeze
61
61
 
62
+ # new API
62
63
  TS_TO_PARAMS = {
63
64
  'remote_host' => 'remote.host',
64
65
  'remote_user' => 'remote.user',