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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +300 -22
  4. data/CONTRIBUTING.md +100 -12
  5. data/bin/ascli +5 -5
  6. data/docs/README.md +10757 -0
  7. data/docs/test-mcp-with-ai.md +202 -0
  8. data/lib/aspera/agent/base.rb +20 -6
  9. data/lib/aspera/agent/connect.rb +53 -16
  10. data/lib/aspera/agent/desktop.rb +52 -17
  11. data/lib/aspera/agent/direct.rb +27 -25
  12. data/lib/aspera/agent/factory.rb +4 -0
  13. data/lib/aspera/agent/httpgw.rb +2 -2
  14. data/lib/aspera/agent/node.rb +34 -2
  15. data/lib/aspera/agent/transferd.rb +39 -3
  16. data/lib/aspera/api/alee.rb +1 -1
  17. data/lib/aspera/api/aoc.rb +56 -51
  18. data/lib/aspera/api/cos_node.rb +2 -2
  19. data/lib/aspera/api/faspex.rb +22 -20
  20. data/lib/aspera/api/httpgw.rb +4 -4
  21. data/lib/aspera/api/node.rb +33 -30
  22. data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
  23. data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
  24. data/lib/aspera/ascmd.rb +25 -23
  25. data/lib/aspera/ascp/installation.rb +43 -40
  26. data/lib/aspera/ascp/management.rb +2 -3
  27. data/lib/aspera/cli/ascp_actions.rb +155 -0
  28. data/lib/aspera/cli/async_transfer_store.rb +81 -0
  29. data/lib/aspera/cli/bootstrapper.rb +197 -0
  30. data/lib/aspera/cli/command_registry.rb +145 -0
  31. data/lib/aspera/cli/command_spec.rb +138 -0
  32. data/lib/aspera/cli/context.rb +71 -0
  33. data/lib/aspera/cli/error.rb +16 -0
  34. data/lib/aspera/cli/extended_value.rb +5 -3
  35. data/lib/aspera/cli/formatter.rb +122 -209
  36. data/lib/aspera/cli/gem_checker.rb +65 -0
  37. data/lib/aspera/cli/hints.rb +2 -2
  38. data/lib/aspera/cli/http.rb +218 -0
  39. data/lib/aspera/cli/info.rb +2 -0
  40. data/lib/aspera/cli/mailer.rb +97 -0
  41. data/lib/aspera/cli/mcp_tool.rb +198 -0
  42. data/lib/aspera/cli/options.schema.yaml +364 -7
  43. data/lib/aspera/cli/{manager.rb → parser.rb} +337 -144
  44. data/lib/aspera/cli/plugins/alee.rb +20 -22
  45. data/lib/aspera/cli/plugins/aoc.rb +1173 -849
  46. data/lib/aspera/cli/plugins/ats.rb +200 -161
  47. data/lib/aspera/cli/plugins/base.rb +470 -107
  48. data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
  49. data/lib/aspera/cli/plugins/config.rb +434 -1032
  50. data/lib/aspera/cli/plugins/console.rb +106 -64
  51. data/lib/aspera/cli/plugins/cos.rb +44 -32
  52. data/lib/aspera/cli/plugins/factory.rb +7 -4
  53. data/lib/aspera/cli/plugins/faspex.rb +296 -259
  54. data/lib/aspera/cli/plugins/faspex5.rb +592 -344
  55. data/lib/aspera/cli/plugins/faspio.rb +49 -51
  56. data/lib/aspera/cli/plugins/httpgw.rb +18 -25
  57. data/lib/aspera/cli/plugins/mcp.rb +279 -0
  58. data/lib/aspera/cli/plugins/node.rb +1001 -797
  59. data/lib/aspera/cli/plugins/oauth.rb +7 -10
  60. data/lib/aspera/cli/plugins/orchestrator.rb +111 -139
  61. data/lib/aspera/cli/plugins/preview.rb +183 -142
  62. data/lib/aspera/cli/plugins/server.rb +125 -80
  63. data/lib/aspera/cli/plugins/shares.rb +301 -107
  64. data/lib/aspera/cli/preset_actions.rb +139 -0
  65. data/lib/aspera/cli/preset_manager.rb +236 -0
  66. data/lib/aspera/cli/result.rb +360 -0
  67. data/lib/aspera/cli/runner.rb +473 -0
  68. data/lib/aspera/cli/secret_finder.rb +40 -0
  69. data/lib/aspera/cli/special_values.rb +1 -0
  70. data/lib/aspera/cli/sync_actions.rb +84 -56
  71. data/lib/aspera/cli/terminal_formatter.rb +65 -0
  72. data/lib/aspera/cli/transfer_actions.rb +83 -0
  73. data/lib/aspera/cli/transfer_agent.rb +116 -51
  74. data/lib/aspera/cli/transfer_progress.rb +9 -9
  75. data/lib/aspera/cli/vault_manager.rb +57 -0
  76. data/lib/aspera/cli/version.rb +1 -1
  77. data/lib/aspera/cli/wizard.rb +26 -26
  78. data/lib/aspera/command_line_builder.rb +23 -23
  79. data/lib/aspera/data_repository.rb +6 -1
  80. data/lib/aspera/dot_container.rb +5 -5
  81. data/lib/aspera/environment.rb +39 -13
  82. data/lib/aspera/exec_spec.rb +13 -0
  83. data/lib/aspera/faspex_gw.rb +2 -2
  84. data/lib/aspera/faspex_postproc.rb +4 -3
  85. data/lib/aspera/graphql.rb +35 -0
  86. data/lib/aspera/hash_ext.rb +6 -0
  87. data/lib/aspera/json_rpc/client.rb +62 -0
  88. data/lib/aspera/json_rpc/version.rb +7 -0
  89. data/lib/aspera/keychain/base.rb +1 -1
  90. data/lib/aspera/keychain/encrypted_hash.rb +2 -2
  91. data/lib/aspera/keychain/factory.rb +4 -4
  92. data/lib/aspera/keychain/macos_security.rb +4 -4
  93. data/lib/aspera/link_header.rb +82 -0
  94. data/lib/aspera/log.rb +23 -5
  95. data/lib/aspera/markdown.rb +85 -2
  96. data/lib/aspera/nagios.rb +2 -2
  97. data/lib/aspera/node_simulator.rb +3 -1
  98. data/lib/aspera/oauth/base.rb +10 -10
  99. data/lib/aspera/oauth/boot.rb +43 -0
  100. data/lib/aspera/oauth/factory.rb +41 -15
  101. data/lib/aspera/oauth/jwt.rb +4 -4
  102. data/lib/aspera/oauth/web.rb +4 -4
  103. data/lib/aspera/oauth.rb +1 -0
  104. data/lib/aspera/persistency_action_once.rb +10 -10
  105. data/lib/aspera/persistency_folder.rb +10 -3
  106. data/lib/aspera/preview/file_types.rb +7 -3
  107. data/lib/aspera/preview/generator.rb +3 -3
  108. data/lib/aspera/preview/utils.rb +1 -1
  109. data/lib/aspera/products/connect.rb +4 -1
  110. data/lib/aspera/products/other.rb +1 -1
  111. data/lib/aspera/products/transferd.rb +3 -3
  112. data/lib/aspera/proxy_auto_config.rb +10 -9
  113. data/lib/aspera/rest.rb +18 -40
  114. data/lib/aspera/rest_error_analyzer.rb +7 -3
  115. data/lib/aspera/rest_errors_aspera.rb +0 -10
  116. data/lib/aspera/rest_list.rb +13 -6
  117. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +14 -4
  118. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +2730 -648
  119. data/lib/aspera/schema/async_tables.yaml +361 -0
  120. data/lib/aspera/schema/documentation.rb +57 -26
  121. data/lib/aspera/schema/reader.rb +60 -11
  122. data/lib/aspera/schema/registry.rb +50 -10
  123. data/lib/aspera/secret_hider.rb +11 -2
  124. data/lib/aspera/ssl.rb +4 -4
  125. data/lib/aspera/sync/conf.schema.yaml +2 -2
  126. data/lib/aspera/sync/database.rb +14 -10
  127. data/lib/aspera/sync/operations.rb +15 -18
  128. data/lib/aspera/temp_file_manager.rb +5 -1
  129. data/lib/aspera/timer_limiter.rb +1 -1
  130. data/lib/aspera/transfer/faux_file.rb +24 -11
  131. data/lib/aspera/transfer/parameters.rb +27 -25
  132. data/lib/aspera/transfer/result.rb +74 -0
  133. data/lib/aspera/transfer/resumer.rb +7 -7
  134. data/lib/aspera/transfer/spec.rb +18 -1
  135. data/lib/aspera/transfer/spec.schema.yaml +26 -8
  136. data/lib/aspera/transfer/uri.rb +1 -1
  137. data/lib/aspera/uri_reader.rb +2 -2
  138. data/lib/aspera/web_auth.rb +2 -2
  139. data/lib/aspera/web_server_simple.rb +8 -5
  140. data/lib/aspera/yaml.rb +2 -1
  141. data.tar.gz.sig +0 -0
  142. metadata +45 -16
  143. metadata.gz.sig +0 -0
  144. data/lib/aspera/cli/main.rb +0 -480
  145. data/lib/aspera/json_rpc.rb +0 -51
@@ -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?){'root_id not allowed for access key'}
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?){'Before using this object, set the node_api attribute to a Aspera::Rest object'}
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
- raise Error, 'Cannot set options `stop` or `start` to false with port zero' if is_local_auto_port && (!@stop || !start)
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
- raise Transfer::Error, start_response.error.description if start_response.status.eql?(:FAILED)
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}"}
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aspera/api/aoc.rb'
3
+ require 'aspera/api/aoc'
4
4
  module Aspera
5
5
  module Api
6
6
  # Aspera License Entitlement Engine API client
@@ -25,23 +25,23 @@ module Aspera
25
25
 
26
26
  class AppInfo
27
27
  # @return [Aspera::Api::AoC] Rest Api object
28
- attr_reader :api
28
+ attr_reader :api
29
29
  # @return [Hash, nil] Package information, `"files"` or `"packages"`
30
- attr_reader :package
30
+ attr_reader :package
31
31
  # @return [Hash] Result of GET /nodes/:id
32
- attr_reader :node_info
32
+ attr_reader :node_info
33
33
  # @return [String] Workspace id
34
- attr_reader :workspace_id
34
+ attr_reader :workspace_id
35
35
  # @return [String] Workspace name
36
- attr_reader :workspace_name
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 [String]
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
- # base API url depends on domain, which could be "qa.xxx" or self-managed domain
117
- def api_base_url(api_domain: SAAS_DOMAIN_PROD)
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
- Aspera.assert(scope, 'Missing mandatory option: scope', type: ParameterError)
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
- # key: access key
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
- auth_params = {
302
- type: :oauth2,
303
- params: {
304
- client_id: client_id,
305
- client_secret: client_secret,
306
- scope: scope
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
- # analyze type of url
310
- url_info = AoC.link_info(url)
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
- else Aspera.error_unexpected_value(auth_params[:grant_method]){'auth, use one of: :web, :jwt'}
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
- # @returns [Node] a node API for access key
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&.lookup_secret(url: node_info['url'], username: node_info['access_key'])
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']}", type: Error)
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')){'metadata field must have name'}
524
- Aspera.assert(field.key?('values')){'metadata field must have 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(!meta_schema.none?{ |i| i['name'].eql?(field['name'])}){"unknown metadata field: #{field['name']}"}
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, `package_data` is modified
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'],
@@ -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
- raise "no such region: #{bucket_region}" if endpoint.nil?
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?){'ats_info missing required keys'}
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',
@@ -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 The `context` query parameter in public link
15
- # @param redirect_uri URI of web UI login
16
- # @param path_authorize Path to provide passcode
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']){'Missing code in answer'}
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 Faspex URL, can be a public link
141
- # @param auth Authentication method: :boot (token in header), :web (open browser), :jwt (client_id + private key), :public_link (context in URL)
142
- # @param password For :boot auth, the token copied directly from browser in developer mode
143
- # @param client_id For :web and :jwt auth, the client_id of web UI application
144
- # @param client_secret For :web auth, the client_secret of web UI application (not needed for :jwt)
145
- # @param redirect_uri For :web auth, the redirect_uri of web UI application (must be the same as in application configuration)
146
- # @param username For :jwt auth, the username of the user to impersonate
147
- # @param private_key For :jwt auth, the private key to sign JWT token
148
- # @param passphrase For :jwt auth, the passphrase of the private key
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
- raise ParameterError, 'Bad faspex5 public link, missing context in query' if encoded_context.nil?
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){'Missing password'}
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){'Missing client_id'}
204
- Aspera.assert(redirect_uri, type: ParameterError){'Missing redirect_uri'}
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){'Missing client_id'}
220
- Aspera.assert(private_key, type: ParameterError){'Missing private_key'}
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: {
@@ -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
- raise Error, 'GW URL shall be with scheme https' unless url.start_with?('https://')
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, '')