aspera-cli 4.14.0 → 4.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
# https://github.com/fastlane-community/security
|
4
4
|
require 'aspera/cli/info'
|
5
|
+
require 'aspera/log'
|
6
|
+
require 'aspera/assert'
|
5
7
|
|
6
8
|
# enhance the gem to support other key chains
|
7
9
|
module Aspera
|
@@ -36,7 +38,7 @@ module Aspera
|
|
36
38
|
url = options&.delete(:url)
|
37
39
|
if !url.nil?
|
38
40
|
uri = URI.parse(url)
|
39
|
-
|
41
|
+
assert(uri.scheme.eql?('https')){'only https'}
|
40
42
|
options[:protocol] = 'htps' # cspell: disable-line
|
41
43
|
raise 'host required in URL' if uri.host.nil?
|
42
44
|
options[:server] = uri.host
|
@@ -45,7 +47,7 @@ module Aspera
|
|
45
47
|
end
|
46
48
|
cmd = ['security', command]
|
47
49
|
options&.each do |k, v|
|
48
|
-
|
50
|
+
assert(supported.key?(k)){"unknown option: #{k}"}
|
49
51
|
next if v.nil?
|
50
52
|
cmd.push("-#{supported[k]}")
|
51
53
|
cmd.push(v.shellescape) unless v.empty?
|
@@ -70,7 +72,7 @@ module Aspera
|
|
70
72
|
end
|
71
73
|
|
72
74
|
def list(options={})
|
73
|
-
|
75
|
+
assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
|
74
76
|
key_chains(execute('list-key_chains', options, LIST_OPTIONS))
|
75
77
|
end
|
76
78
|
|
@@ -89,11 +91,11 @@ module Aspera
|
|
89
91
|
end
|
90
92
|
|
91
93
|
def password(operation, pass_type, options)
|
92
|
-
|
93
|
-
|
94
|
-
|
94
|
+
assert_values(operation, %i[add find delete]){'operation'}
|
95
|
+
assert_values(pass_type, %i[generic internet]){'pass_type'}
|
96
|
+
assert_type(options, Hash)
|
95
97
|
missing = (operation.eql?(:add) ? %i[account service password] : %i[label]) - options.keys
|
96
|
-
|
98
|
+
assert(missing.empty?){"missing options: #{missing}"}
|
97
99
|
options[:getpass] = '' if operation.eql?(:find)
|
98
100
|
output = self.class.execute("#{operation}-#{pass_type}-password", options, ADD_PASS_OPTIONS, @path)
|
99
101
|
raise output.gsub(/^.*: /, '') if output.start_with?('security: ')
|
@@ -127,18 +129,18 @@ module Aspera
|
|
127
129
|
end
|
128
130
|
|
129
131
|
def set(options)
|
130
|
-
|
132
|
+
assert_type(options, Hash){'options'}
|
131
133
|
unsupported = options.keys - %i[label username password url description]
|
132
|
-
|
134
|
+
assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
133
135
|
@keychain.password(
|
134
136
|
:add, :generic, service: options[:label],
|
135
137
|
account: options[:username] || 'none', password: options[:password], comment: options[:description])
|
136
138
|
end
|
137
139
|
|
138
140
|
def get(options)
|
139
|
-
|
141
|
+
assert_type(options, Hash){'options'}
|
140
142
|
unsupported = options.keys - %i[label]
|
141
|
-
|
143
|
+
assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
142
144
|
info = @keychain.password(:find, :generic, label: options[:label])
|
143
145
|
raise 'not found' if info.nil?
|
144
146
|
result = options.clone
|
@@ -153,9 +155,9 @@ module Aspera
|
|
153
155
|
end
|
154
156
|
|
155
157
|
def delete(options)
|
156
|
-
|
158
|
+
assert_type(options, Hash){'options'}
|
157
159
|
unsupported = options.keys - %i[label]
|
158
|
-
|
160
|
+
assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
159
161
|
raise 'delete not implemented, use macos keychain app'
|
160
162
|
end
|
161
163
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aspera/log'
|
4
|
+
|
5
|
+
module Aspera
|
6
|
+
# used for logging http
|
7
|
+
class LineLogger
|
8
|
+
def initialize(level)
|
9
|
+
@level = level
|
10
|
+
@buffer = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def <<(string)
|
14
|
+
return if string.nil? || string.empty?
|
15
|
+
if !string.end_with?("\n")
|
16
|
+
@buffer.push(string)
|
17
|
+
return
|
18
|
+
end
|
19
|
+
Log.log.send(@level, @buffer.join('') + string.chomp)
|
20
|
+
@buffer.clear
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/aspera/log.rb
CHANGED
@@ -2,17 +2,55 @@
|
|
2
2
|
|
3
3
|
require 'aspera/colors'
|
4
4
|
require 'aspera/secret_hider'
|
5
|
+
require 'aspera/environment'
|
6
|
+
require 'aspera/assert'
|
5
7
|
require 'logger'
|
6
8
|
require 'pp'
|
7
9
|
require 'json'
|
8
10
|
require 'singleton'
|
9
11
|
|
12
|
+
# extend Ruby logger with trace levels
|
13
|
+
class Logger
|
14
|
+
TRACE_MAX = 2
|
15
|
+
# add custom level to logger severity
|
16
|
+
module Severity
|
17
|
+
1.upto(TRACE_MAX).each { |level| const_set("TRACE#{level}", - level)}
|
18
|
+
end
|
19
|
+
# quick access to label
|
20
|
+
SEVERITY_LABEL = Severity.constants.each_with_object({}) { |name, hash| hash[Severity.const_get(name)] = name}
|
21
|
+
def format_severity(severity)
|
22
|
+
SEVERITY_LABEL[severity] || 'ANY'
|
23
|
+
end
|
24
|
+
|
25
|
+
# define methods for a given level
|
26
|
+
def self.make_methods(str_level) # rubocop:disable Style/ClassMethodsDefinitions
|
27
|
+
int_level = ::Logger.const_get(str_level.upcase)
|
28
|
+
str_level = str_level.downcase
|
29
|
+
Kernel.send('lave'.reverse, <<-EOM, nil, __FILE__, __LINE__ + 1)
|
30
|
+
def #{str_level}(message = nil, &block)
|
31
|
+
add(#{int_level}, message, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def #{str_level}?
|
35
|
+
level <= #{int_level}
|
36
|
+
end
|
37
|
+
|
38
|
+
def #{str_level}!
|
39
|
+
self.level = #{int_level}
|
40
|
+
end
|
41
|
+
EOM
|
42
|
+
end
|
43
|
+
# declare methods for all levels
|
44
|
+
Logger::Severity.constants.each { |severity| make_methods(severity) }
|
45
|
+
end
|
46
|
+
|
10
47
|
module Aspera
|
11
48
|
# Singleton object for logging
|
12
49
|
class Log
|
13
50
|
include Singleton
|
14
51
|
# where logs are sent to
|
15
52
|
LOG_TYPES = %i[stderr stdout syslog].freeze
|
53
|
+
@@format = :json # rubocop:disable Style/ClassVars
|
16
54
|
# class methods
|
17
55
|
class << self
|
18
56
|
# levels are :debug,:info,:warn,:error,fatal,:unknown
|
@@ -21,22 +59,20 @@ module Aspera
|
|
21
59
|
# get the logger object of singleton
|
22
60
|
def log; instance.logger; end
|
23
61
|
|
24
|
-
# dump object
|
62
|
+
# dump object suitable for Log.log.debug
|
25
63
|
# @param name string or symbol
|
26
64
|
# @param format either pp or json format
|
27
|
-
def dump(name, object
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
"#{name.to_s.green} (#{format})=\n#{result}"
|
39
|
-
end
|
65
|
+
def dump(name, object)
|
66
|
+
result =
|
67
|
+
case @@format
|
68
|
+
when :json
|
69
|
+
JSON.pretty_generate(object) rescue PP.pp(object, +'')
|
70
|
+
when :ruby
|
71
|
+
PP.pp(object, +'')
|
72
|
+
else
|
73
|
+
raise 'wrong parameter, expect ruby or json'
|
74
|
+
end
|
75
|
+
"#{name.to_s.green} (#{@@format})=\n#{result}"
|
40
76
|
end
|
41
77
|
|
42
78
|
# Capture the output of $stderr and log it at debug level
|
@@ -63,26 +99,32 @@ module Aspera
|
|
63
99
|
Logger::Severity.constants.each do |name|
|
64
100
|
return name.downcase.to_sym if @logger.level.eql?(Logger::Severity.const_get(name))
|
65
101
|
end
|
66
|
-
|
67
|
-
raise "INTERNAL ERROR: unexpected level #{@logger.level}"
|
102
|
+
error_unexpected_value(@logger.level){'log level'}
|
68
103
|
end
|
69
104
|
|
70
105
|
# change underlying logger, but keep log level
|
71
106
|
def logger_type=(new_log_type)
|
72
107
|
current_severity_integer = @logger.level unless @logger.nil?
|
73
|
-
current_severity_integer = ENV
|
108
|
+
current_severity_integer = ENV.fetch('AS_LOG_LEVEL', nil) if current_severity_integer.nil? && ENV.key?('AS_LOG_LEVEL')
|
74
109
|
current_severity_integer = Logger::Severity::WARN if current_severity_integer.nil?
|
75
110
|
case new_log_type
|
76
111
|
when :stderr
|
77
|
-
# typed: Logger
|
78
112
|
@logger = Logger.new($stderr)
|
79
113
|
when :stdout
|
80
114
|
@logger = Logger.new($stdout)
|
81
115
|
when :syslog
|
82
116
|
require 'syslog/logger'
|
117
|
+
# the syslog class automatically creates methods from the severity names
|
118
|
+
# we just need to add the mapping (but syslog lowest is DEBUG)
|
119
|
+
1.upto(Logger::TRACE_MAX).each do |level|
|
120
|
+
Syslog::Logger.const_get(:LEVEL_MAP)[Logger.const_get("TRACE#{level}")] = Syslog::LOG_DEBUG
|
121
|
+
end
|
122
|
+
Logger::Severity.constants.each do |severity|
|
123
|
+
Syslog::Logger.make_methods(severity.downcase)
|
124
|
+
end
|
83
125
|
@logger = Syslog::Logger.new(@program_name, Syslog::LOG_LOCAL2)
|
84
126
|
else
|
85
|
-
raise "unknown log type: #{new_log_type
|
127
|
+
raise "unknown log type: #{new_log_type}, use one of: #{LOG_TYPES.join(', ')}"
|
86
128
|
end
|
87
129
|
@logger.level = current_severity_integer
|
88
130
|
@logger_type = new_log_type
|
data/lib/aspera/nagios.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'aspera/log'
|
4
|
+
require 'aspera/assert'
|
3
5
|
require 'date'
|
4
6
|
|
5
7
|
module Aspera
|
@@ -21,8 +23,11 @@ module Aspera
|
|
21
23
|
class << self
|
22
24
|
# process results of a analysis and display status and exit with code
|
23
25
|
def process(data)
|
24
|
-
|
25
|
-
|
26
|
+
assert_type(data, Array)
|
27
|
+
assert(!data.empty?){'data is empty'}
|
28
|
+
%w[status component message].each do |c|
|
29
|
+
assert(data.first.key?(c)){"result must have #{c}"}
|
30
|
+
end
|
26
31
|
res_errors = data.reject{|s|s['status'].eql?('ok')}
|
27
32
|
# keep only errors in case of problem, other ok are assumed so
|
28
33
|
data = res_errors unless res_errors.empty?
|
data/lib/aspera/node.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'aspera/cli/error'
|
3
4
|
require 'aspera/fasp/transfer_spec'
|
4
5
|
require 'aspera/rest'
|
5
6
|
require 'aspera/oauth'
|
6
7
|
require 'aspera/log'
|
8
|
+
require 'aspera/assert'
|
7
9
|
require 'aspera/environment'
|
8
10
|
require 'zlib'
|
9
11
|
require 'base64'
|
@@ -13,14 +15,22 @@ module Aspera
|
|
13
15
|
class Node < Aspera::Rest
|
14
16
|
# permissions
|
15
17
|
ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
|
16
|
-
# prefix for ruby code for filter
|
18
|
+
# prefix for ruby code for filter (deprecated)
|
17
19
|
MATCH_EXEC_PREFIX = 'exec:'
|
20
|
+
MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
|
18
21
|
HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
|
19
22
|
PATH_SEPARATOR = '/'
|
20
23
|
TS_FIELDS_TO_COPY = %w[remote_host remote_user ssh_port fasp_port wss_enabled wss_port].freeze
|
24
|
+
SCOPE_USER = 'user:all'
|
25
|
+
SCOPE_ADMIN = 'admin:all'
|
26
|
+
SCOPE_PREFIX = 'node.'
|
27
|
+
SCOPE_SEPARATOR = ':'
|
28
|
+
SIGNATURE_DELIMITER = '==SIGNATURE=='
|
29
|
+
BEARER_TOKEN_VALIDITY_DEFAULT = 86400
|
30
|
+
BEARER_TOKEN_SCOPE_DEFAULT = SCOPE_USER
|
21
31
|
|
22
32
|
# register node special token decoder
|
23
|
-
Oauth.register_decoder(lambda{|token|
|
33
|
+
Oauth.register_decoder(lambda{|token|Node.decode_bearer_token(token)})
|
24
34
|
|
25
35
|
# class instance variable, access with accessors on class
|
26
36
|
@use_standard_ports = true
|
@@ -28,16 +38,79 @@ module Aspera
|
|
28
38
|
class << self
|
29
39
|
attr_accessor :use_standard_ports
|
30
40
|
|
31
|
-
#
|
32
|
-
# if no prefix: regex
|
33
|
-
# if prefix: ruby code
|
34
|
-
# if expression is nil, then always match
|
41
|
+
# For access keys: provide expression to match entry in folder
|
35
42
|
def file_matcher(match_expression)
|
36
|
-
match_expression
|
37
|
-
|
38
|
-
|
43
|
+
case match_expression
|
44
|
+
when Proc then return match_expression
|
45
|
+
when Regexp then return ->(f){f['name'].match?(match_expression)}
|
46
|
+
when String
|
47
|
+
if match_expression.start_with?(MATCH_EXEC_PREFIX)
|
48
|
+
code = "->(f){#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}"
|
49
|
+
Log.log.warn{"Use of prefix #{MATCH_EXEC_PREFIX} is deprecated (4.15), instead use: @ruby:'#{code}'"}
|
50
|
+
return Environment.secure_eval(code, __FILE__, __LINE__)
|
51
|
+
end
|
52
|
+
return lambda{|f|File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
53
|
+
when NilClass then return ->(_){true}
|
54
|
+
else error_unexpected_value(match_expression.class.name, exception_class: Cli::BadArgument)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def file_matcher_from_argument(options)
|
59
|
+
return file_matcher(options.get_next_argument('filter', type: MATCH_TYPES, mandatory: false))
|
60
|
+
end
|
61
|
+
|
62
|
+
# node API scopes
|
63
|
+
def token_scope(access_key, scope)
|
64
|
+
return [SCOPE_PREFIX, access_key, SCOPE_SEPARATOR, scope].join('')
|
65
|
+
end
|
66
|
+
|
67
|
+
def decode_scope(scope)
|
68
|
+
items = scope.split(SCOPE_SEPARATOR, 2)
|
69
|
+
assert(items.length.eql?(2)){"invalid scope: #{scope}"}
|
70
|
+
assert(items[0].start_with?(SCOPE_PREFIX)){"invalid scope: #{scope}"}
|
71
|
+
return {access_key: items[0][SCOPE_PREFIX.length..-1], scope: items[1]}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Create an Aspera Node bearer token
|
75
|
+
# @param payload [String] JSON payload to be included in the token
|
76
|
+
# @param private_key [OpenSSL::PKey::RSA] Private key to sign the token
|
77
|
+
def bearer_token(access_key:, payload:, private_key:)
|
78
|
+
assert_type(payload, Hash)
|
79
|
+
assert(payload.key?('user_id'))
|
80
|
+
assert_type(payload['user_id'], String)
|
81
|
+
assert(!payload['user_id'].empty?)
|
82
|
+
assert_type(private_key, OpenSSL::PKey::RSA)
|
83
|
+
# manage convenience parameters
|
84
|
+
expiration_sec = payload['_validity'] || BEARER_TOKEN_VALIDITY_DEFAULT
|
85
|
+
payload.delete('_validity')
|
86
|
+
scope = payload['_scope'] || BEARER_TOKEN_SCOPE_DEFAULT
|
87
|
+
payload.delete('_scope')
|
88
|
+
payload['scope'] ||= token_scope(access_key, scope)
|
89
|
+
payload['auth_type'] ||= 'access_key'
|
90
|
+
payload['expires_at'] ||= (Time.now + expiration_sec).utc.strftime('%FT%TZ')
|
91
|
+
payload_json = JSON.generate(payload)
|
92
|
+
return Base64.strict_encode64(Zlib::Deflate.deflate([
|
93
|
+
payload_json,
|
94
|
+
SIGNATURE_DELIMITER,
|
95
|
+
Base64.strict_encode64(private_key.sign(OpenSSL::Digest.new('sha512'), payload_json)).scan(/.{1,60}/).join("\n"),
|
96
|
+
''
|
97
|
+
].join("\n")))
|
98
|
+
end
|
99
|
+
|
100
|
+
def decode_bearer_token(token)
|
101
|
+
return JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition(SIGNATURE_DELIMITER).first)
|
102
|
+
end
|
103
|
+
|
104
|
+
def bearer_headers(bearer_auth, access_key: nil)
|
105
|
+
# if username is not provided, use the access key from the token
|
106
|
+
if access_key.nil?
|
107
|
+
access_key = Aspera::Node.decode_scope(Aspera::Node.decode_bearer_token(Oauth.bearer_extract(bearer_auth))['scope'])[:access_key]
|
108
|
+
assert(!access_key.nil?)
|
39
109
|
end
|
40
|
-
return
|
110
|
+
return {
|
111
|
+
Aspera::Node::HEADER_X_ASPERA_ACCESS_KEY => access_key,
|
112
|
+
'Authorization' => bearer_auth
|
113
|
+
}
|
41
114
|
end
|
42
115
|
end
|
43
116
|
|
@@ -52,16 +125,17 @@ module Aspera
|
|
52
125
|
# @param params [Hash] Rest parameters
|
53
126
|
# @param app_info [Hash,NilClass] special processing for AoC
|
54
127
|
def initialize(params:, app_info: nil, add_tspec: nil)
|
128
|
+
# init Rest
|
55
129
|
super(params)
|
56
130
|
@app_info = app_info
|
57
131
|
# this is added to transfer spec, for instance to add tags (COS)
|
58
132
|
@add_tspec = add_tspec
|
59
133
|
if !@app_info.nil?
|
60
134
|
REQUIRED_APP_INFO_FIELDS.each do |field|
|
61
|
-
|
135
|
+
assert(@app_info.key?(field)){"app_info lacks field #{field}"}
|
62
136
|
end
|
63
137
|
REQUIRED_APP_API_METHODS.each do |method|
|
64
|
-
|
138
|
+
assert(@app_info[:api].respond_to?(method)){"#{@app_info[:api].class} lacks method #{method}"}
|
65
139
|
end
|
66
140
|
end
|
67
141
|
end
|
@@ -90,13 +164,13 @@ module Aspera
|
|
90
164
|
# @param state [Object] state object sent to processing method
|
91
165
|
# @param top_file_id [String] file id to start at (default = access key root file id)
|
92
166
|
# @param top_file_path [String] path of top folder (default = /)
|
93
|
-
# @param block [Proc] processing method,
|
167
|
+
# @param block [Proc] processing method, arguments: entry, path, state
|
94
168
|
def process_folder_tree(state:, top_file_id:, top_file_path: '/', &block)
|
95
|
-
|
96
|
-
|
169
|
+
assert(!top_file_path.nil?){'top_file_path not set'}
|
170
|
+
assert(block){'Missing block'}
|
97
171
|
# start at top folder
|
98
172
|
folders_to_explore = [{id: top_file_id, path: top_file_path}]
|
99
|
-
Log.dump(:folders_to_explore, folders_to_explore)
|
173
|
+
Log.log.debug{Log.dump(:folders_to_explore, folders_to_explore)}
|
100
174
|
until folders_to_explore.empty?
|
101
175
|
current_item = folders_to_explore.shift
|
102
176
|
Log.log.debug{"searching #{current_item[:path]}".bg_green}
|
@@ -108,7 +182,7 @@ module Aspera
|
|
108
182
|
Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
|
109
183
|
[]
|
110
184
|
end
|
111
|
-
Log.dump(:folder_contents, folder_contents)
|
185
|
+
Log.log.debug{Log.dump(:folder_contents, folder_contents)}
|
112
186
|
folder_contents.each do |entry|
|
113
187
|
relative_path = File.join(current_item[:path], entry['name'])
|
114
188
|
Log.log.debug{"process_folder_tree checking #{relative_path}"}
|
@@ -134,7 +208,7 @@ module Aspera
|
|
134
208
|
# @param path [String] file path
|
135
209
|
# @return [Hash] {.api,.file_id}
|
136
210
|
def resolve_api_fid(top_file_id, path)
|
137
|
-
|
211
|
+
assert_type(top_file_id, String)
|
138
212
|
process_last_link = path.end_with?(PATH_SEPARATOR)
|
139
213
|
path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
|
140
214
|
return {api: self, file_id: top_file_id} if path_elements.empty?
|
@@ -203,14 +277,14 @@ module Aspera
|
|
203
277
|
case params[:auth][:type]
|
204
278
|
when :basic
|
205
279
|
ak_name = params[:auth][:username]
|
206
|
-
|
207
|
-
ak_token = Rest.
|
280
|
+
assert(params[:auth][:password]){'no secret in node object'}
|
281
|
+
ak_token = Rest.basic_token(params[:auth][:username], params[:auth][:password])
|
208
282
|
when :oauth2
|
209
283
|
ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
|
210
284
|
# TODO: token_generation_lambda = lambda{|do_refresh|oauth_token(force_refresh: do_refresh)}
|
211
285
|
# get bearer token, possibly use cache
|
212
286
|
ak_token = oauth_token(force_refresh: false)
|
213
|
-
else
|
287
|
+
else error_unexpected_value(params[:auth][:type])
|
214
288
|
end
|
215
289
|
transfer_spec = {
|
216
290
|
'direction' => direction,
|
@@ -235,9 +309,19 @@ module Aspera
|
|
235
309
|
transfer_spec.merge!(Fasp::TransferSpec::AK_TSPEC_BASE)
|
236
310
|
# by default: same address as node API
|
237
311
|
transfer_spec['remote_host'] = URI.parse(params[:base_url]).host
|
312
|
+
# AoC allows specification of other url
|
238
313
|
if !@app_info.nil? && !@app_info[:node_info]['transfer_url'].nil? && !@app_info[:node_info]['transfer_url'].empty?
|
239
314
|
transfer_spec['remote_host'] = @app_info[:node_info]['transfer_url']
|
240
315
|
end
|
316
|
+
info = read('info')[:data]
|
317
|
+
# get the transfer user from info on access key
|
318
|
+
transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
|
319
|
+
# get settings from name.value array to hash key.value
|
320
|
+
settings = info['settings']&.each_with_object({}){|i, h|h[i['name']] = i['value']}
|
321
|
+
# check WSS ports
|
322
|
+
%w[wss_enabled wss_port].each do |i|
|
323
|
+
transfer_spec[i] = settings[i] if settings.key?(i)
|
324
|
+
end if settings.is_a?(Hash)
|
241
325
|
else
|
242
326
|
# retrieve values from API (and keep a copy/cache)
|
243
327
|
@std_t_spec_cache ||= create(
|