aspera-cli 4.7.0 → 4.8.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 +1 -0
- data/README.md +844 -861
- data/bin/ascli +20 -1
- data/bin/asession +37 -34
- data/docs/test_env.conf +11 -3
- data/examples/aoc.rb +13 -12
- data/examples/dascli +26 -0
- data/examples/faspex4.rb +34 -29
- data/examples/transfer.rb +30 -29
- data/lib/aspera/aoc.rb +151 -143
- data/lib/aspera/ascmd.rb +56 -45
- data/lib/aspera/ats_api.rb +6 -5
- data/lib/aspera/cli/basic_auth_plugin.rb +18 -16
- data/lib/aspera/cli/extended_value.rb +32 -30
- data/lib/aspera/cli/formater.rb +103 -111
- data/lib/aspera/cli/info.rb +2 -1
- data/lib/aspera/cli/listener/line_dump.rb +1 -0
- data/lib/aspera/cli/listener/logger.rb +1 -0
- data/lib/aspera/cli/listener/progress.rb +13 -12
- data/lib/aspera/cli/listener/progress_multi.rb +21 -20
- data/lib/aspera/cli/main.rb +106 -89
- data/lib/aspera/cli/manager.rb +96 -85
- data/lib/aspera/cli/plugin.rb +50 -32
- data/lib/aspera/cli/plugins/alee.rb +6 -5
- data/lib/aspera/cli/plugins/aoc.rb +521 -426
- data/lib/aspera/cli/plugins/ats.rb +84 -83
- data/lib/aspera/cli/plugins/bss.rb +30 -27
- data/lib/aspera/cli/plugins/config.rb +483 -397
- data/lib/aspera/cli/plugins/console.rb +17 -15
- data/lib/aspera/cli/plugins/cos.rb +26 -35
- data/lib/aspera/cli/plugins/faspex.rb +201 -168
- data/lib/aspera/cli/plugins/faspex5.rb +109 -74
- data/lib/aspera/cli/plugins/node.rb +378 -189
- data/lib/aspera/cli/plugins/orchestrator.rb +71 -65
- data/lib/aspera/cli/plugins/preview.rb +131 -122
- data/lib/aspera/cli/plugins/server.rb +94 -93
- data/lib/aspera/cli/plugins/shares.rb +42 -28
- data/lib/aspera/cli/plugins/sync.rb +15 -14
- data/lib/aspera/cli/transfer_agent.rb +56 -52
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/colors.rb +29 -28
- data/lib/aspera/command_line_builder.rb +50 -43
- data/lib/aspera/cos_node.rb +64 -38
- data/lib/aspera/data_repository.rb +1 -0
- data/lib/aspera/environment.rb +18 -8
- data/lib/aspera/fasp/agent_base.rb +26 -23
- data/lib/aspera/fasp/agent_connect.rb +35 -30
- data/lib/aspera/fasp/agent_direct.rb +68 -60
- data/lib/aspera/fasp/agent_httpgw.rb +71 -64
- data/lib/aspera/fasp/agent_node.rb +24 -23
- data/lib/aspera/fasp/agent_trsdk.rb +19 -20
- data/lib/aspera/fasp/error.rb +2 -1
- data/lib/aspera/fasp/error_info.rb +79 -68
- data/lib/aspera/fasp/installation.rb +122 -114
- data/lib/aspera/fasp/listener.rb +1 -0
- data/lib/aspera/fasp/parameters.rb +44 -41
- data/lib/aspera/fasp/resume_policy.rb +14 -11
- data/lib/aspera/fasp/transfer_spec.rb +6 -5
- data/lib/aspera/fasp/uri.rb +25 -24
- data/lib/aspera/faspex_gw.rb +83 -72
- data/lib/aspera/hash_ext.rb +10 -12
- data/lib/aspera/id_generator.rb +8 -7
- data/lib/aspera/keychain/encrypted_hash.rb +60 -45
- data/lib/aspera/keychain/macos_security.rb +26 -24
- data/lib/aspera/log.rb +34 -38
- data/lib/aspera/nagios.rb +14 -13
- data/lib/aspera/node.rb +19 -19
- data/lib/aspera/oauth.rb +121 -101
- data/lib/aspera/open_application.rb +6 -5
- data/lib/aspera/persistency_action_once.rb +9 -8
- data/lib/aspera/persistency_folder.rb +10 -9
- data/lib/aspera/preview/file_types.rb +261 -266
- data/lib/aspera/preview/generator.rb +74 -73
- data/lib/aspera/preview/image_error.png +0 -0
- data/lib/aspera/preview/options.rb +7 -6
- data/lib/aspera/preview/utils.rb +30 -33
- data/lib/aspera/preview/video_error.png +0 -0
- data/lib/aspera/proxy_auto_config.rb +25 -23
- data/lib/aspera/rest.rb +73 -74
- data/lib/aspera/rest_call_error.rb +1 -0
- data/lib/aspera/rest_error_analyzer.rb +11 -9
- data/lib/aspera/rest_errors_aspera.rb +5 -4
- data/lib/aspera/secret_hider.rb +68 -0
- data/lib/aspera/ssh.rb +12 -10
- data/lib/aspera/sync.rb +49 -47
- data/lib/aspera/temp_file_manager.rb +7 -5
- data/lib/aspera/timer_limiter.rb +9 -8
- data/lib/aspera/uri_reader.rb +11 -14
- data/lib/aspera/web_auth.rb +17 -15
- data.tar.gz.sig +0 -0
- metadata +117 -34
- metadata.gz.sig +2 -0
- data/bin/dascli +0 -13
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'singleton'
|
3
4
|
require 'aspera/log'
|
4
5
|
|
@@ -7,22 +8,22 @@ module Aspera
|
|
7
8
|
# implements a simple resume policy
|
8
9
|
class ResumePolicy
|
9
10
|
# list of supported parameters and default values
|
10
|
-
DEFAULTS={
|
11
|
-
iter_max:
|
12
|
-
sleep_initial:
|
13
|
-
sleep_factor:
|
14
|
-
sleep_max:
|
15
|
-
}
|
11
|
+
DEFAULTS = {
|
12
|
+
iter_max: 7,
|
13
|
+
sleep_initial: 2,
|
14
|
+
sleep_factor: 2,
|
15
|
+
sleep_max: 60
|
16
|
+
}.freeze
|
16
17
|
|
17
18
|
# @param params see DEFAULTS
|
18
19
|
def initialize(params=nil)
|
19
|
-
@parameters=DEFAULTS.
|
20
|
+
@parameters = DEFAULTS.dup
|
20
21
|
if !params.nil?
|
21
22
|
raise "expecting Hash (or nil), but have #{params.class}" unless params.is_a?(Hash)
|
22
23
|
params.each do |k,v|
|
23
24
|
raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map(&:to_s).join(',')}" unless DEFAULTS.has_key?(k)
|
24
25
|
raise "#{k} must be Integer" unless v.is_a?(Integer)
|
25
|
-
@parameters[k]=v
|
26
|
+
@parameters[k] = v
|
26
27
|
end
|
27
28
|
end
|
28
29
|
Log.log.debug("resume params=#{@parameters}")
|
@@ -30,7 +31,8 @@ module Aspera
|
|
30
31
|
|
31
32
|
# calls block a number of times (resumes) until success or limit reached
|
32
33
|
# this is re-entrant, one resumer can handle multiple transfers in //
|
33
|
-
def
|
34
|
+
def execute_with_resume
|
35
|
+
raise 'block manndatory' unless block_given?
|
34
36
|
# maximum of retry
|
35
37
|
remaining_resumes = @parameters[:iter_max]
|
36
38
|
sleep_seconds = @parameters[:sleep_initial]
|
@@ -39,7 +41,8 @@ module Aspera
|
|
39
41
|
loop do
|
40
42
|
Log.log.debug('transfer starting');
|
41
43
|
begin
|
42
|
-
block
|
44
|
+
# call provided block
|
45
|
+
yield
|
43
46
|
break
|
44
47
|
rescue Fasp::Error => e
|
45
48
|
Log.log.warn("An error occured: #{e.message}");
|
@@ -57,7 +60,7 @@ module Aspera
|
|
57
60
|
end
|
58
61
|
|
59
62
|
# take this retry in account
|
60
|
-
remaining_resumes-=1
|
63
|
+
remaining_resumes -= 1
|
61
64
|
Log.log.warn("resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})");
|
62
65
|
|
63
66
|
# wait a bit before retrying, maybe network condition will be better
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'aspera/fasp/parameters'
|
3
4
|
|
4
5
|
module Aspera
|
@@ -6,14 +7,14 @@ module Aspera
|
|
6
7
|
# parameters for Transfer Spec
|
7
8
|
class TransferSpec
|
8
9
|
# default transfer username for access key based transfers
|
9
|
-
ACCESS_KEY_TRANSFER_USER='xfer'
|
10
|
-
SSH_PORT=33_001
|
11
|
-
UDP_PORT=33_001
|
12
|
-
AK_TSPEC_BASE={
|
10
|
+
ACCESS_KEY_TRANSFER_USER = 'xfer'
|
11
|
+
SSH_PORT = 33_001
|
12
|
+
UDP_PORT = 33_001
|
13
|
+
AK_TSPEC_BASE = {
|
13
14
|
'remote_user' => ACCESS_KEY_TRANSFER_USER,
|
14
15
|
'ssh_port' => SSH_PORT,
|
15
16
|
'fasp_port' => UDP_PORT
|
16
|
-
}
|
17
|
+
}.freeze
|
17
18
|
# define constants for enums of parameters: <paramater>_<enum>, e.g. CIPHER_AES_128
|
18
19
|
Aspera::Fasp::Parameters.description.each do |k,v|
|
19
20
|
next unless v[:enum].is_a?(Array)
|
data/lib/aspera/fasp/uri.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'aspera/log'
|
3
4
|
require 'aspera/command_line_builder'
|
4
5
|
|
@@ -7,38 +8,38 @@ module Aspera
|
|
7
8
|
# translates a "faspe:" URI (used in Faspex) into transfer spec hash
|
8
9
|
class Uri
|
9
10
|
def initialize(fasplink)
|
10
|
-
@fasp_uri=URI.parse(fasplink.gsub(' ','%20'))
|
11
|
+
@fasp_uri = URI.parse(fasplink.gsub(' ','%20'))
|
11
12
|
# TODO: check scheme is faspe
|
12
13
|
end
|
13
14
|
|
14
15
|
def transfer_spec
|
15
|
-
result_ts={}
|
16
|
-
result_ts['remote_host']
|
17
|
-
result_ts['remote_user']
|
18
|
-
result_ts['ssh_port']
|
19
|
-
result_ts['paths']=[{'source'=>URI.decode_www_form_component(@fasp_uri.path)}]
|
16
|
+
result_ts = {}
|
17
|
+
result_ts['remote_host'] = @fasp_uri.host
|
18
|
+
result_ts['remote_user'] = @fasp_uri.user
|
19
|
+
result_ts['ssh_port'] = @fasp_uri.port
|
20
|
+
result_ts['paths'] = [{'source' => URI.decode_www_form_component(@fasp_uri.path)}]
|
20
21
|
# faspex does not encode trailing base64 encoded tags, fix that
|
21
|
-
fixed_query = @fasp_uri.query.gsub(/(=+)$/){|x|'%3D'*x.length}
|
22
|
+
fixed_query = @fasp_uri.query.gsub(/(=+)$/){|x|'%3D' * x.length}
|
22
23
|
|
23
24
|
URI.decode_www_form(fixed_query).each do |i|
|
24
|
-
name=i[0]
|
25
|
-
value=i[1]
|
25
|
+
name = i[0]
|
26
|
+
value = i[1]
|
26
27
|
case name
|
27
|
-
when 'cookie' then result_ts['cookie']=value
|
28
|
-
when 'token' then result_ts['token']=value
|
29
|
-
when 'sshfp' then result_ts['sshfp']=value
|
30
|
-
when 'policy' then result_ts['rate_policy']=value
|
31
|
-
when 'httpport' then result_ts['http_fallback_port']=value.to_i
|
32
|
-
when 'targetrate' then result_ts['target_rate_kbps']=value.to_i
|
33
|
-
when 'minrate' then result_ts['min_rate_kbps']=value.to_i
|
34
|
-
when 'port' then result_ts['fasp_port']=value.to_i
|
35
|
-
when 'bwcap' then result_ts['target_rate_cap_kbps']=value.to_i
|
36
|
-
when 'enc' then result_ts['cipher']=value.gsub(/^aes/,'aes-').gsub(/cfb$/,'-cfb').gsub(/gcm$/,'-gcm').gsub(/--/,'-')
|
37
|
-
when 'tags64' then result_ts['tags']=JSON.parse(Base64.strict_decode64(value))
|
38
|
-
when 'createpath' then result_ts['create_dir']=CommandLineBuilder.yes_to_true(value)
|
39
|
-
when 'fallback' then result_ts['http_fallback']=CommandLineBuilder.yes_to_true(value)
|
40
|
-
when 'lockpolicy' then result_ts['lock_rate_policy']=CommandLineBuilder.yes_to_true(value)
|
41
|
-
when 'lockminrate' then result_ts['lock_min_rate']=CommandLineBuilder.yes_to_true(value)
|
28
|
+
when 'cookie' then result_ts['cookie'] = value
|
29
|
+
when 'token' then result_ts['token'] = value
|
30
|
+
when 'sshfp' then result_ts['sshfp'] = value
|
31
|
+
when 'policy' then result_ts['rate_policy'] = value
|
32
|
+
when 'httpport' then result_ts['http_fallback_port'] = value.to_i
|
33
|
+
when 'targetrate' then result_ts['target_rate_kbps'] = value.to_i
|
34
|
+
when 'minrate' then result_ts['min_rate_kbps'] = value.to_i
|
35
|
+
when 'port' then result_ts['fasp_port'] = value.to_i
|
36
|
+
when 'bwcap' then result_ts['target_rate_cap_kbps'] = value.to_i
|
37
|
+
when 'enc' then result_ts['cipher'] = value.gsub(/^aes/,'aes-').gsub(/cfb$/,'-cfb').gsub(/gcm$/,'-gcm').gsub(/--/,'-')
|
38
|
+
when 'tags64' then result_ts['tags'] = JSON.parse(Base64.strict_decode64(value))
|
39
|
+
when 'createpath' then result_ts['create_dir'] = CommandLineBuilder.yes_to_true(value)
|
40
|
+
when 'fallback' then result_ts['http_fallback'] = CommandLineBuilder.yes_to_true(value)
|
41
|
+
when 'lockpolicy' then result_ts['lock_rate_policy'] = CommandLineBuilder.yes_to_true(value)
|
42
|
+
when 'lockminrate' then result_ts['lock_min_rate'] = CommandLineBuilder.yes_to_true(value)
|
42
43
|
when 'auth' then Log.log.debug("ignoring auth #{name}=#{value}") # TODO: translate into transfer spec ? yes/no
|
43
44
|
when 'v' then Log.log.debug("ignoring v #{name}=#{value}") # TODO: translate into transfer spec ? 2
|
44
45
|
when 'protect' then Log.log.debug("ignoring protect #{name}=#{value}") # TODO: translate into transfer spec ?
|
data/lib/aspera/faspex_gw.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'aspera/log'
|
3
4
|
require 'aspera/aoc'
|
4
5
|
require 'aspera/fasp/transfer_spec'
|
@@ -13,14 +14,14 @@ module Aspera
|
|
13
14
|
# this class answers the Faspex /send API and creates a package on Aspera on Cloud
|
14
15
|
class FaspexGW
|
15
16
|
class FxGwServlet < WEBrick::HTTPServlet::AbstractServlet
|
16
|
-
def initialize(_server,a_aoc_api_user,a_workspace_id)
|
17
|
+
def initialize(_server, a_aoc_api_user, a_workspace_id)
|
17
18
|
super
|
18
|
-
@aoc_api_user=a_aoc_api_user
|
19
|
-
@aoc_workspace_id=a_workspace_id
|
19
|
+
@aoc_api_user = a_aoc_api_user
|
20
|
+
@aoc_workspace_id = a_workspace_id
|
20
21
|
end
|
21
22
|
|
22
23
|
# parameters from user to Faspex API call
|
23
|
-
#{"delivery":{"use_encryption_at_rest":false,"note":"note","sources":[{"paths":["file1"]}],"title":"my title","recipients":["email1"],"send_upload_result":true}}
|
24
|
+
# {"delivery":{"use_encryption_at_rest":false,"note":"note","sources":[{"paths":["file1"]}],"title":"my title","recipients":["email1"],"send_upload_result":true}}
|
24
25
|
# {
|
25
26
|
# "delivery"=>{
|
26
27
|
# "use_encryption_at_rest"=>false,
|
@@ -33,127 +34,137 @@ module Aspera
|
|
33
34
|
# }
|
34
35
|
def process_faspex_send(request, response)
|
35
36
|
raise 'no payload' if request.body.nil?
|
36
|
-
|
37
|
-
|
37
|
+
|
38
|
+
faspex_pkg_parameters = JSON.parse(request.body)
|
39
|
+
faspex_pkg_delivery = faspex_pkg_parameters['delivery']
|
38
40
|
Log.log.debug("faspex pkg create parameters=#{faspex_pkg_parameters}")
|
39
41
|
|
40
42
|
# get recipient ids
|
41
|
-
files_pkg_recipients=[]
|
43
|
+
files_pkg_recipients = []
|
42
44
|
faspex_pkg_delivery['recipients'].each do |recipient_email|
|
43
|
-
user_lookup
|
44
|
-
|
45
|
-
|
46
|
-
|
45
|
+
user_lookup = @aoc_api_user.read('contacts',
|
46
|
+
{ 'current_workspace_id' => @aoc_workspace_id, 'q' => recipient_email })[:data]
|
47
|
+
raise StandardError,
|
48
|
+
"no such unique user: #{recipient_email} / #{user_lookup}" unless !user_lookup.nil? && user_lookup.length.eql?(1)
|
49
|
+
|
50
|
+
recipient_user_info = user_lookup.first
|
51
|
+
files_pkg_recipients.push({
|
52
|
+
'id' => recipient_user_info['source_id'],
|
53
|
+
'type' => recipient_user_info['source_type']
|
54
|
+
})
|
47
55
|
end
|
48
56
|
|
49
57
|
# create a new package with one file
|
50
|
-
the_package
|
51
|
-
'file_names'
|
52
|
-
'name'
|
53
|
-
'note'
|
54
|
-
'recipients'
|
55
|
-
'workspace_id'
|
58
|
+
the_package = @aoc_api_user.create('packages', {
|
59
|
+
'file_names' => faspex_pkg_delivery['sources'][0]['paths'],
|
60
|
+
'name' => faspex_pkg_delivery['title'],
|
61
|
+
'note' => faspex_pkg_delivery['note'],
|
62
|
+
'recipients' => files_pkg_recipients,
|
63
|
+
'workspace_id' => @aoc_workspace_id
|
64
|
+
})[:data]
|
56
65
|
|
57
66
|
# get node information for the node on which package must be created
|
58
|
-
node_info
|
67
|
+
node_info = @aoc_api_user.read("nodes/#{the_package['node_id']}")[:data]
|
59
68
|
|
60
69
|
# get transfer token (for node)
|
61
|
-
node_auth_bearer_token
|
70
|
+
node_auth_bearer_token = @aoc_api_user.oauth_token(scope: AoC.node_scope(node_info['access_key'],
|
71
|
+
AoC::SCOPE_NODE_USER))
|
62
72
|
|
63
73
|
# tell Files what to expect in package: 1 transfer (can also be done after transfer)
|
64
|
-
@aoc_api_user.update("packages/#{the_package['id']}",{'sent'=>true,'transfers_expected'=>1})
|
74
|
+
@aoc_api_user.update("packages/#{the_package['id']}", { 'sent' => true, 'transfers_expected' => 1 })
|
65
75
|
|
66
76
|
# to return an error:
|
67
77
|
# response.status=400
|
68
78
|
# return 'ERROR HERE'
|
69
79
|
|
70
80
|
# TODO: check about xfer_*
|
71
|
-
ts_tags={
|
81
|
+
ts_tags = {
|
72
82
|
'aspera' => {
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
83
|
+
'files' => { 'package_id' => the_package['id'], 'package_operation' => 'upload' },
|
84
|
+
'node' => { 'access_key' => node_info['access_key'], 'file_id' => the_package['contents_file_id'] },
|
85
|
+
'xfer_id' => SecureRandom.uuid,
|
86
|
+
'xfer_retry' => 3600
|
87
|
+
}
|
88
|
+
}
|
77
89
|
# this transfer spec is for transfer to AoC
|
78
|
-
faspex_transfer_spec={
|
79
|
-
'direction'
|
80
|
-
'remote_host'
|
81
|
-
'remote_user'
|
82
|
-
'ssh_port'
|
83
|
-
'fasp_port'
|
84
|
-
'tags'
|
85
|
-
'token'
|
86
|
-
'paths'
|
87
|
-
'cookie'
|
88
|
-
'create_dir'
|
89
|
-
'rate_policy'
|
90
|
-
'rate_policy_allowed'
|
91
|
-
'min_rate_cap_kbps'
|
92
|
-
'min_rate_kbps'
|
90
|
+
faspex_transfer_spec = {
|
91
|
+
'direction' => 'send',
|
92
|
+
'remote_host' => node_info['host'],
|
93
|
+
'remote_user' => Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER,
|
94
|
+
'ssh_port' => Fasp::TransferSpec::SSH_PORT,
|
95
|
+
'fasp_port' => Fasp::TransferSpec::UDP_PORT,
|
96
|
+
'tags' => ts_tags,
|
97
|
+
'token' => node_auth_bearer_token,
|
98
|
+
'paths' => [{ 'destination' => '/' }],
|
99
|
+
'cookie' => 'unused',
|
100
|
+
'create_dir' => true,
|
101
|
+
'rate_policy' => 'fair',
|
102
|
+
'rate_policy_allowed' => 'fixed',
|
103
|
+
'min_rate_cap_kbps' => nil,
|
104
|
+
'min_rate_kbps' => 0,
|
93
105
|
'target_rate_percentage' => nil,
|
94
|
-
'lock_target_rate'
|
95
|
-
'fasp_url'
|
96
|
-
'lock_min_rate'
|
97
|
-
'lock_rate_policy'
|
98
|
-
'source_root'
|
99
|
-
'content_protection'
|
100
|
-
'target_rate_cap_kbps'
|
101
|
-
'target_rate_kbps'
|
102
|
-
'cipher'
|
103
|
-
'cipher_allowed'
|
104
|
-
'http_fallback'
|
105
|
-
'http_fallback_port'
|
106
|
-
'https_fallback_port'
|
107
|
-
'destination_root'
|
106
|
+
'lock_target_rate' => nil,
|
107
|
+
'fasp_url' => 'unused',
|
108
|
+
'lock_min_rate' => true,
|
109
|
+
'lock_rate_policy' => true,
|
110
|
+
'source_root' => '',
|
111
|
+
'content_protection' => nil,
|
112
|
+
'target_rate_cap_kbps' => 20_000, # TODO: is this value useful ?
|
113
|
+
'target_rate_kbps' => 10_000, # TODO: get from where?
|
114
|
+
'cipher' => 'aes-128',
|
115
|
+
'cipher_allowed' => nil,
|
116
|
+
'http_fallback' => false,
|
117
|
+
'http_fallback_port' => nil,
|
118
|
+
'https_fallback_port' => nil,
|
119
|
+
'destination_root' => '/'
|
108
120
|
}
|
109
121
|
# but we place it in a Faspex package creation response
|
110
|
-
faspex_package_create_result={
|
111
|
-
'links'
|
122
|
+
faspex_package_create_result = {
|
123
|
+
'links' => { 'status' => 'unused' },
|
112
124
|
'xfer_sessions' => [faspex_transfer_spec]
|
113
125
|
}
|
114
126
|
Log.log.info("faspex_package_create_result=#{faspex_package_create_result}")
|
115
|
-
response.status=200
|
127
|
+
response.status = 200
|
116
128
|
response.content_type = 'application/json'
|
117
|
-
response.body=JSON.generate(faspex_package_create_result)
|
129
|
+
response.body = JSON.generate(faspex_package_create_result)
|
118
130
|
end
|
119
131
|
|
120
|
-
def do_GET(request, response)
|
132
|
+
def do_GET(request, response)
|
121
133
|
case request.path
|
122
134
|
when '/aspera/faspex/send'
|
123
135
|
process_faspex_send(request, response)
|
124
136
|
else
|
125
|
-
response.status=400
|
126
|
-
|
137
|
+
response.status = 400
|
138
|
+
'ERROR HERE'
|
127
139
|
end
|
128
140
|
end
|
129
141
|
end # FxGwServlet
|
130
142
|
|
131
143
|
class NewUserServlet < WEBrick::HTTPServlet::AbstractServlet
|
132
|
-
def do_GET(request, response)
|
144
|
+
def do_GET(request, response)
|
133
145
|
case request.path
|
134
146
|
when '/newuser'
|
135
|
-
response.status=200
|
147
|
+
response.status = 200
|
136
148
|
response.content_type = 'text/html'
|
137
|
-
response.body='<html><body>hello world</body></html>'
|
149
|
+
response.body = '<html><body>hello world</body></html>'
|
138
150
|
else
|
139
151
|
raise "unsupported path: [#{request.path}]"
|
140
152
|
end
|
141
153
|
end
|
142
154
|
end
|
143
155
|
|
144
|
-
def initialize(a_aoc_api_user,a_workspace_id)
|
156
|
+
def initialize(a_aoc_api_user, a_workspace_id)
|
145
157
|
webrick_options = {
|
146
|
-
Port:
|
147
|
-
Logger:
|
148
|
-
SSLEnable:
|
149
|
-
|
150
|
-
SSLCertName: [['CN',WEBrick::Utils.getservername]]
|
158
|
+
Port: 9443,
|
159
|
+
Logger: Log.log,
|
160
|
+
SSLEnable: true,
|
161
|
+
SSLCertName: [['CN', WEBrick::Utils.getservername]]
|
151
162
|
}
|
152
163
|
Log.log.info("Server started on port #{webrick_options[:Port]}")
|
153
164
|
@server = WEBrick::HTTPServer.new(webrick_options)
|
154
|
-
@server.mount('/aspera/faspex', FxGwServlet,a_aoc_api_user,a_workspace_id)
|
165
|
+
@server.mount('/aspera/faspex', FxGwServlet, a_aoc_api_user, a_workspace_id)
|
155
166
|
@server.mount('/newuser', NewUserServlet)
|
156
|
-
trap('INT') {@server.shutdown}
|
167
|
+
trap('INT') { @server.shutdown }
|
157
168
|
end
|
158
169
|
|
159
170
|
def start_server
|
data/lib/aspera/hash_ext.rb
CHANGED
@@ -1,22 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# for older rubies
|
3
|
-
unless Hash.method_defined?(:dig)
|
4
|
-
class Hash
|
5
|
-
def dig(*path)
|
6
|
-
path.inject(self) do |location, key|
|
7
|
-
location.respond_to?(:keys) ? location[key] : nil
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
2
|
|
13
3
|
class ::Hash
|
14
4
|
def deep_merge(second)
|
15
|
-
merge(second){|_key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge(v2) : v2}
|
5
|
+
merge(second){|_key,v1,v2|Hash === v1 && Hash === v2 ? v1.deep_merge(v2) : v2}
|
16
6
|
end
|
17
7
|
|
18
8
|
def deep_merge!(second)
|
19
|
-
merge!(second){|_key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge!(v2) : v2}
|
9
|
+
merge!(second){|_key,v1,v2|Hash === v1 && Hash === v2 ? v1.deep_merge!(v2) : v2}
|
20
10
|
end
|
21
11
|
end
|
22
12
|
|
@@ -27,3 +17,11 @@ unless Hash.method_defined?(:symbolize_keys)
|
|
27
17
|
end
|
28
18
|
end
|
29
19
|
end
|
20
|
+
|
21
|
+
unless Hash.method_defined?(:stringify_keys)
|
22
|
+
class Hash
|
23
|
+
def stringify_keys
|
24
|
+
return each_with_object({}){|(k,v),memo| memo[k.to_s] = v }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/aspera/id_generator.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'uri'
|
3
4
|
|
4
5
|
module Aspera
|
5
6
|
class IdGenerator
|
6
|
-
ID_SEPARATOR='_'
|
7
|
-
WINDOWS_PROTECTED_CHAR
|
8
|
-
PROTECTED_CHAR_REPLACE='_'
|
7
|
+
ID_SEPARATOR = '_'
|
8
|
+
WINDOWS_PROTECTED_CHAR = %r{[/:"<>\\*?]}.freeze
|
9
|
+
PROTECTED_CHAR_REPLACE = '_'
|
9
10
|
private_constant :ID_SEPARATOR,:PROTECTED_CHAR_REPLACE,:WINDOWS_PROTECTED_CHAR
|
10
11
|
def self.from_list(object_id)
|
11
12
|
if object_id.is_a?(Array)
|
12
|
-
object_id=object_id.
|
13
|
+
object_id = object_id.compact.map do |i|
|
13
14
|
i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
|
14
15
|
end.join(ID_SEPARATOR)
|
15
16
|
end
|
16
17
|
raise 'id must be a String' unless object_id.is_a?(String)
|
17
18
|
return object_id.
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
gsub(WINDOWS_PROTECTED_CHAR,PROTECTED_CHAR_REPLACE). # remove windows forbidden chars
|
20
|
+
gsub('.',PROTECTED_CHAR_REPLACE). # keep dot for extension only (nicer)
|
21
|
+
downcase
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aspera/hash_ext'
|
2
4
|
require 'openssl'
|
3
5
|
|
4
6
|
module Aspera
|
5
7
|
module Keychain
|
6
8
|
class SimpleCipher
|
7
9
|
def initialize(key)
|
8
|
-
@key=Digest::SHA1.hexdigest(key)[0..23]
|
10
|
+
@key = Digest::SHA1.hexdigest(key+('*'*23))[0..23]
|
9
11
|
@cipher = OpenSSL::Cipher.new('DES-EDE3-CBC')
|
10
12
|
end
|
11
13
|
|
@@ -26,92 +28,105 @@ module Aspera
|
|
26
28
|
|
27
29
|
# Manage secrets in a simple Hash
|
28
30
|
class EncryptedHash
|
29
|
-
SEPARATOR='%'
|
31
|
+
SEPARATOR = '%'
|
32
|
+
ACCEPTED_KEYS = %i[username url secret description].freeze
|
30
33
|
private_constant :SEPARATOR
|
34
|
+
attr_reader :legacy_detected
|
31
35
|
def initialize(values)
|
32
36
|
raise 'values shall be Hash' unless values.is_a?(Hash)
|
33
|
-
@all_secrets=values
|
37
|
+
@all_secrets = values
|
38
|
+
end
|
39
|
+
|
40
|
+
def identifier(options)
|
41
|
+
return options[:username] if options[:url].to_s.empty?
|
42
|
+
%i[url username].map{|s|options[s]}.join(SEPARATOR)
|
34
43
|
end
|
35
44
|
|
36
45
|
def set(options)
|
37
46
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
38
|
-
unsupported=options.keys-
|
47
|
+
unsupported = options.keys - ACCEPTED_KEYS
|
39
48
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
40
|
-
username=options[:username]
|
49
|
+
username = options[:username]
|
41
50
|
raise 'options shall have username' if username.nil?
|
42
|
-
url=options[:url]
|
51
|
+
url = options[:url]
|
43
52
|
raise 'options shall have username' if url.nil?
|
44
|
-
secret=options[:secret]
|
53
|
+
secret = options[:secret]
|
45
54
|
raise 'options shall have secret' if secret.nil?
|
46
|
-
key=
|
55
|
+
key = identifier(options)
|
47
56
|
raise "secret #{key} already exist, delete first" if @all_secrets.has_key?(key)
|
48
|
-
obj={username: username, url: url, secret: SimpleCipher.new(key).encrypt(secret)}
|
49
|
-
obj[:description]=options[:description] if options.has_key?(:description)
|
50
|
-
@all_secrets[key]=obj
|
57
|
+
obj = {username: username, url: url, secret: SimpleCipher.new(key).encrypt(secret)}
|
58
|
+
obj[:description] = options[:description] if options.has_key?(:description)
|
59
|
+
@all_secrets[key] = obj.stringify_keys
|
51
60
|
nil
|
52
61
|
end
|
53
62
|
|
54
63
|
def list
|
55
|
-
result=[]
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
64
|
+
result = []
|
65
|
+
legacy_detected=false
|
66
|
+
@all_secrets.each do |name,value|
|
67
|
+
normal = # normalized version
|
68
|
+
case value
|
69
|
+
when String
|
70
|
+
legacy_detected=true
|
71
|
+
{username: name, url: '', secret: value}
|
72
|
+
when Hash then value.symbolize_keys
|
73
|
+
else raise 'error secret must be String (legacy) or Hash (new)'
|
74
|
+
end
|
75
|
+
normal[:description] = '' unless normal.has_key?(:description)
|
76
|
+
extraneous_keys=normal.keys - ACCEPTED_KEYS
|
77
|
+
Log.log.error("wrongs keys in secret hash: #{extraneous_keys.map(&:to_s).join(',')}") unless extraneous_keys.empty?
|
78
|
+
result.push(normal)
|
68
79
|
end
|
80
|
+
Log.log.warn('Legacy vault format detected in config file, please refer to documentation to convert to new format.') if legacy_detected
|
69
81
|
return result
|
70
82
|
end
|
71
83
|
|
72
84
|
def delete(options)
|
73
85
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
74
|
-
unsupported=options.keys-[
|
86
|
+
unsupported = options.keys - %i[username url]
|
75
87
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
76
|
-
username=options[:username]
|
88
|
+
username = options[:username]
|
77
89
|
raise 'options shall have username' if username.nil?
|
78
|
-
url=options[:url]
|
79
|
-
key=nil
|
90
|
+
url = options[:url]
|
91
|
+
key = nil
|
80
92
|
if !url.nil?
|
81
|
-
extk=
|
82
|
-
key=extk if @all_secrets.has_key?(extk)
|
93
|
+
extk = identifier(options)
|
94
|
+
key = extk if @all_secrets.has_key?(extk)
|
83
95
|
end
|
84
96
|
# backward compatibility: TODO: remove in future ? (make url mandatory ?)
|
85
|
-
key=username if key.nil? && @all_secrets.has_key?(username)
|
97
|
+
key = username if key.nil? && @all_secrets.has_key?(username)
|
86
98
|
raise 'no such secret' if key.nil?
|
87
99
|
@all_secrets.delete(key)
|
88
100
|
end
|
89
101
|
|
90
102
|
def get(options)
|
91
103
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
92
|
-
unsupported=options.keys-[
|
104
|
+
unsupported = options.keys - %i[username url]
|
93
105
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
94
|
-
username=options[:username]
|
106
|
+
username = options[:username]
|
95
107
|
raise 'options shall have username' if username.nil?
|
96
|
-
url=options[:url]
|
97
|
-
|
108
|
+
url = options[:url]
|
109
|
+
info = nil
|
98
110
|
if !url.nil?
|
99
|
-
|
111
|
+
info = @all_secrets[identifier(options)]
|
100
112
|
end
|
101
113
|
# backward compatibility: TODO: remove in future ? (make url mandatory ?)
|
102
|
-
if
|
103
|
-
|
114
|
+
if info.nil?
|
115
|
+
info = @all_secrets[username]
|
104
116
|
end
|
105
|
-
result=options.clone
|
106
|
-
case
|
117
|
+
result = options.clone
|
118
|
+
case info
|
107
119
|
when NilClass
|
108
|
-
raise
|
120
|
+
raise "no such secret: #{options[:url]} #{username}"
|
109
121
|
when String
|
110
|
-
result
|
122
|
+
result[:secret] = info
|
123
|
+
result[:description] = ''
|
111
124
|
when Hash
|
112
|
-
|
113
|
-
|
114
|
-
|
125
|
+
info=info.symbolize_keys
|
126
|
+
key = identifier(options)
|
127
|
+
plain = SimpleCipher.new(key).decrypt(info[:secret])
|
128
|
+
result[:secret] = plain
|
129
|
+
result[:description] = info[:description]
|
115
130
|
else raise 'error'
|
116
131
|
end
|
117
132
|
return result
|