aspera-cli 4.4.0 → 4.7.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
- data/README.md +2095 -1503
- data/bin/ascli +2 -1
- data/bin/asession +4 -5
- data/docs/test_env.conf +3 -0
- data/examples/aoc.rb +4 -3
- data/examples/faspex4.rb +25 -25
- data/examples/proxy.pac +1 -1
- data/examples/transfer.rb +17 -17
- data/lib/aspera/aoc.rb +238 -185
- data/lib/aspera/ascmd.rb +93 -83
- data/lib/aspera/ats_api.rb +11 -10
- data/lib/aspera/cli/basic_auth_plugin.rb +13 -14
- data/lib/aspera/cli/extended_value.rb +42 -33
- data/lib/aspera/cli/formater.rb +142 -108
- data/lib/aspera/cli/info.rb +17 -0
- data/lib/aspera/cli/listener/line_dump.rb +3 -2
- data/lib/aspera/cli/listener/logger.rb +2 -1
- data/lib/aspera/cli/listener/progress.rb +16 -18
- data/lib/aspera/cli/listener/progress_multi.rb +18 -21
- data/lib/aspera/cli/main.rb +173 -149
- data/lib/aspera/cli/manager.rb +163 -168
- data/lib/aspera/cli/plugin.rb +43 -31
- data/lib/aspera/cli/plugins/alee.rb +6 -6
- data/lib/aspera/cli/plugins/aoc.rb +405 -370
- data/lib/aspera/cli/plugins/ats.rb +86 -79
- data/lib/aspera/cli/plugins/bss.rb +14 -16
- data/lib/aspera/cli/plugins/config.rb +580 -362
- data/lib/aspera/cli/plugins/console.rb +23 -19
- data/lib/aspera/cli/plugins/cos.rb +18 -18
- data/lib/aspera/cli/plugins/faspex.rb +201 -158
- data/lib/aspera/cli/plugins/faspex5.rb +80 -57
- data/lib/aspera/cli/plugins/node.rb +183 -166
- data/lib/aspera/cli/plugins/orchestrator.rb +71 -67
- data/lib/aspera/cli/plugins/preview.rb +92 -96
- data/lib/aspera/cli/plugins/server.rb +79 -75
- data/lib/aspera/cli/plugins/shares.rb +35 -19
- data/lib/aspera/cli/plugins/sync.rb +20 -22
- data/lib/aspera/cli/transfer_agent.rb +76 -113
- data/lib/aspera/cli/version.rb +2 -1
- data/lib/aspera/colors.rb +35 -27
- data/lib/aspera/command_line_builder.rb +48 -34
- data/lib/aspera/cos_node.rb +29 -21
- data/lib/aspera/data_repository.rb +3 -2
- data/lib/aspera/environment.rb +50 -45
- data/lib/aspera/fasp/{manager.rb → agent_base.rb} +28 -25
- data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +52 -43
- data/lib/aspera/fasp/{local.rb → agent_direct.rb} +58 -72
- data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +37 -43
- data/lib/aspera/fasp/{node.rb → agent_node.rb} +35 -16
- data/lib/aspera/fasp/agent_trsdk.rb +104 -0
- data/lib/aspera/fasp/error.rb +2 -1
- data/lib/aspera/fasp/error_info.rb +68 -52
- data/lib/aspera/fasp/installation.rb +152 -124
- data/lib/aspera/fasp/listener.rb +1 -0
- data/lib/aspera/fasp/parameters.rb +87 -92
- data/lib/aspera/fasp/parameters.yaml +305 -249
- data/lib/aspera/fasp/resume_policy.rb +11 -14
- data/lib/aspera/fasp/transfer_spec.rb +26 -0
- data/lib/aspera/fasp/uri.rb +22 -21
- data/lib/aspera/faspex_gw.rb +55 -89
- data/lib/aspera/hash_ext.rb +4 -3
- data/lib/aspera/id_generator.rb +8 -7
- data/lib/aspera/keychain/encrypted_hash.rb +121 -0
- data/lib/aspera/keychain/macos_security.rb +90 -0
- data/lib/aspera/log.rb +55 -37
- data/lib/aspera/nagios.rb +13 -12
- data/lib/aspera/node.rb +30 -25
- data/lib/aspera/oauth.rb +175 -226
- data/lib/aspera/open_application.rb +4 -3
- data/lib/aspera/persistency_action_once.rb +6 -6
- data/lib/aspera/persistency_folder.rb +5 -9
- data/lib/aspera/preview/file_types.rb +6 -5
- data/lib/aspera/preview/generator.rb +25 -24
- data/lib/aspera/preview/options.rb +16 -14
- data/lib/aspera/preview/utils.rb +98 -98
- data/lib/aspera/{proxy_auto_config.erb.js → proxy_auto_config.js} +23 -31
- data/lib/aspera/proxy_auto_config.rb +111 -20
- data/lib/aspera/rest.rb +154 -135
- data/lib/aspera/rest_call_error.rb +2 -2
- data/lib/aspera/rest_error_analyzer.rb +23 -25
- data/lib/aspera/rest_errors_aspera.rb +15 -14
- data/lib/aspera/ssh.rb +12 -10
- data/lib/aspera/sync.rb +42 -41
- data/lib/aspera/temp_file_manager.rb +18 -14
- data/lib/aspera/timer_limiter.rb +2 -1
- data/lib/aspera/uri_reader.rb +7 -5
- data/lib/aspera/web_auth.rb +79 -76
- metadata +116 -29
- data/docs/Makefile +0 -66
- data/docs/README.erb.md +0 -3973
- data/docs/README.md +0 -13
- data/docs/diagrams.txt +0 -49
- data/docs/doc_tools.rb +0 -58
- data/lib/aspera/api_detector.rb +0 -60
- data/lib/aspera/cli/plugins/shares2.rb +0 -114
- data/lib/aspera/secrets.rb +0 -20
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'singleton'
|
|
2
3
|
require 'aspera/log'
|
|
3
4
|
|
|
@@ -5,13 +6,12 @@ module Aspera
|
|
|
5
6
|
module Fasp
|
|
6
7
|
# implements a simple resume policy
|
|
7
8
|
class ResumePolicy
|
|
8
|
-
|
|
9
9
|
# list of supported parameters and default values
|
|
10
10
|
DEFAULTS={
|
|
11
|
-
:
|
|
12
|
-
:
|
|
13
|
-
:
|
|
14
|
-
:
|
|
11
|
+
iter_max: 7,
|
|
12
|
+
sleep_initial: 2,
|
|
13
|
+
sleep_factor: 2,
|
|
14
|
+
sleep_max: 60
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
# @param params see DEFAULTS
|
|
@@ -20,12 +20,9 @@ module Aspera
|
|
|
20
20
|
if !params.nil?
|
|
21
21
|
raise "expecting Hash (or nil), but have #{params.class}" unless params.is_a?(Hash)
|
|
22
22
|
params.each do |k,v|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
else
|
|
27
|
-
raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map{|i|i.to_s}.join(",")}"
|
|
28
|
-
end
|
|
23
|
+
raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map(&:to_s).join(',')}" unless DEFAULTS.has_key?(k)
|
|
24
|
+
raise "#{k} must be Integer" unless v.is_a?(Integer)
|
|
25
|
+
@parameters[k]=v
|
|
29
26
|
end
|
|
30
27
|
end
|
|
31
28
|
Log.log.debug("resume params=#{@parameters}")
|
|
@@ -45,9 +42,9 @@ module Aspera
|
|
|
45
42
|
block.call
|
|
46
43
|
break
|
|
47
44
|
rescue Fasp::Error => e
|
|
48
|
-
Log.log.warn("An error occured: #{e.message}"
|
|
45
|
+
Log.log.warn("An error occured: #{e.message}");
|
|
49
46
|
# failure in ascp
|
|
50
|
-
if e.retryable?
|
|
47
|
+
if e.retryable?
|
|
51
48
|
# exit if we exceed the max number of retry
|
|
52
49
|
raise Fasp::Error,'Maximum number of retry reached' if remaining_resumes <= 0
|
|
53
50
|
else
|
|
@@ -61,7 +58,7 @@ module Aspera
|
|
|
61
58
|
|
|
62
59
|
# take this retry in account
|
|
63
60
|
remaining_resumes-=1
|
|
64
|
-
Log.log.warn(
|
|
61
|
+
Log.log.warn("resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})");
|
|
65
62
|
|
|
66
63
|
# wait a bit before retrying, maybe network condition will be better
|
|
67
64
|
sleep(sleep_seconds)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'aspera/fasp/parameters'
|
|
3
|
+
|
|
4
|
+
module Aspera
|
|
5
|
+
module Fasp
|
|
6
|
+
# parameters for Transfer Spec
|
|
7
|
+
class TransferSpec
|
|
8
|
+
# 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={
|
|
13
|
+
'remote_user' => ACCESS_KEY_TRANSFER_USER,
|
|
14
|
+
'ssh_port' => SSH_PORT,
|
|
15
|
+
'fasp_port' => UDP_PORT
|
|
16
|
+
}
|
|
17
|
+
# define constants for enums of parameters: <paramater>_<enum>, e.g. CIPHER_AES_128
|
|
18
|
+
Aspera::Fasp::Parameters.description.each do |k,v|
|
|
19
|
+
next unless v[:enum].is_a?(Array)
|
|
20
|
+
v[:enum].each do |enum|
|
|
21
|
+
TransferSpec.const_set("#{k.to_s.upcase}_#{enum.upcase.gsub(/[^A-Z0-9]/,'_')}", enum.freeze)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/aspera/fasp/uri.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'aspera/log'
|
|
2
3
|
require 'aspera/command_line_builder'
|
|
3
4
|
|
|
@@ -15,33 +16,33 @@ module Aspera
|
|
|
15
16
|
result_ts['remote_host']=@fasp_uri.host
|
|
16
17
|
result_ts['remote_user']=@fasp_uri.user
|
|
17
18
|
result_ts['ssh_port']=@fasp_uri.port
|
|
18
|
-
result_ts['paths']=[{
|
|
19
|
+
result_ts['paths']=[{'source'=>URI.decode_www_form_component(@fasp_uri.path)}]
|
|
19
20
|
# faspex does not encode trailing base64 encoded tags, fix that
|
|
20
21
|
fixed_query = @fasp_uri.query.gsub(/(=+)$/){|x|'%3D'*x.length}
|
|
21
22
|
|
|
22
|
-
URI
|
|
23
|
+
URI.decode_www_form(fixed_query).each do |i|
|
|
23
24
|
name=i[0]
|
|
24
25
|
value=i[1]
|
|
25
26
|
case name
|
|
26
|
-
when 'cookie'
|
|
27
|
-
when 'token'
|
|
28
|
-
when '
|
|
29
|
-
when '
|
|
30
|
-
when '
|
|
31
|
-
when '
|
|
32
|
-
when '
|
|
33
|
-
when '
|
|
34
|
-
when '
|
|
35
|
-
when '
|
|
36
|
-
when '
|
|
37
|
-
when '
|
|
38
|
-
when '
|
|
39
|
-
when '
|
|
40
|
-
when '
|
|
41
|
-
when 'auth'
|
|
42
|
-
when 'v'
|
|
43
|
-
when 'protect'
|
|
44
|
-
else Log.log.
|
|
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)
|
|
42
|
+
when 'auth' then Log.log.debug("ignoring auth #{name}=#{value}") # TODO: translate into transfer spec ? yes/no
|
|
43
|
+
when 'v' then Log.log.debug("ignoring v #{name}=#{value}") # TODO: translate into transfer spec ? 2
|
|
44
|
+
when 'protect' then Log.log.debug("ignoring protect #{name}=#{value}") # TODO: translate into transfer spec ?
|
|
45
|
+
else Log.log.warn("URI parameter ignored: #{name} = #{value}")
|
|
45
46
|
end
|
|
46
47
|
end
|
|
47
48
|
return result_ts
|
data/lib/aspera/faspex_gw.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'aspera/log'
|
|
2
3
|
require 'aspera/aoc'
|
|
3
|
-
require 'aspera/
|
|
4
|
+
require 'aspera/fasp/transfer_spec'
|
|
4
5
|
require 'aspera/cli/main'
|
|
5
6
|
require 'webrick'
|
|
6
7
|
require 'webrick/https'
|
|
@@ -12,7 +13,8 @@ module Aspera
|
|
|
12
13
|
# this class answers the Faspex /send API and creates a package on Aspera on Cloud
|
|
13
14
|
class FaspexGW
|
|
14
15
|
class FxGwServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
15
|
-
def initialize(
|
|
16
|
+
def initialize(_server,a_aoc_api_user,a_workspace_id)
|
|
17
|
+
super
|
|
16
18
|
@aoc_api_user=a_aoc_api_user
|
|
17
19
|
@aoc_workspace_id=a_workspace_id
|
|
18
20
|
end
|
|
@@ -30,27 +32,27 @@ module Aspera
|
|
|
30
32
|
# }
|
|
31
33
|
# }
|
|
32
34
|
def process_faspex_send(request, response)
|
|
33
|
-
raise
|
|
35
|
+
raise 'no payload' if request.body.nil?
|
|
34
36
|
faspex_pkg_parameters=JSON.parse(request.body)
|
|
35
37
|
faspex_pkg_delivery=faspex_pkg_parameters['delivery']
|
|
36
|
-
Log.log.debug
|
|
38
|
+
Log.log.debug("faspex pkg create parameters=#{faspex_pkg_parameters}")
|
|
37
39
|
|
|
38
40
|
# get recipient ids
|
|
39
41
|
files_pkg_recipients=[]
|
|
40
42
|
faspex_pkg_delivery['recipients'].each do |recipient_email|
|
|
41
|
-
user_lookup=@aoc_api_user.read(
|
|
42
|
-
raise StandardError,"no such unique user: #{recipient_email} / #{user_lookup}" unless !user_lookup.nil?
|
|
43
|
+
user_lookup=@aoc_api_user.read('contacts',{'current_workspace_id'=>@aoc_workspace_id,'q'=>recipient_email})[:data]
|
|
44
|
+
raise StandardError,"no such unique user: #{recipient_email} / #{user_lookup}" unless !user_lookup.nil? && user_lookup.length.eql?(1)
|
|
43
45
|
recipient_user_info=user_lookup.first
|
|
44
|
-
files_pkg_recipients.push({
|
|
46
|
+
files_pkg_recipients.push({'id'=>recipient_user_info['source_id'],'type'=>recipient_user_info['source_type']})
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
# create a new package with one file
|
|
48
|
-
the_package=@aoc_api_user.create(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
the_package=@aoc_api_user.create('packages',{
|
|
51
|
+
'file_names' =>faspex_pkg_delivery['sources'][0]['paths'],
|
|
52
|
+
'name' =>faspex_pkg_delivery['title'],
|
|
53
|
+
'note' =>faspex_pkg_delivery['note'],
|
|
54
|
+
'recipients' =>files_pkg_recipients,
|
|
55
|
+
'workspace_id'=>@aoc_workspace_id})[:data]
|
|
54
56
|
|
|
55
57
|
# get node information for the node on which package must be created
|
|
56
58
|
node_info=@aoc_api_user.read("nodes/#{the_package['node_id']}")[:data]
|
|
@@ -59,50 +61,50 @@ module Aspera
|
|
|
59
61
|
node_auth_bearer_token=@aoc_api_user.oauth_token(scope: AoC.node_scope(node_info['access_key'],AoC::SCOPE_NODE_USER))
|
|
60
62
|
|
|
61
63
|
# tell Files what to expect in package: 1 transfer (can also be done after transfer)
|
|
62
|
-
@aoc_api_user.update("packages/#{the_package['id']}",{
|
|
64
|
+
@aoc_api_user.update("packages/#{the_package['id']}",{'sent'=>true,'transfers_expected'=>1})
|
|
65
|
+
|
|
66
|
+
# to return an error:
|
|
67
|
+
# response.status=400
|
|
68
|
+
# return 'ERROR HERE'
|
|
63
69
|
|
|
64
|
-
if false
|
|
65
|
-
response.status=400
|
|
66
|
-
return "ERROR HERE"
|
|
67
|
-
end
|
|
68
70
|
# TODO: check about xfer_*
|
|
69
71
|
ts_tags={
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
'aspera' => {
|
|
73
|
+
'files' => { 'package_id' => the_package['id'], 'package_operation' => 'upload' },
|
|
74
|
+
'node' => { 'access_key' => node_info['access_key'], 'file_id' => the_package['contents_file_id'] },
|
|
75
|
+
'xfer_id' => SecureRandom.uuid,
|
|
76
|
+
'xfer_retry' => 3600 } }
|
|
75
77
|
# this transfer spec is for transfer to AoC
|
|
76
78
|
faspex_transfer_spec={
|
|
77
79
|
'direction' => 'send',
|
|
78
80
|
'remote_host' => node_info['host'],
|
|
79
|
-
'remote_user' =>
|
|
80
|
-
'ssh_port' =>
|
|
81
|
-
'fasp_port' =>
|
|
81
|
+
'remote_user' => Fasp::TransferSpec::ACCESS_KEY_TRANSFER_USER,
|
|
82
|
+
'ssh_port' => Fasp::TransferSpec::SSH_PORT,
|
|
83
|
+
'fasp_port' => Fasp::TransferSpec::UDP_PORT,
|
|
82
84
|
'tags' => ts_tags,
|
|
83
85
|
'token' => node_auth_bearer_token,
|
|
84
86
|
'paths' => [{'destination' => '/'}],
|
|
85
87
|
'cookie' => 'unused',
|
|
86
88
|
'create_dir' => true,
|
|
87
89
|
'rate_policy' => 'fair',
|
|
88
|
-
'rate_policy_allowed'
|
|
89
|
-
'min_rate_cap_kbps'
|
|
90
|
-
'min_rate_kbps'
|
|
90
|
+
'rate_policy_allowed' => 'fixed',
|
|
91
|
+
'min_rate_cap_kbps' => nil,
|
|
92
|
+
'min_rate_kbps' => 0,
|
|
91
93
|
'target_rate_percentage' => nil,
|
|
92
|
-
'lock_target_rate'
|
|
93
|
-
'fasp_url'
|
|
94
|
-
'lock_min_rate'
|
|
95
|
-
'lock_rate_policy'
|
|
96
|
-
'source_root'
|
|
97
|
-
'content_protection'
|
|
98
|
-
'target_rate_cap_kbps' =>
|
|
99
|
-
'target_rate_kbps'
|
|
100
|
-
'cipher'
|
|
101
|
-
'cipher_allowed'
|
|
102
|
-
'http_fallback'
|
|
103
|
-
'http_fallback_port'
|
|
104
|
-
'https_fallback_port'
|
|
105
|
-
'destination_root'
|
|
94
|
+
'lock_target_rate' => nil,
|
|
95
|
+
'fasp_url' => 'unused',
|
|
96
|
+
'lock_min_rate' => true,
|
|
97
|
+
'lock_rate_policy' => true,
|
|
98
|
+
'source_root' => '',
|
|
99
|
+
'content_protection' => nil,
|
|
100
|
+
'target_rate_cap_kbps' => 20_000, # TODO: is this value useful ?
|
|
101
|
+
'target_rate_kbps' => 10_000, # TODO: get from where?
|
|
102
|
+
'cipher' => 'aes-128',
|
|
103
|
+
'cipher_allowed' => nil,
|
|
104
|
+
'http_fallback' => false,
|
|
105
|
+
'http_fallback_port' => nil,
|
|
106
|
+
'https_fallback_port' => nil,
|
|
107
|
+
'destination_root' => '/'
|
|
106
108
|
}
|
|
107
109
|
# but we place it in a Faspex package creation response
|
|
108
110
|
faspex_package_create_result={
|
|
@@ -111,28 +113,27 @@ module Aspera
|
|
|
111
113
|
}
|
|
112
114
|
Log.log.info("faspex_package_create_result=#{faspex_package_create_result}")
|
|
113
115
|
response.status=200
|
|
114
|
-
response.content_type =
|
|
116
|
+
response.content_type = 'application/json'
|
|
115
117
|
response.body=JSON.generate(faspex_package_create_result)
|
|
116
118
|
end
|
|
117
119
|
|
|
118
|
-
def do_GET
|
|
120
|
+
def do_GET(request, response) # rubocop:disable Naming/MethodName
|
|
119
121
|
case request.path
|
|
120
122
|
when '/aspera/faspex/send'
|
|
121
123
|
process_faspex_send(request, response)
|
|
122
124
|
else
|
|
123
125
|
response.status=400
|
|
124
|
-
return
|
|
125
|
-
raise "unsupported path: #{request.path}"
|
|
126
|
+
return 'ERROR HERE'
|
|
126
127
|
end
|
|
127
128
|
end
|
|
128
129
|
end # FxGwServlet
|
|
129
130
|
|
|
130
131
|
class NewUserServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
131
|
-
def do_GET
|
|
132
|
+
def do_GET(request, response) # rubocop:disable Naming/MethodName
|
|
132
133
|
case request.path
|
|
133
134
|
when '/newuser'
|
|
134
135
|
response.status=200
|
|
135
|
-
response.content_type =
|
|
136
|
+
response.content_type = 'text/html'
|
|
136
137
|
response.body='<html><body>hello world</body></html>'
|
|
137
138
|
else
|
|
138
139
|
raise "unsupported path: [#{request.path}]"
|
|
@@ -140,49 +141,14 @@ module Aspera
|
|
|
140
141
|
end
|
|
141
142
|
end
|
|
142
143
|
|
|
143
|
-
def fill_self_signed_cert(options)
|
|
144
|
-
key = OpenSSL::PKey::RSA.new(4096)
|
|
145
|
-
cert = OpenSSL::X509::Certificate.new
|
|
146
|
-
cert.subject = cert.issuer = OpenSSL::X509::Name.parse("/C=FR/O=Test/OU=Test/CN=Test")
|
|
147
|
-
cert.not_before = Time.now
|
|
148
|
-
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
|
149
|
-
cert.public_key = key.public_key
|
|
150
|
-
cert.serial = 0x0
|
|
151
|
-
cert.version = 2
|
|
152
|
-
ef = OpenSSL::X509::ExtensionFactory.new
|
|
153
|
-
ef.issuer_certificate = cert
|
|
154
|
-
ef.subject_certificate = cert
|
|
155
|
-
cert.extensions = [
|
|
156
|
-
ef.create_extension("basicConstraints","CA:TRUE", true),
|
|
157
|
-
ef.create_extension("subjectKeyIdentifier", "hash"),
|
|
158
|
-
# ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
|
|
159
|
-
]
|
|
160
|
-
cert.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always,issuer:always"))
|
|
161
|
-
cert.sign(key, OpenSSL::Digest::SHA256.new)
|
|
162
|
-
options[:SSLPrivateKey] = key
|
|
163
|
-
options[:SSLCertificate] = cert
|
|
164
|
-
end
|
|
165
|
-
|
|
166
144
|
def initialize(a_aoc_api_user,a_workspace_id)
|
|
167
145
|
webrick_options = {
|
|
168
|
-
:
|
|
169
|
-
:
|
|
170
|
-
:
|
|
171
|
-
|
|
172
|
-
:
|
|
173
|
-
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
|
|
146
|
+
Port: 9443,
|
|
147
|
+
Logger: Log.log,
|
|
148
|
+
SSLEnable: true,
|
|
149
|
+
SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
|
|
150
|
+
SSLCertName: [['CN',WEBrick::Utils.getservername]]
|
|
174
151
|
}
|
|
175
|
-
case 2
|
|
176
|
-
when 0
|
|
177
|
-
# generate self signed cert
|
|
178
|
-
webrick_options[:SSLCertName] = [ [ 'CN',WEBrick::Utils::getservername ] ]
|
|
179
|
-
Log.log.error(">>>#{webrick_options[:SSLCertName]}")
|
|
180
|
-
when 1
|
|
181
|
-
fill_self_signed_cert(webrick_options)
|
|
182
|
-
when 2
|
|
183
|
-
webrick_options[:SSLPrivateKey] =OpenSSL::PKey::RSA.new(File.read('/Users/laurent/workspace/Tools/certificate/myserver.key'))
|
|
184
|
-
webrick_options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read('/Users/laurent/workspace/Tools/certificate/myserver.crt'))
|
|
185
|
-
end
|
|
186
152
|
Log.log.info("Server started on port #{webrick_options[:Port]}")
|
|
187
153
|
@server = WEBrick::HTTPServer.new(webrick_options)
|
|
188
154
|
@server.mount('/aspera/faspex', FxGwServlet,a_aoc_api_user,a_workspace_id)
|
data/lib/aspera/hash_ext.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
# for older rubies
|
|
2
3
|
unless Hash.method_defined?(:dig)
|
|
3
4
|
class Hash
|
|
@@ -11,18 +12,18 @@ end
|
|
|
11
12
|
|
|
12
13
|
class ::Hash
|
|
13
14
|
def deep_merge(second)
|
|
14
|
-
|
|
15
|
+
merge(second){|_key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge(v2) : v2}
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def deep_merge!(second)
|
|
18
|
-
|
|
19
|
+
merge!(second){|_key,v1,v2|Hash===v1&&Hash===v2 ? v1.deep_merge!(v2) : v2}
|
|
19
20
|
end
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
unless Hash.method_defined?(:symbolize_keys)
|
|
23
24
|
class Hash
|
|
24
25
|
def symbolize_keys
|
|
25
|
-
return
|
|
26
|
+
return each_with_object({}){|(k,v),memo| memo[k.to_sym] = v; }
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
end
|
data/lib/aspera/id_generator.rb
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
1
2
|
require 'uri'
|
|
2
3
|
|
|
3
4
|
module Aspera
|
|
4
5
|
class IdGenerator
|
|
5
6
|
ID_SEPARATOR='_'
|
|
6
|
-
WINDOWS_PROTECTED_CHAR=%r{[/:"
|
|
7
|
+
WINDOWS_PROTECTED_CHAR=%r{[/:"<>\\*?]}
|
|
7
8
|
PROTECTED_CHAR_REPLACE='_'
|
|
8
9
|
private_constant :ID_SEPARATOR,:PROTECTED_CHAR_REPLACE,:WINDOWS_PROTECTED_CHAR
|
|
9
10
|
def self.from_list(object_id)
|
|
10
11
|
if object_id.is_a?(Array)
|
|
11
|
-
object_id=object_id.
|
|
12
|
-
|
|
12
|
+
object_id=object_id.reject(&:nil?).map do |i|
|
|
13
|
+
i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
|
|
13
14
|
end.join(ID_SEPARATOR)
|
|
14
15
|
end
|
|
15
|
-
raise
|
|
16
|
+
raise 'id must be a String' unless object_id.is_a?(String)
|
|
16
17
|
return object_id.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
gsub(WINDOWS_PROTECTED_CHAR,PROTECTED_CHAR_REPLACE). # remove windows forbidden chars
|
|
19
|
+
gsub('.',PROTECTED_CHAR_REPLACE). # keep dot for extension only (nicer)
|
|
20
|
+
downcase
|
|
20
21
|
end
|
|
21
22
|
end
|
|
22
23
|
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'openssl'
|
|
3
|
+
|
|
4
|
+
module Aspera
|
|
5
|
+
module Keychain
|
|
6
|
+
class SimpleCipher
|
|
7
|
+
def initialize(key)
|
|
8
|
+
@key=Digest::SHA1.hexdigest(key)[0..23]
|
|
9
|
+
@cipher = OpenSSL::Cipher.new('DES-EDE3-CBC')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def encrypt(value)
|
|
13
|
+
@cipher.encrypt
|
|
14
|
+
@cipher.key = @key
|
|
15
|
+
s = @cipher.update(value) + @cipher.final
|
|
16
|
+
s.unpack1('H*')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def decrypt(value)
|
|
20
|
+
@cipher.decrypt
|
|
21
|
+
@cipher.key = @key
|
|
22
|
+
s = [value].pack('H*').unpack('C*').pack('c*')
|
|
23
|
+
@cipher.update(s) + @cipher.final
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Manage secrets in a simple Hash
|
|
28
|
+
class EncryptedHash
|
|
29
|
+
SEPARATOR='%'
|
|
30
|
+
private_constant :SEPARATOR
|
|
31
|
+
def initialize(values)
|
|
32
|
+
raise 'values shall be Hash' unless values.is_a?(Hash)
|
|
33
|
+
@all_secrets=values
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def set(options)
|
|
37
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
|
38
|
+
unsupported=options.keys-[:username,:url,:secret,:description]
|
|
39
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
|
40
|
+
username=options[:username]
|
|
41
|
+
raise 'options shall have username' if username.nil?
|
|
42
|
+
url=options[:url]
|
|
43
|
+
raise 'options shall have username' if url.nil?
|
|
44
|
+
secret=options[:secret]
|
|
45
|
+
raise 'options shall have secret' if secret.nil?
|
|
46
|
+
key=[url,username].join(SEPARATOR)
|
|
47
|
+
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
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def list
|
|
55
|
+
result=[]
|
|
56
|
+
@all_secrets.each do |k,v|
|
|
57
|
+
case v
|
|
58
|
+
when String
|
|
59
|
+
o={username: k, url: '', description: ''}
|
|
60
|
+
when Hash
|
|
61
|
+
o=v.clone
|
|
62
|
+
o.delete(:secret)
|
|
63
|
+
o[:description]||=''
|
|
64
|
+
else raise 'error'
|
|
65
|
+
end
|
|
66
|
+
o[:description]=v[:description] if v.is_a?(Hash) && v[:description].is_a?(String)
|
|
67
|
+
result.push(o)
|
|
68
|
+
end
|
|
69
|
+
return result
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def delete(options)
|
|
73
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
|
74
|
+
unsupported=options.keys-[:username,:url]
|
|
75
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
|
76
|
+
username=options[:username]
|
|
77
|
+
raise 'options shall have username' if username.nil?
|
|
78
|
+
url=options[:url]
|
|
79
|
+
key=nil
|
|
80
|
+
if !url.nil?
|
|
81
|
+
extk=[url,username].join(SEPARATOR)
|
|
82
|
+
key=extk if @all_secrets.has_key?(extk)
|
|
83
|
+
end
|
|
84
|
+
# backward compatibility: TODO: remove in future ? (make url mandatory ?)
|
|
85
|
+
key=username if key.nil? && @all_secrets.has_key?(username)
|
|
86
|
+
raise 'no such secret' if key.nil?
|
|
87
|
+
@all_secrets.delete(key)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def get(options)
|
|
91
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
|
92
|
+
unsupported=options.keys-[:username,:url]
|
|
93
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
|
94
|
+
username=options[:username]
|
|
95
|
+
raise 'options shall have username' if username.nil?
|
|
96
|
+
url=options[:url]
|
|
97
|
+
val=nil
|
|
98
|
+
if !url.nil?
|
|
99
|
+
val=@all_secrets[[url,username].join(SEPARATOR)]
|
|
100
|
+
end
|
|
101
|
+
# backward compatibility: TODO: remove in future ? (make url mandatory ?)
|
|
102
|
+
if val.nil?
|
|
103
|
+
val=@all_secrets[username]
|
|
104
|
+
end
|
|
105
|
+
result=options.clone
|
|
106
|
+
case val
|
|
107
|
+
when NilClass
|
|
108
|
+
raise 'no such secret'
|
|
109
|
+
when String
|
|
110
|
+
result.merge!({secret: val, description: ''})
|
|
111
|
+
when Hash
|
|
112
|
+
key=[url,username].join(SEPARATOR)
|
|
113
|
+
plain=SimpleCipher.new(key).decrypt(val[:secret])
|
|
114
|
+
result.merge!({secret: plain, description: val[:description]})
|
|
115
|
+
else raise 'error'
|
|
116
|
+
end
|
|
117
|
+
return result
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'security'
|
|
3
|
+
|
|
4
|
+
# enhance the gem to support other keychains
|
|
5
|
+
module Security
|
|
6
|
+
class Keychain
|
|
7
|
+
class << self
|
|
8
|
+
def by_name(name)
|
|
9
|
+
keychains_from_output('security list-keychains').select{|kc|kc.filename.end_with?("/#{name}.keychain-db")}.first
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class Password
|
|
15
|
+
class << self
|
|
16
|
+
# add some login to original method
|
|
17
|
+
alias orig_flags_for_options flags_for_options
|
|
18
|
+
def flags_for_options(options = {})
|
|
19
|
+
keychain=options.delete(:keychain)
|
|
20
|
+
url=options.delete(:url)
|
|
21
|
+
if !url.nil?
|
|
22
|
+
uri=URI.parse(url)
|
|
23
|
+
raise 'only https' unless uri.scheme.eql?('https')
|
|
24
|
+
options[:r]='htps'
|
|
25
|
+
raise 'host required in URL' if uri.host.nil?
|
|
26
|
+
options[:s]=uri.host
|
|
27
|
+
options[:p]=uri.path unless ['','/'].include?(uri.path)
|
|
28
|
+
options[:P]=uri.port unless uri.port.eql?(443) && !url.include?(':443/')
|
|
29
|
+
end
|
|
30
|
+
flags=[orig_flags_for_options(options)]
|
|
31
|
+
flags.push(keychain.filename) unless keychain.nil?
|
|
32
|
+
flags.join(' ')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
module Aspera
|
|
39
|
+
module Keychain
|
|
40
|
+
# keychain based on macOS keychain, using `security` cmmand line
|
|
41
|
+
class MacosSecurity
|
|
42
|
+
def initialize(name=nil)
|
|
43
|
+
@keychain=name.nil? ? Security::Keychain.default_keychain : Security::Keychain.by_name(name)
|
|
44
|
+
raise "no such keychain #{name}" if @keychain.nil?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def set(options)
|
|
48
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
|
49
|
+
unsupported=options.keys-[:username,:url,:secret,:description]
|
|
50
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
|
51
|
+
username=options[:username]
|
|
52
|
+
raise 'options shall have username' if username.nil?
|
|
53
|
+
url=options[:url]
|
|
54
|
+
raise 'options shall have url' if url.nil?
|
|
55
|
+
secret=options[:secret]
|
|
56
|
+
raise 'options shall have secret' if secret.nil?
|
|
57
|
+
raise 'set not implemented'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def get(options)
|
|
61
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
|
62
|
+
unsupported=options.keys-[:username,:url]
|
|
63
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
|
64
|
+
username=options[:username]
|
|
65
|
+
raise 'options shall have username' if username.nil?
|
|
66
|
+
url=options[:url]
|
|
67
|
+
raise 'options shall have url' if url.nil?
|
|
68
|
+
info=Security::InternetPassword.find(keychain: @keychain, url: url, account: username)
|
|
69
|
+
raise 'not found' if info.nil?
|
|
70
|
+
result=options.clone
|
|
71
|
+
result.merge!({secret: info.password, description: info.attributes['icmt']})
|
|
72
|
+
return result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def list
|
|
76
|
+
raise 'list not implemented'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def delete(options)
|
|
80
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
|
81
|
+
unsupported=options.keys-[:username,:url]
|
|
82
|
+
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
|
83
|
+
username=options[:username]
|
|
84
|
+
raise 'options shall have username' if username.nil?
|
|
85
|
+
url=options[:url]
|
|
86
|
+
raise "delete not implemented #{url}"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|