aspera-cli 4.13.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +81 -7
- data/CONTRIBUTING.md +22 -6
- data/README.md +2038 -1080
- data/bin/ascli +18 -9
- data/bin/asession +12 -14
- data/examples/dascli +1 -1
- data/examples/proxy.pac +1 -1
- data/examples/rubyc +24 -0
- data/lib/aspera/aoc.rb +219 -159
- data/lib/aspera/ascmd.rb +25 -14
- data/lib/aspera/cli/basic_auth_plugin.rb +12 -9
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +47 -12
- data/lib/aspera/cli/formatter.rb +260 -179
- data/lib/aspera/cli/hints.rb +80 -0
- data/lib/aspera/cli/main.rb +104 -156
- data/lib/aspera/cli/manager.rb +259 -209
- data/lib/aspera/cli/plugin.rb +123 -63
- data/lib/aspera/cli/plugins/alee.rb +2 -3
- data/lib/aspera/cli/plugins/aoc.rb +341 -261
- data/lib/aspera/cli/plugins/ats.rb +22 -21
- data/lib/aspera/cli/plugins/bss.rb +5 -5
- data/lib/aspera/cli/plugins/config.rb +578 -627
- data/lib/aspera/cli/plugins/console.rb +44 -6
- data/lib/aspera/cli/plugins/cos.rb +15 -17
- data/lib/aspera/cli/plugins/faspex.rb +114 -100
- data/lib/aspera/cli/plugins/faspex5.rb +411 -264
- data/lib/aspera/cli/plugins/node.rb +354 -259
- data/lib/aspera/cli/plugins/orchestrator.rb +61 -29
- data/lib/aspera/cli/plugins/preview.rb +82 -90
- data/lib/aspera/cli/plugins/server.rb +79 -32
- data/lib/aspera/cli/plugins/shares.rb +55 -42
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +66 -73
- data/lib/aspera/cli/transfer_progress.rb +74 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +12 -8
- data/lib/aspera/command_line_builder.rb +14 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/data/6 +0 -0
- data/lib/aspera/environment.rb +24 -9
- data/lib/aspera/fasp/agent_aspera.rb +126 -0
- data/lib/aspera/fasp/agent_base.rb +31 -77
- data/lib/aspera/fasp/agent_connect.rb +25 -21
- data/lib/aspera/fasp/agent_direct.rb +89 -103
- data/lib/aspera/fasp/agent_httpgw.rb +231 -149
- data/lib/aspera/fasp/agent_node.rb +41 -34
- data/lib/aspera/fasp/agent_trsdk.rb +75 -32
- data/lib/aspera/fasp/error_info.rb +4 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +53 -195
- data/lib/aspera/fasp/management.rb +244 -0
- data/lib/aspera/fasp/parameters.rb +71 -37
- data/lib/aspera/fasp/parameters.yaml +76 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +3 -3
- data/lib/aspera/fasp/transfer_spec.rb +7 -6
- data/lib/aspera/fasp/uri.rb +26 -24
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/hash_ext.rb +14 -4
- data/lib/aspera/json_rpc.rb +49 -0
- data/lib/aspera/keychain/macos_security.rb +13 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +58 -16
- data/lib/aspera/node.rb +157 -92
- data/lib/aspera/oauth.rb +37 -19
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +1 -1
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/file_types.rb +4 -2
- data/lib/aspera/preview/generator.rb +22 -35
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +73 -16
- data/lib/aspera/preview/utils.rb +21 -28
- data/lib/aspera/proxy_auto_config.js +2 -2
- data/lib/aspera/rest.rb +136 -68
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +15 -14
- data/lib/aspera/rest_errors_aspera.rb +37 -34
- data/lib/aspera/secret_hider.rb +18 -15
- data/lib/aspera/ssh.rb +5 -2
- data/lib/aspera/sync.rb +127 -119
- data/lib/aspera/temp_file_manager.rb +10 -3
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +9 -4
- data.tar.gz.sig +0 -0
- metadata +34 -17
- metadata.gz.sig +0 -0
- data/docs/test_env.conf +0 -186
- 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/data/7 +0 -0
- data/lib/aspera/fasp/listener.rb +0 -13
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
|
-
|
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,20 +38,25 @@ 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
|
44
45
|
USER_INFO_FIELDS_MIN = %w[name email id default_workspace_id organization_id].freeze
|
46
|
+
# types of events for shared folder creation
|
47
|
+
# Node events: permission.created permission.modified permission.deleted
|
48
|
+
PERMISSIONS_CREATED = ['permission.created'].freeze
|
49
|
+
DEFAULT_WORKSPACE = ''
|
45
50
|
|
46
|
-
private_constant :
|
51
|
+
private_constant :MAX_AOC_URL_REDIRECT,
|
47
52
|
:GLOBAL_CLIENT_APPS,
|
48
53
|
:DATA_REPO_INDEX_START,
|
49
54
|
:COOKIE_PREFIX_CONSOLE_AOC,
|
50
55
|
:PUBLIC_LINK_PATHS,
|
51
56
|
:JWT_AUDIENCE,
|
52
57
|
:OAUTH_API_SUBPATH,
|
53
|
-
:USER_INFO_FIELDS_MIN
|
58
|
+
:USER_INFO_FIELDS_MIN,
|
59
|
+
:PERMISSIONS_CREATED
|
54
60
|
|
55
61
|
# various API scopes supported
|
56
62
|
SCOPE_FILES_SELF = 'self'
|
@@ -58,13 +64,9 @@ module Aspera
|
|
58
64
|
SCOPE_FILES_ADMIN = 'admin:all'
|
59
65
|
SCOPE_FILES_ADMIN_USER = 'admin-user:all'
|
60
66
|
SCOPE_FILES_ADMIN_USER_USER = "#{SCOPE_FILES_ADMIN_USER}+#{SCOPE_FILES_USER}"
|
61
|
-
SCOPE_NODE_USER = 'user:all'
|
62
|
-
SCOPE_NODE_ADMIN = 'admin:all'
|
63
67
|
FILES_APP = 'files'
|
64
68
|
PACKAGES_APP = 'packages'
|
65
69
|
API_V1 = 'api/v1'
|
66
|
-
# error message when entity not found
|
67
|
-
ENTITY_NOT_FOUND = 'No such'
|
68
70
|
|
69
71
|
# class static methods
|
70
72
|
class << self
|
@@ -75,20 +77,6 @@ module Aspera
|
|
75
77
|
return client_name, Base64.urlsafe_encode64(DataRepository.instance.data(DATA_REPO_INDEX_START + client_index))
|
76
78
|
end
|
77
79
|
|
78
|
-
# @param url of AoC instance
|
79
|
-
# @return organization id in url and AoC domain: ibmaspera.com, asperafiles.com or qa.asperafiles.com, etc...
|
80
|
-
def parse_url(aoc_org_url)
|
81
|
-
uri = URI.parse(aoc_org_url.gsub(%r{/+$}, ''))
|
82
|
-
instance_fqdn = uri.host
|
83
|
-
Log.log.debug{"instance_fqdn=#{instance_fqdn}"}
|
84
|
-
raise "No host found in URL.Please check URL format: https://myorg.#{PROD_DOMAIN}" if instance_fqdn.nil?
|
85
|
-
organization, instance_domain = instance_fqdn.split('.', 2)
|
86
|
-
Log.log.debug{"instance_domain=#{instance_domain}"}
|
87
|
-
Log.log.debug{"organization=#{organization}"}
|
88
|
-
raise "expecting a public FQDN for #{PRODUCT_NAME}" if instance_domain.nil?
|
89
|
-
return organization, instance_domain
|
90
|
-
end
|
91
|
-
|
92
80
|
# base API url depends on domain, which could be "qa.xxx"
|
93
81
|
def api_base_url(organization: 'api', api_domain: PROD_DOMAIN)
|
94
82
|
return "https://#{organization}.#{api_domain}"
|
@@ -97,118 +85,131 @@ module Aspera
|
|
97
85
|
def metering_api(entitlement_id, customer_id, api_domain=PROD_DOMAIN)
|
98
86
|
return Rest.new({
|
99
87
|
base_url: "#{api_base_url(api_domain: api_domain)}/metering/v1",
|
100
|
-
headers: {'X-Aspera-Entitlement-Authorization' => Rest.
|
88
|
+
headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_token(entitlement_id, customer_id)}
|
101
89
|
})
|
102
90
|
end
|
103
91
|
|
104
|
-
#
|
105
|
-
def
|
106
|
-
|
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
|
107
98
|
end
|
108
99
|
|
109
|
-
#
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
133
138
|
end
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
Log.log.debug{"redirect to: #{public_link_url}"}
|
141
|
-
end # loop
|
142
|
-
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
|
+
}
|
143
145
|
end
|
144
146
|
end # static methods
|
145
147
|
|
146
|
-
|
147
|
-
OPTIONS_NEW = %i[link url auth client_id client_secret scope redirect_uri private_key passphrase username password].freeze
|
148
|
-
|
149
|
-
# @param any of OPTIONS_NEW + subpath
|
150
|
-
def initialize(opt)
|
151
|
-
raise ArgumentError, 'Missing mandatory option: scope' if opt[:scope].nil?
|
148
|
+
attr_reader :private_link
|
152
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?
|
153
157
|
# access key secrets are provided out of band to get node api access
|
154
158
|
# key: access key
|
155
159
|
# value: associated secret
|
156
|
-
@secret_finder =
|
160
|
+
@secret_finder = secret_finder
|
161
|
+
@workspace_name = workspace
|
157
162
|
@cache_user_info = nil
|
158
163
|
@cache_url_token_info = nil
|
159
|
-
|
160
164
|
# init rest params
|
161
165
|
aoc_rest_p = {auth: {type: :oauth2}}
|
162
166
|
# shortcut to auth section
|
163
167
|
aoc_auth_p = aoc_rest_p[:auth]
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
173
178
|
# this is the base API url
|
174
|
-
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])
|
175
180
|
# API URL, including subpath (version ...)
|
176
|
-
aoc_rest_p[:base_url] = "#{api_url_base}/#{
|
177
|
-
#
|
178
|
-
aoc_auth_p[:base_url] = "#{api_url_base}/#{OAUTH_API_SUBPATH}/#{organization}"
|
179
|
-
aoc_auth_p[:client_id] =
|
180
|
-
aoc_auth_p[:client_secret] =
|
181
|
-
aoc_auth_p[:scope] =
|
182
|
-
|
183
|
-
# filled if pub link
|
184
|
-
if !aoc_auth_p.key?(:grant_method)
|
185
|
-
raise ArgumentError, 'Missing mandatory option: auth' if opt[:auth].nil?
|
186
|
-
aoc_auth_p[:grant_method] = opt[:auth]
|
187
|
-
end
|
188
|
-
|
189
|
-
if aoc_auth_p[:client_id].nil?
|
190
|
-
aoc_auth_p[:client_id], aoc_auth_p[:client_secret] = self.class.get_client_info
|
191
|
-
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
|
192
187
|
|
193
188
|
# fill other auth parameters based on Oauth method
|
194
189
|
case aoc_auth_p[:grant_method]
|
195
190
|
when :web
|
196
|
-
raise ArgumentError, 'Missing mandatory option: redirect_uri' if
|
197
|
-
aoc_auth_p[:web] = {redirect_uri:
|
191
|
+
raise ArgumentError, 'Missing mandatory option: redirect_uri' if redirect_uri.nil?
|
192
|
+
aoc_auth_p[:web] = {redirect_uri: redirect_uri}
|
198
193
|
when :jwt
|
199
|
-
raise ArgumentError, 'Missing mandatory option: private_key' if
|
200
|
-
raise ArgumentError, 'Missing mandatory option: username' if
|
194
|
+
raise ArgumentError, 'Missing mandatory option: private_key' if private_key.nil?
|
195
|
+
raise ArgumentError, 'Missing mandatory option: username' if username.nil?
|
201
196
|
aoc_auth_p[:jwt] = {
|
202
|
-
private_key_obj: OpenSSL::PKey::RSA.new(
|
197
|
+
private_key_obj: OpenSSL::PKey::RSA.new(private_key, passphrase),
|
203
198
|
payload: {
|
204
|
-
iss: aoc_auth_p[:client_id],
|
205
|
-
sub:
|
199
|
+
iss: aoc_auth_p[:client_id], # issuer
|
200
|
+
sub: username, # subject
|
206
201
|
aud: JWT_AUDIENCE
|
207
202
|
}
|
208
203
|
}
|
209
204
|
# add jwt payload for global ids
|
210
|
-
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])
|
211
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?
|
212
213
|
# basic auth required for /token
|
213
214
|
aoc_auth_p[:auth] = {type: :basic, username: aoc_auth_p[:client_id], password: aoc_auth_p[:client_secret]}
|
214
215
|
else raise "ERROR: unsupported auth method: #{aoc_auth_p[:grant_method]}"
|
@@ -216,7 +217,7 @@ module Aspera
|
|
216
217
|
super(aoc_rest_p)
|
217
218
|
end
|
218
219
|
|
219
|
-
def
|
220
|
+
def public_link
|
220
221
|
return nil unless params[:auth][:grant_method].eql?(:aoc_pub_link)
|
221
222
|
return @cache_url_token_info unless @cache_url_token_info.nil?
|
222
223
|
# TODO: can there be several in list ?
|
@@ -224,41 +225,104 @@ module Aspera
|
|
224
225
|
return @cache_url_token_info
|
225
226
|
end
|
226
227
|
|
227
|
-
def
|
228
|
-
|
229
|
-
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'])
|
230
230
|
end
|
231
231
|
|
232
|
-
def
|
233
|
-
|
234
|
-
|
235
|
-
@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'] ?
|
236
235
|
end
|
237
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
|
+
|
238
243
|
# cached user information
|
239
244
|
def current_user_info(exception: false)
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
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?}
|
252
256
|
return @cache_user_info
|
253
257
|
end
|
254
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
|
+
|
255
319
|
# @param node_id [String] identifier of node in AoC
|
256
320
|
# @param workspace_id [String] workspace identifier
|
257
321
|
# @param workspace_name [String] workspace name
|
258
|
-
# @param scope e.g.
|
322
|
+
# @param scope e.g. Aspera::Node::SCOPE_USER, or nil (requires secret)
|
259
323
|
# @param package_info [Hash] created package information
|
260
324
|
# @returns [Aspera::Node] a node API for access key
|
261
|
-
def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope:
|
325
|
+
def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: Aspera::Node::SCOPE_USER, package_info: nil)
|
262
326
|
raise 'invalid type for node_id' unless node_id.is_a?(String)
|
263
327
|
node_info = read("nodes/#{node_id}")[:data]
|
264
328
|
if workspace_name.nil? && !workspace_id.nil?
|
@@ -287,35 +351,13 @@ module Aspera
|
|
287
351
|
else
|
288
352
|
# OAuth bearer token
|
289
353
|
node_rest_params[:auth] = params[:auth].clone
|
290
|
-
node_rest_params[:auth][:scope] =
|
354
|
+
node_rest_params[:auth][:scope] = Aspera::Node.token_scope(node_info['access_key'], scope)
|
291
355
|
# special header required for bearer token only
|
292
356
|
node_rest_params[:headers] = {Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => node_info['access_key']}
|
293
357
|
end
|
294
358
|
return Node.new(params: node_rest_params, app_info: app_info)
|
295
359
|
end
|
296
360
|
|
297
|
-
# Query entity type by name and returns the id if a single entry only
|
298
|
-
# @param entity_type path of entity in API
|
299
|
-
# @param entity_name name of searched entity
|
300
|
-
# @param options additional search options
|
301
|
-
def lookup_entity_by_name(entity_type, entity_name, options={})
|
302
|
-
# returns entities whose name contains value (case insensitive)
|
303
|
-
matching_items = read(entity_type, options.merge({'q' => CGI.escape(entity_name)}))[:data]
|
304
|
-
case matching_items.length
|
305
|
-
when 1 then return matching_items.first
|
306
|
-
when 0 then raise %Q{#{ENTITY_NOT_FOUND} #{entity_type}: "#{entity_name}"}
|
307
|
-
else
|
308
|
-
# multiple case insensitive partial matches, try case insensitive full match
|
309
|
-
# (anyway AoC does not allow creation of 2 entities with same case insensitive name)
|
310
|
-
name_matches = matching_items.select{|i|i['name'].casecmp?(entity_name)}
|
311
|
-
case name_matches.length
|
312
|
-
when 1 then return name_matches.first
|
313
|
-
when 0 then raise %Q(#{entity_type}: multiple case insensitive partial match for: "#{entity_name}": #{matching_items.map{|i|i['name']}} but no case insensitive full match. Please be more specific or give exact name.) # rubocop:disable Layout/LineLength
|
314
|
-
else raise "Two entities cannot have the same case insensitive name: #{name_matches.map{|i|i['name']}}"
|
315
|
-
end
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
361
|
# Check metadata: remove when validation is done server side
|
320
362
|
def validate_metadata(pkg_data)
|
321
363
|
# validate only for shared inboxes
|
@@ -331,7 +373,7 @@ module Aspera
|
|
331
373
|
pkg_meta = pkg_data['metadata']
|
332
374
|
raise "package requires metadata: #{meta_schema}" unless pkg_data.key?('metadata')
|
333
375
|
raise 'metadata must be an Array' unless pkg_meta.is_a?(Array)
|
334
|
-
Log.dump(:metadata, pkg_meta)
|
376
|
+
Log.log.debug{Log.dump(:metadata, pkg_meta)}
|
335
377
|
pkg_meta.each do |field|
|
336
378
|
raise 'metadata field must be Hash' unless field.is_a?(Hash)
|
337
379
|
raise 'metadata field must have name' unless field.key?('name')
|
@@ -367,7 +409,7 @@ module Aspera
|
|
367
409
|
# email: user, else dropbox
|
368
410
|
entity_type = short_recipient_info.include?('@') ? 'contacts' : 'dropboxes'
|
369
411
|
begin
|
370
|
-
full_recipient_info =
|
412
|
+
full_recipient_info = lookup_by_name(entity_type, short_recipient_info, {'current_workspace_id' => ws_id})
|
371
413
|
rescue RuntimeError => e
|
372
414
|
raise e unless e.message.start_with?(ENTITY_NOT_FOUND)
|
373
415
|
# dropboxes cannot be created on the fly
|
@@ -375,7 +417,8 @@ module Aspera
|
|
375
417
|
# unknown user: create it as external user
|
376
418
|
full_recipient_info = create('contacts', {
|
377
419
|
'current_workspace_id' => ws_id,
|
378
|
-
'email' => short_recipient_info
|
420
|
+
'email' => short_recipient_info
|
421
|
+
}.merge(new_user_option))[:data]
|
379
422
|
end
|
380
423
|
short_recipient_info = if entity_type.eql?('dropboxes')
|
381
424
|
{'id' => full_recipient_info['id'], 'type' => 'dropbox'}
|
@@ -448,7 +491,7 @@ module Aspera
|
|
448
491
|
}
|
449
492
|
end
|
450
493
|
|
451
|
-
# Add
|
494
|
+
# Add transfer spec
|
452
495
|
# callback in Aspera::Node (transfer_spec_gen4)
|
453
496
|
def add_ts_tags(transfer_spec:, app_info:)
|
454
497
|
# translate transfer direction to upload/download
|
@@ -488,7 +531,10 @@ module Aspera
|
|
488
531
|
'package_id' => app_info[:package_id],
|
489
532
|
'package_name' => app_info[:package_name],
|
490
533
|
'package_operation' => transfer_type
|
491
|
-
}
|
534
|
+
}
|
535
|
+
}
|
536
|
+
}
|
537
|
+
})
|
492
538
|
end
|
493
539
|
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['files']['node_id'] = app_info[:node_info]['id']
|
494
540
|
transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['app'] = app_info[:app]
|
@@ -496,7 +542,10 @@ module Aspera
|
|
496
542
|
|
497
543
|
ID_AK_ADMIN = 'ASPERA_ACCESS_KEY_ADMIN'
|
498
544
|
# Callback from Plugins::Node
|
499
|
-
|
545
|
+
# add application specific tags to permissions creation
|
546
|
+
# @param create_param [Hash] parameters for creating permissions
|
547
|
+
# @param app_info [Hash] application information
|
548
|
+
def permissions_set_create_params(create_param:, app_info:)
|
500
549
|
# workspace shared folder:
|
501
550
|
# access_id = "#{ID_AK_ADMIN}_WS_#{app_info[:workspace_id]}"
|
502
551
|
default_params = {
|
@@ -514,10 +563,15 @@ module Aspera
|
|
514
563
|
'shared_by_email' => current_user_info['email'],
|
515
564
|
# 'shared_with_name' => access_id,
|
516
565
|
'access_key' => app_info[:node_info]['access_key'],
|
517
|
-
'node' => app_info[:node_info]['name']
|
566
|
+
'node' => app_info[:node_info]['name']
|
567
|
+
}
|
568
|
+
}
|
569
|
+
}
|
570
|
+
}
|
571
|
+
}
|
518
572
|
create_param.deep_merge!(default_params)
|
519
573
|
if create_param.key?('with')
|
520
|
-
contact_info =
|
574
|
+
contact_info = lookup_by_name(
|
521
575
|
'contacts',
|
522
576
|
create_param['with'],
|
523
577
|
{'current_workspace_id' => app_info[:workspace_id], 'context' => 'share_folder'})
|
@@ -531,14 +585,20 @@ module Aspera
|
|
531
585
|
end
|
532
586
|
|
533
587
|
# Callback from Plugins::Node
|
534
|
-
|
588
|
+
# send shared folder event to AoC
|
589
|
+
# @param created_data [Hash] response from permission creation
|
590
|
+
# @param app_info [Hash] hash with app info
|
591
|
+
# @param types [Array] event types
|
592
|
+
def permissions_send_event(created_data:, app_info:, types: PERMISSIONS_CREATED)
|
593
|
+
raise "INTERNAL: (assert) Invalid event types: #{types}" unless types.is_a?(Array) && !types.empty?
|
535
594
|
event_creation = {
|
536
|
-
'types' =>
|
595
|
+
'types' => types,
|
537
596
|
'node_id' => app_info[:node_info]['id'],
|
538
597
|
'workspace_id' => app_info[:workspace_id],
|
539
|
-
'data' => created_data
|
598
|
+
'data' => created_data
|
540
599
|
}
|
541
|
-
# (optional). The name of the folder to be displayed to the destination user.
|
600
|
+
# (optional). The name of the folder to be displayed to the destination user.
|
601
|
+
# Use it if its value is different from the "share_as" field.
|
542
602
|
event_creation['link_name'] = app_info[:opt_link_name] unless app_info[:opt_link_name].nil?
|
543
603
|
create('events', event_creation)
|
544
604
|
end
|