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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +54 -3
- data/CONTRIBUTING.md +7 -7
- data/README.md +1457 -880
- data/bin/ascli +18 -9
- data/bin/asession +12 -14
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +198 -127
- data/lib/aspera/ascmd.rb +24 -14
- 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 +47 -12
- data/lib/aspera/cli/formatter.rb +260 -171
- data/lib/aspera/cli/hints.rb +80 -0
- data/lib/aspera/cli/main.rb +101 -147
- data/lib/aspera/cli/manager.rb +160 -124
- data/lib/aspera/cli/plugin.rb +70 -59
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +239 -273
- data/lib/aspera/cli/plugins/ats.rb +8 -5
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +516 -375
- 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 +99 -84
- data/lib/aspera/cli/plugins/faspex5.rb +179 -148
- data/lib/aspera/cli/plugins/node.rb +219 -153
- data/lib/aspera/cli/plugins/orchestrator.rb +52 -17
- data/lib/aspera/cli/plugins/preview.rb +46 -32
- data/lib/aspera/cli/plugins/server.rb +57 -17
- data/lib/aspera/cli/plugins/shares.rb +34 -12
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +45 -55
- data/lib/aspera/cli/transfer_progress.rb +74 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +14 -11
- data/lib/aspera/cos_node.rb +3 -2
- data/lib/aspera/environment.rb +17 -6
- 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 +21 -22
- data/lib/aspera/fasp/agent_direct.rb +88 -102
- data/lib/aspera/fasp/agent_httpgw.rb +196 -192
- data/lib/aspera/fasp/agent_node.rb +41 -34
- data/lib/aspera/fasp/agent_trsdk.rb +75 -34
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +43 -184
- data/lib/aspera/fasp/management.rb +244 -0
- data/lib/aspera/fasp/parameters.rb +59 -26
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/transfer_spec.rb +1 -1
- data/lib/aspera/fasp/uri.rb +4 -4
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/json_rpc.rb +49 -0
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +57 -16
- data/lib/aspera/node.rb +97 -14
- data/lib/aspera/oauth.rb +36 -18
- data/lib/aspera/open_application.rb +4 -4
- 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 +24 -13
- data/lib/aspera/preview/utils.rb +19 -26
- data/lib/aspera/rest.rb +103 -72
- 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 +14 -16
- data/lib/aspera/ssh.rb +4 -1
- data/lib/aspera/sync.rb +128 -122
- 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 +33 -15
- 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/bin/ascli
CHANGED
@@ -1,29 +1,38 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
1
|
+
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
5
|
-
|
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
|
-
|
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(
|
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
|
-
|
26
|
-
|
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__)
|
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
|
12
|
-
|
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
|
-
|
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 "#{
|
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?(
|
64
|
-
Aspera::Log.instance.level = transfer_spec[
|
65
|
-
transfer_spec.delete(
|
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?(
|
69
|
-
Aspera::Fasp::Parameters.file_list_folder = transfer_spec[
|
70
|
-
transfer_spec.delete(
|
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
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,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 :
|
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.
|
88
|
+
headers: {'X-Aspera-Entitlement-Authorization' => Rest.basic_token(entitlement_id, customer_id)}
|
103
89
|
})
|
104
90
|
end
|
105
91
|
|
106
|
-
#
|
107
|
-
def
|
108
|
-
|
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
|
-
#
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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 =
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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}/#{
|
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
|
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
|
199
|
-
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}
|
200
193
|
when :jwt
|
201
|
-
raise ArgumentError, 'Missing mandatory option: private_key' if
|
202
|
-
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?
|
203
196
|
aoc_auth_p[:jwt] = {
|
204
|
-
private_key_obj: OpenSSL::PKey::RSA.new(
|
197
|
+
private_key_obj: OpenSSL::PKey::RSA.new(private_key, passphrase),
|
205
198
|
payload: {
|
206
|
-
iss: aoc_auth_p[:client_id],
|
207
|
-
sub:
|
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
|
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
|
230
|
-
|
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
|
235
|
-
|
236
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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.
|
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:
|
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] =
|
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
|
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
|
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(
|