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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +0 -1
- data/CHANGELOG.md +71 -52
- data/CONTRIBUTING.md +31 -6
- data/README.md +404 -259
- data/bin/asession +2 -2
- data/docs/test_env.conf +1 -0
- data/examples/aoc.rb +2 -2
- data/examples/dascli +11 -11
- data/examples/faspex4.rb +7 -7
- data/examples/node.rb +1 -1
- data/examples/proxy.pac +2 -2
- data/examples/server.rb +3 -3
- data/lib/aspera/aoc.rb +105 -40
- data/lib/aspera/cli/extended_value.rb +4 -4
- data/lib/aspera/cli/{formater.rb → formatter.rb} +7 -7
- data/lib/aspera/cli/listener/progress.rb +1 -1
- data/lib/aspera/cli/listener/progress_multi.rb +2 -2
- data/lib/aspera/cli/main.rb +18 -18
- data/lib/aspera/cli/manager.rb +5 -5
- data/lib/aspera/cli/plugin.rb +23 -20
- data/lib/aspera/cli/plugins/aoc.rb +75 -112
- data/lib/aspera/cli/plugins/ats.rb +6 -6
- data/lib/aspera/cli/plugins/config.rb +84 -83
- data/lib/aspera/cli/plugins/cos.rb +1 -1
- data/lib/aspera/cli/plugins/faspex.rb +38 -38
- data/lib/aspera/cli/plugins/faspex5.rb +187 -43
- data/lib/aspera/cli/plugins/node.rb +30 -37
- data/lib/aspera/cli/plugins/orchestrator.rb +7 -4
- data/lib/aspera/cli/plugins/preview.rb +10 -9
- data/lib/aspera/cli/plugins/server.rb +1 -1
- data/lib/aspera/cli/plugins/shares.rb +67 -43
- data/lib/aspera/cli/transfer_agent.rb +16 -16
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/command_line_builder.rb +70 -66
- data/lib/aspera/cos_node.rb +9 -9
- data/lib/aspera/fasp/agent_base.rb +3 -1
- data/lib/aspera/fasp/agent_connect.rb +23 -23
- data/lib/aspera/fasp/agent_direct.rb +13 -14
- data/lib/aspera/fasp/agent_httpgw.rb +20 -19
- data/lib/aspera/fasp/agent_node.rb +13 -15
- data/lib/aspera/fasp/agent_trsdk.rb +1 -1
- data/lib/aspera/fasp/installation.rb +5 -5
- data/lib/aspera/fasp/listener.rb +1 -1
- data/lib/aspera/fasp/parameters.rb +49 -41
- data/lib/aspera/fasp/parameters.yaml +311 -212
- data/lib/aspera/fasp/resume_policy.rb +2 -2
- data/lib/aspera/fasp/transfer_spec.rb +0 -13
- data/lib/aspera/faspex_gw.rb +80 -161
- data/lib/aspera/faspex_postproc.rb +77 -0
- data/lib/aspera/log.rb +7 -7
- data/lib/aspera/nagios.rb +6 -6
- data/lib/aspera/node.rb +24 -19
- data/lib/aspera/oauth.rb +50 -47
- data/lib/aspera/proxy_auto_config.js +22 -22
- data/lib/aspera/proxy_auto_config.rb +3 -3
- data/lib/aspera/rest.rb +12 -10
- data/lib/aspera/rest_error_analyzer.rb +5 -5
- data/lib/aspera/secret_hider.rb +4 -3
- data/lib/aspera/ssh.rb +4 -4
- data/lib/aspera/sync.rb +37 -36
- data/lib/aspera/web_auth.rb +7 -59
- data/lib/aspera/web_server_simple.rb +76 -0
- data.tar.gz.sig +0 -0
- metadata +6 -4
- 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
|
-
|
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 :
|
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 :
|
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
|
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
|
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.
|
113
|
+
return oauth.create_token(oauth.specific_parameters)
|
114
114
|
}, lambda { |oauth|
|
115
115
|
return [
|
116
|
-
oauth.
|
117
|
-
oauth.
|
118
|
-
oauth.
|
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.
|
127
|
-
oauth.optional_scope_client_id.merge(response_type: 'code', redirect_uri: oauth.
|
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
|
-
|
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 =
|
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.
|
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.
|
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 -
|
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.
|
159
|
+
}.merge(oauth.specific_parameters[:payload])
|
160
160
|
Log.log.debug{"JWT jwt_payload=[#{jwt_payload}]"}
|
161
|
-
rsa_private = oauth.
|
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.
|
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.
|
167
|
+
return [oauth.specific_parameters.dig(:payload, :sub)]
|
168
168
|
}
|
169
169
|
|
170
|
-
attr_reader :
|
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
|
-
# :
|
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
|
-
@
|
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(@
|
196
|
+
self.class.token_creator(@generic_parameters[:grant_method])
|
195
197
|
# specific parameters for the creation type
|
196
|
-
@
|
197
|
-
if @
|
198
|
-
uri = URI.parse(@
|
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: @
|
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
|
-
@
|
211
|
-
@
|
212
|
-
@
|
213
|
-
Log.dump(:
|
214
|
-
Log.dump(:
|
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: @
|
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] = @
|
232
|
-
call_params[:client_id] = @
|
233
|
-
call_params[:client_secret] = @
|
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
|
-
@
|
245
|
-
self.class.id_creator(@
|
246
|
-
@
|
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
|
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[@
|
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(@
|
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: #{@
|
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[@
|
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
|
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,
|
13
|
+
function localHostOrDomainIs(host, host_domain) {
|
14
14
|
var h1 = host.toLowerCase();
|
15
|
-
var h2 =
|
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 ==
|
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,
|
43
|
-
if (typeof str !=
|
42
|
+
function shExpMatch(str, shell_expr) {
|
43
|
+
if (typeof str != 'string' || typeof shell_expr != 'string')
|
44
44
|
return false;
|
45
|
-
if (
|
45
|
+
if (shell_expr == '*')
|
46
46
|
return true;
|
47
|
-
if (str ==
|
47
|
+
if (str == '' && shell_expr == '')
|
48
48
|
return true;
|
49
49
|
str = str.toLowerCase();
|
50
|
-
|
50
|
+
shell_expr = shell_expr.toLowerCase();
|
51
51
|
var len = str.length;
|
52
|
-
var pieces =
|
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] ==
|
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 =
|
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 ==
|
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 ==
|
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 !=
|
103
|
+
if (typeof gmt != 'string')
|
104
104
|
gmt = false;
|
105
105
|
else {
|
106
106
|
gmt = gmt.toUpperCase();
|
107
|
-
if (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 ==
|
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 ==
|
141
|
-
var months =
|
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 !=
|
187
|
+
if (typeof gmt != 'string')
|
188
188
|
gmt = false;
|
189
189
|
else {
|
190
190
|
gmt = gmt.toUpperCase();
|
191
|
-
if (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) {|
|
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
|
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
|
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?(
|
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
|
-
#
|
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
|
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(
|
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
|
-
|
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?(
|
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} -> #{
|
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
|
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]) { |
|
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
|
-
|
92
|
+
log_file = instance.log_file
|
93
93
|
# log error for further analysis (file must exist to activate)
|
94
|
-
return if
|
95
|
-
File.open(
|
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
|
data/lib/aspera/secret_hider.rb
CHANGED
@@ -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
|
14
|
-
ALL_SECRETS = [
|
15
|
-
# regex that define
|
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
|
-
|
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
|
-
|
43
|
+
error_message += "\nHint: home not created in Windows?"
|
44
44
|
end
|
45
|
-
raise
|
45
|
+
raise error_message
|
46
46
|
end
|
47
|
-
# send
|
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' => {
|
14
|
-
'watchd' => {
|
15
|
-
'apply_local_docroot' => {
|
16
|
-
'quiet' => {
|
17
|
-
'ws_connect' => {
|
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' => {
|
24
|
-
'local_dir' => {
|
25
|
-
'remote_dir' => {
|
26
|
-
'local_db_dir' => {
|
27
|
-
'remote_db_dir' => {
|
28
|
-
'host' => {
|
29
|
-
'user' => {
|
30
|
-
'private_key_paths' => {
|
31
|
-
'direction' => {
|
32
|
-
'checksum' => {
|
33
|
-
'tags' => {
|
34
|
-
|
35
|
-
'tcp_port' => {
|
36
|
-
'rate_policy' => {
|
37
|
-
'target_rate' => {
|
38
|
-
'cooloff' => {
|
39
|
-
'pending_max' => {
|
40
|
-
'scan_intensity' => {
|
41
|
-
'cipher' => {
|
42
|
-
'transfer_threads' => {
|
43
|
-
'preserve_time' => {
|
44
|
-
'preserve_access_time' => {
|
45
|
-
'preserve_modification_time' => {
|
46
|
-
'preserve_uid' => {
|
47
|
-
'preserve_gid' => {
|
48
|
-
'create_dir' => {
|
49
|
-
'reset' => {
|
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' => {
|
52
|
-
'cookie' => {
|
53
|
-
'token' => {
|
54
|
-
'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',
|