aspera-cli 4.14.0 → 4.15.0

Sign up to get free protection for your applications and to get access to all the features.
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(