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
|
@@ -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(
|