aspera-cli 4.25.0.pre2 → 4.25.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/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +60 -18
- data/README.md +164 -133
- data/lib/aspera/agent/factory.rb +9 -6
- data/lib/aspera/agent/transferd.rb +4 -4
- data/lib/aspera/api/aoc.rb +33 -24
- data/lib/aspera/api/ats.rb +1 -0
- data/lib/aspera/api/faspex.rb +11 -5
- data/lib/aspera/ascmd.rb +1 -1
- data/lib/aspera/ascp/installation.rb +5 -5
- data/lib/aspera/cli/formatter.rb +15 -62
- data/lib/aspera/cli/manager.rb +8 -42
- data/lib/aspera/cli/plugins/aoc.rb +48 -30
- data/lib/aspera/cli/plugins/ats.rb +30 -36
- data/lib/aspera/cli/plugins/base.rb +6 -6
- data/lib/aspera/cli/plugins/config.rb +5 -4
- data/lib/aspera/cli/plugins/faspex.rb +5 -3
- data/lib/aspera/cli/plugins/faspex5.rb +10 -8
- data/lib/aspera/cli/plugins/faspio.rb +3 -1
- data/lib/aspera/cli/plugins/node.rb +9 -6
- data/lib/aspera/cli/plugins/oauth.rb +12 -11
- data/lib/aspera/cli/plugins/preview.rb +2 -2
- data/lib/aspera/cli/transfer_agent.rb +1 -2
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +5 -5
- data/lib/aspera/dot_container.rb +108 -0
- data/lib/aspera/id_generator.rb +7 -10
- data/lib/aspera/oauth/base.rb +25 -38
- data/lib/aspera/oauth/factory.rb +5 -6
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +4 -3
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/preview/file_types.rb +1 -1
- data/lib/aspera/rest.rb +5 -2
- data/lib/aspera/ssh.rb +6 -5
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/transfer/parameters.rb +6 -6
- data/lib/aspera/transfer/spec.schema.yaml +3 -3
- data/lib/aspera/transfer/spec_doc.rb +11 -21
- data/lib/aspera/uri_reader.rb +17 -3
- data.tar.gz.sig +0 -0
- metadata +2 -1
- metadata.gz.sig +0 -0
data/lib/aspera/api/aoc.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Aspera
|
|
|
33
33
|
# types of events for shared folder creation
|
|
34
34
|
# Node events: permission.created permission.modified permission.deleted
|
|
35
35
|
PERMISSIONS_CREATED = ['permission.created'].freeze
|
|
36
|
-
# Special
|
|
36
|
+
# Special user identifier when creating workspace shared folders
|
|
37
37
|
ID_AK_ADMIN = 'ASPERA_ACCESS_KEY_ADMIN'
|
|
38
38
|
|
|
39
39
|
private_constant :MAX_AOC_URL_REDIRECT,
|
|
@@ -48,11 +48,13 @@ module Aspera
|
|
|
48
48
|
:ID_AK_ADMIN
|
|
49
49
|
|
|
50
50
|
# various API scopes supported
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
module Scope
|
|
52
|
+
SELF = 'self'
|
|
53
|
+
USER = 'user:all'
|
|
54
|
+
ADMIN = 'admin:all'
|
|
55
|
+
ADMIN_USER = 'admin-user:all'
|
|
56
|
+
ADMIN_USER_USER = "#{ADMIN_USER}+#{USER}"
|
|
57
|
+
end
|
|
56
58
|
FILES_APP = 'files'
|
|
57
59
|
PACKAGES_APP = 'packages'
|
|
58
60
|
API_V1 = 'api/v1'
|
|
@@ -227,10 +229,12 @@ module Aspera
|
|
|
227
229
|
@workspace_info = nil
|
|
228
230
|
@home_info = nil
|
|
229
231
|
auth_params = {
|
|
230
|
-
type:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
type: :oauth2,
|
|
233
|
+
params: {
|
|
234
|
+
client_id: client_id,
|
|
235
|
+
client_secret: client_secret,
|
|
236
|
+
scope: scope
|
|
237
|
+
}
|
|
234
238
|
}
|
|
235
239
|
# analyze type of url
|
|
236
240
|
url_info = AoC.link_info(url)
|
|
@@ -256,20 +260,20 @@ module Aspera
|
|
|
256
260
|
Aspera.assert(username, 'Missing mandatory option: username', type: ParameterError)
|
|
257
261
|
auth_params[:private_key_obj] = OpenSSL::PKey::RSA.new(private_key, passphrase)
|
|
258
262
|
auth_params[:payload] = {
|
|
259
|
-
iss:
|
|
263
|
+
iss: client_id, # issuer
|
|
260
264
|
sub: username, # subject
|
|
261
265
|
aud: JWT_AUDIENCE
|
|
262
266
|
}
|
|
263
267
|
# add jwt payload for global client id
|
|
264
|
-
auth_params[:payload][:org] = url_info[:organization] if GLOBAL_CLIENT_APPS.include?(
|
|
268
|
+
auth_params[:payload][:org] = url_info[:organization] if GLOBAL_CLIENT_APPS.include?(client_id)
|
|
265
269
|
auth_params[:cache_ids] = [url_info[:organization]]
|
|
266
270
|
when :url_json
|
|
267
|
-
auth_params[:url] = {grant_type: 'url_token'} #
|
|
271
|
+
auth_params[:url] = {grant_type: 'url_token'} # Query arguments
|
|
268
272
|
auth_params[:json] = {url_token: url_info[:token]} # JSON body
|
|
269
273
|
# password protection of link
|
|
270
274
|
auth_params[:json][:password] = password unless password.nil?
|
|
271
275
|
# basic auth required for /token
|
|
272
|
-
auth_params[:auth] = {type: :basic, username:
|
|
276
|
+
auth_params[:auth] = {type: :basic, username: client_id, password: client_secret}
|
|
273
277
|
else Aspera.error_unexpected_value(auth_params[:grant_method]){'auth, use one of: :web, :jwt'}
|
|
274
278
|
end
|
|
275
279
|
super(
|
|
@@ -386,11 +390,12 @@ module Aspera
|
|
|
386
390
|
@home_info
|
|
387
391
|
end
|
|
388
392
|
|
|
389
|
-
#
|
|
390
|
-
# @param
|
|
391
|
-
# @param
|
|
392
|
-
# @param
|
|
393
|
-
# @param
|
|
393
|
+
# Return a Node API for given node id, in a given context (files, packages), for the given scope.
|
|
394
|
+
# @param node_id [String] identifier of node in AoC
|
|
395
|
+
# @param workspace_id [String,nil] workspace identifier
|
|
396
|
+
# @param workspace_name [String,nil] workspace name
|
|
397
|
+
# @param scope [String,nil] e.g. Node::SCOPE_USER, or Node::SCOPE_ADMIN, or nil (requires secret)
|
|
398
|
+
# @param package_info [Hash,nil] created package information
|
|
394
399
|
# @returns [Node] a node API for access key
|
|
395
400
|
def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: Node::SCOPE_USER, package_info: nil)
|
|
396
401
|
Aspera.assert_type(node_id, String)
|
|
@@ -409,18 +414,22 @@ module Aspera
|
|
|
409
414
|
app_info[:package_name] = package_info['name']
|
|
410
415
|
end
|
|
411
416
|
node_params = {base_url: node_info['url']}
|
|
412
|
-
|
|
413
|
-
|
|
417
|
+
ak_secret = @secret_finder&.lookup_secret(url: node_info['url'], username: node_info['access_key'])
|
|
418
|
+
# If secret is available, or no scope, use basic auth
|
|
419
|
+
if scope.nil? || ak_secret
|
|
420
|
+
Aspera.assert(ak_secret, "Secret not found for access key #{node_info['access_key']}@#{node_info['url']}", type: Error)
|
|
414
421
|
node_params[:auth] = {
|
|
415
422
|
type: :basic,
|
|
416
423
|
username: node_info['access_key'],
|
|
417
|
-
password:
|
|
424
|
+
password: ak_secret
|
|
418
425
|
}
|
|
419
426
|
else
|
|
420
427
|
# OAuth bearer token
|
|
421
428
|
node_params[:auth] = auth_params.clone
|
|
422
|
-
node_params[:auth][:
|
|
423
|
-
|
|
429
|
+
node_params[:auth][:params] ||= {}
|
|
430
|
+
node_params[:auth][:params][:scope] = Node.token_scope(node_info['access_key'], scope)
|
|
431
|
+
node_params[:auth][:params][:owner_access] = true if scope.eql?(Node::SCOPE_ADMIN)
|
|
432
|
+
# Special header required for bearer token only
|
|
424
433
|
node_params[:headers] = {Node::HEADER_X_ASPERA_ACCESS_KEY => node_info['access_key']}
|
|
425
434
|
end
|
|
426
435
|
node_params[:app_info] = app_info
|
data/lib/aspera/api/ats.rb
CHANGED
data/lib/aspera/api/faspex.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Aspera
|
|
|
34
34
|
query: {
|
|
35
35
|
response_type: :code,
|
|
36
36
|
state: @context,
|
|
37
|
-
client_id: client_id,
|
|
37
|
+
client_id: params[:client_id],
|
|
38
38
|
redirect_uri: @redirect_uri
|
|
39
39
|
},
|
|
40
40
|
exception: false,
|
|
@@ -46,7 +46,7 @@ module Aspera
|
|
|
46
46
|
raise Error, info['action_message'] if info['action_message']
|
|
47
47
|
Aspera.assert(info['code']){'Missing code in answer'}
|
|
48
48
|
# Exchange code for token
|
|
49
|
-
return create_token_call(
|
|
49
|
+
return create_token_call(base_params.merge(
|
|
50
50
|
grant_type: 'authorization_code',
|
|
51
51
|
code: info['code'],
|
|
52
52
|
redirect_uri: @redirect_uri
|
|
@@ -155,7 +155,9 @@ module Aspera
|
|
|
155
155
|
base_url: "#{base_url}/#{PATH_AUTH}",
|
|
156
156
|
grant_method: :faspex_pub_link,
|
|
157
157
|
context: encoded_context,
|
|
158
|
-
|
|
158
|
+
params: {
|
|
159
|
+
client_id: config[:client_id]
|
|
160
|
+
},
|
|
159
161
|
redirect_uri: config[:redirect_uri]
|
|
160
162
|
}
|
|
161
163
|
}
|
|
@@ -177,7 +179,9 @@ module Aspera
|
|
|
177
179
|
type: :oauth2,
|
|
178
180
|
base_url: "#{url}/#{PATH_AUTH}",
|
|
179
181
|
grant_method: :web,
|
|
180
|
-
|
|
182
|
+
params: {
|
|
183
|
+
client_id: client_id
|
|
184
|
+
},
|
|
181
185
|
redirect_uri: redirect_uri
|
|
182
186
|
}
|
|
183
187
|
}
|
|
@@ -190,7 +194,9 @@ module Aspera
|
|
|
190
194
|
type: :oauth2,
|
|
191
195
|
base_url: "#{url}/#{PATH_AUTH}",
|
|
192
196
|
grant_method: :jwt,
|
|
193
|
-
|
|
197
|
+
params: {
|
|
198
|
+
client_id: client_id
|
|
199
|
+
},
|
|
194
200
|
payload: {
|
|
195
201
|
iss: client_id, # issuer
|
|
196
202
|
aud: client_id, # audience (this field is not clear...)
|
data/lib/aspera/ascmd.rb
CHANGED
|
@@ -89,7 +89,7 @@ module Aspera
|
|
|
89
89
|
# Version 2 allows use of reverse proxy with multiple addresses.
|
|
90
90
|
# @param [Symbol] one of OPERATIONS
|
|
91
91
|
# @param [Array] parameters for "as" command
|
|
92
|
-
# @return result of command, type depends on command
|
|
92
|
+
# @return [Boolean,Array,Hash] result of command, type depends on command
|
|
93
93
|
def execute_single(action_sym, arguments, version: 1, host: nil)
|
|
94
94
|
arguments = [] if arguments.nil?
|
|
95
95
|
Log.log.debug{"execute_single:#{action_sym}:#{arguments}"}
|
|
@@ -294,11 +294,11 @@ module Aspera
|
|
|
294
294
|
end
|
|
295
295
|
|
|
296
296
|
# Retrieves ascp binary for current system architecture from URL or file
|
|
297
|
-
# @param url [String]
|
|
298
|
-
# @param folder [String]
|
|
299
|
-
# @param backup [
|
|
300
|
-
# @param with_exe [
|
|
301
|
-
# @param &block [Proc]
|
|
297
|
+
# @param url [String] URL to SDK archive, or SpecialValues::DEF
|
|
298
|
+
# @param folder [String] Destination folder path
|
|
299
|
+
# @param backup [Boolean] If destination folder exists, then rename
|
|
300
|
+
# @param with_exe [Boolean] If false, only retrieves files, but do not generate or restrict access
|
|
301
|
+
# @param &block [Proc] A lambda that receives a file path from archive and tells destination sub folder(end with /) or file, or nil to not extract
|
|
302
302
|
# @return [Array] name, ascp version (from execution), folder
|
|
303
303
|
def install_sdk(url: nil, version: nil, folder: nil, backup: true, with_exe: true, &block)
|
|
304
304
|
url = sdk_url_for_platform(version: version) if url.nil? || url.eql?('DEF')
|
data/lib/aspera/cli/formatter.rb
CHANGED
|
@@ -8,6 +8,7 @@ require 'aspera/environment'
|
|
|
8
8
|
require 'aspera/log'
|
|
9
9
|
require 'aspera/assert'
|
|
10
10
|
require 'aspera/markdown'
|
|
11
|
+
require 'aspera/dot_container'
|
|
11
12
|
require 'terminal-table'
|
|
12
13
|
require 'tty-spinner'
|
|
13
14
|
require 'yaml'
|
|
@@ -64,9 +65,10 @@ module Aspera
|
|
|
64
65
|
end
|
|
65
66
|
end
|
|
66
67
|
|
|
68
|
+
# Give Markdown String, or matched data, return formatted string for terminal
|
|
67
69
|
# used by spec_doc
|
|
68
70
|
# @param match [MatchData,String]
|
|
69
|
-
def
|
|
71
|
+
def markdown_text(match)
|
|
70
72
|
if match.is_a?(String)
|
|
71
73
|
match = Markdown::FORMATS.match(match)
|
|
72
74
|
Aspera.assert(match)
|
|
@@ -84,11 +86,11 @@ module Aspera
|
|
|
84
86
|
end
|
|
85
87
|
end
|
|
86
88
|
|
|
87
|
-
#
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
until
|
|
91
|
-
current =
|
|
89
|
+
# Replace special values with a readable version on terminal
|
|
90
|
+
def replace_specific_for_terminal(input_hash)
|
|
91
|
+
hash_to_process = [input_hash]
|
|
92
|
+
until hash_to_process.empty?
|
|
93
|
+
current = hash_to_process.pop
|
|
92
94
|
current.each do |key, value|
|
|
93
95
|
case value
|
|
94
96
|
when NilClass
|
|
@@ -100,73 +102,24 @@ module Aspera
|
|
|
100
102
|
when Array
|
|
101
103
|
if value.empty?
|
|
102
104
|
current[key] = special_format('empty list')
|
|
105
|
+
elsif value.all?(String)
|
|
106
|
+
current[key] = value.join(',')
|
|
103
107
|
else
|
|
104
108
|
value.each do |item|
|
|
105
|
-
|
|
109
|
+
hash_to_process.push(item) if item.is_a?(Hash)
|
|
106
110
|
end
|
|
107
111
|
end
|
|
108
112
|
when Hash
|
|
109
113
|
if value.empty?
|
|
110
114
|
current[key] = special_format('empty dict')
|
|
111
115
|
else
|
|
112
|
-
|
|
116
|
+
hash_to_process.push(value)
|
|
113
117
|
end
|
|
114
118
|
end
|
|
115
119
|
end
|
|
116
120
|
end
|
|
117
121
|
end
|
|
118
122
|
|
|
119
|
-
# Given a list of string, display that list in a single cell
|
|
120
|
-
def list_to_string(list)
|
|
121
|
-
list.join(',')
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Build new prefix
|
|
125
|
-
def add_prefix(prefix, key)
|
|
126
|
-
[prefix, key].compact.join('.')
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Add elements of enumerator to the stack, in reverse order
|
|
130
|
-
def add_elements(stack, prefix, enum)
|
|
131
|
-
enum.reverse_each do |key, value|
|
|
132
|
-
stack.push([add_prefix(prefix, key), value])
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Flatten a Hash into single level hash
|
|
137
|
-
def flatten_hash(input)
|
|
138
|
-
Aspera.assert_type(input, Hash)
|
|
139
|
-
return input if input.empty?
|
|
140
|
-
flat = {}
|
|
141
|
-
# tail (pop,push) contains the next element to display
|
|
142
|
-
stack = [[nil, input]]
|
|
143
|
-
until stack.empty?
|
|
144
|
-
prefix, current = stack.pop
|
|
145
|
-
# empty things will be displayed as such
|
|
146
|
-
if current.respond_to?(:empty?) && current.empty?
|
|
147
|
-
flat[prefix] = current
|
|
148
|
-
next
|
|
149
|
-
end
|
|
150
|
-
case current
|
|
151
|
-
when Hash
|
|
152
|
-
add_elements(stack, prefix, current)
|
|
153
|
-
when Array
|
|
154
|
-
if current.none?{ |i| i.is_a?(Array) || i.is_a?(Hash)}
|
|
155
|
-
flat[prefix] = list_to_string(current.map(&:to_s))
|
|
156
|
-
elsif current.all?{ |i| i.is_a?(Hash) && i.keys == ['name']}
|
|
157
|
-
flat[prefix] = list_to_string(current.map{ |i| i['name']})
|
|
158
|
-
elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
|
|
159
|
-
add_elements(stack, prefix, current.each_with_object({}){ |i, h| h[i['name']] = i['value']})
|
|
160
|
-
else
|
|
161
|
-
add_elements(stack, prefix, current.each_with_index.map{ |v, i| [i, v]})
|
|
162
|
-
end
|
|
163
|
-
else
|
|
164
|
-
flat[prefix] = current
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
flat
|
|
168
|
-
end
|
|
169
|
-
|
|
170
123
|
def all_but(list)
|
|
171
124
|
list = [list] unless list.is_a?(Array)
|
|
172
125
|
return list.map{ |i| "#{FIELDS_LESS}#{i}"}.unshift(SpecialValues::ALL)
|
|
@@ -356,13 +309,13 @@ module Aspera
|
|
|
356
309
|
if data.empty?
|
|
357
310
|
display_message(:data, self.class.special_format('empty dict'))
|
|
358
311
|
else
|
|
359
|
-
data =
|
|
312
|
+
data = DotContainer.new(data).to_dotted if @options[:flat_hash]
|
|
360
313
|
display_table([data], compute_fields([data], fields), single: true)
|
|
361
314
|
end
|
|
362
315
|
when :object_list
|
|
363
316
|
# :object_list is an Array of Hash, where key=column name
|
|
364
317
|
Aspera.assert_array_all(data, Hash){'result'}
|
|
365
|
-
data = data.map{ |obj|
|
|
318
|
+
data = data.map{ |obj| DotContainer.new(obj).to_dotted} if @options[:flat_hash]
|
|
366
319
|
display_table(data, compute_fields(data, fields), single: type.eql?(:single_object))
|
|
367
320
|
when :value_list
|
|
368
321
|
# :value_list is a simple array of values, name of column provided in `name`
|
|
@@ -474,7 +427,7 @@ module Aspera
|
|
|
474
427
|
return
|
|
475
428
|
end
|
|
476
429
|
filter_columns_on_select(object_array)
|
|
477
|
-
object_array.each{ |i| self.class.
|
|
430
|
+
object_array.each{ |i| self.class.replace_specific_for_terminal(i)}
|
|
478
431
|
# if table has only one element, and only one field, display the value
|
|
479
432
|
if object_array.length == 1 && fields.length == 1
|
|
480
433
|
Log.log.debug("display_table: single element, field: #{fields.first}")
|
data/lib/aspera/cli/manager.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'aspera/colors'
|
|
|
6
6
|
require 'aspera/secret_hider'
|
|
7
7
|
require 'aspera/log'
|
|
8
8
|
require 'aspera/assert'
|
|
9
|
+
require 'aspera/dot_container'
|
|
9
10
|
require 'io/console'
|
|
10
11
|
require 'optparse'
|
|
11
12
|
|
|
@@ -486,11 +487,12 @@ module Aspera
|
|
|
486
487
|
Log.log.trace1('After parse')
|
|
487
488
|
rescue OptionParser::InvalidOption => e
|
|
488
489
|
Log.log.trace1{"InvalidOption #{e}".red}
|
|
490
|
+
# An option like --a.b.c=d does: a={"b":{"c":ext_val(d)}}
|
|
489
491
|
if (m = e.args.first.match(/^--([a-z\-]+)\.([^=]+)=(.+)$/))
|
|
490
492
|
option, path, value = m.captures
|
|
491
493
|
option_sym = self.class.option_line_to_name(option).to_sym
|
|
492
494
|
if @declared_options.key?(option_sym)
|
|
493
|
-
set_option(option_sym,
|
|
495
|
+
set_option(option_sym, DotContainer.dotted_to_container(path, smart_convert(value), get_option(option_sym)), where: 'dotted')
|
|
494
496
|
retry
|
|
495
497
|
end
|
|
496
498
|
end
|
|
@@ -561,14 +563,14 @@ module Aspera
|
|
|
561
563
|
|
|
562
564
|
# Read remaining args and build an Array or Hash
|
|
563
565
|
# @param value [nil] Argument to `@:` extended value
|
|
564
|
-
def args_as_extended(
|
|
566
|
+
def args_as_extended(arg)
|
|
565
567
|
# This extended value does not take args (`@:`)
|
|
566
|
-
ExtendedValue.assert_no_value(
|
|
568
|
+
ExtendedValue.assert_no_value(arg, :p)
|
|
567
569
|
result = nil
|
|
568
570
|
get_next_argument(:args, multiple: true).each do |arg|
|
|
569
571
|
Aspera.assert(arg.include?(OPTION_VALUE_SEPARATOR)){"Positional argument: #{arg} does not inlude #{OPTION_VALUE_SEPARATOR}"}
|
|
570
|
-
path,
|
|
571
|
-
result =
|
|
572
|
+
path, value = arg.split(OPTION_VALUE_SEPARATOR, 2)
|
|
573
|
+
result = DotContainer.dotted_to_container(path, smart_convert(value), result)
|
|
572
574
|
end
|
|
573
575
|
result
|
|
574
576
|
end
|
|
@@ -590,40 +592,6 @@ module Aspera
|
|
|
590
592
|
end
|
|
591
593
|
end
|
|
592
594
|
|
|
593
|
-
# Convert `String` to `Integer`, or keep `String` if not `Integer`
|
|
594
|
-
def int_or_string(value)
|
|
595
|
-
Integer(value, exception: false) || value
|
|
596
|
-
end
|
|
597
|
-
|
|
598
|
-
def new_hash_or_array_from_key(key)
|
|
599
|
-
key.is_a?(Integer) ? [] : {}
|
|
600
|
-
end
|
|
601
|
-
|
|
602
|
-
def array_requires_integer_index!(container, index)
|
|
603
|
-
Aspera.assert(container.is_a?(Hash) || index.is_a?(Integer)){'Using String index when Integer index used previously'}
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
# Insert extended value `value` into struct `result` at `path`
|
|
607
|
-
# @param path [String] dotted path
|
|
608
|
-
# @param value [String] Smart expression
|
|
609
|
-
# @param result [NilClass, Hash, Array] current value
|
|
610
|
-
# @return [Hash, Array]
|
|
611
|
-
def dotted_to_extended(path, value, result = nil)
|
|
612
|
-
# Typed keys
|
|
613
|
-
keys = path.split(OPTION_DOTTED_SEPARATOR).map{ |k| int_or_string(k)}
|
|
614
|
-
# Create, or re-use first level container
|
|
615
|
-
current = (result ||= new_hash_or_array_from_key(keys.first))
|
|
616
|
-
# walk the path, and create sub-containers if necessary
|
|
617
|
-
keys.each_cons(2) do |k, next_k|
|
|
618
|
-
array_requires_integer_index!(current, k)
|
|
619
|
-
current = (current[k] ||= new_hash_or_array_from_key(next_k))
|
|
620
|
-
end
|
|
621
|
-
# Assign value at last index
|
|
622
|
-
array_requires_integer_index!(current, keys.last)
|
|
623
|
-
current[keys.last] = smart_convert(value)
|
|
624
|
-
result
|
|
625
|
-
end
|
|
626
|
-
|
|
627
595
|
# generate command line option from option symbol
|
|
628
596
|
def symbol_to_option(symbol, opt_val = nil)
|
|
629
597
|
result = [OPTION_PREFIX, symbol.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)].join
|
|
@@ -677,15 +645,13 @@ module Aspera
|
|
|
677
645
|
OPTION_SEP_SYMBOL = '_'
|
|
678
646
|
# Option value separator on command line, e.g. in --option-blah=foo, the "="
|
|
679
647
|
OPTION_VALUE_SEPARATOR = '='
|
|
680
|
-
# "." : An option like --a.b.c=d does: a={"b":{"c":ext_val(d)}}
|
|
681
|
-
OPTION_DOTTED_SEPARATOR = '.'
|
|
682
648
|
# Starts an option, e.g. in --option-blah, the two first "--"
|
|
683
649
|
OPTION_PREFIX = '--'
|
|
684
650
|
# when this is alone, this stops option processing
|
|
685
651
|
OPTIONS_STOP = '--'
|
|
686
652
|
SOURCE_USER = 'cmdline' # cspell:disable-line
|
|
687
653
|
|
|
688
|
-
private_constant :BOOL_YES, :BOOL_NO, :FALSE_VALUES, :TRUE_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :OPTION_VALUE_SEPARATOR, :
|
|
654
|
+
private_constant :BOOL_YES, :BOOL_NO, :FALSE_VALUES, :TRUE_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :OPTION_VALUE_SEPARATOR, :OPTION_PREFIX, :OPTIONS_STOP, :SOURCE_USER
|
|
689
655
|
end
|
|
690
656
|
end
|
|
691
657
|
end
|
|
@@ -82,9 +82,10 @@ module Aspera
|
|
|
82
82
|
}
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
#
|
|
86
|
-
# @param
|
|
87
|
-
# @
|
|
85
|
+
# Get folder path that does not exist
|
|
86
|
+
# @param base [String] Base folder path
|
|
87
|
+
# @param always [Boolean] `true` always add number, `false` only if base folder already exists
|
|
88
|
+
# @return [String] Folder path that does not exist, with possible .<number> extension
|
|
88
89
|
def next_available_folder(base, always: false)
|
|
89
90
|
counter = always ? 1 : 0
|
|
90
91
|
loop do
|
|
@@ -175,7 +176,7 @@ module Aspera
|
|
|
175
176
|
auto_set_jwt = true
|
|
176
177
|
Aspera.error_not_implemented
|
|
177
178
|
# aoc_api.oauth.grant_method = :web
|
|
178
|
-
# aoc_api.oauth.scope = Api::AoC::
|
|
179
|
+
# aoc_api.oauth.scope = Api::AoC::Scope::ADMIN
|
|
179
180
|
# aoc_api.oauth.specific_parameters[:redirect_uri] = REDIRECT_LOCALHOST
|
|
180
181
|
end
|
|
181
182
|
myself = aoc_api.read('self')
|
|
@@ -206,6 +207,7 @@ module Aspera
|
|
|
206
207
|
@cache_workspace_info = nil
|
|
207
208
|
@cache_home_node_file = nil
|
|
208
209
|
@cache_api_aoc = nil
|
|
210
|
+
@scope = Api::AoC::Scope::USER
|
|
209
211
|
options.declare(:workspace, 'Name of workspace', allowed: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE)
|
|
210
212
|
options.declare(:new_user_option, 'New user creation option for unknown package recipients', allowed: Hash)
|
|
211
213
|
options.declare(:validate_metadata, 'Validate shared inbox metadata', allowed: Allowed::TYPES_BOOLEAN, default: true)
|
|
@@ -215,39 +217,48 @@ module Aspera
|
|
|
215
217
|
Node.declare_options(options)
|
|
216
218
|
end
|
|
217
219
|
|
|
220
|
+
# Change API scope for subsequent calls, re-instantiate API object
|
|
221
|
+
# @param new_scope [String] New scope
|
|
222
|
+
def change_api_scope(new_scope)
|
|
223
|
+
@cache_api_aoc = nil
|
|
224
|
+
@scope = new_scope
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# create an API object with the same options, but with a different subpath
|
|
228
|
+
# @param aoc_base_path [String] New subpath
|
|
229
|
+
# @return [Api::AoC] API object for AoC (is Rest)
|
|
218
230
|
def api_from_options(aoc_base_path)
|
|
219
|
-
# create an API object with the same options, but with a different subpath
|
|
220
231
|
return new_with_options(
|
|
221
232
|
Api::AoC,
|
|
222
|
-
|
|
233
|
+
kwargs: {
|
|
234
|
+
scope: @scope,
|
|
223
235
|
subpath: aoc_base_path,
|
|
224
236
|
secret_finder: config
|
|
225
237
|
},
|
|
226
|
-
|
|
227
|
-
scope: Api::AoC::SCOPE_FILES_USER,
|
|
238
|
+
option: {
|
|
228
239
|
workspace: nil
|
|
229
240
|
}
|
|
230
241
|
)
|
|
231
242
|
end
|
|
232
243
|
|
|
233
244
|
# AoC Rest object
|
|
234
|
-
# @return [Rest
|
|
245
|
+
# @return [Api::AoC] API object for AoC (is Rest)
|
|
235
246
|
def aoc_api
|
|
236
247
|
if @cache_api_aoc.nil?
|
|
237
248
|
@cache_api_aoc = api_from_options(Api::AoC::API_V1)
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
transfer.httpgw_url_cb = lambda{organization['http_gateway_server_url']}
|
|
249
|
+
transfer.httpgw_url_cb = lambda do
|
|
250
|
+
organization = @cache_api_aoc.read('organization')
|
|
241
251
|
# @cache_api_aoc.current_user_info['connect_disabled']
|
|
252
|
+
organization['http_gateway_server_url'] if organization['http_gateway_enabled'] && organization['http_gateway_server_url']
|
|
242
253
|
end
|
|
243
254
|
end
|
|
244
255
|
return @cache_api_aoc
|
|
245
256
|
end
|
|
246
257
|
|
|
247
258
|
# Generate or update Hash with workspace id and name (option), if not already set
|
|
248
|
-
# @param hash [Hash,
|
|
249
|
-
# @param string [
|
|
250
|
-
# @param name [
|
|
259
|
+
# @param hash [Hash,nil] Optional base hash (modified)
|
|
260
|
+
# @param string [Boolean] true to set key as string, else as symbol
|
|
261
|
+
# @param name [Boolean] include name
|
|
251
262
|
# @return [Hash] with key `workspace_[id,name]` (symbol or string) only if defined
|
|
252
263
|
def workspace_id_hash(hash: nil, string: false, name: false)
|
|
253
264
|
info = aoc_api.workspace
|
|
@@ -320,8 +331,11 @@ module Aspera
|
|
|
320
331
|
NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
|
|
321
332
|
private_constant :NODE4_EXT_COMMANDS
|
|
322
333
|
|
|
323
|
-
#
|
|
324
|
-
# @param
|
|
334
|
+
# Execute a node gen4 command
|
|
335
|
+
# @param command_repo [Symbol] command to execute
|
|
336
|
+
# @param node_id [String] Node identifier
|
|
337
|
+
# @param file_id [String] Root file id for the operation (can be AK root, or other, e.g. package, or link). If `nil` use AK root file id.
|
|
338
|
+
# @param scope [String] node scope (Node::SCOPE_<USER|ADMIN>), or nil (requires secret)
|
|
325
339
|
def execute_nodegen4_command(command_repo, node_id, file_id: nil, scope: nil)
|
|
326
340
|
top_node_api = aoc_api.node_api_from(
|
|
327
341
|
node_id: node_id,
|
|
@@ -423,7 +437,7 @@ module Aspera
|
|
|
423
437
|
when :client_registration_token then default_fields.push('value', 'data.client_subject_scopes', 'created_at')
|
|
424
438
|
when :contact
|
|
425
439
|
default_fields = %w[source_type source_id name email]
|
|
426
|
-
default_query = {'include_only_user_personal_contacts' => true} if
|
|
440
|
+
default_query = {'include_only_user_personal_contacts' => true} if @scope == Api::AoC::Scope::USER
|
|
427
441
|
when :node then default_fields.push('name', 'host', 'access_key')
|
|
428
442
|
when :operation then default_fields = nil
|
|
429
443
|
when :short_link then default_fields.push('short_url', 'data.url_token_data.purpose')
|
|
@@ -456,7 +470,7 @@ module Aspera
|
|
|
456
470
|
return Main.result_success
|
|
457
471
|
when :do
|
|
458
472
|
command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
|
|
459
|
-
return execute_nodegen4_command(command_repo, res_id)
|
|
473
|
+
return execute_nodegen4_command(command_repo, res_id, scope: Api::Node::SCOPE_ADMIN)
|
|
460
474
|
when :bearer_token
|
|
461
475
|
node_api = aoc_api.node_api_from(
|
|
462
476
|
node_id: res_id,
|
|
@@ -515,13 +529,15 @@ module Aspera
|
|
|
515
529
|
end
|
|
516
530
|
end
|
|
517
531
|
|
|
518
|
-
ADMIN_ACTIONS = %i[ats resource usage_reports analytics subscription auth_providers].concat(ADMIN_OBJECTS).freeze
|
|
532
|
+
ADMIN_ACTIONS = %i[ats bearer_token resource usage_reports analytics subscription auth_providers].concat(ADMIN_OBJECTS).freeze
|
|
519
533
|
|
|
520
534
|
def execute_admin_action
|
|
521
|
-
#
|
|
522
|
-
|
|
535
|
+
# change scope to admin
|
|
536
|
+
change_api_scope(Api::AoC::Scope::ADMIN)
|
|
523
537
|
command_admin = options.get_next_command(ADMIN_ACTIONS)
|
|
524
538
|
case command_admin
|
|
539
|
+
when :bearer_token
|
|
540
|
+
return Main.result_text(aoc_api.oauth.authorization)
|
|
525
541
|
when :resource
|
|
526
542
|
Log.log.warn('resource command is deprecated (4.18), directly use the specific command instead')
|
|
527
543
|
return execute_resource_action(options.get_next_argument('resource', accept_list: ADMIN_OBJECTS))
|
|
@@ -650,13 +666,13 @@ module Aspera
|
|
|
650
666
|
when :ats
|
|
651
667
|
ats_api = Rest.new(**aoc_api.params.deep_merge({
|
|
652
668
|
base_url: "#{aoc_api.base_url}/admin/ats/pub/v1",
|
|
653
|
-
auth: {scope: Api::AoC::
|
|
669
|
+
auth: {params: {scope: Api::AoC::Scope::ADMIN_USER}}
|
|
654
670
|
}))
|
|
655
|
-
return Ats.new(context: context).
|
|
671
|
+
return Ats.new(context: context, api: ats_api).execute_action
|
|
656
672
|
when :analytics
|
|
657
673
|
analytics_api = Rest.new(**aoc_api.params.deep_merge({
|
|
658
674
|
base_url: "#{aoc_api.base_url.gsub('/api/v1', '')}/analytics/v2",
|
|
659
|
-
auth: {scope: Api::AoC::
|
|
675
|
+
auth: {params: {scope: Api::AoC::Scope::ADMIN_USER}}
|
|
660
676
|
}))
|
|
661
677
|
command_analytics = options.get_next_command(%i[application_events transfers files])
|
|
662
678
|
case command_analytics
|
|
@@ -681,13 +697,13 @@ module Aspera
|
|
|
681
697
|
start_date_persistency = PersistencyActionOnce.new(
|
|
682
698
|
manager: persistency,
|
|
683
699
|
data: saved_date,
|
|
684
|
-
id: IdGenerator.from_list(
|
|
700
|
+
id: IdGenerator.from_list(
|
|
685
701
|
'aoc_ana_date',
|
|
686
702
|
options.get_option(:url, mandatory: true),
|
|
687
703
|
aoc_api.workspace[:name],
|
|
688
704
|
event_resource_type.to_s,
|
|
689
705
|
event_resource_id
|
|
690
|
-
|
|
706
|
+
)
|
|
691
707
|
)
|
|
692
708
|
start_date_time = saved_date.first
|
|
693
709
|
stop_date_time = Time.now.utc.strftime('%FT%T.%LZ')
|
|
@@ -840,9 +856,10 @@ module Aspera
|
|
|
840
856
|
manager: persistency,
|
|
841
857
|
data: [],
|
|
842
858
|
id: IdGenerator.from_list(
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
859
|
+
'aoc_recv',
|
|
860
|
+
options.get_option(:url, mandatory: true),
|
|
861
|
+
aoc_api.workspace[:id],
|
|
862
|
+
aoc_api.additional_persistence_ids
|
|
846
863
|
)
|
|
847
864
|
)
|
|
848
865
|
end
|
|
@@ -1079,6 +1096,7 @@ module Aspera
|
|
|
1079
1096
|
end
|
|
1080
1097
|
end
|
|
1081
1098
|
when :automation
|
|
1099
|
+
change_api_scope(Api::AoC::Scope::ADMIN_USER)
|
|
1082
1100
|
Log.log.warn('BETA: work under progress')
|
|
1083
1101
|
# automation api is not in the same place
|
|
1084
1102
|
automation_api = Rest.new(**aoc_api.params, base_url: aoc_api.base_url.gsub('/api/', '/automation/'))
|