aspera-cli 4.26.1 → 4.27.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 +300 -22
- data/CONTRIBUTING.md +100 -12
- data/bin/ascli +5 -5
- data/docs/README.md +10757 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +20 -6
- data/lib/aspera/agent/connect.rb +53 -16
- data/lib/aspera/agent/desktop.rb +52 -17
- data/lib/aspera/agent/direct.rb +27 -25
- data/lib/aspera/agent/factory.rb +4 -0
- data/lib/aspera/agent/httpgw.rb +2 -2
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +39 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +56 -51
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +22 -20
- data/lib/aspera/api/httpgw.rb +4 -4
- data/lib/aspera/api/node.rb +33 -30
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +25 -23
- data/lib/aspera/ascp/installation.rb +43 -40
- data/lib/aspera/ascp/management.rb +2 -3
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +81 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +138 -0
- data/lib/aspera/cli/context.rb +71 -0
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +5 -3
- data/lib/aspera/cli/formatter.rb +122 -209
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +218 -0
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +198 -0
- data/lib/aspera/cli/options.schema.yaml +364 -7
- data/lib/aspera/cli/{manager.rb → parser.rb} +337 -144
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1173 -849
- data/lib/aspera/cli/plugins/ats.rb +200 -161
- data/lib/aspera/cli/plugins/base.rb +470 -107
- data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
- data/lib/aspera/cli/plugins/config.rb +434 -1032
- data/lib/aspera/cli/plugins/console.rb +106 -64
- data/lib/aspera/cli/plugins/cos.rb +44 -32
- data/lib/aspera/cli/plugins/factory.rb +7 -4
- data/lib/aspera/cli/plugins/faspex.rb +296 -259
- data/lib/aspera/cli/plugins/faspex5.rb +592 -344
- data/lib/aspera/cli/plugins/faspio.rb +49 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +1001 -797
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +111 -139
- data/lib/aspera/cli/plugins/preview.rb +183 -142
- data/lib/aspera/cli/plugins/server.rb +125 -80
- data/lib/aspera/cli/plugins/shares.rb +301 -107
- data/lib/aspera/cli/preset_actions.rb +139 -0
- data/lib/aspera/cli/preset_manager.rb +236 -0
- data/lib/aspera/cli/result.rb +360 -0
- data/lib/aspera/cli/runner.rb +473 -0
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +84 -56
- data/lib/aspera/cli/terminal_formatter.rb +65 -0
- data/lib/aspera/cli/transfer_actions.rb +83 -0
- data/lib/aspera/cli/transfer_agent.rb +116 -51
- data/lib/aspera/cli/transfer_progress.rb +9 -9
- data/lib/aspera/cli/vault_manager.rb +57 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +26 -26
- data/lib/aspera/command_line_builder.rb +23 -23
- data/lib/aspera/data_repository.rb +6 -1
- data/lib/aspera/dot_container.rb +5 -5
- data/lib/aspera/environment.rb +39 -13
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +4 -3
- data/lib/aspera/graphql.rb +35 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/base.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +2 -2
- data/lib/aspera/keychain/factory.rb +4 -4
- data/lib/aspera/keychain/macos_security.rb +4 -4
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +23 -5
- data/lib/aspera/markdown.rb +85 -2
- data/lib/aspera/nagios.rb +2 -2
- data/lib/aspera/node_simulator.rb +3 -1
- data/lib/aspera/oauth/base.rb +10 -10
- data/lib/aspera/oauth/boot.rb +43 -0
- data/lib/aspera/oauth/factory.rb +41 -15
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/web.rb +4 -4
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_action_once.rb +10 -10
- data/lib/aspera/persistency_folder.rb +10 -3
- data/lib/aspera/preview/file_types.rb +7 -3
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/connect.rb +4 -1
- data/lib/aspera/products/other.rb +1 -1
- data/lib/aspera/products/transferd.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +10 -9
- data/lib/aspera/rest.rb +18 -40
- data/lib/aspera/rest_error_analyzer.rb +7 -3
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +13 -6
- data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +14 -4
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +2730 -648
- data/lib/aspera/schema/async_tables.yaml +361 -0
- data/lib/aspera/schema/documentation.rb +57 -26
- data/lib/aspera/schema/reader.rb +60 -11
- data/lib/aspera/schema/registry.rb +50 -10
- data/lib/aspera/secret_hider.rb +11 -2
- data/lib/aspera/ssl.rb +4 -4
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +14 -10
- data/lib/aspera/sync/operations.rb +15 -18
- data/lib/aspera/temp_file_manager.rb +5 -1
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +27 -25
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +7 -7
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +26 -8
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +8 -5
- data/lib/aspera/yaml.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +45 -16
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/main.rb +0 -480
- data/lib/aspera/json_rpc.rb +0 -51
data/lib/aspera/agent/node.rb
CHANGED
|
@@ -13,6 +13,38 @@ module Aspera
|
|
|
13
13
|
# this singleton class is used by the CLI to provide a common interface to start a transfer
|
|
14
14
|
# before using it, the use must set the `node_api` member.
|
|
15
15
|
class Node < Base
|
|
16
|
+
class << self
|
|
17
|
+
# Re-query a previously started transfer via the Node REST API.
|
|
18
|
+
# agent_params must contain 'url', 'username', 'password' (and optionally 'root_id').
|
|
19
|
+
# @return [Hash] normalized status hash
|
|
20
|
+
def transfer_status(transfer_id, agent_params)
|
|
21
|
+
rest_params = {base_url: agent_params['url']}
|
|
22
|
+
if OAuth::Factory.bearer_auth?(agent_params['password'])
|
|
23
|
+
rest_params[:headers] = Api::Node.bearer_headers(agent_params['password'], access_key: agent_params['username'])
|
|
24
|
+
else
|
|
25
|
+
rest_params[:auth] = {type: :basic, username: agent_params['username'], password: agent_params['password']}
|
|
26
|
+
end
|
|
27
|
+
node_api = Rest.new(**rest_params)
|
|
28
|
+
data = node_api.read("ops/transfers/#{transfer_id}") || {'status' => 'unknown'}
|
|
29
|
+
normalize_status(data)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Normalize a node API transfer hash into the standard async-transfer fields.
|
|
33
|
+
def normalize_status(data)
|
|
34
|
+
result = {'bytes_transferred' => data['bytes_transferred'].to_i}
|
|
35
|
+
case data['status']
|
|
36
|
+
when 'completed'
|
|
37
|
+
result['status'] = 'completed'
|
|
38
|
+
result['ended_at'] = Time.now.utc.iso8601
|
|
39
|
+
when 'failed'
|
|
40
|
+
result.merge!('status' => 'failed', 'ended_at' => Time.now.utc.iso8601, 'error' => data['error_desc'].to_s)
|
|
41
|
+
else
|
|
42
|
+
result['status'] = 'running'
|
|
43
|
+
end
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
16
48
|
# @param url [String] the base url of the node api
|
|
17
49
|
# @param username [String] the username to use for the node api
|
|
18
50
|
# @param password [String] the password to use for the node api
|
|
@@ -30,7 +62,7 @@ module Aspera
|
|
|
30
62
|
@root_id = root_id
|
|
31
63
|
rest_params = {base_url: url}
|
|
32
64
|
if OAuth::Factory.bearer_auth?(password)
|
|
33
|
-
Aspera.assert(!@root_id.nil
|
|
65
|
+
Aspera.assert(!@root_id.nil?, 'root_id not allowed for access key')
|
|
34
66
|
rest_params[:headers] = Api::Node.bearer_headers(password, access_key: username)
|
|
35
67
|
else
|
|
36
68
|
rest_params[:auth] = {
|
|
@@ -117,7 +149,7 @@ module Aspera
|
|
|
117
149
|
|
|
118
150
|
# used internally to ensure node api is set before using.
|
|
119
151
|
def node_api_
|
|
120
|
-
Aspera.assert(!@node_api.nil
|
|
152
|
+
Aspera.assert(!@node_api.nil?, 'Before using this object, set the node_api attribute to a Aspera::Rest object')
|
|
121
153
|
return @node_api
|
|
122
154
|
end
|
|
123
155
|
end
|
|
@@ -21,6 +21,37 @@ module Aspera
|
|
|
21
21
|
|
|
22
22
|
private_constant :LOCAL_SOCKET_ADDR, :PORT_SEP, :AUTO_LOCAL_TCP_PORT
|
|
23
23
|
|
|
24
|
+
class << self
|
|
25
|
+
# Re-query a previously started transfer from a running transferd daemon.
|
|
26
|
+
# agent_params must contain 'url' (the daemon endpoint, e.g. "127.0.0.1:12345").
|
|
27
|
+
# @return [Hash] normalized status hash
|
|
28
|
+
def transfer_status(transfer_id, agent_params)
|
|
29
|
+
client = ::Transferd::Api::TransferService::Stub.new(agent_params['url'], :this_channel_is_insecure)
|
|
30
|
+
result = {'bytes_transferred' => 0}
|
|
31
|
+
client.monitor_transfers(::Transferd::Api::RegistrationRequest.new(transferId: [transfer_id])) do |response|
|
|
32
|
+
result = case response.status
|
|
33
|
+
when :COMPLETED
|
|
34
|
+
{
|
|
35
|
+
'status' => 'completed', 'ended_at' => Time.now.utc.iso8601,
|
|
36
|
+
'bytes_transferred' => response.transferInfo.bytesTransferred
|
|
37
|
+
}
|
|
38
|
+
when :FAILED, :CANCELED
|
|
39
|
+
{
|
|
40
|
+
'status' => response.status == :FAILED ? 'failed' : 'cancelled',
|
|
41
|
+
'ended_at' => Time.now.utc.iso8601, 'bytes_transferred' => 0,
|
|
42
|
+
'error' => JSON.parse(response.message.to_s)['Description']
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
{'status' => 'running', 'bytes_transferred' => response.transferInfo.bytesTransferred}
|
|
46
|
+
end
|
|
47
|
+
break
|
|
48
|
+
end
|
|
49
|
+
result
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
{'status' => 'running', 'bytes_transferred' => 0, 'error' => e.message}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
24
55
|
# @param url [String] URL of the transfer manager daemon
|
|
25
56
|
# @param start [Boolean] If `false`, expect that an external daemon is already running
|
|
26
57
|
# @param stop [Boolean] If `false`, do not shutdown daemon on exit
|
|
@@ -35,9 +66,10 @@ module Aspera
|
|
|
35
66
|
@transfer_id = nil
|
|
36
67
|
@stop = stop
|
|
37
68
|
is_local_auto_port = url.eql?(AUTO_LOCAL_TCP_PORT)
|
|
38
|
-
|
|
69
|
+
Aspera.assert(!(is_local_auto_port && (!@stop || !start)), type: Error){'Cannot set options `stop` or `start` to false with port zero'}
|
|
39
70
|
# keep PID for optional shutdown
|
|
40
71
|
@daemon_pid = nil
|
|
72
|
+
@daemon_endpoint = nil
|
|
41
73
|
daemon_endpoint = url
|
|
42
74
|
Log.dump(:daemon_endpoint, daemon_endpoint)
|
|
43
75
|
# retry loop
|
|
@@ -48,6 +80,7 @@ module Aspera
|
|
|
48
80
|
@transfer_client = ::Transferd::Api::TransferService::Stub.new(daemon_endpoint, :this_channel_is_insecure)
|
|
49
81
|
# Initiate actual connection
|
|
50
82
|
get_info_response = @transfer_client.get_info(::Transferd::Api::InstanceInfoRequest.new)
|
|
83
|
+
@daemon_endpoint = daemon_endpoint
|
|
51
84
|
Log.log.debug{"Daemon info: #{get_info_response}"}
|
|
52
85
|
Log.log.warn('Attached to existing daemon') unless @daemon_pid || !start || !@stop
|
|
53
86
|
at_exit{shutdown}
|
|
@@ -55,7 +88,7 @@ module Aspera
|
|
|
55
88
|
# if transferd is external: do not start it, or other error
|
|
56
89
|
raise if !start || !e.message.include?('failed to connect')
|
|
57
90
|
# we already tried to start a daemon, but it failed
|
|
58
|
-
Aspera.assert(@daemon_pid.nil?){"Daemon started with PID #{@daemon_pid}, but connection failed to #{daemon_endpoint}
|
|
91
|
+
Aspera.assert(@daemon_pid.nil?){"Daemon started with PID #{@daemon_pid}, but connection failed to #{daemon_endpoint}"}
|
|
59
92
|
Log.log.warn('no daemon present, starting daemon...') if !start
|
|
60
93
|
# transferd only supports local ip and port
|
|
61
94
|
daemon_uri = URI.parse("ipv4://#{daemon_endpoint}")
|
|
@@ -105,6 +138,9 @@ module Aspera
|
|
|
105
138
|
end
|
|
106
139
|
end
|
|
107
140
|
|
|
141
|
+
# Actual endpoint the daemon is listening on (resolved after connect, e.g. when port 0 was used)
|
|
142
|
+
attr_reader :daemon_endpoint
|
|
143
|
+
|
|
108
144
|
# :reek:UnusedParameters token_regenerator
|
|
109
145
|
def start_transfer(transfer_spec, token_regenerator: nil)
|
|
110
146
|
Transfer::Spec.fix_transferd_resume_policy(transfer_spec)
|
|
@@ -116,7 +152,7 @@ module Aspera
|
|
|
116
152
|
) # transfer definition
|
|
117
153
|
# send start transfer request to the transfer manager daemon
|
|
118
154
|
start_response = @transfer_client.start_transfer(transfer_request)
|
|
119
|
-
|
|
155
|
+
Aspera.assert(!start_response.status.eql?(:FAILED), type: Transfer::Error){start_response.error.description}
|
|
120
156
|
Log.log.debug{"start transfer response #{start_response}"}
|
|
121
157
|
@transfer_id = start_response.transferId
|
|
122
158
|
Log.log.debug{"transfer started with id #{@transfer_id}"}
|
data/lib/aspera/api/alee.rb
CHANGED
data/lib/aspera/api/aoc.rb
CHANGED
|
@@ -25,23 +25,23 @@ module Aspera
|
|
|
25
25
|
|
|
26
26
|
class AppInfo
|
|
27
27
|
# @return [Aspera::Api::AoC] Rest Api object
|
|
28
|
-
attr_reader
|
|
28
|
+
attr_reader :api
|
|
29
29
|
# @return [Hash, nil] Package information, `"files"` or `"packages"`
|
|
30
|
-
attr_reader
|
|
30
|
+
attr_reader :package
|
|
31
31
|
# @return [Hash] Result of GET /nodes/:id
|
|
32
|
-
attr_reader
|
|
32
|
+
attr_reader :node_info
|
|
33
33
|
# @return [String] Workspace id
|
|
34
|
-
attr_reader
|
|
34
|
+
attr_reader :workspace_id
|
|
35
35
|
# @return [String] Workspace name
|
|
36
|
-
attr_reader
|
|
36
|
+
attr_reader :workspace_name
|
|
37
37
|
# @return [String, nil] "Share as" link name
|
|
38
38
|
attr_accessor :opt_link_name
|
|
39
39
|
|
|
40
40
|
# @param api [Api::Aoc] Rest Api object
|
|
41
41
|
# @param package [Hash,nil] package information or nil
|
|
42
42
|
# @param node_info [Hash] Result of GET /nodes/:id
|
|
43
|
-
# @param workspace_id
|
|
44
|
-
# @param workspace_name [String]
|
|
43
|
+
# @param workspace_id [String] Workspace identifier
|
|
44
|
+
# @param workspace_name [String] Workspace name
|
|
45
45
|
def initialize(api, package, node_info, workspace_id, workspace_name)
|
|
46
46
|
@api = api
|
|
47
47
|
@package = package
|
|
@@ -68,8 +68,8 @@ module Aspera
|
|
|
68
68
|
GLOBAL_CLIENT_APPS = DataRepository::ELEMENTS.select{ |i| i.to_s.start_with?(CLIENT_ID_PREFIX)}.freeze
|
|
69
69
|
# cookie prefix so that console can decode identity
|
|
70
70
|
COOKIE_PREFIX_CONSOLE_AOC = 'aspera.aoc'
|
|
71
|
-
# path in URL of public links
|
|
72
|
-
PUBLIC_LINK_PATHS = %w[/packages/public/receive /packages/public/send /files/public /public/files /public/package /public/send].freeze
|
|
71
|
+
# path in URL of public links (warning if not in list)
|
|
72
|
+
PUBLIC_LINK_PATHS = %w[/packages/public/receive /packages/public/send /files/public /public/files /public/files/ /public/package /public/send /public/send/package].freeze
|
|
73
73
|
JWT_AUDIENCE = 'https://api.asperafiles.com/api/v1/oauth2/token'
|
|
74
74
|
OAUTH_API_SUBPATH = 'api/v1/oauth2'
|
|
75
75
|
# minimum fields for user info if retrieval fails
|
|
@@ -113,8 +113,10 @@ module Aspera
|
|
|
113
113
|
return client_key, DataRepository.instance.item(client_key)
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
-
#
|
|
117
|
-
|
|
116
|
+
# Base API url depends on domain, which could be "qa.xxx" or self-managed domain
|
|
117
|
+
# @param api_domain [String] the AoC domain (e.g. "ibmaspera.com" (SAAS_DOMAIN_PROD) or a self-managed domain)
|
|
118
|
+
# @return [String] the base URL for the AoC API
|
|
119
|
+
def api_base_url(api_domain)
|
|
118
120
|
return "https://api.#{api_domain}"
|
|
119
121
|
end
|
|
120
122
|
|
|
@@ -197,7 +199,7 @@ module Aspera
|
|
|
197
199
|
# @option return [Integer] :total The total number of items
|
|
198
200
|
def call_paging(query: {})
|
|
199
201
|
Aspera.assert_type(query, Hash){'query'}
|
|
200
|
-
Aspera.assert(block_given
|
|
202
|
+
Aspera.assert(block_given?, 'block required for call_paging')
|
|
201
203
|
# set default large page if user does not specify own parameters. AoC Caps to 1000 anyway
|
|
202
204
|
query['per_page'] = 1000 unless query.key?('per_page')
|
|
203
205
|
max_items = query.delete(RestList::MAX_ITEMS)
|
|
@@ -211,7 +213,7 @@ module Aspera
|
|
|
211
213
|
new_query = query.clone
|
|
212
214
|
new_query['page'] = current_page
|
|
213
215
|
result_data, result_http = yield(new_query)
|
|
214
|
-
Aspera.assert(result_http)
|
|
216
|
+
Aspera.assert(result_http, 'expected HTTP result from paging block')
|
|
215
217
|
total_count = result_http[HEADER_X_TOTAL_COUNT]&.to_i
|
|
216
218
|
page_count += 1
|
|
217
219
|
current_page += 1
|
|
@@ -271,7 +273,7 @@ module Aspera
|
|
|
271
273
|
scope: nil,
|
|
272
274
|
subpath: API_V1,
|
|
273
275
|
secret_finder: nil,
|
|
274
|
-
# below: OAuth::AUTH_OPTIONS
|
|
276
|
+
# parameters below: OAuth::AUTH_OPTIONS
|
|
275
277
|
url:,
|
|
276
278
|
auth:,
|
|
277
279
|
client_id: nil,
|
|
@@ -284,12 +286,18 @@ module Aspera
|
|
|
284
286
|
)
|
|
285
287
|
# Test here because link may set url
|
|
286
288
|
Aspera.assert(url, 'Missing mandatory option: url', type: ParameterError)
|
|
287
|
-
|
|
289
|
+
# analyze type of url
|
|
290
|
+
url_info = self.class.link_info(url)
|
|
291
|
+
Log.dump(:url_info, url_info)
|
|
292
|
+
@private_link = url_info[:private_link]
|
|
293
|
+
Aspera.assert(auth, 'Missing mandatory option: auth', type: ParameterError) unless url_info.key?(:token)
|
|
294
|
+
# this is the base API url
|
|
295
|
+
api_url_base = self.class.api_base_url(url_info[:instance_domain])
|
|
296
|
+
Aspera.assert(scope, 'Missing mandatory option: scope', type: ParameterError) unless auth.eql?(:none)
|
|
288
297
|
# default values for client id
|
|
289
298
|
client_id, client_secret = self.class.get_client_info if client_id.nil?
|
|
290
299
|
# access key secrets are provided out of band to get node api access
|
|
291
|
-
#
|
|
292
|
-
# value: associated secret
|
|
300
|
+
# maps access key (String) to its associated secret (String)
|
|
293
301
|
@secret_finder = secret_finder
|
|
294
302
|
@cache_user_info = nil
|
|
295
303
|
@cache_url_token_info = nil
|
|
@@ -298,28 +306,21 @@ module Aspera
|
|
|
298
306
|
# Used only for init: provided by user
|
|
299
307
|
@ws_ids = {id: nil, name: nil}
|
|
300
308
|
@home_info = nil
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
309
|
+
base_args = {
|
|
310
|
+
base_url: "#{api_url_base}/#{subpath}",
|
|
311
|
+
auth: {
|
|
312
|
+
type: :oauth2,
|
|
313
|
+
grant_method: url_info.key?(:token) ? :url_json : auth,
|
|
314
|
+
base_url: "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{url_info[:organization]}",
|
|
315
|
+
params: {
|
|
316
|
+
client_id: client_id,
|
|
317
|
+
client_secret: client_secret,
|
|
318
|
+
scope: scope
|
|
319
|
+
}
|
|
307
320
|
}
|
|
308
321
|
}
|
|
309
|
-
#
|
|
310
|
-
|
|
311
|
-
Log.dump(:url_info, url_info)
|
|
312
|
-
@private_link = url_info[:private_link]
|
|
313
|
-
auth_params[:grant_method] = if url_info.key?(:token)
|
|
314
|
-
:url_json
|
|
315
|
-
else
|
|
316
|
-
Aspera.assert(auth, 'Missing mandatory option: auth', type: ParameterError)
|
|
317
|
-
auth
|
|
318
|
-
end
|
|
319
|
-
# this is the base API url
|
|
320
|
-
api_url_base = self.class.api_base_url(api_domain: url_info[:instance_domain])
|
|
321
|
-
# auth URL
|
|
322
|
-
auth_params[:base_url] = "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{url_info[:organization]}"
|
|
322
|
+
# shortcut alias
|
|
323
|
+
auth_params = base_args[:auth]
|
|
323
324
|
# fill other auth parameters based on OAuth method
|
|
324
325
|
case auth_params[:grant_method]
|
|
325
326
|
when :web
|
|
@@ -344,12 +345,16 @@ module Aspera
|
|
|
344
345
|
auth_params[:json][:password] = password unless password.nil?
|
|
345
346
|
# basic auth required for /token
|
|
346
347
|
auth_params[:auth] = {type: :basic, username: client_id, password: client_secret}
|
|
347
|
-
|
|
348
|
+
when :boot
|
|
349
|
+
# cookie is optional: if absent, existing cache is used
|
|
350
|
+
auth_params[:cookie] = password unless password.nil?
|
|
351
|
+
auth_params[:username] = username unless username.nil?
|
|
352
|
+
when :none
|
|
353
|
+
auth_params.clear
|
|
354
|
+
auth_params[:type] = :none
|
|
355
|
+
else Aspera.error_unexpected_value(auth_params[:grant_method]){'auth, use one of: web, jwt, boot'}
|
|
348
356
|
end
|
|
349
|
-
super(
|
|
350
|
-
base_url: "#{api_url_base}/#{subpath}",
|
|
351
|
-
auth: auth_params
|
|
352
|
-
)
|
|
357
|
+
super(**base_args)
|
|
353
358
|
end
|
|
354
359
|
|
|
355
360
|
# Read using the query and paging
|
|
@@ -473,17 +478,17 @@ module Aspera
|
|
|
473
478
|
# @param workspace_name [String,nil] workspace name
|
|
474
479
|
# @param scope [String,nil] e.g. Node::Scope::USER, or Node::Scope::ADMIN, or nil (requires secret)
|
|
475
480
|
# @param package_info [Hash,nil] created package information
|
|
476
|
-
# @
|
|
481
|
+
# @return [Node] a node API for access key
|
|
477
482
|
def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: Node::Scope::USER, package_info: nil)
|
|
478
483
|
Aspera.assert_type(node_id, String)
|
|
479
484
|
node_info = read("nodes/#{node_id}")
|
|
480
485
|
workspace_name = read("workspaces/#{workspace_id}")['name'] if workspace_name.nil? && !workspace_id.nil?
|
|
481
486
|
app_info = AppInfo.new(self, package_info, node_info, workspace_id, workspace_name)
|
|
482
487
|
node_params = {base_url: node_info['url']}
|
|
483
|
-
ak_secret = @secret_finder&.
|
|
488
|
+
ak_secret = @secret_finder&.lookup(url: node_info['url'], username: node_info['access_key'])
|
|
484
489
|
# If secret is available, or no scope, use basic auth
|
|
485
490
|
if scope.nil? || ak_secret
|
|
486
|
-
Aspera.assert(ak_secret, "Secret not found for access key #{node_info['access_key']}@#{node_info['url']}"
|
|
491
|
+
Aspera.assert(ak_secret, type: Error){"Secret not found for access key #{node_info['access_key']}@#{node_info['url']}"}
|
|
487
492
|
node_params[:auth] = {
|
|
488
493
|
type: :basic,
|
|
489
494
|
username: node_info['access_key'],
|
|
@@ -520,10 +525,10 @@ module Aspera
|
|
|
520
525
|
Log.dump(:metadata, pkg_meta)
|
|
521
526
|
pkg_meta.each do |field|
|
|
522
527
|
Aspera.assert_type(field, Hash){'metadata field'}
|
|
523
|
-
Aspera.assert(field.key?('name')
|
|
524
|
-
Aspera.assert(field.key?('values')
|
|
528
|
+
Aspera.assert(field.key?('name'), 'metadata field must have name')
|
|
529
|
+
Aspera.assert(field.key?('values'), 'metadata field must have values')
|
|
525
530
|
Aspera.assert_type(field['values'], Array){'metadata field values'}
|
|
526
|
-
Aspera.assert(
|
|
531
|
+
Aspera.assert(meta_schema.any?{ |i| i['name'].eql?(field['name'])}){"unknown metadata field: #{field['name']}"}
|
|
527
532
|
end
|
|
528
533
|
meta_schema.each do |field|
|
|
529
534
|
provided = pkg_meta.select{ |i| i['name'].eql?(field['name'])}
|
|
@@ -538,7 +543,7 @@ module Aspera
|
|
|
538
543
|
# @param package_data [Hash] The whole package creation payload
|
|
539
544
|
# @param rcpt_lst_field [String] The field in structure, i.e. recipients or bcc_recipients
|
|
540
545
|
# @param new_user_option [Hash] Additional fields for contact creation
|
|
541
|
-
# @return nil
|
|
546
|
+
# @return [nil] `package_data` is modified in place
|
|
542
547
|
def resolve_package_recipients(package_data, rcpt_lst_field, new_user_option)
|
|
543
548
|
return unless package_data.key?(rcpt_lst_field)
|
|
544
549
|
Aspera.assert_type(package_data[rcpt_lst_field], Array){rcpt_lst_field}
|
|
@@ -602,7 +607,7 @@ module Aspera
|
|
|
602
607
|
# @param package_data [Hash] package creation (with extensions...)
|
|
603
608
|
# @param validate_meta [TrueClass,FalseClass] true to validate parameters locally
|
|
604
609
|
# @param new_user_option [Hash,NilClass] options if an unknown user is specified
|
|
605
|
-
# @return transfer spec, node api and package information
|
|
610
|
+
# @return [Array] transfer spec, node api and package information
|
|
606
611
|
def create_package_simple(package_data, validate_meta, new_user_option)
|
|
607
612
|
update_package_metadata_for_api(package_data)
|
|
608
613
|
# list of files to include in package, optional
|
|
@@ -749,7 +754,7 @@ module Aspera
|
|
|
749
754
|
# @param types [Array] event types
|
|
750
755
|
def permissions_send_event(event_data:, app_info:, types: PERMISSIONS_CREATED)
|
|
751
756
|
Aspera.assert_type(types, Array)
|
|
752
|
-
Aspera.assert(!types.empty
|
|
757
|
+
Aspera.assert(!types.empty?, 'types must not be empty')
|
|
753
758
|
event_creation = {
|
|
754
759
|
'types' => types,
|
|
755
760
|
'node_id' => app_info.node_info['id'],
|
data/lib/aspera/api/cos_node.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Aspera
|
|
|
24
24
|
endpoints = Aspera::Rest.new(base_url: service_credentials['endpoints']).read('')
|
|
25
25
|
Log.dump(:endpoints, endpoints)
|
|
26
26
|
endpoint = endpoints.dig('service-endpoints', 'regional', bucket_region, 'public', bucket_region)
|
|
27
|
-
|
|
27
|
+
Aspera.assert(!endpoint.nil?){"no such region: #{bucket_region}"}
|
|
28
28
|
return {
|
|
29
29
|
instance_id: service_credentials['resource_instance_id'],
|
|
30
30
|
api_key: service_credentials['apikey'],
|
|
@@ -63,7 +63,7 @@ module Aspera
|
|
|
63
63
|
ats_info = XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
|
|
64
64
|
Log.dump(:ats_info, ats_info)
|
|
65
65
|
Aspera.assert_hash_all(ats_info, String, nil){'ats_info'}
|
|
66
|
-
Aspera.assert((FASP_INFO_KEYS - ats_info.keys).empty
|
|
66
|
+
Aspera.assert((FASP_INFO_KEYS - ats_info.keys).empty?, 'ats_info missing required keys')
|
|
67
67
|
Aspera.assert_hash_all(ats_info['AccessKey'], String, String){'ats_info'}
|
|
68
68
|
@storage_credentials = {
|
|
69
69
|
'type' => 'token',
|
data/lib/aspera/api/faspex.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'aspera/assert'
|
|
3
4
|
require 'aspera/rest'
|
|
4
5
|
require 'aspera/oauth/base'
|
|
5
6
|
require 'aspera/rest_list'
|
|
@@ -11,9 +12,9 @@ module Aspera
|
|
|
11
12
|
class << self
|
|
12
13
|
attr_accessor :additional_info
|
|
13
14
|
end
|
|
14
|
-
# @param context
|
|
15
|
-
# @param redirect_uri
|
|
16
|
-
# @param path_authorize
|
|
15
|
+
# @param context [String] The `context` query parameter in public link
|
|
16
|
+
# @param redirect_uri [String] URI of web UI login
|
|
17
|
+
# @param path_authorize [String] Path to provide passcode
|
|
17
18
|
def initialize(
|
|
18
19
|
context:,
|
|
19
20
|
redirect_uri:,
|
|
@@ -45,7 +46,7 @@ module Aspera
|
|
|
45
46
|
info = Rest.query_to_h(URI.parse(http['Location']).query)
|
|
46
47
|
Log.dump(:info, info)
|
|
47
48
|
raise Error, info['action_message'] if info['action_message']
|
|
48
|
-
Aspera.assert(info['code']
|
|
49
|
+
Aspera.assert(info['code'], 'Missing code in answer')
|
|
49
50
|
# Exchange code for token
|
|
50
51
|
return create_token_call(base_params.merge(
|
|
51
52
|
grant_type: 'authorization_code',
|
|
@@ -87,6 +88,7 @@ module Aspera
|
|
|
87
88
|
email_notifications
|
|
88
89
|
alternate_addresses
|
|
89
90
|
webhooks
|
|
91
|
+
file_processing
|
|
90
92
|
].freeze
|
|
91
93
|
# states for jobs not in final state
|
|
92
94
|
JOB_RUNNING = %w[queued working].freeze
|
|
@@ -124,7 +126,7 @@ module Aspera
|
|
|
124
126
|
mfa_code
|
|
125
127
|
]
|
|
126
128
|
class << self
|
|
127
|
-
# @return true if the URL is a public link
|
|
129
|
+
# @return [Boolean] true if the URL is a public link
|
|
128
130
|
def public_link?(url)
|
|
129
131
|
url.include?('?context=')
|
|
130
132
|
end
|
|
@@ -137,15 +139,15 @@ module Aspera
|
|
|
137
139
|
end
|
|
138
140
|
attr_reader :pub_link_context
|
|
139
141
|
|
|
140
|
-
# @param url
|
|
141
|
-
# @param auth
|
|
142
|
-
# @param password
|
|
143
|
-
# @param client_id
|
|
144
|
-
# @param client_secret
|
|
145
|
-
# @param redirect_uri
|
|
146
|
-
# @param username
|
|
147
|
-
# @param private_key
|
|
148
|
-
# @param passphrase
|
|
142
|
+
# @param url [String] Faspex URL, can be a public link
|
|
143
|
+
# @param auth [Symbol] Authentication method: :boot (token in header), :web (open browser), :jwt (client_id + private key), :public_link (context in URL)
|
|
144
|
+
# @param password [String, nil] For :boot auth, the token copied directly from browser in developer mode
|
|
145
|
+
# @param client_id [String, nil] For :web and :jwt auth, the client_id of web UI application
|
|
146
|
+
# @param client_secret [String, nil] For :web auth, the client_secret of web UI application (not needed for :jwt)
|
|
147
|
+
# @param redirect_uri [String, nil] For :web auth, the redirect_uri of web UI application (must be the same as in application configuration)
|
|
148
|
+
# @param username [String, nil] For :jwt auth, the username of the user to impersonate
|
|
149
|
+
# @param private_key [String, nil] For :jwt auth, the private key to sign JWT token
|
|
150
|
+
# @param passphrase [String, nil] For :jwt auth, the passphrase of the private key
|
|
149
151
|
def initialize(
|
|
150
152
|
url:,
|
|
151
153
|
auth:,
|
|
@@ -168,7 +170,7 @@ module Aspera
|
|
|
168
170
|
Log.dump(:final_url, final_url, level: :trace1)
|
|
169
171
|
# Get context from query
|
|
170
172
|
encoded_context = Rest.query_to_h(URI.parse(final_url).query)['context']
|
|
171
|
-
|
|
173
|
+
Aspera.assert(!encoded_context.nil?, type: ParameterError){'Bad faspex5 public link, missing context in query'}
|
|
172
174
|
# public link information (contains passcode and allowed usage)
|
|
173
175
|
@pub_link_context = JSON.parse(Base64.decode64(encoded_context))
|
|
174
176
|
Log.dump(:pub_link_context, @pub_link_context, level: :trace1)
|
|
@@ -193,15 +195,15 @@ module Aspera
|
|
|
193
195
|
}
|
|
194
196
|
# old: headers: {'Passcode' => @pub_link_context['passcode']}
|
|
195
197
|
when :boot
|
|
196
|
-
Aspera.assert(password, type: ParameterError)
|
|
198
|
+
Aspera.assert(password, 'Missing password', type: ParameterError)
|
|
197
199
|
# the password here is the token copied directly from browser in developer mode
|
|
198
200
|
{
|
|
199
201
|
base_url: "#{url}/#{root}",
|
|
200
202
|
headers: {'Authorization' => password}
|
|
201
203
|
}
|
|
202
204
|
when :web
|
|
203
|
-
Aspera.assert(client_id, type: ParameterError)
|
|
204
|
-
Aspera.assert(redirect_uri, type: ParameterError)
|
|
205
|
+
Aspera.assert(client_id, 'Missing client_id', type: ParameterError)
|
|
206
|
+
Aspera.assert(redirect_uri, 'Missing redirect_uri', type: ParameterError)
|
|
205
207
|
# opens a browser and ask user to auth using web
|
|
206
208
|
{
|
|
207
209
|
base_url: "#{url}/#{root}",
|
|
@@ -216,8 +218,8 @@ module Aspera
|
|
|
216
218
|
}
|
|
217
219
|
}
|
|
218
220
|
when :jwt
|
|
219
|
-
Aspera.assert(client_id, type: ParameterError)
|
|
220
|
-
Aspera.assert(private_key, type: ParameterError)
|
|
221
|
+
Aspera.assert(client_id, 'Missing client_id', type: ParameterError)
|
|
222
|
+
Aspera.assert(private_key, 'Missing private_key', type: ParameterError)
|
|
221
223
|
{
|
|
222
224
|
base_url: "#{url}/#{root}",
|
|
223
225
|
auth: {
|
data/lib/aspera/api/httpgw.rb
CHANGED
|
@@ -81,7 +81,7 @@ module Aspera
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# Check header ourself and give precise error message, as websocket will only throw error without details
|
|
84
|
-
# @param [String] Response Header
|
|
84
|
+
# @param header [String] Response Header
|
|
85
85
|
# @return [String] Response Header
|
|
86
86
|
def validated_ws_response_header(header)
|
|
87
87
|
first_line = header.split("\r\n").first
|
|
@@ -210,7 +210,7 @@ module Aspera
|
|
|
210
210
|
fileIndex: file_index
|
|
211
211
|
}
|
|
212
212
|
file = file_to_send[:file]
|
|
213
|
-
file = File.open(file) unless file.is_a?(Transfer::FauxFile)
|
|
213
|
+
file = File.open(file) unless file.is_a?(Transfer::FauxFile) # rubocop:disable Style/FileOpen
|
|
214
214
|
begin
|
|
215
215
|
until file.eof?
|
|
216
216
|
slice_bin_data = file.read(@upload_chunk_size)
|
|
@@ -291,7 +291,7 @@ module Aspera
|
|
|
291
291
|
return @api_info
|
|
292
292
|
end
|
|
293
293
|
|
|
294
|
-
# @return the base url of the gateway
|
|
294
|
+
# @return [String] the base url of the gateway
|
|
295
295
|
def base_url
|
|
296
296
|
return @gw_root_url
|
|
297
297
|
end
|
|
@@ -308,7 +308,7 @@ module Aspera
|
|
|
308
308
|
Log.dump(:gw_url, url)
|
|
309
309
|
# add scheme if missing
|
|
310
310
|
url = "https://#{url}" unless url.match?(%r{^[a-z]{1,6}://})
|
|
311
|
-
|
|
311
|
+
Aspera.assert(url.start_with?('https://'), type: Error){'GW URL shall be with scheme https'}
|
|
312
312
|
# remove trailing slash and version (o=only once) if present
|
|
313
313
|
# TODO: issue warning ?
|
|
314
314
|
url = url.chomp('/').gsub(%r{/#{API_V1}$}o, '')
|