aspera-cli 4.4.0 → 4.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|