aspera-cli 4.16.0 → 4.17.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 +50 -19
- data/CONTRIBUTING.md +3 -1
- data/README.md +965 -793
- data/bin/asession +29 -21
- data/lib/aspera/{fasp/agent_alpha.rb → agent/alpha.rb} +26 -25
- data/lib/aspera/{fasp/agent_base.rb → agent/base.rb} +15 -12
- data/lib/aspera/{fasp/agent_connect.rb → agent/connect.rb} +13 -11
- data/lib/aspera/{fasp/agent_direct.rb → agent/direct.rb} +49 -53
- data/lib/aspera/{fasp/agent_httpgw.rb → agent/httpgw.rb} +20 -19
- data/lib/aspera/{fasp/agent_node.rb → agent/node.rb} +20 -33
- data/lib/aspera/{fasp/agent_trsdk.rb → agent/trsdk.rb} +11 -11
- data/lib/aspera/api/aoc.rb +586 -0
- data/lib/aspera/api/ats.rb +46 -0
- data/lib/aspera/api/cos_node.rb +95 -0
- data/lib/aspera/api/node.rb +344 -0
- data/lib/aspera/ascmd.rb +46 -10
- data/lib/aspera/{fasp → ascp}/installation.rb +5 -5
- data/lib/aspera/{fasp → ascp}/management.rb +3 -8
- data/lib/aspera/{fasp → ascp}/products.rb +1 -1
- data/lib/aspera/assert.rb +30 -30
- data/lib/aspera/cli/basic_auth_plugin.rb +11 -10
- data/lib/aspera/cli/extended_value.rb +1 -1
- data/lib/aspera/cli/formatter.rb +13 -13
- data/lib/aspera/cli/hints.rb +5 -5
- data/lib/aspera/cli/main.rb +35 -28
- data/lib/aspera/cli/manager.rb +25 -24
- data/lib/aspera/cli/plugin.rb +22 -15
- data/lib/aspera/cli/plugin_factory.rb +61 -0
- data/lib/aspera/cli/plugins/alee.rb +7 -7
- data/lib/aspera/cli/plugins/aoc.rb +83 -77
- data/lib/aspera/cli/plugins/ats.rb +32 -33
- data/lib/aspera/cli/plugins/bss.rb +3 -4
- data/lib/aspera/cli/plugins/config.rb +169 -186
- data/lib/aspera/cli/plugins/console.rb +8 -6
- data/lib/aspera/cli/plugins/cos.rb +19 -18
- data/lib/aspera/cli/plugins/faspex.rb +61 -54
- data/lib/aspera/cli/plugins/faspex5.rb +150 -103
- data/lib/aspera/cli/plugins/node.rb +68 -73
- data/lib/aspera/cli/plugins/orchestrator.rb +34 -44
- data/lib/aspera/cli/plugins/preview.rb +31 -31
- data/lib/aspera/cli/plugins/server.rb +31 -33
- data/lib/aspera/cli/plugins/shares.rb +13 -11
- data/lib/aspera/cli/sync_actions.rb +8 -8
- data/lib/aspera/cli/transfer_agent.rb +32 -19
- data/lib/aspera/cli/transfer_progress.rb +1 -1
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +5 -0
- data/lib/aspera/command_line_builder.rb +14 -14
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +2 -3
- data/lib/aspera/faspex_gw.rb +5 -6
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/id_generator.rb +2 -2
- data/lib/aspera/json_rpc.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +6 -6
- data/lib/aspera/keychain/macos_security.rb +27 -22
- data/lib/aspera/log.rb +2 -2
- data/lib/aspera/nagios.rb +3 -3
- data/lib/aspera/node_simulator.rb +5 -6
- data/lib/aspera/oauth/base.rb +143 -0
- data/lib/aspera/oauth/factory.rb +124 -0
- data/lib/aspera/oauth/generic.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +51 -0
- data/lib/aspera/oauth/url_json.rb +31 -0
- data/lib/aspera/oauth/web.rb +50 -0
- data/lib/aspera/oauth.rb +5 -331
- data/lib/aspera/open_application.rb +7 -7
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/generator.rb +5 -5
- data/lib/aspera/preview/terminal.rb +3 -2
- data/lib/aspera/preview/utils.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +4 -4
- data/lib/aspera/rest.rb +175 -144
- data/lib/aspera/rest_errors_aspera.rb +3 -3
- data/lib/aspera/resumer.rb +77 -0
- data/lib/aspera/ssh.rb +6 -1
- data/lib/aspera/{fasp → transfer}/error.rb +3 -3
- data/lib/aspera/{fasp → transfer}/error_info.rb +1 -1
- data/lib/aspera/{fasp → transfer}/faux_file.rb +1 -1
- data/lib/aspera/{fasp → transfer}/parameters.rb +58 -89
- data/lib/aspera/{fasp/transfer_spec.rb → transfer/spec.rb} +18 -16
- data/lib/aspera/{fasp/parameters.yaml → transfer/spec.yaml} +4 -99
- data/lib/aspera/{fasp → transfer}/sync.rb +32 -32
- data/lib/aspera/{fasp → transfer}/uri.rb +9 -8
- data/lib/aspera/web_server_simple.rb +11 -3
- data.tar.gz.sig +0 -0
- metadata +36 -63
- metadata.gz.sig +0 -0
- data/lib/aspera/aoc.rb +0 -601
- data/lib/aspera/ats_api.rb +0 -47
- data/lib/aspera/cos_node.rb +0 -94
- data/lib/aspera/fasp/resume_policy.rb +0 -79
- data/lib/aspera/node.rb +0 -339
|
@@ -29,20 +29,20 @@ module Aspera
|
|
|
29
29
|
# Called by provider of definition before constructor of this class so that params_definition has all mandatory fields
|
|
30
30
|
def normalize_description(full_description)
|
|
31
31
|
full_description.each do |name, options|
|
|
32
|
-
assert_type(options, Hash){name}
|
|
32
|
+
Aspera.assert_type(options, Hash){name}
|
|
33
33
|
unsupported_keys = options.keys - OPTIONS_KEYS
|
|
34
|
-
assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
|
|
35
|
-
assert(options.key?(:cli)){"Missing key: cli for #{name}"}
|
|
36
|
-
assert_type(options[:cli], Hash){'Key: cli'}
|
|
37
|
-
assert(options[:cli].key?(:type)){'Missing key: cli.type'}
|
|
38
|
-
assert_values(options[:cli][:type], CLI_OPTION_TYPES){"Unsupported processing type for #{name}"}
|
|
34
|
+
Aspera.assert(unsupported_keys.empty?){"Unsupported definition keys: #{unsupported_keys}"}
|
|
35
|
+
Aspera.assert(options.key?(:cli)){"Missing key: cli for #{name}"}
|
|
36
|
+
Aspera.assert_type(options[:cli], Hash){'Key: cli'}
|
|
37
|
+
Aspera.assert(options[:cli].key?(:type)){'Missing key: cli.type'}
|
|
38
|
+
Aspera.assert_values(options[:cli][:type], CLI_OPTION_TYPES){"Unsupported processing type for #{name}"}
|
|
39
39
|
# by default : optional
|
|
40
40
|
options[:mandatory] ||= false
|
|
41
41
|
options[:desc] ||= ''
|
|
42
42
|
options[:desc] = "DEPRECATED: #{options[:deprecation]}\n#{options[:desc]}" if options.key?(:deprecation)
|
|
43
43
|
cli = options[:cli]
|
|
44
44
|
unsupported_cli_keys = cli.keys - CLI_KEYS
|
|
45
|
-
assert(unsupported_cli_keys.empty?){"Unsupported cli keys: #{unsupported_cli_keys}"}
|
|
45
|
+
Aspera.assert(unsupported_cli_keys.empty?){"Unsupported cli keys: #{unsupported_cli_keys}"}
|
|
46
46
|
# by default : string, unless it's without arg
|
|
47
47
|
options[:accepted_types] ||= options[:cli][:type].eql?(:opt_without_arg) ? :bool : :string
|
|
48
48
|
# single type is placed in array
|
|
@@ -111,7 +111,7 @@ module Aspera
|
|
|
111
111
|
end
|
|
112
112
|
processing_type = read ? :get_value : options[:cli][:type]
|
|
113
113
|
# check mandatory parameter (nil is valid value)
|
|
114
|
-
raise
|
|
114
|
+
raise Transfer::Error, "Missing mandatory parameter: #{name}" if options[:mandatory] && !@param_hash.key?(name)
|
|
115
115
|
parameter_value = @param_hash[name]
|
|
116
116
|
# no default setting
|
|
117
117
|
# parameter_value=options[:default] if parameter_value.nil? and options.has_key?(:default)
|
|
@@ -123,11 +123,11 @@ module Aspera
|
|
|
123
123
|
when :hash then Hash
|
|
124
124
|
when :int then Integer
|
|
125
125
|
when :bool then [TrueClass, FalseClass]
|
|
126
|
-
else error_unexpected_value(type_symbol)
|
|
126
|
+
else Aspera.error_unexpected_value(type_symbol)
|
|
127
127
|
end
|
|
128
128
|
end.flatten
|
|
129
129
|
# check that value is of expected type
|
|
130
|
-
raise
|
|
130
|
+
raise Transfer::Error, "#{name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, " \
|
|
131
131
|
unless parameter_value.nil? || expected_classes.include?(parameter_value.class)
|
|
132
132
|
# special processing will be requested with type get_value
|
|
133
133
|
@used_param_names.push(name) unless processing_type.eql?(:special)
|
|
@@ -149,10 +149,10 @@ module Aspera
|
|
|
149
149
|
# :convert has name of class and encoding method
|
|
150
150
|
conversion_class, conversion_method = options[:cli][:convert].split('.')
|
|
151
151
|
converted_value = Kernel.const_get(conversion_class).send(conversion_method, parameter_value)
|
|
152
|
-
raise
|
|
152
|
+
raise Transfer::Error, "unsupported #{name}: #{parameter_value}" if converted_value.nil?
|
|
153
153
|
parameter_value = converted_value
|
|
154
154
|
when NilClass
|
|
155
|
-
else error_unexpected_value(options[:cli][:convert].class)
|
|
155
|
+
else Aspera.error_unexpected_value(options[:cli][:convert].class)
|
|
156
156
|
end
|
|
157
157
|
|
|
158
158
|
case processing_type
|
|
@@ -161,14 +161,14 @@ module Aspera
|
|
|
161
161
|
when :ignore, :special # ignore this parameter or process later
|
|
162
162
|
return
|
|
163
163
|
when :envvar # set in env var
|
|
164
|
-
assert(options[:cli].key?(:variable)){'missing key: cli.variable'}
|
|
164
|
+
Aspera.assert(options[:cli].key?(:variable)){'missing key: cli.variable'}
|
|
165
165
|
@result[:env][options[:cli][:variable]] = parameter_value
|
|
166
166
|
when :opt_without_arg # if present and true : just add option without value
|
|
167
167
|
add_param = false
|
|
168
168
|
case parameter_value
|
|
169
169
|
when false then nil # nothing to put on command line, no creation by default
|
|
170
170
|
when true then add_param = true
|
|
171
|
-
else error_unexpected_value(parameter_value){name}
|
|
171
|
+
else Aspera.error_unexpected_value(parameter_value){name}
|
|
172
172
|
end
|
|
173
173
|
add_param = !add_param if options[:add_on_false]
|
|
174
174
|
add_command_line_options([options[:cli][:switch]]) if add_param
|
data/lib/aspera/coverage.rb
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
if ENV.key?('ENABLE_COVERAGE')
|
|
5
5
|
require 'simplecov'
|
|
6
6
|
require 'securerandom'
|
|
7
|
-
# compute
|
|
8
|
-
# use dirname instead of gsub, in case folder separator is not /
|
|
7
|
+
# compute development top folder based on this source location
|
|
9
8
|
development_root = 3.times.inject(File.realpath(__FILE__)) { |p, _| File.dirname(p) }
|
|
10
9
|
SimpleCov.root(development_root)
|
|
11
10
|
SimpleCov.enable_for_subprocesses if SimpleCov.respond_to?(:enable_for_subprocesses)
|
|
@@ -30,7 +30,7 @@ module Aspera
|
|
|
30
30
|
return format('%08x-%04x-%04x-%04x-%04x%08x', *raw_data.unpack('NnnnnN'))
|
|
31
31
|
when :'aspera.global-cli-client', :'aspera.drive'
|
|
32
32
|
return Base64.urlsafe_encode64(raw_data)
|
|
33
|
-
else error_unexpected_value(name)
|
|
33
|
+
else Aspera.error_unexpected_value(name)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
data/lib/aspera/environment.rb
CHANGED
|
@@ -56,8 +56,7 @@ module Aspera
|
|
|
56
56
|
when /s390/
|
|
57
57
|
return CPU_S390
|
|
58
58
|
when /arm/, /aarch64/
|
|
59
|
-
|
|
60
|
-
return CPU_X86_64 if os.eql?(OS_X)
|
|
59
|
+
return CPU_ARM64
|
|
61
60
|
end
|
|
62
61
|
raise "Unknown CPU: #{RbConfig::CONFIG['host_cpu']}"
|
|
63
62
|
end
|
|
@@ -90,7 +89,7 @@ module Aspera
|
|
|
90
89
|
|
|
91
90
|
# value is provided in block
|
|
92
91
|
def write_file_restricted(path, force: false, mode: nil)
|
|
93
|
-
assert(block_given?, exception_class: Aspera::InternalError)
|
|
92
|
+
Aspera.assert(block_given?, exception_class: Aspera::InternalError)
|
|
94
93
|
if force || !File.exist?(path)
|
|
95
94
|
# Windows may give error
|
|
96
95
|
File.unlink(path) rescue nil
|
data/lib/aspera/faspex_gw.rb
CHANGED
|
@@ -8,12 +8,11 @@ require 'json'
|
|
|
8
8
|
module Aspera
|
|
9
9
|
# Simulate the Faspex 4 /send API and creates a package on Aspera on Cloud or Faspex 5
|
|
10
10
|
class Faspex4GWServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
11
|
-
# @param app_api
|
|
11
|
+
# @param app_api
|
|
12
12
|
# @param app_context [String]
|
|
13
13
|
def initialize(server, app_api, app_context)
|
|
14
|
-
assert_values(app_api.class.name, ['Aspera::AoC', 'Aspera::Rest'])
|
|
14
|
+
Aspera.assert_values(app_api.class.name, ['Aspera::Api::AoC', 'Aspera::Rest'])
|
|
15
15
|
super(server)
|
|
16
|
-
# typed: Aspera::AoC
|
|
17
16
|
@app_api = app_api
|
|
18
17
|
@app_context = app_context
|
|
19
18
|
end
|
|
@@ -51,7 +50,7 @@ module Aspera
|
|
|
51
50
|
operation: 'POST',
|
|
52
51
|
subpath: "packages/#{package['id']}/transfer_spec/upload",
|
|
53
52
|
headers: {'Accept' => 'application/json'},
|
|
54
|
-
url_params: {transfer_type:
|
|
53
|
+
url_params: {transfer_type: Cli::Plugins::Faspex5::TRANSFER_CONNECT},
|
|
55
54
|
json_params: {paths: [{'destination'=>'/'}]}
|
|
56
55
|
)[:data]
|
|
57
56
|
transfer_spec.delete('authentication')
|
|
@@ -72,11 +71,11 @@ module Aspera
|
|
|
72
71
|
# compare string, as class is not yet known here
|
|
73
72
|
faspex_package_create_result =
|
|
74
73
|
case @app_api.class.name
|
|
75
|
-
when 'Aspera::AoC'
|
|
74
|
+
when 'Aspera::Api::AoC'
|
|
76
75
|
faspex4_send_to_aoc(faspex_pkg_parameters)
|
|
77
76
|
when 'Aspera::Rest'
|
|
78
77
|
faspex4_send_to_faspex5(faspex_pkg_parameters)
|
|
79
|
-
else error_unexpected_value(@app_api.class.name)
|
|
78
|
+
else Aspera.error_unexpected_value(@app_api.class.name)
|
|
80
79
|
end
|
|
81
80
|
Log.log.info{"faspex_package_create_result=#{faspex_package_create_result}"}
|
|
82
81
|
response.status = 200
|
|
@@ -12,7 +12,7 @@ module Aspera
|
|
|
12
12
|
class Faspex4PostProcServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
13
13
|
ALLOWED_PARAMETERS = %i[root script_folder fail_on_error timeout_seconds].freeze
|
|
14
14
|
def initialize(server, parameters)
|
|
15
|
-
assert_type(parameters, Hash)
|
|
15
|
+
Aspera.assert_type(parameters, Hash)
|
|
16
16
|
@parameters = parameters.symbolize_keys
|
|
17
17
|
Log.log.debug{Log.dump(:post_proc_parameters, @parameters)}
|
|
18
18
|
raise "unexpected key in parameters config: only: #{ALLOWED_PARAMETERS.join(', ')}" if @parameters.keys.any?{|k|!ALLOWED_PARAMETERS.include?(k)}
|
data/lib/aspera/id_generator.rb
CHANGED
|
@@ -17,10 +17,10 @@ module Aspera
|
|
|
17
17
|
i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
|
|
18
18
|
end.join(ID_SEPARATOR)
|
|
19
19
|
end
|
|
20
|
-
assert_type(object_id, String)
|
|
20
|
+
Aspera.assert_type(object_id, String)
|
|
21
21
|
return object_id
|
|
22
22
|
.gsub(WINDOWS_PROTECTED_CHAR, PROTECTED_CHAR_REPLACE) # remove windows forbidden chars
|
|
23
|
-
.gsub('.', PROTECTED_CHAR_REPLACE)
|
|
23
|
+
.gsub('.', PROTECTED_CHAR_REPLACE) # keep dot for extension only (nicer)
|
|
24
24
|
.downcase
|
|
25
25
|
end
|
|
26
26
|
end
|
data/lib/aspera/json_rpc.rb
CHANGED
|
@@ -35,11 +35,11 @@ module Aspera
|
|
|
35
35
|
params: args,
|
|
36
36
|
id: @request_id += 1
|
|
37
37
|
})[:data]
|
|
38
|
-
assert_type(data, Hash){'response'}
|
|
39
|
-
assert(data['jsonrpc'] == JSON_RPC_VERSION){'bad version in response'}
|
|
40
|
-
assert(data.key?('id')){'missing id in response'}
|
|
41
|
-
assert(!(data.key?('error') && data.key?('result'))){'both error and response'}
|
|
42
|
-
assert(
|
|
38
|
+
Aspera.assert_type(data, Hash){'response'}
|
|
39
|
+
Aspera.assert(data['jsonrpc'] == JSON_RPC_VERSION){'bad version in response'}
|
|
40
|
+
Aspera.assert(data.key?('id')){'missing id in response'}
|
|
41
|
+
Aspera.assert(!(data.key?('error') && data.key?('result'))){'both error and response'}
|
|
42
|
+
Aspera.assert(
|
|
43
43
|
!data.key?('error') ||
|
|
44
44
|
data['error'].is_a?(Hash) &&
|
|
45
45
|
data['error']['code'].is_a?(Integer) &&
|
|
@@ -17,7 +17,7 @@ module Aspera
|
|
|
17
17
|
CONTENT_KEYS = %i[label username password url description].freeze
|
|
18
18
|
FILE_KEYS = %w[version type cipher data].sort.freeze
|
|
19
19
|
def initialize(path, current_password)
|
|
20
|
-
assert_type(path, String){'path to vault file'}
|
|
20
|
+
Aspera.assert_type(path, String){'path to vault file'}
|
|
21
21
|
@path = path
|
|
22
22
|
@all_secrets = {}
|
|
23
23
|
vault_encrypted_data = nil
|
|
@@ -25,7 +25,7 @@ module Aspera
|
|
|
25
25
|
vault_file = File.read(@path)
|
|
26
26
|
if vault_file.start_with?('---')
|
|
27
27
|
vault_info = YAML.parse(vault_file).to_ruby
|
|
28
|
-
assert(vault_info.keys.sort == FILE_KEYS){'Invalid vault file'}
|
|
28
|
+
Aspera.assert(vault_info.keys.sort == FILE_KEYS){'Invalid vault file'}
|
|
29
29
|
@cipher_name = vault_info['cipher']
|
|
30
30
|
vault_encrypted_data = vault_info['data']
|
|
31
31
|
else
|
|
@@ -65,11 +65,11 @@ module Aspera
|
|
|
65
65
|
# set a secret
|
|
66
66
|
# @param options [Hash] with keys :label, :username, :password, :url, :description
|
|
67
67
|
def set(options)
|
|
68
|
-
assert_type(options, Hash){'options'}
|
|
68
|
+
Aspera.assert_type(options, Hash){'options'}
|
|
69
69
|
unsupported = options.keys - CONTENT_KEYS
|
|
70
|
-
assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
|
70
|
+
Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
|
71
71
|
options.each_pair do |k, v|
|
|
72
|
-
assert_type(v, String){k.to_s}
|
|
72
|
+
Aspera.assert_type(v, String){k.to_s}
|
|
73
73
|
end
|
|
74
74
|
label = options.delete(:label)
|
|
75
75
|
raise "secret #{label} already exist, delete first" if @all_secrets.key?(label)
|
|
@@ -94,7 +94,7 @@ module Aspera
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def get(label:, exception: true)
|
|
97
|
-
assert(@all_secrets.key?(label)){"Label not found: #{label}"} if exception
|
|
97
|
+
Aspera.assert(@all_secrets.key?(label)){"Label not found: #{label}"} if exception
|
|
98
98
|
result = @all_secrets[label].clone
|
|
99
99
|
result[:label] = label if result.is_a?(Hash)
|
|
100
100
|
return result
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
require 'aspera/cli/info'
|
|
5
5
|
require 'aspera/log'
|
|
6
6
|
require 'aspera/assert'
|
|
7
|
+
require 'open3'
|
|
7
8
|
|
|
8
9
|
# enhance the gem to support other key chains
|
|
9
10
|
module Aspera
|
|
@@ -11,6 +12,8 @@ module Aspera
|
|
|
11
12
|
module MacosSecurity
|
|
12
13
|
# keychain based on macOS keychain, using `security` command line
|
|
13
14
|
class Keychain
|
|
15
|
+
# https://www.unix.com/man-page/osx/1/security/
|
|
16
|
+
SECURITY_UTILITY = 'security'
|
|
14
17
|
DOMAINS = %i[user system common dynamic].freeze
|
|
15
18
|
LIST_OPTIONS = {
|
|
16
19
|
domain: :c
|
|
@@ -38,25 +41,27 @@ module Aspera
|
|
|
38
41
|
url = options&.delete(:url)
|
|
39
42
|
if !url.nil?
|
|
40
43
|
uri = URI.parse(url)
|
|
41
|
-
assert(uri.scheme.eql?('https')){'only https'}
|
|
44
|
+
Aspera.assert(uri.scheme.eql?('https')){'only https'}
|
|
42
45
|
options[:protocol] = 'htps' # cspell: disable-line
|
|
43
46
|
raise 'host required in URL' if uri.host.nil?
|
|
44
47
|
options[:server] = uri.host
|
|
45
48
|
options[:path] = uri.path unless ['', '/'].include?(uri.path)
|
|
46
49
|
options[:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
|
|
47
50
|
end
|
|
48
|
-
|
|
51
|
+
command_line = [SECURITY_UTILITY, command]
|
|
49
52
|
options&.each do |k, v|
|
|
50
|
-
assert(supported.key?(k)){"unknown option: #{k}"}
|
|
53
|
+
Aspera.assert(supported.key?(k)){"unknown option: #{k}"}
|
|
51
54
|
next if v.nil?
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
command_line.push("-#{supported[k]}")
|
|
56
|
+
command_line.push(v.shellescape) unless v.empty?
|
|
54
57
|
end
|
|
55
|
-
|
|
56
|
-
Log.log.debug{"executing>>#{
|
|
57
|
-
|
|
58
|
-
Log.log.debug{"
|
|
59
|
-
|
|
58
|
+
command_line.push(last_opt) unless last_opt.nil?
|
|
59
|
+
Log.log.debug{"executing>>#{command_line.join(' ')}"}
|
|
60
|
+
stdout, stderr, status = Open3.capture3(*command_line)
|
|
61
|
+
Log.log.debug{"status=#{status}, stderr=#{stderr}"}
|
|
62
|
+
Log.log.trace1{"stdout=#{stdout}"}
|
|
63
|
+
raise "#{SECURITY_UTILITY} failed: #{status.exitstatus} : #{stderr}" unless status.success?
|
|
64
|
+
return stdout
|
|
60
65
|
end
|
|
61
66
|
|
|
62
67
|
def key_chains(output)
|
|
@@ -72,7 +77,7 @@ module Aspera
|
|
|
72
77
|
end
|
|
73
78
|
|
|
74
79
|
def list(options={})
|
|
75
|
-
assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
|
|
80
|
+
Aspera.assert_values(options[:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless options[:domain].nil?
|
|
76
81
|
key_chains(execute('list-key_chains', options, LIST_OPTIONS))
|
|
77
82
|
end
|
|
78
83
|
|
|
@@ -91,15 +96,15 @@ module Aspera
|
|
|
91
96
|
end
|
|
92
97
|
|
|
93
98
|
def password(operation, pass_type, options)
|
|
94
|
-
assert_values(operation, %i[add find delete]){'operation'}
|
|
95
|
-
assert_values(pass_type, %i[generic internet]){'pass_type'}
|
|
96
|
-
assert_type(options, Hash)
|
|
99
|
+
Aspera.assert_values(operation, %i[add find delete]){'operation'}
|
|
100
|
+
Aspera.assert_values(pass_type, %i[generic internet]){'pass_type'}
|
|
101
|
+
Aspera.assert_type(options, Hash)
|
|
97
102
|
missing = (operation.eql?(:add) ? %i[account service password] : %i[label]) - options.keys
|
|
98
|
-
assert(missing.empty?){"missing options: #{missing}"}
|
|
103
|
+
Aspera.assert(missing.empty?){"missing options: #{missing}"}
|
|
99
104
|
options[:getpass] = '' if operation.eql?(:find)
|
|
100
105
|
output = self.class.execute("#{operation}-#{pass_type}-password", options, ADD_PASS_OPTIONS, @path)
|
|
101
106
|
raise output.gsub(/^.*: /, '') if output.start_with?('security: ')
|
|
102
|
-
return
|
|
107
|
+
return unless operation.eql?(:find)
|
|
103
108
|
attributes = {}
|
|
104
109
|
output.split("\n").each do |line|
|
|
105
110
|
case line
|
|
@@ -129,18 +134,18 @@ module Aspera
|
|
|
129
134
|
end
|
|
130
135
|
|
|
131
136
|
def set(options)
|
|
132
|
-
assert_type(options, Hash){'options'}
|
|
137
|
+
Aspera.assert_type(options, Hash){'options'}
|
|
133
138
|
unsupported = options.keys - %i[label username password url description]
|
|
134
|
-
assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
|
139
|
+
Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
|
135
140
|
@keychain.password(
|
|
136
141
|
:add, :generic, service: options[:label],
|
|
137
142
|
account: options[:username] || 'none', password: options[:password], comment: options[:description])
|
|
138
143
|
end
|
|
139
144
|
|
|
140
145
|
def get(options)
|
|
141
|
-
assert_type(options, Hash){'options'}
|
|
146
|
+
Aspera.assert_type(options, Hash){'options'}
|
|
142
147
|
unsupported = options.keys - %i[label]
|
|
143
|
-
assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
|
148
|
+
Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
|
144
149
|
info = @keychain.password(:find, :generic, label: options[:label])
|
|
145
150
|
raise 'not found' if info.nil?
|
|
146
151
|
result = options.clone
|
|
@@ -155,9 +160,9 @@ module Aspera
|
|
|
155
160
|
end
|
|
156
161
|
|
|
157
162
|
def delete(options)
|
|
158
|
-
assert_type(options, Hash){'options'}
|
|
163
|
+
Aspera.assert_type(options, Hash){'options'}
|
|
159
164
|
unsupported = options.keys - %i[label]
|
|
160
|
-
assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
|
165
|
+
Aspera.assert(unsupported.empty?){"unsupported options: #{unsupported}"}
|
|
161
166
|
raise 'delete not implemented, use macos keychain app'
|
|
162
167
|
end
|
|
163
168
|
end
|
data/lib/aspera/log.rb
CHANGED
|
@@ -14,7 +14,7 @@ class Logger
|
|
|
14
14
|
TRACE_MAX = 2
|
|
15
15
|
# add custom level to logger severity
|
|
16
16
|
module Severity
|
|
17
|
-
1.upto(TRACE_MAX).each { |level| const_set("TRACE#{level}", - level)}
|
|
17
|
+
1.upto(TRACE_MAX).each { |level| const_set(:"TRACE#{level}", - level)}
|
|
18
18
|
end
|
|
19
19
|
# quick access to label
|
|
20
20
|
SEVERITY_LABEL = Severity.constants.each_with_object({}) { |name, hash| hash[Severity.const_get(name)] = name}
|
|
@@ -99,7 +99,7 @@ module Aspera
|
|
|
99
99
|
Logger::Severity.constants.each do |name|
|
|
100
100
|
return name.downcase.to_sym if @logger.level.eql?(Logger::Severity.const_get(name))
|
|
101
101
|
end
|
|
102
|
-
error_unexpected_value(@logger.level){'log level'}
|
|
102
|
+
Aspera.error_unexpected_value(@logger.level){'log level'}
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
# change underlying logger, but keep log level
|
data/lib/aspera/nagios.rb
CHANGED
|
@@ -23,10 +23,10 @@ module Aspera
|
|
|
23
23
|
class << self
|
|
24
24
|
# process results of a analysis and display status and exit with code
|
|
25
25
|
def process(data)
|
|
26
|
-
assert_type(data, Array)
|
|
27
|
-
assert(!data.empty?){'data is empty'}
|
|
26
|
+
Aspera.assert_type(data, Array)
|
|
27
|
+
Aspera.assert(!data.empty?){'data is empty'}
|
|
28
28
|
%w[status component message].each do |c|
|
|
29
|
-
assert(data.first.key?(c)){"result must have #{c}"}
|
|
29
|
+
Aspera.assert(data.first.key?(c)){"result must have #{c}"}
|
|
30
30
|
end
|
|
31
31
|
res_errors = data.reject{|s|s['status'].eql?('ok')}
|
|
32
32
|
# keep only errors in case of problem, other ok are assumed so
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'aspera/ascp/installation'
|
|
4
|
+
require 'aspera/agent/direct'
|
|
3
5
|
require 'aspera/log'
|
|
4
|
-
require 'aspera/fasp/installation'
|
|
5
|
-
require 'aspera/fasp/agent_direct'
|
|
6
6
|
require 'webrick'
|
|
7
7
|
require 'json'
|
|
8
8
|
|
|
@@ -11,12 +11,12 @@ module Aspera
|
|
|
11
11
|
class NodeSimulatorServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
12
12
|
PATH_TRANSFERS = '/ops/transfers'
|
|
13
13
|
PATH_ONE_TRANSFER = %r{/ops/transfers/(.+)$}
|
|
14
|
-
# @param app_api [
|
|
14
|
+
# @param app_api [Api::AoC]
|
|
15
15
|
# @param app_context [String]
|
|
16
16
|
def initialize(server, credentials, transfer)
|
|
17
17
|
super(server)
|
|
18
18
|
@credentials = credentials
|
|
19
|
-
@xfer_manager =
|
|
19
|
+
@xfer_manager = Agent::Direct.new
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def do_POST(request, response)
|
|
@@ -27,7 +27,6 @@ module Aspera
|
|
|
27
27
|
result = session[:ts].clone
|
|
28
28
|
result['id'] = job_id
|
|
29
29
|
set_json_response(response, result)
|
|
30
|
-
Log.log.debug{">>> transfer started: #{job_id}"}
|
|
31
30
|
else
|
|
32
31
|
set_json_response(response, [{error: 'Bad request'}], code: 400)
|
|
33
32
|
end
|
|
@@ -36,7 +35,7 @@ module Aspera
|
|
|
36
35
|
def do_GET(request, response)
|
|
37
36
|
case request.path
|
|
38
37
|
when '/info'
|
|
39
|
-
info =
|
|
38
|
+
info = Ascp::Installation.instance.ascp_info
|
|
40
39
|
set_json_response(response, {
|
|
41
40
|
application: 'node',
|
|
42
41
|
current_time: Time.now.utc.iso8601(0),
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/oauth/factory'
|
|
4
|
+
require 'aspera/log'
|
|
5
|
+
require 'aspera/assert'
|
|
6
|
+
require 'aspera/id_generator'
|
|
7
|
+
require 'date'
|
|
8
|
+
|
|
9
|
+
module Aspera
|
|
10
|
+
module OAuth
|
|
11
|
+
# Implement OAuth 2 for the REST client and generate a bearer token
|
|
12
|
+
# call get_authorization() to get a token.
|
|
13
|
+
# bearer tokens are kept in memory and also in a file cache for later re-use
|
|
14
|
+
# if a token is expired (api returns 4xx), call again get_authorization(refresh: true)
|
|
15
|
+
# https://tools.ietf.org/html/rfc6749
|
|
16
|
+
class Base
|
|
17
|
+
# scope can be modified after creation
|
|
18
|
+
attr_writer :scope
|
|
19
|
+
|
|
20
|
+
# [M]=mandatory [D]=has default value [O]=Optional/nil
|
|
21
|
+
# @param base_url [M] URL of authentication API
|
|
22
|
+
# @param auth [O] basic auth parameters
|
|
23
|
+
# @param client_id [O]
|
|
24
|
+
# @param client_secret [O]
|
|
25
|
+
# @param scope [O]
|
|
26
|
+
# @param path_token [D] API end point to create a token
|
|
27
|
+
# @param token_field [D] field in result that contains the token
|
|
28
|
+
def initialize(
|
|
29
|
+
base_url:,
|
|
30
|
+
auth: nil,
|
|
31
|
+
client_id: nil,
|
|
32
|
+
client_secret: nil,
|
|
33
|
+
scope: nil,
|
|
34
|
+
path_token: 'token', # default endpoint for /token to generate token
|
|
35
|
+
token_field: 'access_token' # field with token in result of call to path_token
|
|
36
|
+
)
|
|
37
|
+
Aspera.assert_type(base_url, String)
|
|
38
|
+
Aspera.assert(respond_to?(:create_token), 'create_token method must be defined', exception_class: InternalError)
|
|
39
|
+
@base_url = base_url
|
|
40
|
+
@path_token = path_token
|
|
41
|
+
@token_field = token_field
|
|
42
|
+
@client_id = client_id
|
|
43
|
+
@client_secret = client_secret
|
|
44
|
+
@scope = scope
|
|
45
|
+
@identifiers = []
|
|
46
|
+
@identifiers.push(auth[:username]) if auth.is_a?(Hash) && auth.key?(:username)
|
|
47
|
+
# this is the OAuth API
|
|
48
|
+
@api = Rest.new(
|
|
49
|
+
base_url: @base_url,
|
|
50
|
+
redirect_max: 2,
|
|
51
|
+
auth: auth)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# helper method to create token as per RFC
|
|
55
|
+
def create_token_call(www_params)
|
|
56
|
+
Log.log.debug{'Generating a new token'.bg_green}
|
|
57
|
+
return @api.call(
|
|
58
|
+
operation: 'POST',
|
|
59
|
+
subpath: @path_token,
|
|
60
|
+
headers: {'Accept' => 'application/json'},
|
|
61
|
+
www_body_params: www_params)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return Hash with optional general parameters
|
|
65
|
+
def optional_scope_client_id(add_secret: false)
|
|
66
|
+
call_params = {}
|
|
67
|
+
call_params[:scope] = @scope unless @scope.nil?
|
|
68
|
+
call_params[:client_id] = @client_id unless @client_id.nil?
|
|
69
|
+
call_params[:client_secret] = @client_secret if add_secret && !@client_id.nil?
|
|
70
|
+
return call_params
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# OAuth v2 token generation
|
|
74
|
+
# @param use_refresh_token set to true to force refresh or re-generation (if previous failed)
|
|
75
|
+
def get_authorization(use_refresh_token: false, use_cache: true)
|
|
76
|
+
# generate token unique identifier for persistency (memory/disk cache)
|
|
77
|
+
token_id = IdGenerator.from_list(Factory.id(
|
|
78
|
+
@base_url,
|
|
79
|
+
@grant_method,
|
|
80
|
+
@identifiers,
|
|
81
|
+
@scope
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
# get token_data from cache (or nil), token_data is what is returned by /token
|
|
85
|
+
token_data = Factory.instance.persist_mgr.get(token_id) if use_cache
|
|
86
|
+
token_data = JSON.parse(token_data) unless token_data.nil?
|
|
87
|
+
# Optional optimization: check if node token is expired based on decoded content then force refresh if close enough
|
|
88
|
+
# might help in case the transfer agent cannot refresh himself
|
|
89
|
+
# `direct` agent is equipped with refresh code
|
|
90
|
+
if !use_refresh_token && !token_data.nil?
|
|
91
|
+
decoded_token = OAuth::Factory.instance.decode_token(token_data[@token_field])
|
|
92
|
+
Log.log.debug{Log.dump('decoded_token', decoded_token)} unless decoded_token.nil?
|
|
93
|
+
if decoded_token.is_a?(Hash)
|
|
94
|
+
expires_at_sec =
|
|
95
|
+
if decoded_token['expires_at'].is_a?(String) then DateTime.parse(decoded_token['expires_at']).to_time
|
|
96
|
+
elsif decoded_token['exp'].is_a?(Integer) then Time.at(decoded_token['exp'])
|
|
97
|
+
end
|
|
98
|
+
# force refresh if we see a token too close from expiration
|
|
99
|
+
use_refresh_token = true if expires_at_sec.is_a?(Time) && (expires_at_sec - Time.now) < OAuth::Factory.instance.globals[:token_expiration_guard_sec]
|
|
100
|
+
Log.log.debug{"Expiration: #{expires_at_sec} / #{use_refresh_token}"}
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# an API was already called, but failed, we need to regenerate or refresh
|
|
105
|
+
if use_refresh_token
|
|
106
|
+
if token_data.is_a?(Hash) && token_data.key?('refresh_token') && !token_data['refresh_token'].eql?('not_supported')
|
|
107
|
+
# save possible refresh token, before deleting the cache
|
|
108
|
+
refresh_token = token_data['refresh_token']
|
|
109
|
+
end
|
|
110
|
+
# delete cache
|
|
111
|
+
Factory.instance.persist_mgr.delete(token_id)
|
|
112
|
+
token_data = nil
|
|
113
|
+
# lets try the existing refresh token
|
|
114
|
+
if !refresh_token.nil?
|
|
115
|
+
Log.log.info{"refresh=[#{refresh_token}]".bg_green}
|
|
116
|
+
# try to refresh
|
|
117
|
+
# note: AoC admin token has no refresh, and lives by default 1800secs
|
|
118
|
+
resp = create_token_call(optional_scope_client_id.merge(grant_type: 'refresh_token', refresh_token: refresh_token))
|
|
119
|
+
if resp[:http].code.start_with?('2')
|
|
120
|
+
# save only if success
|
|
121
|
+
json_data = resp[:http].body
|
|
122
|
+
token_data = JSON.parse(json_data)
|
|
123
|
+
Factory.instance.persist_mgr.put(token_id, json_data)
|
|
124
|
+
else
|
|
125
|
+
Log.log.debug{"refresh failed: #{resp[:http].body}".bg_red}
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# no cache, nor refresh: generate a token
|
|
131
|
+
if token_data.nil?
|
|
132
|
+
resp = create_token
|
|
133
|
+
json_data = resp[:http].body
|
|
134
|
+
token_data = JSON.parse(json_data)
|
|
135
|
+
Factory.instance.persist_mgr.put(token_id, json_data)
|
|
136
|
+
end # if ! in_cache
|
|
137
|
+
Aspera.assert(token_data.key?(@token_field)){"API error: No such field in answer: #{@token_field}"}
|
|
138
|
+
# ok we shall have a token here
|
|
139
|
+
return OAuth::Factory.bearer_build(token_data[@token_field])
|
|
140
|
+
end
|
|
141
|
+
end # OAuth
|
|
142
|
+
end
|
|
143
|
+
end
|