aspera-cli 4.14.0 → 4.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
- data/lib/aspera/sync.rb +0 -213
data/lib/aspera/aoc.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'aspera/log'
|
|
4
|
+
require 'aspera/assert'
|
|
4
5
|
require 'aspera/rest'
|
|
5
6
|
require 'aspera/hash_ext'
|
|
6
7
|
require 'aspera/data_repository'
|
|
7
8
|
require 'aspera/fasp/transfer_spec'
|
|
9
|
+
require 'aspera/node'
|
|
8
10
|
require 'base64'
|
|
9
11
|
require 'cgi'
|
|
10
12
|
|
|
@@ -27,17 +29,16 @@ module Aspera
|
|
|
27
29
|
class AoC < Aspera::Rest
|
|
28
30
|
PRODUCT_NAME = 'Aspera on Cloud'
|
|
29
31
|
# Production domain of AoC
|
|
30
|
-
PROD_DOMAIN = 'ibmaspera.com'
|
|
32
|
+
PROD_DOMAIN = 'ibmaspera.com' # cspell:disable-line
|
|
31
33
|
# to avoid infinite loop in pub link redirection
|
|
32
|
-
|
|
34
|
+
MAX_AOC_URL_REDIRECT = 10
|
|
35
|
+
CLIENT_ID_PREFIX = 'aspera.'
|
|
33
36
|
# Well-known AoC globals client apps
|
|
34
|
-
GLOBAL_CLIENT_APPS =
|
|
35
|
-
# index offset in data repository of client app
|
|
36
|
-
DATA_REPO_INDEX_START = 4
|
|
37
|
+
GLOBAL_CLIENT_APPS = DataRepository::ELEMENTS.select{|i|i.to_s.start_with?(CLIENT_ID_PREFIX)}.freeze
|
|
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,10 +46,10 @@ 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 :
|
|
51
|
+
private_constant :MAX_AOC_URL_REDIRECT,
|
|
50
52
|
:GLOBAL_CLIENT_APPS,
|
|
51
|
-
:DATA_REPO_INDEX_START,
|
|
52
53
|
:COOKIE_PREFIX_CONSOLE_AOC,
|
|
53
54
|
:PUBLIC_LINK_PATHS,
|
|
54
55
|
:JWT_AUDIENCE,
|
|
@@ -62,8 +63,6 @@ module Aspera
|
|
|
62
63
|
SCOPE_FILES_ADMIN = 'admin:all'
|
|
63
64
|
SCOPE_FILES_ADMIN_USER = 'admin-user:all'
|
|
64
65
|
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
66
|
FILES_APP = 'files'
|
|
68
67
|
PACKAGES_APP = 'packages'
|
|
69
68
|
API_V1 = 'api/v1'
|
|
@@ -71,24 +70,9 @@ module Aspera
|
|
|
71
70
|
# class static methods
|
|
72
71
|
class << self
|
|
73
72
|
# strings /Applications/Aspera\ Drive.app/Contents/MacOS/AsperaDrive|grep -E '.{100}==$'|base64 --decode
|
|
74
|
-
def get_client_info(client_name=
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return client_name, Base64.urlsafe_encode64(DataRepository.instance.data(DATA_REPO_INDEX_START + client_index))
|
|
78
|
-
end
|
|
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
|
|
73
|
+
def get_client_info(client_name=nil)
|
|
74
|
+
client_key = client_name.nil? ? GLOBAL_CLIENT_APPS.first : client_name.to_sym
|
|
75
|
+
return client_key, DataRepository.instance.item(client_key)
|
|
92
76
|
end
|
|
93
77
|
|
|
94
78
|
# base API url depends on domain, which could be "qa.xxx"
|
|
@@ -99,126 +83,140 @@ module Aspera
|
|
|
99
83
|
def metering_api(entitlement_id, customer_id, api_domain=PROD_DOMAIN)
|
|
100
84
|
return Rest.new({
|
|
101
85
|
base_url: "#{api_base_url(api_domain: api_domain)}/metering/v1",
|
|
102
|
-
headers: {'X-Aspera-Entitlement-Authorization' => Rest.
|
|
86
|
+
headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_token(entitlement_id, customer_id)}
|
|
103
87
|
})
|
|
104
88
|
end
|
|
105
89
|
|
|
106
|
-
#
|
|
107
|
-
def
|
|
108
|
-
|
|
90
|
+
# split host of http://myorg.asperafiles.com in org and domain
|
|
91
|
+
def url_parts(uri)
|
|
92
|
+
raise "No host found in URL.Please check URL format: https://myorg.#{PROD_DOMAIN}" if uri.host.nil?
|
|
93
|
+
parts = uri.host.split('.', 2)
|
|
94
|
+
assert(parts.length == 2){"expecting a public FQDN for #{PRODUCT_NAME}"}
|
|
95
|
+
return parts
|
|
109
96
|
end
|
|
110
97
|
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
98
|
+
# @param url [String] URL of AoC public link
|
|
99
|
+
# @return [Hash] information about public link, or nil if not a public link
|
|
100
|
+
def link_info(url)
|
|
101
|
+
final_uri = Rest.new({base_url: url, redirect_max: MAX_AOC_URL_REDIRECT}).read('')[:http].uri
|
|
102
|
+
raise 'AoC shall redirect to login page' if final_uri.query.nil?
|
|
103
|
+
decoded_query = Rest.decode_query(final_uri.query)
|
|
104
|
+
# is that a public link ?
|
|
105
|
+
if decoded_query.key?('token')
|
|
106
|
+
Log.log.warn{"Unknown pub link path: #{final_uri.path}"} unless PUBLIC_LINK_PATHS.include?(final_uri.path)
|
|
107
|
+
# ok we get it !
|
|
108
|
+
return {
|
|
109
|
+
instance_domain: url_parts(final_uri)[1],
|
|
110
|
+
url: 'https://' + final_uri.host,
|
|
111
|
+
token: decoded_query['token']
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
Log.log.debug{"path=#{final_uri.path} does not end with /login"} unless final_uri.path.end_with?('/login')
|
|
115
|
+
if decoded_query['state']
|
|
116
|
+
# can be a private link
|
|
117
|
+
state_uri = URI.parse(decoded_query['state'])
|
|
118
|
+
if state_uri.query && decoded_query['redirect_uri']
|
|
119
|
+
decoded_state = Rest.decode_query(state_uri.query)
|
|
120
|
+
if decoded_state.key?('short_link_url')
|
|
121
|
+
if (m = state_uri.path.match(%r{/files/workspaces/([0-9]+)/all/([0-9]+):([0-9]+)}))
|
|
122
|
+
redirect_uri = URI.parse(decoded_query['redirect_uri'])
|
|
123
|
+
parts = url_parts(redirect_uri)
|
|
124
|
+
return {
|
|
125
|
+
instance_domain: parts[1],
|
|
126
|
+
organization: parts[0],
|
|
127
|
+
url: 'https://' + redirect_uri.host,
|
|
128
|
+
private_link: {
|
|
129
|
+
workspace_id: m[1],
|
|
130
|
+
node_id: m[2],
|
|
131
|
+
file_id: m[3]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
end
|
|
135
136
|
end
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Log.log.debug{"redirect to: #{public_link_url}"}
|
|
143
|
-
end # loop
|
|
144
|
-
raise "exceeded max redirection: #{MAX_REDIRECT}"
|
|
137
|
+
end
|
|
138
|
+
parts = url_parts(URI.parse(url))
|
|
139
|
+
return {
|
|
140
|
+
instance_domain: parts[1],
|
|
141
|
+
organization: parts[0]
|
|
142
|
+
}
|
|
145
143
|
end
|
|
146
144
|
end # static methods
|
|
147
145
|
|
|
148
|
-
|
|
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?
|
|
146
|
+
attr_reader :private_link
|
|
154
147
|
|
|
148
|
+
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,
|
|
149
|
+
password: nil, workspace: nil, secret_finder: nil)
|
|
150
|
+
# test here because link may set url
|
|
151
|
+
raise ArgumentError, 'Missing mandatory option: url' if url.nil?
|
|
152
|
+
raise ArgumentError, 'Missing mandatory option: scope' if scope.nil?
|
|
153
|
+
# default values for client id
|
|
154
|
+
client_id, client_secret = self.class.get_client_info if client_id.nil?
|
|
155
155
|
# access key secrets are provided out of band to get node api access
|
|
156
156
|
# key: access key
|
|
157
157
|
# value: associated secret
|
|
158
|
-
@secret_finder =
|
|
158
|
+
@secret_finder = secret_finder
|
|
159
|
+
@workspace_name = workspace
|
|
159
160
|
@cache_user_info = nil
|
|
160
161
|
@cache_url_token_info = nil
|
|
161
|
-
|
|
162
|
+
@context_cache = nil
|
|
162
163
|
# init rest params
|
|
163
164
|
aoc_rest_p = {auth: {type: :oauth2}}
|
|
164
165
|
# shortcut to auth section
|
|
165
166
|
aoc_auth_p = aoc_rest_p[:auth]
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
167
|
+
# analyze type of url
|
|
168
|
+
url_info = AoC.link_info(url)
|
|
169
|
+
Log.log.debug{Log.dump(:url_info, url_info)}
|
|
170
|
+
@private_link = url_info[:private_link]
|
|
171
|
+
aoc_auth_p[:grant_method] = if url_info.key?(:token)
|
|
172
|
+
:aoc_pub_link
|
|
173
|
+
else
|
|
174
|
+
raise ArgumentError, 'Missing mandatory option: auth' if auth.nil?
|
|
175
|
+
auth
|
|
176
|
+
end
|
|
175
177
|
# this is the base API url
|
|
176
|
-
api_url_base = self.class.api_base_url(api_domain: instance_domain)
|
|
178
|
+
api_url_base = self.class.api_base_url(api_domain: url_info[:instance_domain])
|
|
177
179
|
# API URL, including subpath (version ...)
|
|
178
|
-
aoc_rest_p[:base_url] = "#{api_url_base}/#{
|
|
179
|
-
#
|
|
180
|
-
aoc_auth_p[:base_url] = "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{organization}"
|
|
181
|
-
aoc_auth_p[:client_id] =
|
|
182
|
-
aoc_auth_p[:client_secret] =
|
|
183
|
-
aoc_auth_p[: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
|
|
180
|
+
aoc_rest_p[:base_url] = "#{api_url_base}/#{subpath}"
|
|
181
|
+
# auth URL
|
|
182
|
+
aoc_auth_p[:base_url] = "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{url_info[:organization]}"
|
|
183
|
+
aoc_auth_p[:client_id] = client_id
|
|
184
|
+
aoc_auth_p[:client_secret] = client_secret
|
|
185
|
+
aoc_auth_p[:scope] = scope
|
|
194
186
|
|
|
195
187
|
# fill other auth parameters based on Oauth method
|
|
196
188
|
case aoc_auth_p[:grant_method]
|
|
197
189
|
when :web
|
|
198
|
-
raise ArgumentError, 'Missing mandatory option: redirect_uri' if
|
|
199
|
-
aoc_auth_p[:web] = {redirect_uri:
|
|
190
|
+
raise ArgumentError, 'Missing mandatory option: redirect_uri' if redirect_uri.nil?
|
|
191
|
+
aoc_auth_p[:web] = {redirect_uri: redirect_uri}
|
|
200
192
|
when :jwt
|
|
201
|
-
raise ArgumentError, 'Missing mandatory option: private_key' if
|
|
202
|
-
raise ArgumentError, 'Missing mandatory option: username' if
|
|
193
|
+
raise ArgumentError, 'Missing mandatory option: private_key' if private_key.nil?
|
|
194
|
+
raise ArgumentError, 'Missing mandatory option: username' if username.nil?
|
|
203
195
|
aoc_auth_p[:jwt] = {
|
|
204
|
-
private_key_obj: OpenSSL::PKey::RSA.new(
|
|
196
|
+
private_key_obj: OpenSSL::PKey::RSA.new(private_key, passphrase),
|
|
205
197
|
payload: {
|
|
206
|
-
iss: aoc_auth_p[:client_id],
|
|
207
|
-
sub:
|
|
198
|
+
iss: aoc_auth_p[:client_id], # issuer
|
|
199
|
+
sub: username, # subject
|
|
208
200
|
aud: JWT_AUDIENCE
|
|
209
201
|
}
|
|
210
202
|
}
|
|
211
203
|
# add jwt payload for global ids
|
|
212
|
-
aoc_auth_p[:jwt][:payload][:org] = organization if GLOBAL_CLIENT_APPS.include?(aoc_auth_p[:client_id])
|
|
204
|
+
aoc_auth_p[:jwt][:payload][:org] = url_info[:organization] if GLOBAL_CLIENT_APPS.include?(aoc_auth_p[:client_id])
|
|
213
205
|
when :aoc_pub_link
|
|
206
|
+
aoc_auth_p[:aoc_pub_link] = {
|
|
207
|
+
url: {grant_type: 'url_token'}, # URL arguments
|
|
208
|
+
json: {url_token: url_info[:token]} # JSON body
|
|
209
|
+
}
|
|
210
|
+
# password protection of link
|
|
211
|
+
aoc_auth_p[:aoc_pub_link][:json][:password] = password unless password.nil?
|
|
214
212
|
# basic auth required for /token
|
|
215
213
|
aoc_auth_p[:auth] = {type: :basic, username: aoc_auth_p[:client_id], password: aoc_auth_p[:client_secret]}
|
|
216
|
-
else
|
|
214
|
+
else error_unexpected_value(aoc_auth_p[:grant_method])
|
|
217
215
|
end
|
|
218
216
|
super(aoc_rest_p)
|
|
219
217
|
end
|
|
220
218
|
|
|
221
|
-
def
|
|
219
|
+
def public_link
|
|
222
220
|
return nil unless params[:auth][:grant_method].eql?(:aoc_pub_link)
|
|
223
221
|
return @cache_url_token_info unless @cache_url_token_info.nil?
|
|
224
222
|
# TODO: can there be several in list ?
|
|
@@ -226,42 +224,100 @@ module Aspera
|
|
|
226
224
|
return @cache_url_token_info
|
|
227
225
|
end
|
|
228
226
|
|
|
229
|
-
def
|
|
230
|
-
|
|
231
|
-
return [] # TODO : url_token_data['id'] ?
|
|
227
|
+
def assert_public_link_types(expected)
|
|
228
|
+
assert_values(public_link['purpose'], expected){'public link type'}
|
|
232
229
|
end
|
|
233
230
|
|
|
234
|
-
def
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
@secret_finder = secret_finder
|
|
231
|
+
def additional_persistence_ids
|
|
232
|
+
return [current_user_info['id']] if public_link.nil?
|
|
233
|
+
return [] # TODO : public_link['id'] ?
|
|
238
234
|
end
|
|
239
235
|
|
|
240
236
|
# cached user information
|
|
241
237
|
def current_user_info(exception: false)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
end
|
|
238
|
+
return @cache_user_info unless @cache_user_info.nil?
|
|
239
|
+
# get our user's default information
|
|
240
|
+
@cache_user_info =
|
|
241
|
+
begin
|
|
242
|
+
read('self')[:data]
|
|
243
|
+
rescue StandardError => e
|
|
244
|
+
raise e if exception
|
|
245
|
+
Log.log.debug{"ignoring error: #{e}"}
|
|
246
|
+
{}
|
|
247
|
+
end
|
|
248
|
+
USER_INFO_FIELDS_MIN.each{|f|@cache_user_info[f] = nil if @cache_user_info[f].nil?}
|
|
254
249
|
return @cache_user_info
|
|
255
250
|
end
|
|
256
251
|
|
|
252
|
+
# @param application [Symbol] :files or :packages
|
|
253
|
+
# @return [Hash] current context information: workspace, and home node/file if app is "Files"
|
|
254
|
+
def context(application = nil)
|
|
255
|
+
return @context_cache unless @context_cache.nil?
|
|
256
|
+
assert(!application.nil?){'application must be set once'}
|
|
257
|
+
assert_values(application, %i[files packages])
|
|
258
|
+
ws_id =
|
|
259
|
+
if !public_link.nil?
|
|
260
|
+
Log.log.debug('Using workspace of public link')
|
|
261
|
+
public_link['data']['workspace_id']
|
|
262
|
+
elsif !private_link.nil?
|
|
263
|
+
Log.log.debug('Using workspace of private link')
|
|
264
|
+
private_link[:workspace_id]
|
|
265
|
+
elsif @workspace_name.eql?(DEFAULT_WORKSPACE)
|
|
266
|
+
Log.log.debug('Using default workspace'.green)
|
|
267
|
+
raise 'User does not have default workspace, please specify workspace' if current_user_info['default_workspace_id'].nil?
|
|
268
|
+
current_user_info['default_workspace_id']
|
|
269
|
+
elsif @workspace_name.nil?
|
|
270
|
+
nil
|
|
271
|
+
else
|
|
272
|
+
lookup_by_name('workspaces', @workspace_name)['id']
|
|
273
|
+
end
|
|
274
|
+
ws_info =
|
|
275
|
+
if ws_id.nil?
|
|
276
|
+
nil
|
|
277
|
+
else
|
|
278
|
+
read("workspaces/#{ws_id}")[:data]
|
|
279
|
+
end
|
|
280
|
+
@context_cache = if ws_info.nil?
|
|
281
|
+
{
|
|
282
|
+
workspace_id: nil,
|
|
283
|
+
workspace_name: 'Shared folders'
|
|
284
|
+
}
|
|
285
|
+
else
|
|
286
|
+
{
|
|
287
|
+
workspace_id: ws_info['id'],
|
|
288
|
+
workspace_name: ws_info['name']
|
|
289
|
+
}
|
|
290
|
+
end
|
|
291
|
+
return @context_cache unless application.eql?(:files)
|
|
292
|
+
if !public_link.nil?
|
|
293
|
+
assert_public_link_types(['view_shared_file'])
|
|
294
|
+
@context_cache[:home_node_id] = public_link['data']['node_id']
|
|
295
|
+
@context_cache[:home_file_id] = public_link['data']['file_id']
|
|
296
|
+
elsif !private_link.nil?
|
|
297
|
+
@context_cache[:home_node_id] = private_link[:node_id]
|
|
298
|
+
@context_cache[:home_file_id] = private_link[:file_id]
|
|
299
|
+
elsif ws_info
|
|
300
|
+
@context_cache[:home_node_id] = ws_info['home_node_id']
|
|
301
|
+
@context_cache[:home_file_id] = ws_info['home_file_id']
|
|
302
|
+
else
|
|
303
|
+
# not part of any workspace, but has some folder shared
|
|
304
|
+
user_info = current_user_info(exception: true) rescue {'read_only_home_node_id' => nil, 'read_only_home_file_id' => nil}
|
|
305
|
+
@context_cache[:home_node_id] = user_info['read_only_home_node_id']
|
|
306
|
+
@context_cache[:home_file_id] = user_info['read_only_home_file_id']
|
|
307
|
+
end
|
|
308
|
+
raise "Cannot get user's home node id, check your default workspace or specify one" if @context_cache[:home_node_id].to_s.empty?
|
|
309
|
+
Log.log.debug{Log.dump(:context, @context_cache)}
|
|
310
|
+
return @context_cache
|
|
311
|
+
end
|
|
312
|
+
|
|
257
313
|
# @param node_id [String] identifier of node in AoC
|
|
258
314
|
# @param workspace_id [String] workspace identifier
|
|
259
315
|
# @param workspace_name [String] workspace name
|
|
260
|
-
# @param scope e.g.
|
|
316
|
+
# @param scope e.g. Aspera::Node::SCOPE_USER, or nil (requires secret)
|
|
261
317
|
# @param package_info [Hash] created package information
|
|
262
318
|
# @returns [Aspera::Node] a node API for access key
|
|
263
|
-
def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope:
|
|
264
|
-
|
|
319
|
+
def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: Aspera::Node::SCOPE_USER, package_info: nil)
|
|
320
|
+
assert_type(node_id, String)
|
|
265
321
|
node_info = read("nodes/#{node_id}")[:data]
|
|
266
322
|
if workspace_name.nil? && !workspace_id.nil?
|
|
267
323
|
workspace_name = read("workspaces/#{workspace_id}")[:data]['name']
|
|
@@ -289,7 +345,7 @@ module Aspera
|
|
|
289
345
|
else
|
|
290
346
|
# OAuth bearer token
|
|
291
347
|
node_rest_params[:auth] = params[:auth].clone
|
|
292
|
-
node_rest_params[:auth][:scope] =
|
|
348
|
+
node_rest_params[:auth][:scope] = Aspera::Node.token_scope(node_info['access_key'], scope)
|
|
293
349
|
# special header required for bearer token only
|
|
294
350
|
node_rest_params[:headers] = {Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => node_info['access_key']}
|
|
295
351
|
end
|
|
@@ -308,16 +364,16 @@ module Aspera
|
|
|
308
364
|
Log.log.debug('no metadata in shared inbox')
|
|
309
365
|
return
|
|
310
366
|
end
|
|
367
|
+
assert(pkg_data.key?('metadata')){"package requires metadata: #{meta_schema}"}
|
|
311
368
|
pkg_meta = pkg_data['metadata']
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
Log.dump(:metadata, pkg_meta)
|
|
369
|
+
assert_type(pkg_meta, Array){'metadata'}
|
|
370
|
+
Log.log.debug{Log.dump(:metadata, pkg_meta)}
|
|
315
371
|
pkg_meta.each do |field|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
372
|
+
assert_type(field, Hash){'metadata field'}
|
|
373
|
+
assert(field.key?('name')){'metadata field must have name'}
|
|
374
|
+
assert(field.key?('values')){'metadata field must have values'}
|
|
375
|
+
assert_type(field['values'], Array){'metadata field values'}
|
|
376
|
+
assert(!meta_schema.select{|i|i['name'].eql?(field['name'])}.empty?){"unknown metadata field: #{field['name']}"}
|
|
321
377
|
end
|
|
322
378
|
meta_schema.each do |field|
|
|
323
379
|
provided = pkg_meta.select{|i|i['name'].eql?(field['name'])}
|
|
@@ -334,15 +390,15 @@ module Aspera
|
|
|
334
390
|
# @return nil package_data is modified
|
|
335
391
|
def resolve_package_recipients(package_data, ws_id, recipient_list_field, new_user_option)
|
|
336
392
|
return unless package_data.key?(recipient_list_field)
|
|
337
|
-
|
|
393
|
+
assert_type(package_data[recipient_list_field], Array){recipient_list_field}
|
|
338
394
|
new_user_option = {'package_contact' => true} if new_user_option.nil?
|
|
339
|
-
|
|
395
|
+
assert_type(new_user_option, Hash){'new_user_option'}
|
|
340
396
|
# list with resolved elements
|
|
341
397
|
resolved_list = []
|
|
342
398
|
package_data[recipient_list_field].each do |short_recipient_info|
|
|
343
399
|
case short_recipient_info
|
|
344
400
|
when Hash # native API information, check keys
|
|
345
|
-
|
|
401
|
+
assert(short_recipient_info.keys.sort.eql?(%w[id type])){"#{recipient_list_field} element shall have fields: id and type"}
|
|
346
402
|
when String # CLI helper: need to resolve provided name to type/id
|
|
347
403
|
# email: user, else dropbox
|
|
348
404
|
entity_type = short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
|
|
@@ -355,7 +411,8 @@ module Aspera
|
|
|
355
411
|
# unknown user: create it as external user
|
|
356
412
|
full_recipient_info = create('contacts', {
|
|
357
413
|
'current_workspace_id' => ws_id,
|
|
358
|
-
'email' => short_recipient_info
|
|
414
|
+
'email' => short_recipient_info
|
|
415
|
+
}.merge(new_user_option))[:data]
|
|
359
416
|
end
|
|
360
417
|
short_recipient_info = if entity_type.eql?('dropboxes')
|
|
361
418
|
{'id' => full_recipient_info['id'], 'type' => 'dropbox'}
|
|
@@ -387,7 +444,7 @@ module Aspera
|
|
|
387
444
|
})
|
|
388
445
|
end
|
|
389
446
|
pkg_data['metadata'] = api_meta
|
|
390
|
-
else
|
|
447
|
+
else error_unexpected_value(pkg_meta.class)
|
|
391
448
|
end
|
|
392
449
|
return nil
|
|
393
450
|
end
|
|
@@ -428,7 +485,7 @@ module Aspera
|
|
|
428
485
|
}
|
|
429
486
|
end
|
|
430
487
|
|
|
431
|
-
# Add
|
|
488
|
+
# Add transfer spec
|
|
432
489
|
# callback in Aspera::Node (transfer_spec_gen4)
|
|
433
490
|
def add_ts_tags(transfer_spec:, app_info:)
|
|
434
491
|
# translate transfer direction to upload/download
|
|
@@ -450,7 +507,7 @@ module Aspera
|
|
|
450
507
|
# Console cookie
|
|
451
508
|
################
|
|
452
509
|
# we are sure that fields are not nil
|
|
453
|
-
cookie_elements = [app_info[:app], current_user_info['name'], current_user_info['email']].map{|e|Base64.strict_encode64(e)}
|
|
510
|
+
cookie_elements = [app_info[:app], current_user_info['name'] || 'public link', current_user_info['email'] || 'none'].map{|e|Base64.strict_encode64(e)}
|
|
454
511
|
cookie_elements.unshift(COOKIE_PREFIX_CONSOLE_AOC)
|
|
455
512
|
transfer_spec['cookie'] = cookie_elements.join(':')
|
|
456
513
|
# Application tags
|
|
@@ -468,7 +525,10 @@ module Aspera
|
|
|
468
525
|
'package_id' => app_info[:package_id],
|
|
469
526
|
'package_name' => app_info[:package_name],
|
|
470
527
|
'package_operation' => transfer_type
|
|
471
|
-
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
})
|
|
472
532
|
end
|
|
473
533
|
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['files']['node_id'] = app_info[:node_info]['id']
|
|
474
534
|
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['app'] = app_info[:app]
|
|
@@ -497,7 +557,12 @@ module Aspera
|
|
|
497
557
|
'shared_by_email' => current_user_info['email'],
|
|
498
558
|
# 'shared_with_name' => access_id,
|
|
499
559
|
'access_key' => app_info[:node_info]['access_key'],
|
|
500
|
-
'node' => app_info[:node_info]['name']
|
|
560
|
+
'node' => app_info[:node_info]['name']
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
501
566
|
create_param.deep_merge!(default_params)
|
|
502
567
|
if create_param.key?('with')
|
|
503
568
|
contact_info = lookup_by_name(
|
|
@@ -519,7 +584,8 @@ module Aspera
|
|
|
519
584
|
# @param app_info [Hash] hash with app info
|
|
520
585
|
# @param types [Array] event types
|
|
521
586
|
def permissions_send_event(created_data:, app_info:, types: PERMISSIONS_CREATED)
|
|
522
|
-
|
|
587
|
+
assert_type(types, Array)
|
|
588
|
+
assert(!types.empty?)
|
|
523
589
|
event_creation = {
|
|
524
590
|
'types' => types,
|
|
525
591
|
'node_id' => app_info[:node_info]['id'],
|