aspera-cli 4.14.0 → 4.15.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 (90) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +54 -3
  4. data/CONTRIBUTING.md +7 -7
  5. data/README.md +1457 -880
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/proxy.pac +1 -1
  9. data/lib/aspera/aoc.rb +198 -127
  10. data/lib/aspera/ascmd.rb +24 -14
  11. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  12. data/lib/aspera/cli/error.rb +17 -0
  13. data/lib/aspera/cli/extended_value.rb +47 -12
  14. data/lib/aspera/cli/formatter.rb +260 -171
  15. data/lib/aspera/cli/hints.rb +80 -0
  16. data/lib/aspera/cli/main.rb +101 -147
  17. data/lib/aspera/cli/manager.rb +160 -124
  18. data/lib/aspera/cli/plugin.rb +70 -59
  19. data/lib/aspera/cli/plugins/alee.rb +0 -1
  20. data/lib/aspera/cli/plugins/aoc.rb +239 -273
  21. data/lib/aspera/cli/plugins/ats.rb +8 -5
  22. data/lib/aspera/cli/plugins/bss.rb +2 -2
  23. data/lib/aspera/cli/plugins/config.rb +516 -375
  24. data/lib/aspera/cli/plugins/console.rb +40 -0
  25. data/lib/aspera/cli/plugins/cos.rb +4 -5
  26. data/lib/aspera/cli/plugins/faspex.rb +99 -84
  27. data/lib/aspera/cli/plugins/faspex5.rb +179 -148
  28. data/lib/aspera/cli/plugins/node.rb +219 -153
  29. data/lib/aspera/cli/plugins/orchestrator.rb +52 -17
  30. data/lib/aspera/cli/plugins/preview.rb +46 -32
  31. data/lib/aspera/cli/plugins/server.rb +57 -17
  32. data/lib/aspera/cli/plugins/shares.rb +34 -12
  33. data/lib/aspera/cli/sync_actions.rb +68 -0
  34. data/lib/aspera/cli/transfer_agent.rb +45 -55
  35. data/lib/aspera/cli/transfer_progress.rb +74 -0
  36. data/lib/aspera/cli/version.rb +1 -1
  37. data/lib/aspera/colors.rb +3 -1
  38. data/lib/aspera/command_line_builder.rb +14 -11
  39. data/lib/aspera/cos_node.rb +3 -2
  40. data/lib/aspera/environment.rb +17 -6
  41. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  42. data/lib/aspera/fasp/agent_base.rb +31 -77
  43. data/lib/aspera/fasp/agent_connect.rb +21 -22
  44. data/lib/aspera/fasp/agent_direct.rb +88 -102
  45. data/lib/aspera/fasp/agent_httpgw.rb +196 -192
  46. data/lib/aspera/fasp/agent_node.rb +41 -34
  47. data/lib/aspera/fasp/agent_trsdk.rb +75 -34
  48. data/lib/aspera/fasp/error_info.rb +2 -2
  49. data/lib/aspera/fasp/faux_file.rb +52 -0
  50. data/lib/aspera/fasp/installation.rb +43 -184
  51. data/lib/aspera/fasp/management.rb +244 -0
  52. data/lib/aspera/fasp/parameters.rb +59 -26
  53. data/lib/aspera/fasp/parameters.yaml +75 -8
  54. data/lib/aspera/fasp/products.rb +162 -0
  55. data/lib/aspera/fasp/transfer_spec.rb +1 -1
  56. data/lib/aspera/fasp/uri.rb +4 -4
  57. data/lib/aspera/faspex_gw.rb +2 -2
  58. data/lib/aspera/faspex_postproc.rb +2 -2
  59. data/lib/aspera/hash_ext.rb +2 -2
  60. data/lib/aspera/json_rpc.rb +49 -0
  61. data/lib/aspera/line_logger.rb +23 -0
  62. data/lib/aspera/log.rb +57 -16
  63. data/lib/aspera/node.rb +97 -14
  64. data/lib/aspera/oauth.rb +36 -18
  65. data/lib/aspera/open_application.rb +4 -4
  66. data/lib/aspera/persistency_folder.rb +2 -2
  67. data/lib/aspera/preview/file_types.rb +4 -2
  68. data/lib/aspera/preview/generator.rb +22 -35
  69. data/lib/aspera/preview/options.rb +2 -0
  70. data/lib/aspera/preview/terminal.rb +24 -13
  71. data/lib/aspera/preview/utils.rb +19 -26
  72. data/lib/aspera/rest.rb +103 -72
  73. data/lib/aspera/rest_call_error.rb +1 -1
  74. data/lib/aspera/rest_error_analyzer.rb +15 -14
  75. data/lib/aspera/rest_errors_aspera.rb +37 -34
  76. data/lib/aspera/secret_hider.rb +14 -16
  77. data/lib/aspera/ssh.rb +4 -1
  78. data/lib/aspera/sync.rb +128 -122
  79. data/lib/aspera/temp_file_manager.rb +10 -3
  80. data/lib/aspera/web_auth.rb +10 -7
  81. data/lib/aspera/web_server_simple.rb +9 -4
  82. data.tar.gz.sig +0 -0
  83. metadata +33 -15
  84. metadata.gz.sig +0 -0
  85. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  86. data/lib/aspera/cli/listener/logger.rb +0 -22
  87. data/lib/aspera/cli/listener/progress.rb +0 -50
  88. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  89. data/lib/aspera/cli/plugins/sync.rb +0 -44
  90. data/lib/aspera/fasp/listener.rb +0 -13
data/bin/ascli CHANGED
@@ -1,29 +1,38 @@
1
- #!/usr/bin/env ruby -EUTF-8:UTF-8
1
+ #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'rubygems'
5
- require 'securerandom'
6
- # compute gem root based on this script location
7
- GEM_ROOT = File.realpath(File.join(File.dirname(File.realpath(__FILE__)), '..'))
4
+ Encoding.default_internal = Encoding::UTF_8
5
+ Encoding.default_external = Encoding::UTF_8
8
6
  # coverage for tests
9
7
  if ENV.key?('ENABLE_COVERAGE')
10
8
  require 'simplecov'
11
- SimpleCov.root(GEM_ROOT)
9
+ require 'securerandom'
10
+ # compute gem source root based on this script location, assuming it is in bin/
11
+ # use dirname instead of gsub, in case folder separator is not /
12
+ development_root = File.dirname(File.dirname(File.realpath(__FILE__)))
13
+ SimpleCov.root(development_root)
12
14
  SimpleCov.enable_for_subprocesses if SimpleCov.respond_to?(:enable_for_subprocesses)
13
15
  # keep cache data for 1 day (must be longer that time to run the whole test suite)
14
16
  SimpleCov.merge_timeout(86400)
15
17
  SimpleCov.command_name(SecureRandom.uuid)
16
18
  SimpleCov.at_exit do
17
19
  original_file_descriptor = $stdout
18
- $stdout.reopen(File.join(GEM_ROOT, 'simplecov.log'))
20
+ $stdout.reopen(File.join(development_root, 'simplecov.log'))
19
21
  SimpleCov.result.format!
20
22
  $stdout.reopen(original_file_descriptor)
21
23
  end
22
24
  SimpleCov.start
23
25
  end
24
26
  # if in development, add path to gem
25
- $LOAD_PATH.unshift(File.join(GEM_ROOT, 'lib'))
26
- require 'aspera/cli/main'
27
+ #
28
+ begin
29
+ require 'aspera/cli/main'
30
+ rescue LoadError
31
+ # development environment
32
+ development_root = File.dirname(File.dirname(File.realpath(__FILE__)))
33
+ $LOAD_PATH.unshift(File.join(development_root, 'lib'))
34
+ require 'aspera/cli/main'
35
+ end
27
36
  require 'aspera/environment'
28
37
  Aspera::Environment.fix_home
29
38
  Aspera::Cli::Main.new(ARGV).process_command_line
data/bin/asession CHANGED
@@ -2,16 +2,16 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  # Laurent Martin/2017
5
- $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
5
+ $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/../lib")
6
6
  require 'aspera/fasp/agent_direct'
7
- require 'aspera/cli/listener/line_dump'
8
7
  require 'aspera/cli/extended_value'
9
8
  require 'aspera/log'
10
9
  require 'json'
11
- # extended transfer spec parameter to change log level
12
- TS_LOGLEVEL = 'EX_loglevel'
10
+ # extended transfer spec parameter (only used in asession)
11
+ # Change log level
12
+ TS_LOG_LEVEL = 'EX_loglevel'
13
13
  # by default go to /tmp/username.filelist
14
- TS_TMP_FILELIST_FOLDER = 'EX_file_list_folder'
14
+ TS_TMP_FILE_LIST_FOLDER = 'EX_file_list_folder'
15
15
 
16
16
  SAMPLE_DEMO = '"remote_host":"demo.asperasoft.com","remote_user":"asperaweb","ssh_port":33001,"remote_password":"demoaspera"'
17
17
  SAMPLE_DEMO2 = '"direction":"receive","destination_root":"./test.dir"'
@@ -34,7 +34,7 @@ def assert_usage(assertion, error_message)
34
34
  $stderr.puts(' {"type":"START","source":"/aspera-test-dir-tiny/200KB.2"}')
35
35
  $stderr.puts(' {"type":"START","source":"xx","destination":"yy"}')
36
36
  $stderr.puts(' {"type":"DONE"}')
37
- $stderr.puts(%Q(Note: debug information can be placed on STDERR, using the "#{TS_LOGLEVEL}" parameter in transfer spec (debug=0)))
37
+ $stderr.puts(%Q(Note: debug information can be placed on STDERR, using the "#{TS_LOG_LEVEL}" parameter in transfer spec (debug=0)))
38
38
  $stderr.puts('EXAMPLES')
39
39
  $stderr.puts(%Q( asession @json:'{#{SAMPLE_DEMO},#{SAMPLE_DEMO2},"paths":[{"source":"/aspera-test-dir-tiny/200KB.1"}]}'))
40
40
  $stderr.puts(%q( echo '{"remote_host":...}'|asession @json:@stdin))
@@ -60,19 +60,17 @@ end
60
60
  # ensure right type
61
61
  assert_usage(transfer_spec.is_a?(Hash), "the value must be a hash table#{parameter_source_err_msg}")
62
62
  # additional debug capability
63
- if transfer_spec.key?(TS_LOGLEVEL)
64
- Aspera::Log.instance.level = transfer_spec[TS_LOGLEVEL]
65
- transfer_spec.delete(TS_LOGLEVEL)
63
+ if transfer_spec.key?(TS_LOG_LEVEL)
64
+ Aspera::Log.instance.level = transfer_spec[TS_LOG_LEVEL]
65
+ transfer_spec.delete(TS_LOG_LEVEL)
66
66
  end
67
67
  # possibly override temp folder
68
- if transfer_spec.key?(TS_TMP_FILELIST_FOLDER)
69
- Aspera::Fasp::Parameters.file_list_folder = transfer_spec[TS_TMP_FILELIST_FOLDER]
70
- transfer_spec.delete(TS_TMP_FILELIST_FOLDER)
68
+ if transfer_spec.key?(TS_TMP_FILE_LIST_FOLDER)
69
+ Aspera::Fasp::Parameters.file_list_folder = transfer_spec[TS_TMP_FILE_LIST_FOLDER]
70
+ transfer_spec.delete(TS_TMP_FILE_LIST_FOLDER)
71
71
  end
72
72
  # get local agent (ascp), disable ascp output on stdout to not mix with JSON events
73
73
  client = Aspera::Fasp::AgentDirect.new({quiet: true})
74
- # display JSON instead of legacy Lines
75
- client.add_listener(Aspera::Cli::Listener::LineDump.new)
76
74
  # start transfer (asynchronous)
77
75
  job_id = client.start_transfer(transfer_spec)
78
76
  # async commands
data/examples/proxy.pac CHANGED
@@ -1,4 +1,4 @@
1
- /* demo proxy pac for ascli */
1
+ /* demo proxy pac for `ascli` */
2
2
  function FindProxyForURL(url, host) {
3
3
  /* Normalize the URL for pattern matching */
4
4
  url = url.toLowerCase();
data/lib/aspera/aoc.rb CHANGED
@@ -5,6 +5,7 @@ require 'aspera/rest'
5
5
  require 'aspera/hash_ext'
6
6
  require 'aspera/data_repository'
7
7
  require 'aspera/fasp/transfer_spec'
8
+ require 'aspera/node'
8
9
  require 'base64'
9
10
  require 'cgi'
10
11
 
@@ -27,9 +28,9 @@ module Aspera
27
28
  class AoC < Aspera::Rest
28
29
  PRODUCT_NAME = 'Aspera on Cloud'
29
30
  # Production domain of AoC
30
- PROD_DOMAIN = 'ibmaspera.com'
31
+ PROD_DOMAIN = 'ibmaspera.com' # cspell:disable-line
31
32
  # to avoid infinite loop in pub link redirection
32
- MAX_REDIRECT = 10
33
+ MAX_AOC_URL_REDIRECT = 10
33
34
  # Well-known AoC globals client apps
34
35
  GLOBAL_CLIENT_APPS = %w[aspera.global-cli-client aspera.drive].freeze
35
36
  # index offset in data repository of client app
@@ -37,7 +38,7 @@ module Aspera
37
38
  # cookie prefix so that console can decode identity
38
39
  COOKIE_PREFIX_CONSOLE_AOC = 'aspera.aoc'
39
40
  # path in URL of public links
40
- PUBLIC_LINK_PATHS = %w[/packages/public/receive /packages/public/send /files/public].freeze
41
+ PUBLIC_LINK_PATHS = %w[/packages/public/receive /packages/public/send /files/public /public/files /public/send].freeze
41
42
  JWT_AUDIENCE = 'https://api.asperafiles.com/api/v1/oauth2/token'
42
43
  OAUTH_API_SUBPATH = 'api/v1/oauth2'
43
44
  # minimum fields for user info if retrieval fails
@@ -45,8 +46,9 @@ module Aspera
45
46
  # types of events for shared folder creation
46
47
  # Node events: permission.created permission.modified permission.deleted
47
48
  PERMISSIONS_CREATED = ['permission.created'].freeze
49
+ DEFAULT_WORKSPACE = ''
48
50
 
49
- private_constant :MAX_REDIRECT,
51
+ private_constant :MAX_AOC_URL_REDIRECT,
50
52
  :GLOBAL_CLIENT_APPS,
51
53
  :DATA_REPO_INDEX_START,
52
54
  :COOKIE_PREFIX_CONSOLE_AOC,
@@ -62,8 +64,6 @@ module Aspera
62
64
  SCOPE_FILES_ADMIN = 'admin:all'
63
65
  SCOPE_FILES_ADMIN_USER = 'admin-user:all'
64
66
  SCOPE_FILES_ADMIN_USER_USER = "#{SCOPE_FILES_ADMIN_USER}+#{SCOPE_FILES_USER}"
65
- SCOPE_NODE_USER = 'user:all'
66
- SCOPE_NODE_ADMIN = 'admin:all'
67
67
  FILES_APP = 'files'
68
68
  PACKAGES_APP = 'packages'
69
69
  API_V1 = 'api/v1'
@@ -77,20 +77,6 @@ module Aspera
77
77
  return client_name, Base64.urlsafe_encode64(DataRepository.instance.data(DATA_REPO_INDEX_START + client_index))
78
78
  end
79
79
 
80
- # @param url of AoC instance
81
- # @return organization id in url and AoC domain: ibmaspera.com, asperafiles.com or qa.asperafiles.com, etc...
82
- def parse_url(aoc_org_url)
83
- uri = URI.parse(aoc_org_url.gsub(%r{/+$}, ''))
84
- instance_fqdn = uri.host
85
- Log.log.debug{"instance_fqdn=#{instance_fqdn}"}
86
- raise "No host found in URL.Please check URL format: https://myorg.#{PROD_DOMAIN}" if instance_fqdn.nil?
87
- organization, instance_domain = instance_fqdn.split('.', 2)
88
- Log.log.debug{"instance_domain=#{instance_domain}"}
89
- Log.log.debug{"organization=#{organization}"}
90
- raise "expecting a public FQDN for #{PRODUCT_NAME}" if instance_domain.nil?
91
- return organization, instance_domain
92
- end
93
-
94
80
  # base API url depends on domain, which could be "qa.xxx"
95
81
  def api_base_url(organization: 'api', api_domain: PROD_DOMAIN)
96
82
  return "https://#{organization}.#{api_domain}"
@@ -99,118 +85,131 @@ module Aspera
99
85
  def metering_api(entitlement_id, customer_id, api_domain=PROD_DOMAIN)
100
86
  return Rest.new({
101
87
  base_url: "#{api_base_url(api_domain: api_domain)}/metering/v1",
102
- headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_creds(entitlement_id, customer_id)}
88
+ headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_token(entitlement_id, customer_id)}
103
89
  })
104
90
  end
105
91
 
106
- # node API scopes
107
- def node_scope(access_key, scope)
108
- return "node.#{access_key}:#{scope}"
92
+ # split host of http://myorg.asperafiles.com in org and domain
93
+ def url_parts(uri)
94
+ raise "No host found in URL.Please check URL format: https://myorg.#{PROD_DOMAIN}" if uri.host.nil?
95
+ parts = uri.host.split('.', 2)
96
+ raise "expecting a public FQDN for #{PRODUCT_NAME}" unless parts.length == 2
97
+ return parts
109
98
  end
110
99
 
111
- # check option "link"
112
- # if present try to get token value (resolve redirection if short links used)
113
- # then set options url/token/auth
114
- def resolve_pub_link(a_auth, a_opt)
115
- public_link_url = a_opt[:link]
116
- return if public_link_url.nil?
117
- raise 'do not use both link and url options' unless a_opt[:url].nil?
118
- redirect_count = 0
119
- while redirect_count <= MAX_REDIRECT
120
- uri = URI.parse(public_link_url)
121
- # detect if it's an expected format
122
- if PUBLIC_LINK_PATHS.include?(uri.path)
123
- url_param_token_pair = URI.decode_www_form(uri.query).find{|e|e.first.eql?('token')}
124
- raise ArgumentError, 'link option must be URL with "token" parameter' if url_param_token_pair.nil?
125
- # ok we get it !
126
- a_opt[:url] = 'https://' + uri.host
127
- a_auth[:grant_method] = :aoc_pub_link
128
- a_auth[:aoc_pub_link] = {
129
- url: {grant_type: 'url_token'}, # URL args
130
- json: {url_token: url_param_token_pair.last} # JSON body
131
- }
132
- # password protection of link
133
- a_auth[:aoc_pub_link][:json][:password] = a_opt[:password] unless a_opt[:password].nil?
134
- return # SUCCESS
100
+ # @param url [String] URL of AoC public link
101
+ # @return [Hash] information about public link, or nil if not a public link
102
+ def link_info(url)
103
+ final_uri = Rest.new({base_url: url, redirect_max: MAX_AOC_URL_REDIRECT}).read('')[:http].uri
104
+ raise 'AoC shall redirect to login page' if final_uri.query.nil?
105
+ decoded_query = Rest.decode_query(final_uri.query)
106
+ # is that a public link ?
107
+ if decoded_query.key?('token')
108
+ Log.log.warn{"Unknown pub link path: #{final_uri.path}"} unless PUBLIC_LINK_PATHS.include?(final_uri.path)
109
+ # ok we get it !
110
+ return {
111
+ instance_domain: url_parts(final_uri)[1],
112
+ url: 'https://' + final_uri.host,
113
+ token: decoded_query['token']
114
+ }
115
+ end
116
+ Log.log.debug{"path=#{final_uri.path} does not end with /login"} unless final_uri.path.end_with?('/login')
117
+ if decoded_query['state']
118
+ # can be a private link
119
+ state_uri = URI.parse(decoded_query['state'])
120
+ if state_uri.query && decoded_query['redirect_uri']
121
+ decoded_state = Rest.decode_query(state_uri.query)
122
+ if decoded_state.key?('short_link_url')
123
+ if (m = state_uri.path.match(%r{/files/workspaces/([0-9]+)/all/([0-9]+):([0-9]+)}))
124
+ redirect_uri = URI.parse(decoded_query['redirect_uri'])
125
+ parts = url_parts(redirect_uri)
126
+ return {
127
+ instance_domain: parts[1],
128
+ organization: parts[0],
129
+ url: 'https://' + redirect_uri.host,
130
+ private_link: {
131
+ workspace_id: m[1],
132
+ node_id: m[2],
133
+ file_id: m[3]
134
+ }
135
+ }
136
+ end
137
+ end
135
138
  end
136
- Log.log.debug{"no expected format: #{public_link_url}"}
137
- r = Net::HTTP.get_response(uri)
138
- # not a redirection
139
- raise ArgumentError, 'link option must be redirect or have token parameter' unless r.code.start_with?('3')
140
- public_link_url = r['location']
141
- raise 'no location in redirection' if public_link_url.nil?
142
- Log.log.debug{"redirect to: #{public_link_url}"}
143
- end # loop
144
- raise "exceeded max redirection: #{MAX_REDIRECT}"
139
+ end
140
+ parts = url_parts(URI.parse(url))
141
+ return {
142
+ instance_domain: parts[1],
143
+ organization: parts[0]
144
+ }
145
145
  end
146
146
  end # static methods
147
147
 
148
- # CLI options that are also options to initialize
149
- OPTIONS_NEW = %i[link url auth client_id client_secret scope redirect_uri private_key passphrase username password].freeze
150
-
151
- # @param any of OPTIONS_NEW + subpath
152
- def initialize(opt)
153
- raise ArgumentError, 'Missing mandatory option: scope' if opt[:scope].nil?
148
+ attr_reader :private_link
154
149
 
150
+ def initialize(subpath: API_V1, url:, auth:, client_id: nil, client_secret: nil, scope: nil, redirect_uri: nil, private_key: nil, passphrase: nil, username: nil,
151
+ password: nil, workspace: nil, secret_finder: nil)
152
+ # test here because link may set url
153
+ raise ArgumentError, 'Missing mandatory option: url' if url.nil?
154
+ raise ArgumentError, 'Missing mandatory option: scope' if scope.nil?
155
+ # default values for client id
156
+ client_id, client_secret = self.class.get_client_info if client_id.nil?
155
157
  # access key secrets are provided out of band to get node api access
156
158
  # key: access key
157
159
  # value: associated secret
158
- @secret_finder = nil
160
+ @secret_finder = secret_finder
161
+ @workspace_name = workspace
159
162
  @cache_user_info = nil
160
163
  @cache_url_token_info = nil
161
-
162
164
  # init rest params
163
165
  aoc_rest_p = {auth: {type: :oauth2}}
164
166
  # shortcut to auth section
165
167
  aoc_auth_p = aoc_rest_p[:auth]
166
-
167
- # sets opt[:url], aoc_rest_p[:auth][:grant_method], [:auth][:aoc_pub_link] if there is a link
168
- self.class.resolve_pub_link(aoc_auth_p, opt)
169
-
170
- # test here because link may set url
171
- raise ArgumentError, 'Missing mandatory option: url' if opt[:url].nil?
172
-
173
- # get org name and domain from url
174
- organization, instance_domain = self.class.parse_url(opt[:url])
168
+ # analyze type of url
169
+ url_info = AoC.link_info(url)
170
+ Log.log.debug{Log.dump(:url_info, url_info)}
171
+ @private_link = url_info[:private_link]
172
+ aoc_auth_p[:grant_method] = if url_info.key?(:token)
173
+ :aoc_pub_link
174
+ else
175
+ raise ArgumentError, 'Missing mandatory option: auth' if auth.nil?
176
+ auth
177
+ end
175
178
  # this is the base API url
176
- api_url_base = self.class.api_base_url(api_domain: instance_domain)
179
+ api_url_base = self.class.api_base_url(api_domain: url_info[:instance_domain])
177
180
  # API URL, including subpath (version ...)
178
- aoc_rest_p[:base_url] = "#{api_url_base}/#{opt[:subpath]}"
179
- # base auth URL
180
- aoc_auth_p[:base_url] = "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{organization}"
181
- aoc_auth_p[:client_id] = opt[:client_id]
182
- aoc_auth_p[:client_secret] = opt[:client_secret]
183
- aoc_auth_p[:scope] = opt[:scope]
184
-
185
- # filled if pub link
186
- if !aoc_auth_p.key?(:grant_method)
187
- raise ArgumentError, 'Missing mandatory option: auth' if opt[:auth].nil?
188
- aoc_auth_p[:grant_method] = opt[:auth]
189
- end
190
-
191
- if aoc_auth_p[:client_id].nil?
192
- aoc_auth_p[:client_id], aoc_auth_p[:client_secret] = self.class.get_client_info
193
- end
181
+ aoc_rest_p[:base_url] = "#{api_url_base}/#{subpath}"
182
+ # auth URL
183
+ aoc_auth_p[:base_url] = "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{url_info[:organization]}"
184
+ aoc_auth_p[:client_id] = client_id
185
+ aoc_auth_p[:client_secret] = client_secret
186
+ aoc_auth_p[:scope] = scope
194
187
 
195
188
  # fill other auth parameters based on Oauth method
196
189
  case aoc_auth_p[:grant_method]
197
190
  when :web
198
- raise ArgumentError, 'Missing mandatory option: redirect_uri' if opt[:redirect_uri].nil?
199
- aoc_auth_p[:web] = {redirect_uri: opt[:redirect_uri]}
191
+ raise ArgumentError, 'Missing mandatory option: redirect_uri' if redirect_uri.nil?
192
+ aoc_auth_p[:web] = {redirect_uri: redirect_uri}
200
193
  when :jwt
201
- raise ArgumentError, 'Missing mandatory option: private_key' if opt[:private_key].nil?
202
- raise ArgumentError, 'Missing mandatory option: username' if opt[:username].nil?
194
+ raise ArgumentError, 'Missing mandatory option: private_key' if private_key.nil?
195
+ raise ArgumentError, 'Missing mandatory option: username' if username.nil?
203
196
  aoc_auth_p[:jwt] = {
204
- private_key_obj: OpenSSL::PKey::RSA.new(opt[:private_key], opt[:passphrase]),
197
+ private_key_obj: OpenSSL::PKey::RSA.new(private_key, passphrase),
205
198
  payload: {
206
- iss: aoc_auth_p[:client_id], # issuer
207
- sub: opt[:username], # subject
199
+ iss: aoc_auth_p[:client_id], # issuer
200
+ sub: username, # subject
208
201
  aud: JWT_AUDIENCE
209
202
  }
210
203
  }
211
204
  # add jwt payload for global ids
212
- aoc_auth_p[:jwt][:payload][:org] = organization if GLOBAL_CLIENT_APPS.include?(aoc_auth_p[:client_id])
205
+ aoc_auth_p[:jwt][:payload][:org] = url_info[:organization] if GLOBAL_CLIENT_APPS.include?(aoc_auth_p[:client_id])
213
206
  when :aoc_pub_link
207
+ aoc_auth_p[:aoc_pub_link] = {
208
+ url: {grant_type: 'url_token'}, # URL arguments
209
+ json: {url_token: url_info[:token]} # JSON body
210
+ }
211
+ # password protection of link
212
+ aoc_auth_p[:aoc_pub_link][:json][:password] = password unless password.nil?
214
213
  # basic auth required for /token
215
214
  aoc_auth_p[:auth] = {type: :basic, username: aoc_auth_p[:client_id], password: aoc_auth_p[:client_secret]}
216
215
  else raise "ERROR: unsupported auth method: #{aoc_auth_p[:grant_method]}"
@@ -218,7 +217,7 @@ module Aspera
218
217
  super(aoc_rest_p)
219
218
  end
220
219
 
221
- def url_token_data
220
+ def public_link
222
221
  return nil unless params[:auth][:grant_method].eql?(:aoc_pub_link)
223
222
  return @cache_url_token_info unless @cache_url_token_info.nil?
224
223
  # TODO: can there be several in list ?
@@ -226,41 +225,104 @@ module Aspera
226
225
  return @cache_url_token_info
227
226
  end
228
227
 
229
- def additional_persistence_ids
230
- return [current_user_info['id']] if url_token_data.nil?
231
- return [] # TODO : url_token_data['id'] ?
228
+ def assert_public_link_types(expected)
229
+ raise "public link type is #{public_link['purpose']} but action requires one of #{expected.join(',')}" unless expected.include?(public_link['purpose'])
232
230
  end
233
231
 
234
- def secret_finder=(secret_finder)
235
- raise 'secret finder already set' unless @secret_finder.nil?
236
- raise 'secret finder must have lookup_secret' unless secret_finder.respond_to?(:lookup_secret)
237
- @secret_finder = secret_finder
232
+ def additional_persistence_ids
233
+ return [current_user_info['id']] if public_link.nil?
234
+ return [] # TODO : public_link['id'] ?
238
235
  end
239
236
 
237
+ # def secret_finder=(secret_finder)
238
+ # raise 'secret finder already set' unless @secret_finder.nil?
239
+ # raise 'secret finder must have lookup_secret' unless secret_finder.respond_to?(:lookup_secret)
240
+ # @secret_finder = secret_finder
241
+ # end
242
+
240
243
  # cached user information
241
244
  def current_user_info(exception: false)
242
- if @cache_user_info.nil?
243
- # get our user's default information
244
- @cache_user_info =
245
- begin
246
- read('self')[:data]
247
- rescue StandardError => e
248
- raise e if exception
249
- Log.log.debug{"ignoring error: #{e}"}
250
- {}
251
- end
252
- USER_INFO_FIELDS_MIN.each{|f|@cache_user_info[f] = 'unknown' if @cache_user_info[f].nil?}
253
- end
245
+ return @cache_user_info unless @cache_user_info.nil?
246
+ # get our user's default information
247
+ @cache_user_info =
248
+ begin
249
+ read('self')[:data]
250
+ rescue StandardError => e
251
+ raise e if exception
252
+ Log.log.debug{"ignoring error: #{e}"}
253
+ {}
254
+ end
255
+ USER_INFO_FIELDS_MIN.each{|f|@cache_user_info[f] = 'unknown' if @cache_user_info[f].nil?}
254
256
  return @cache_user_info
255
257
  end
256
258
 
259
+ # @param application [Symbol] :files or :packages
260
+ # @return [Hash] current context information: workspace, and home node/file if app is "Files"
261
+ def context(application = nil)
262
+ return @context_cache unless @context_cache.nil?
263
+ raise 'context must be initialized with application' if application.nil?
264
+ ws_id =
265
+ if !public_link.nil?
266
+ Log.log.debug('Using workspace of public link')
267
+ public_link['data']['workspace_id']
268
+ elsif !private_link.nil?
269
+ Log.log.debug('Using workspace of private link')
270
+ private_link[:workspace_id]
271
+ elsif @workspace_name.eql?(DEFAULT_WORKSPACE)
272
+ Log.log.debug('Using default workspace'.green)
273
+ raise 'User does not have default workspace, please specify workspace' if current_user_info['default_workspace_id'].nil?
274
+ current_user_info['default_workspace_id']
275
+ elsif @workspace_name.nil?
276
+ nil
277
+ else
278
+ lookup_by_name('workspaces', @workspace_name)['id']
279
+ end
280
+ ws_info =
281
+ if ws_id.nil?
282
+ nil
283
+ else
284
+ read("workspaces/#{ws_id}")[:data]
285
+ end
286
+ @context_cache = if ws_info.nil?
287
+ {
288
+ workspace_id: nil,
289
+ workspace_name: 'Shared folders'
290
+ }
291
+ else
292
+ {
293
+ workspace_id: ws_info['id'],
294
+ workspace_name: ws_info['name']
295
+ }
296
+ end
297
+ return @context_cache unless application.eql?(:files)
298
+ if !public_link.nil?
299
+ assert_public_link_types(['view_shared_file'])
300
+ @context_cache[:home_node_id] = public_link['data']['node_id']
301
+ @context_cache[:home_file_id] = public_link['data']['file_id']
302
+ elsif !private_link.nil?
303
+ @context_cache[:home_node_id] = private_link[:node_id]
304
+ @context_cache[:home_file_id] = private_link[:file_id]
305
+ elsif ws_info
306
+ @context_cache[:home_node_id] = ws_info['home_node_id']
307
+ @context_cache[:home_file_id] = ws_info['home_file_id']
308
+ else
309
+ # not part of any workspace, but has some folder shared
310
+ user_info = current_user_info(exception: true) rescue {'read_only_home_node_id' => nil, 'read_only_home_file_id' => nil}
311
+ @context_cache[:home_node_id] = user_info['read_only_home_node_id']
312
+ @context_cache[:home_file_id] = user_info['read_only_home_file_id']
313
+ end
314
+ raise "Cannot get user's home node id, check your default workspace or specify one" if @context_cache[:home_node_id].to_s.empty?
315
+ Log.log.debug{Log.dump(:context, @context_cache)}
316
+ return @context_cache
317
+ end
318
+
257
319
  # @param node_id [String] identifier of node in AoC
258
320
  # @param workspace_id [String] workspace identifier
259
321
  # @param workspace_name [String] workspace name
260
- # @param scope e.g. SCOPE_NODE_USER, or nil (requires secret)
322
+ # @param scope e.g. Aspera::Node::SCOPE_USER, or nil (requires secret)
261
323
  # @param package_info [Hash] created package information
262
324
  # @returns [Aspera::Node] a node API for access key
263
- def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: SCOPE_NODE_USER, package_info: nil)
325
+ def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: Aspera::Node::SCOPE_USER, package_info: nil)
264
326
  raise 'invalid type for node_id' unless node_id.is_a?(String)
265
327
  node_info = read("nodes/#{node_id}")[:data]
266
328
  if workspace_name.nil? && !workspace_id.nil?
@@ -289,7 +351,7 @@ module Aspera
289
351
  else
290
352
  # OAuth bearer token
291
353
  node_rest_params[:auth] = params[:auth].clone
292
- node_rest_params[:auth][:scope] = self.class.node_scope(node_info['access_key'], scope)
354
+ node_rest_params[:auth][:scope] = Aspera::Node.token_scope(node_info['access_key'], scope)
293
355
  # special header required for bearer token only
294
356
  node_rest_params[:headers] = {Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => node_info['access_key']}
295
357
  end
@@ -311,7 +373,7 @@ module Aspera
311
373
  pkg_meta = pkg_data['metadata']
312
374
  raise "package requires metadata: #{meta_schema}" unless pkg_data.key?('metadata')
313
375
  raise 'metadata must be an Array' unless pkg_meta.is_a?(Array)
314
- Log.dump(:metadata, pkg_meta)
376
+ Log.log.debug{Log.dump(:metadata, pkg_meta)}
315
377
  pkg_meta.each do |field|
316
378
  raise 'metadata field must be Hash' unless field.is_a?(Hash)
317
379
  raise 'metadata field must have name' unless field.key?('name')
@@ -355,7 +417,8 @@ module Aspera
355
417
  # unknown user: create it as external user
356
418
  full_recipient_info = create('contacts', {
357
419
  'current_workspace_id' => ws_id,
358
- 'email' => short_recipient_info}.merge(new_user_option))[:data]
420
+ 'email' => short_recipient_info
421
+ }.merge(new_user_option))[:data]
359
422
  end
360
423
  short_recipient_info = if entity_type.eql?('dropboxes')
361
424
  {'id' => full_recipient_info['id'], 'type' => 'dropbox'}
@@ -428,7 +491,7 @@ module Aspera
428
491
  }
429
492
  end
430
493
 
431
- # Add transferspec
494
+ # Add transfer spec
432
495
  # callback in Aspera::Node (transfer_spec_gen4)
433
496
  def add_ts_tags(transfer_spec:, app_info:)
434
497
  # translate transfer direction to upload/download
@@ -468,7 +531,10 @@ module Aspera
468
531
  'package_id' => app_info[:package_id],
469
532
  'package_name' => app_info[:package_name],
470
533
  'package_operation' => transfer_type
471
- }}}})
534
+ }
535
+ }
536
+ }
537
+ })
472
538
  end
473
539
  transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['files']['node_id'] = app_info[:node_info]['id']
474
540
  transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['app'] = app_info[:app]
@@ -497,7 +563,12 @@ module Aspera
497
563
  'shared_by_email' => current_user_info['email'],
498
564
  # 'shared_with_name' => access_id,
499
565
  'access_key' => app_info[:node_info]['access_key'],
500
- 'node' => app_info[:node_info]['name']}}}}}
566
+ 'node' => app_info[:node_info]['name']
567
+ }
568
+ }
569
+ }
570
+ }
571
+ }
501
572
  create_param.deep_merge!(default_params)
502
573
  if create_param.key?('with')
503
574
  contact_info = lookup_by_name(