aspera-cli 4.10.0 → 4.12.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 +19 -0
- data/CHANGELOG.md +528 -0
- data/CONTRIBUTING.md +143 -0
- data/README.md +977 -589
- data/bin/ascli +4 -4
- data/bin/asession +12 -12
- data/docs/test_env.conf +29 -19
- data/examples/aoc.rb +6 -6
- data/examples/dascli +18 -16
- data/examples/faspex4.rb +15 -15
- data/examples/node.rb +12 -12
- data/examples/proxy.pac +2 -2
- data/examples/server.rb +12 -12
- data/lib/aspera/aoc.rb +344 -272
- data/lib/aspera/ascmd.rb +56 -54
- data/lib/aspera/ats_api.rb +4 -4
- data/lib/aspera/cli/basic_auth_plugin.rb +15 -12
- data/lib/aspera/cli/extended_value.rb +9 -9
- data/lib/aspera/cli/{formater.rb → formatter.rb} +69 -69
- data/lib/aspera/cli/listener/line_dump.rb +1 -1
- data/lib/aspera/cli/listener/logger.rb +1 -1
- data/lib/aspera/cli/listener/progress.rb +5 -6
- data/lib/aspera/cli/listener/progress_multi.rb +16 -21
- data/lib/aspera/cli/main.rb +72 -73
- data/lib/aspera/cli/manager.rb +112 -112
- data/lib/aspera/cli/plugin.rb +68 -48
- data/lib/aspera/cli/plugins/alee.rb +4 -4
- data/lib/aspera/cli/plugins/aoc.rb +322 -720
- data/lib/aspera/cli/plugins/ats.rb +50 -52
- data/lib/aspera/cli/plugins/bss.rb +10 -10
- data/lib/aspera/cli/plugins/config.rb +514 -410
- data/lib/aspera/cli/plugins/console.rb +12 -12
- data/lib/aspera/cli/plugins/cos.rb +18 -20
- data/lib/aspera/cli/plugins/faspex.rb +134 -136
- data/lib/aspera/cli/plugins/faspex5.rb +235 -70
- data/lib/aspera/cli/plugins/node.rb +378 -309
- data/lib/aspera/cli/plugins/orchestrator.rb +52 -49
- data/lib/aspera/cli/plugins/preview.rb +129 -120
- data/lib/aspera/cli/plugins/server.rb +137 -83
- data/lib/aspera/cli/plugins/shares.rb +77 -52
- data/lib/aspera/cli/plugins/sync.rb +13 -33
- data/lib/aspera/cli/transfer_agent.rb +61 -61
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +78 -74
- data/lib/aspera/cos_node.rb +31 -29
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +30 -28
- data/lib/aspera/fasp/agent_base.rb +17 -15
- data/lib/aspera/fasp/agent_connect.rb +34 -32
- data/lib/aspera/fasp/agent_direct.rb +70 -73
- data/lib/aspera/fasp/agent_httpgw.rb +79 -74
- data/lib/aspera/fasp/agent_node.rb +26 -26
- data/lib/aspera/fasp/agent_trsdk.rb +20 -20
- data/lib/aspera/fasp/error.rb +3 -2
- data/lib/aspera/fasp/error_info.rb +11 -8
- data/lib/aspera/fasp/installation.rb +80 -80
- data/lib/aspera/fasp/listener.rb +2 -2
- data/lib/aspera/fasp/parameters.rb +103 -92
- data/lib/aspera/fasp/parameters.yaml +313 -214
- data/lib/aspera/fasp/resume_policy.rb +10 -10
- data/lib/aspera/fasp/transfer_spec.rb +22 -2
- data/lib/aspera/fasp/uri.rb +7 -7
- data/lib/aspera/faspex_gw.rb +80 -159
- data/lib/aspera/faspex_postproc.rb +77 -0
- data/lib/aspera/hash_ext.rb +3 -3
- data/lib/aspera/id_generator.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +23 -28
- data/lib/aspera/keychain/macos_security.rb +21 -20
- data/lib/aspera/log.rb +13 -13
- data/lib/aspera/nagios.rb +24 -23
- data/lib/aspera/node.rb +217 -38
- data/lib/aspera/oauth.rb +78 -74
- data/lib/aspera/open_application.rb +19 -11
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +13 -13
- data/lib/aspera/preview/file_types.rb +8 -8
- data/lib/aspera/preview/generator.rb +67 -67
- data/lib/aspera/preview/utils.rb +27 -27
- data/lib/aspera/proxy_auto_config.js +63 -63
- data/lib/aspera/proxy_auto_config.rb +19 -19
- data/lib/aspera/rest.rb +65 -67
- data/lib/aspera/rest_call_error.rb +2 -1
- data/lib/aspera/rest_error_analyzer.rb +22 -21
- data/lib/aspera/rest_errors_aspera.rb +16 -16
- data/lib/aspera/secret_hider.rb +17 -14
- data/lib/aspera/ssh.rb +15 -14
- data/lib/aspera/sync.rb +177 -62
- data/lib/aspera/temp_file_manager.rb +2 -2
- data/lib/aspera/uri_reader.rb +4 -4
- data/lib/aspera/web_auth.rb +13 -64
- data/lib/aspera/web_server_simple.rb +76 -0
- data.tar.gz.sig +0 -0
- metadata +11 -6
- metadata.gz.sig +0 -0
data/lib/aspera/secret_hider.rb
CHANGED
@@ -7,16 +7,17 @@ module Aspera
|
|
7
7
|
class SecretHider
|
8
8
|
# display string for hidden secrets
|
9
9
|
HIDDEN_PASSWORD = '🔑'
|
10
|
+
# env vars for ascp with secrets
|
11
|
+
ASCP_ENV_SECRETS = %w[ASPERA_SCP_PASS ASPERA_SCP_KEY ASPERA_SCP_FILEPASS ASPERA_PROXY_PASS ASPERA_SCP_TOKEN].freeze
|
10
12
|
# keys in hash that contain secrets
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
REGEX_LOG_REPLACES=[
|
13
|
+
KEY_SECRETS = %w[password secret passphrase _key apikey crn token].freeze
|
14
|
+
ALL_SECRETS = [ASCP_ENV_SECRETS, KEY_SECRETS].flatten.freeze
|
15
|
+
# regex that define named captures :begin and :end
|
16
|
+
REGEX_LOG_REPLACES = [
|
16
17
|
# CLI manager get/set options
|
17
18
|
/(?<begin>[sg]et (#{KEY_SECRETS.join('|')})=).*(?<end>)/,
|
18
19
|
# env var ascp exec
|
19
|
-
/(?<begin> (#{
|
20
|
+
/(?<begin> (#{ASCP_ENV_SECRETS.join('|')})=)(\\.|[^ ])*(?<end> )/,
|
20
21
|
# rendered JSON
|
21
22
|
/(?<begin>["':][^"]*(#{ALL_SECRETS.join('|')})[^"]*["']?[=>: ]+")[^"]+(?<end>")/,
|
22
23
|
# option "secret"
|
@@ -26,13 +27,14 @@ module Aspera
|
|
26
27
|
# private key values
|
27
28
|
/(?<begin>--+BEGIN .+ KEY--+)[[:ascii:]]+?(?<end>--+?END .+ KEY--+)/
|
28
29
|
].freeze
|
29
|
-
private_constant :HIDDEN_PASSWORD
|
30
|
+
private_constant :HIDDEN_PASSWORD, :ASCP_ENV_SECRETS, :KEY_SECRETS, :ALL_SECRETS, :REGEX_LOG_REPLACES
|
30
31
|
@log_secrets = false
|
31
32
|
class << self
|
32
33
|
attr_accessor :log_secrets
|
34
|
+
|
33
35
|
def log_formatter(original_formatter)
|
34
36
|
original_formatter ||= Logger::Formatter.new
|
35
|
-
#
|
37
|
+
# NOTE: that @log_secrets may be set AFTER this init is done, so it's done at runtime
|
36
38
|
return lambda do |severity, datetime, progname, msg|
|
37
39
|
if msg.is_a?(String) && !@log_secrets
|
38
40
|
REGEX_LOG_REPLACES.each do |regx|
|
@@ -43,31 +45,32 @@ module Aspera
|
|
43
45
|
end
|
44
46
|
end
|
45
47
|
|
46
|
-
def secret?(keyword,value)
|
47
|
-
keyword=keyword.to_s if keyword.is_a?(Symbol)
|
48
|
+
def secret?(keyword, value)
|
49
|
+
keyword = keyword.to_s if keyword.is_a?(Symbol)
|
48
50
|
# only Strings can be secrets, not booleans, or hash, arrays
|
49
51
|
keyword.is_a?(String) && ALL_SECRETS.any?{|kw|keyword.include?(kw)} && value.is_a?(String)
|
50
52
|
end
|
51
53
|
|
52
|
-
def deep_remove_secret(obj,is_name_value: false)
|
54
|
+
def deep_remove_secret(obj, is_name_value: false)
|
53
55
|
case obj
|
54
56
|
when Array
|
55
57
|
if is_name_value
|
56
58
|
obj.each do |i|
|
57
|
-
i['value']=HIDDEN_PASSWORD if secret?(i['parameter'],i['value'])
|
59
|
+
i['value'] = HIDDEN_PASSWORD if secret?(i['parameter'], i['value'])
|
58
60
|
end
|
59
61
|
else
|
60
62
|
obj.each{|i|deep_remove_secret(i)}
|
61
63
|
end
|
62
64
|
when Hash
|
63
|
-
obj.each do |k,v|
|
64
|
-
if secret?(k,v)
|
65
|
+
obj.each do |k, v|
|
66
|
+
if secret?(k, v)
|
65
67
|
obj[k] = HIDDEN_PASSWORD
|
66
68
|
elsif obj[k].is_a?(Hash)
|
67
69
|
deep_remove_secret(obj[k])
|
68
70
|
end
|
69
71
|
end
|
70
72
|
end
|
73
|
+
return obj
|
71
74
|
end
|
72
75
|
end
|
73
76
|
end
|
data/lib/aspera/ssh.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'net/ssh'
|
4
4
|
|
5
|
-
#
|
5
|
+
# HACK: deactivate ed25519 and ecdsa private keys from ssh identities, as it usually hurts
|
6
6
|
begin
|
7
|
-
module Net;module SSH;module Authentication;class Session;private; def default_keys; %w[~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa];end;end;end;end;end # rubocop:disable Layout/AccessModifierIndentation, Layout/EmptyLinesAroundAccessModifier, Layout/LineLength
|
7
|
+
module Net; module SSH; module Authentication; class Session; private; def default_keys; %w[~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa]; end; end; end; end; end # rubocop:disable Layout/AccessModifierIndentation, Layout/EmptyLinesAroundAccessModifier, Layout/LineLength, Style/Semicolon
|
8
8
|
rescue StandardError
|
9
9
|
# ignore errors
|
10
10
|
end
|
@@ -15,43 +15,44 @@ module Aspera
|
|
15
15
|
class Ssh
|
16
16
|
# ssh_options: same as Net::SSH.start
|
17
17
|
# see: https://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
|
18
|
-
def initialize(host,username,ssh_options)
|
19
|
-
Log.log.debug
|
20
|
-
Log.log.debug
|
18
|
+
def initialize(host, username, ssh_options)
|
19
|
+
Log.log.debug{"ssh:#{username}@#{host}"}
|
20
|
+
Log.log.debug{"ssh_options:#{ssh_options}"}
|
21
21
|
@host = host
|
22
22
|
@username = username
|
23
23
|
@ssh_options = ssh_options
|
24
24
|
@ssh_options[:logger] = Log.log
|
25
25
|
end
|
26
26
|
|
27
|
-
def execute(cmd,input=nil)
|
27
|
+
def execute(cmd, input=nil)
|
28
28
|
if cmd.is_a?(Array)
|
29
29
|
# concatenate arguments, enclose in double quotes
|
30
30
|
cmd = cmd.map{|v|%Q("#{v}")}.join(' ')
|
31
31
|
end
|
32
|
-
Log.log.debug
|
32
|
+
Log.log.debug{"cmd=#{cmd}"}
|
33
33
|
response = []
|
34
34
|
Net::SSH.start(@host, @username, @ssh_options) do |session|
|
35
35
|
ssh_channel = session.open_channel do |channel|
|
36
36
|
# prepare stdout processing
|
37
|
-
channel.on_data{|_chan,data|response.push(data)}
|
37
|
+
channel.on_data{|_chan, data|response.push(data)}
|
38
38
|
# prepare stderr processing, stderr if type = 1
|
39
39
|
channel.on_extended_data do |_chan, _type, data|
|
40
|
-
|
40
|
+
error_message = "#{cmd}: [#{data.chomp}]"
|
41
41
|
# Happens when windows user hasn't logged in and created home account.
|
42
42
|
if data.include?('Could not chdir to home directory')
|
43
|
-
|
43
|
+
error_message += "\nHint: home not created in Windows?"
|
44
44
|
end
|
45
|
-
raise
|
45
|
+
raise error_message
|
46
46
|
end
|
47
|
-
# send
|
48
|
-
channel.send('cexe'.reverse,cmd){|_ch,_success|channel.send_data(input) unless input.nil?}
|
47
|
+
# send command to SSH channel (execute)
|
48
|
+
channel.send('cexe'.reverse, cmd){|_ch, _success|channel.send_data(input) unless input.nil?}
|
49
49
|
end
|
50
50
|
# wait for channel to finish (command exit)
|
51
51
|
ssh_channel.wait
|
52
52
|
# main ssh session loop
|
53
53
|
session.loop
|
54
|
-
end #
|
54
|
+
end # start
|
55
|
+
# response as single string
|
55
56
|
return response.join
|
56
57
|
end
|
57
58
|
end
|
data/lib/aspera/sync.rb
CHANGED
@@ -1,90 +1,205 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'aspera/command_line_builder'
|
4
|
+
require 'aspera/fasp/installation'
|
5
|
+
require 'json'
|
6
|
+
require 'base64'
|
4
7
|
|
5
8
|
module Aspera
|
6
9
|
# builds command line arg for async
|
7
10
|
class Sync
|
8
|
-
|
11
|
+
PARAMS_VX_INSTANCE =
|
9
12
|
{
|
10
|
-
'alt_logdir' => {
|
11
|
-
'watchd' => {
|
12
|
-
'apply_local_docroot' => {
|
13
|
-
'quiet' => {
|
13
|
+
'alt_logdir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
14
|
+
'watchd' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
15
|
+
'apply_local_docroot' => { cli: { type: :opt_without_arg}},
|
16
|
+
'quiet' => { cli: { type: :opt_without_arg}},
|
17
|
+
'ws_connect' => { cli: { type: :opt_without_arg}}
|
14
18
|
}.freeze
|
15
|
-
|
19
|
+
|
20
|
+
# map sync session parameters to transfer spec: sync -> ts, true if same
|
21
|
+
PARAMS_VX_SESSION =
|
16
22
|
{
|
17
|
-
'name' => {
|
18
|
-
'local_dir' => {
|
19
|
-
'remote_dir' => {
|
20
|
-
'local_db_dir' => {
|
21
|
-
'remote_db_dir' => {
|
22
|
-
'host' => {
|
23
|
-
'user' => {
|
24
|
-
'
|
25
|
-
'direction' => {
|
26
|
-
'checksum' => {
|
27
|
-
'
|
28
|
-
|
29
|
-
'
|
30
|
-
'
|
31
|
-
'
|
32
|
-
'
|
33
|
-
'
|
34
|
-
'
|
35
|
-
'
|
36
|
-
'
|
37
|
-
'
|
38
|
-
'
|
39
|
-
'
|
40
|
-
'
|
41
|
-
'
|
42
|
-
|
43
|
-
'
|
44
|
-
|
45
|
-
'
|
46
|
-
'
|
23
|
+
'name' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
24
|
+
'local_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
25
|
+
'remote_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
26
|
+
'local_db_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
27
|
+
'remote_db_dir' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
28
|
+
'host' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: :remote_host},
|
29
|
+
'user' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: :remote_user},
|
30
|
+
'private_key_paths' => { cli: { type: :opt_with_arg, switch: '--private-key-path'}, accepted_types: :array},
|
31
|
+
'direction' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
32
|
+
'checksum' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
33
|
+
'tags' => { cli: { type: :opt_with_arg, switch: '--tags64', convert: 'Aspera::Fasp::Parameters.convert_json64'},
|
34
|
+
accepted_types: :hash, ts: true},
|
35
|
+
'tcp_port' => { cli: { type: :opt_with_arg}, accepted_types: :int, ts: :ssh_port},
|
36
|
+
'rate_policy' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
37
|
+
'target_rate' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
38
|
+
'cooloff' => { cli: { type: :opt_with_arg}, accepted_types: :int},
|
39
|
+
'pending_max' => { cli: { type: :opt_with_arg}, accepted_types: :int},
|
40
|
+
'scan_intensity' => { cli: { type: :opt_with_arg}, accepted_types: :string},
|
41
|
+
'cipher' => { cli: { type: :opt_with_arg}, accepted_types: :string, ts: true},
|
42
|
+
'transfer_threads' => { cli: { type: :opt_with_arg}, accepted_types: :int},
|
43
|
+
'preserve_time' => { cli: { type: :opt_without_arg}, ts: :preserve_times},
|
44
|
+
'preserve_access_time' => { cli: { type: :opt_without_arg}, ts: nil},
|
45
|
+
'preserve_modification_time' => { cli: { type: :opt_without_arg}, ts: nil},
|
46
|
+
'preserve_uid' => { cli: { type: :opt_without_arg}, ts: :preserve_file_owner_uid},
|
47
|
+
'preserve_gid' => { cli: { type: :opt_without_arg}, ts: :preserve_file_owner_gid},
|
48
|
+
'create_dir' => { cli: { type: :opt_without_arg}, ts: true},
|
49
|
+
'reset' => { cli: { type: :opt_without_arg}},
|
50
|
+
# NOTE: only one env var, but multiple sessions... could be a problem
|
51
|
+
'remote_password' => { cli: { type: :envvar, variable: 'ASPERA_SCP_PASS'}, ts: true},
|
52
|
+
'cookie' => { cli: { type: :envvar, variable: 'ASPERA_SCP_COOKIE'}, ts: true},
|
53
|
+
'token' => { cli: { type: :envvar, variable: 'ASPERA_SCP_TOKEN'}, ts: true},
|
54
|
+
'license' => { cli: { type: :envvar, variable: 'ASPERA_SCP_LICENSE'}}
|
47
55
|
}.freeze
|
48
56
|
|
49
|
-
Aspera::CommandLineBuilder.normalize_description(
|
50
|
-
Aspera::CommandLineBuilder.normalize_description(
|
57
|
+
Aspera::CommandLineBuilder.normalize_description(PARAMS_VX_INSTANCE)
|
58
|
+
Aspera::CommandLineBuilder.normalize_description(PARAMS_VX_SESSION)
|
51
59
|
|
52
|
-
|
60
|
+
PARAMS_VX_KEYS = %w[instance sessions].freeze
|
53
61
|
|
54
|
-
|
55
|
-
|
56
|
-
|
62
|
+
# new API
|
63
|
+
TS_TO_PARAMS = {
|
64
|
+
'remote_host' => 'remote.host',
|
65
|
+
'remote_user' => 'remote.user',
|
66
|
+
'remote_password' => 'remote.pass',
|
67
|
+
'sshfp' => 'remote.fingerprint',
|
68
|
+
'ssh_port' => 'remote.port',
|
69
|
+
'wss_port' => 'remote.ws_port',
|
70
|
+
'proxy' => 'remote.proxy',
|
71
|
+
'token' => 'remote.token',
|
72
|
+
'tags' => 'tags'
|
73
|
+
}.freeze
|
74
|
+
|
75
|
+
ASYNC_EXECUTABLE = 'async'
|
57
76
|
|
58
|
-
|
77
|
+
private_constant :PARAMS_VX_INSTANCE, :PARAMS_VX_SESSION, :PARAMS_VX_KEYS, :ASYNC_EXECUTABLE
|
59
78
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
79
|
+
class << self
|
80
|
+
def update_parameters_with_transfer_spec(sync_params, transfer_spec)
|
81
|
+
if sync_params.key?('local')
|
82
|
+
# async native JSON format
|
83
|
+
raise StandardError, 'local must be Hash' unless sync_params['local'].is_a?(Hash)
|
84
|
+
TS_TO_PARAMS.each do |ts_param, sy_path|
|
85
|
+
next unless transfer_spec.key?(ts_param)
|
86
|
+
sy_dig = sy_path.split('.')
|
87
|
+
param = sy_dig.pop
|
88
|
+
hash = sy_dig.empty? ? sync_params : sync_params[sy_dig.first]
|
89
|
+
hash = sync_params[sy_dig.first] = {} if hash.nil?
|
90
|
+
hash[param] = transfer_spec[ts_param]
|
91
|
+
end
|
92
|
+
# 'remote.path',
|
93
|
+
sync_params['remote']['connect_mode'] ||= sync_params['remote'].key?('ws_port') ? 'ws' : 'ssh'
|
94
|
+
sync_params['remote']['private_key_paths'] ||= Fasp::Installation.instance.bypass_keys if transfer_spec.key?('token')
|
95
|
+
sync_params['remote']['path'] ||= '/' if transfer_spec.dig(*%w[tags aspera node file_id])
|
96
|
+
elsif sync_params.key?('sessions')
|
97
|
+
sync_params['sessions'].each do |session|
|
98
|
+
PARAMS_VX_SESSION.each do |async_param, behaviour|
|
99
|
+
if behaviour.key?(:ts)
|
100
|
+
tspec_param = behaviour[:ts].is_a?(TrueClass) ? async_param : behaviour[:ts].to_s
|
101
|
+
session[async_param] ||= transfer_spec[tspec_param] if transfer_spec.key?(tspec_param)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
session['private_key_paths'] = Fasp::Installation.instance.bypass_keys if transfer_spec.key?('token')
|
105
|
+
session['remote_dir'] = '/' if transfer_spec.dig(*%w[tags aspera node file_id])
|
106
|
+
end
|
107
|
+
else
|
108
|
+
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
109
|
+
end
|
110
|
+
Log.dump(:sync, sync_params)
|
111
|
+
end
|
112
|
+
end
|
66
113
|
|
67
|
-
|
114
|
+
attr_reader :env_args
|
115
|
+
|
116
|
+
def initialize(sync_params)
|
117
|
+
raise StandardError, 'parameter must be Hash' unless sync_params.is_a?(Hash)
|
118
|
+
@env_args = {
|
68
119
|
args: [],
|
69
120
|
env: {}
|
70
121
|
}
|
122
|
+
if sync_params.key?('local')
|
123
|
+
# async native JSON format
|
124
|
+
raise StandardError, 'remote must be Hash' unless sync_params['remote'].is_a?(Hash)
|
125
|
+
@env_args[:args] = "--conf64=#{Base64.strict_encode64(JSON.generate(sync_params))}"
|
126
|
+
elsif sync_params.key?('sessions')
|
127
|
+
# ascli JSON format
|
128
|
+
raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
|
129
|
+
sync_params.keys.push('instance').uniq.sort.eql?(PARAMS_VX_KEYS)
|
130
|
+
raise StandardError, 'sessions key must be Array' unless sync_params['sessions'].is_a?(Array)
|
131
|
+
raise StandardError, 'sessions key requires at least one Hash' unless sync_params['sessions'].first.is_a?(Hash)
|
71
132
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
133
|
+
if sync_params.key?('instance')
|
134
|
+
raise StandardError, 'instance key must be Hash' unless sync_params['instance'].is_a?(Hash)
|
135
|
+
instance_builder = Aspera::CommandLineBuilder.new(sync_params['instance'], PARAMS_VX_INSTANCE)
|
136
|
+
instance_builder.process_params
|
137
|
+
instance_builder.add_env_args(@env_args[:env], @env_args[:args])
|
138
|
+
end
|
139
|
+
|
140
|
+
sync_params['sessions'].each do |session_params|
|
141
|
+
raise StandardError, 'sessions must contain hashes' unless session_params.is_a?(Hash)
|
142
|
+
raise StandardError, 'session must contain at leat name' unless session_params.key?('name')
|
143
|
+
session_builder = Aspera::CommandLineBuilder.new(session_params, PARAMS_VX_SESSION)
|
144
|
+
session_builder.process_params
|
145
|
+
session_builder.add_env_args(@env_args[:env], @env_args[:args])
|
146
|
+
end
|
147
|
+
else
|
148
|
+
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
77
149
|
end
|
150
|
+
end
|
78
151
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
152
|
+
def start
|
153
|
+
Log.log.debug{"execute: #{@env_args[:env].map{|k, v| "#{k}=\"#{v}\""}.join(' ')} \"#{ASYNC_EXECUTABLE}\" \"#{@env_args[:args].join('" "')}\""}
|
154
|
+
res = system(@env_args[:env], [ASYNC_EXECUTABLE, ASYNC_EXECUTABLE], *@env_args[:args])
|
155
|
+
Log.log.debug{"result=#{res}"}
|
156
|
+
case res
|
157
|
+
when true then return nil
|
158
|
+
when false then raise "failed: #{$CHILD_STATUS}"
|
159
|
+
when nil then raise "not started: #{$CHILD_STATUS}"
|
160
|
+
else raise 'internal error: unspecified case'
|
85
161
|
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class SyncAdmin
|
166
|
+
ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
|
167
|
+
private_constant :ASYNC_ADMIN_EXECUTABLE
|
168
|
+
def initialize(sync_params, session_name)
|
169
|
+
@cmdline = [ASYNC_ADMIN_EXECUTABLE, '--quiet']
|
170
|
+
if sync_params.key?('local')
|
171
|
+
raise 'Missing session name' if sync_params['name'].nil?
|
172
|
+
raise 'Session not found' unless session_name.nil? || session_name.eql?(sync_params['name'])
|
173
|
+
@cmdline.push("--name=#{sync_params['name']}")
|
174
|
+
if sync_params.key?('local_db_dir')
|
175
|
+
@cmdline.push("--local-db-dir=#{sync_params['local_db_dir']}")
|
176
|
+
elsif sync_params.dig('local', 'path')
|
177
|
+
@cmdline.push("--local-dir=#{sync_params.dig('local', 'path')}")
|
178
|
+
else
|
179
|
+
raise 'Missing either local_db_dir or local.path'
|
180
|
+
end
|
181
|
+
elsif sync_params.key?('sessions')
|
182
|
+
session = session_name.nil? ? sync_params['sessions'].first : sync_params['sessions'].find{|s|s['name'].eql?(session_name)}
|
183
|
+
raise 'Session not found' if session.nil?
|
184
|
+
raise 'Missing session name' if session['name'].nil?
|
185
|
+
@cmdline.push("--name=#{session['name']}")
|
186
|
+
if session.key?('local_db_dir')
|
187
|
+
@cmdline.push("--local-db-dir=#{session['local_db_dir']}")
|
188
|
+
elsif session.key?('local_dir')
|
189
|
+
@cmdline.push("--local-dir=#{session['local_dir']}")
|
190
|
+
else
|
191
|
+
raise 'Missing either local_db_dir or local_dir'
|
192
|
+
end
|
193
|
+
else
|
194
|
+
raise 'At least one of `local` or `sessions` must be present in async parameters'
|
195
|
+
end
|
196
|
+
end
|
86
197
|
|
87
|
-
|
198
|
+
def status
|
199
|
+
stdout, stderr, status = Open3.capture3(*@cmdline)
|
200
|
+
Log.log.debug{"status=#{status}, stderr=#{stderr}"}
|
201
|
+
raise "Sync failed: #{status.exitstatus} : #{stderr}" unless status.success?
|
202
|
+
return stdout.split("\n").each_with_object({}){|l, m|i = l.split(/: */); m[i.first.lstrip] = i.last.lstrip} # rubocop:disable Style/Semicolon
|
88
203
|
end
|
89
204
|
end
|
90
205
|
end
|
@@ -11,7 +11,7 @@ module Aspera
|
|
11
11
|
SEC_IN_DAY = 86_400
|
12
12
|
# assume no transfer last longer than this
|
13
13
|
# (garbage collect file list which were not deleted after transfer)
|
14
|
-
FILE_LIST_AGE_MAX_SEC =
|
14
|
+
FILE_LIST_AGE_MAX_SEC = SEC_IN_DAY * 5
|
15
15
|
private_constant :SEC_IN_DAY, :FILE_LIST_AGE_MAX_SEC
|
16
16
|
include Singleton
|
17
17
|
def initialize
|
@@ -53,7 +53,7 @@ module Aspera
|
|
53
53
|
age_sec = (Time.now - File.stat(file_path).mtime).to_i
|
54
54
|
# check age of file, delete too old
|
55
55
|
if File.file?(file_path) && (age_sec > FILE_LIST_AGE_MAX_SEC)
|
56
|
-
Log.log.debug
|
56
|
+
Log.log.debug{"garbage collecting #{name}"}
|
57
57
|
File.delete(file_path)
|
58
58
|
end
|
59
59
|
end
|
data/lib/aspera/uri_reader.rb
CHANGED
@@ -10,12 +10,12 @@ module Aspera
|
|
10
10
|
def read(uri_to_read)
|
11
11
|
proxy_uri = URI.parse(uri_to_read)
|
12
12
|
case proxy_uri.scheme
|
13
|
-
when 'http','https'
|
14
|
-
return Rest.new(base_url: uri_to_read,redirect_max: 5).call(operation: 'GET', subpath: '', headers: {'Accept' => 'text/plain'})[:data]
|
15
|
-
when 'file',NilClass
|
13
|
+
when 'http', 'https'
|
14
|
+
return Rest.new(base_url: uri_to_read, redirect_max: 5).call(operation: 'GET', subpath: '', headers: {'Accept' => 'text/plain'})[:data]
|
15
|
+
when 'file', NilClass
|
16
16
|
local_file_path = proxy_uri.path
|
17
17
|
raise 'URL shall have a path, check syntax' if local_file_path.nil?
|
18
|
-
local_file_path = File.expand_path(local_file_path.gsub(
|
18
|
+
local_file_path = File.expand_path(local_file_path.gsub(%r{^/}, '')) if %r{^/(~|.|..)/}.match?(local_file_path)
|
19
19
|
return File.read(local_file_path)
|
20
20
|
else
|
21
21
|
raise "unknown scheme: [#{proxy_uri.scheme}] for [#{uri_to_read}]"
|
data/lib/aspera/web_auth.rb
CHANGED
@@ -1,22 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
4
|
-
require 'webrick/https'
|
3
|
+
require 'aspera/web_server_simple'
|
5
4
|
require 'stringio'
|
6
5
|
|
7
6
|
module Aspera
|
8
7
|
# servlet called on callback: it records the callback request
|
9
8
|
class WebAuthServlet < WEBrick::HTTPServlet::AbstractServlet
|
10
|
-
def initialize(server,application) # additional args get here
|
9
|
+
def initialize(server, application) # additional args get here
|
11
10
|
Log.log.debug('WebAuthServlet initialize')
|
12
11
|
super(server)
|
13
12
|
@app = application
|
14
13
|
end
|
15
14
|
|
16
15
|
def service(request, response)
|
17
|
-
Log.log.debug
|
18
|
-
raise WEBrick::HTTPStatus::MethodNotAllowed,"unexpected method: #{request.request_method}" unless request.request_method.eql?('GET')
|
19
|
-
raise WEBrick::HTTPStatus::NotFound,"unexpected path: #{request.path}" unless request.path.eql?(@app.expected_path)
|
16
|
+
Log.log.debug{"received request from browser #{request.request_method} #{request.path}"}
|
17
|
+
raise WEBrick::HTTPStatus::MethodNotAllowed, "unexpected method: #{request.request_method}" unless request.request_method.eql?('GET')
|
18
|
+
raise WEBrick::HTTPStatus::NotFound, "unexpected path: #{request.path}" unless request.path.eql?(@app.expected_path)
|
20
19
|
# acquire lock and signal change
|
21
20
|
@app.mutex.synchronize do
|
22
21
|
@app.query = request.query
|
@@ -29,81 +28,31 @@ module Aspera
|
|
29
28
|
end
|
30
29
|
end # WebAuthServlet
|
31
30
|
|
32
|
-
# generates and adds self signed cert to provided webrick options
|
33
|
-
#def fill_self_signed_cert(cert,key)
|
34
|
-
# cert.subject = cert.issuer = OpenSSL::X509::Name.parse('/C=FR/O=Test/OU=Test/CN=Test')
|
35
|
-
# cert.not_before = Time.now
|
36
|
-
# cert.not_after = Time.now + 365 * 24 * 60 * 60
|
37
|
-
# cert.public_key = key.public_key
|
38
|
-
# cert.serial = 0x0
|
39
|
-
# cert.version = 2
|
40
|
-
# ef = OpenSSL::X509::ExtensionFactory.new
|
41
|
-
# ef.issuer_certificate = cert
|
42
|
-
# ef.subject_certificate = cert
|
43
|
-
# cert.extensions = [
|
44
|
-
# ef.create_extension('basicConstraints','CA:TRUE', true),
|
45
|
-
# ef.create_extension('subjectKeyIdentifier', 'hash'),
|
46
|
-
# # ef.create_extension('keyUsage', 'cRLSign,keyCertSign', true),
|
47
|
-
# ]
|
48
|
-
# cert.add_extension(ef.create_extension('authorityKeyIdentifier','keyid:always,issuer:always'))
|
49
|
-
# cert.sign(key, OpenSSL::Digest::SHA256.new)
|
50
|
-
#end
|
51
|
-
|
52
31
|
# start a local web server, then start a browser that will callback the local server upon authentication
|
53
|
-
class WebAuth
|
54
|
-
attr_reader :expected_path
|
32
|
+
class WebAuth < WebServerSimple
|
33
|
+
attr_reader :expected_path, :mutex, :cond
|
55
34
|
attr_writer :query
|
35
|
+
|
56
36
|
# @param endpoint_url [String] e.g. 'https://127.0.0.1:12345'
|
57
37
|
def initialize(endpoint_url)
|
58
38
|
uri = URI.parse(endpoint_url)
|
39
|
+
super(uri)
|
59
40
|
# parameters for servlet
|
60
|
-
@query = nil
|
61
41
|
@mutex = Mutex.new
|
62
42
|
@cond = ConditionVariable.new
|
63
43
|
@expected_path = uri.path.empty? ? '/' : uri.path
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
Port: uri.port,
|
68
|
-
Logger: Log.log,
|
69
|
-
AccessLog: [[self, WEBrick::AccessLog::COMMON_LOG_FORMAT]] # replace default access log to call local method "<<" below
|
70
|
-
}
|
71
|
-
case uri.scheme
|
72
|
-
when 'http'
|
73
|
-
Log.log.debug('HTTP mode')
|
74
|
-
when 'https'
|
75
|
-
webrick_options[:SSLEnable] = true
|
76
|
-
# a- automatic certificate generation
|
77
|
-
webrick_options[:SSLCertName] = [['CN',WEBrick::Utils.getservername]]
|
78
|
-
# b- generate self signed cert
|
79
|
-
#webrick_options[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(4096)
|
80
|
-
#webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new
|
81
|
-
#self.class.fill_self_signed_cert(webrick_options[:SSLCertificate],webrick_options[:SSLPrivateKey])
|
82
|
-
## c- good cert
|
83
|
-
#webrick_options[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(File.read('.../myserver.key'))
|
84
|
-
#webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read('.../myserver.crt'))
|
85
|
-
end
|
86
|
-
# self signed certificate generates characters on STDERR, see create_self_signed_cert in webrick/ssl.rb
|
87
|
-
Log.capture_stderr { @server = WEBrick::HTTPServer.new(webrick_options) }
|
88
|
-
@server.mount(@expected_path, WebAuthServlet, self) # additional args provided to constructor
|
89
|
-
Thread.new { @server.start }
|
90
|
-
end
|
91
|
-
|
92
|
-
# log web server access ( option AccessLog )
|
93
|
-
def <<(access_log)
|
94
|
-
Log.log.debug{"webrick log #{access_log.chomp}"}
|
44
|
+
@query = nil
|
45
|
+
mount(@expected_path, WebAuthServlet, self) # additional args provided to constructor
|
46
|
+
Thread.new { start }
|
95
47
|
end
|
96
48
|
|
97
49
|
# wait for request on web server
|
98
50
|
# @return Hash the query
|
99
51
|
def received_request
|
100
|
-
# shall be called only once
|
101
|
-
raise 'error, received_request called twice ?' if @server.nil?
|
102
52
|
# wait for signal from thread
|
103
53
|
@mutex.synchronize{@cond.wait(@mutex)}
|
104
54
|
# tell server thread to stop
|
105
|
-
|
106
|
-
@server = nil
|
55
|
+
shutdown
|
107
56
|
return @query
|
108
57
|
end
|
109
58
|
end
|