aspera-cli 4.7.0 → 4.9.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/README.md +1267 -999
- data/bin/ascli +20 -1
- data/bin/asession +37 -34
- data/docs/test_env.conf +7 -3
- data/examples/aoc.rb +13 -12
- data/examples/dascli +23 -0
- data/examples/faspex4.rb +34 -29
- data/examples/{transfer.rb → node.rb} +31 -59
- data/examples/server.rb +93 -0
- data/lib/aspera/aoc.rb +153 -143
- data/lib/aspera/ascmd.rb +56 -45
- data/lib/aspera/ats_api.rb +9 -6
- data/lib/aspera/cli/basic_auth_plugin.rb +18 -16
- data/lib/aspera/cli/extended_value.rb +33 -30
- data/lib/aspera/cli/formater.rb +105 -111
- data/lib/aspera/cli/info.rb +3 -2
- 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 +110 -90
- data/lib/aspera/cli/manager.rb +99 -88
- data/lib/aspera/cli/plugin.rb +98 -39
- data/lib/aspera/cli/plugins/alee.rb +6 -5
- data/lib/aspera/cli/plugins/aoc.rb +581 -450
- 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 +488 -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 +206 -172
- data/lib/aspera/cli/plugins/faspex5.rb +109 -74
- data/lib/aspera/cli/plugins/node.rb +379 -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 +50 -150
- data/lib/aspera/cli/plugins/shares.rb +61 -27
- data/lib/aspera/cli/plugins/sync.rb +15 -14
- data/lib/aspera/cli/transfer_agent.rb +75 -64
- 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 +33 -10
- data/lib/aspera/fasp/agent_base.rb +35 -30
- 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 +130 -126
- data/lib/aspera/fasp/listener.rb +1 -0
- data/lib/aspera/fasp/parameters.rb +71 -60
- data/lib/aspera/fasp/parameters.yaml +69 -17
- 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 +23 -13
- data/lib/aspera/id_generator.rb +16 -13
- data/lib/aspera/keychain/encrypted_hash.rb +61 -46
- data/lib/aspera/keychain/macos_security.rb +26 -24
- data/lib/aspera/log.rb +35 -39
- data/lib/aspera/nagios.rb +36 -28
- data/lib/aspera/node.rb +19 -19
- data/lib/aspera/oauth.rb +120 -100
- data/lib/aspera/open_application.rb +25 -22
- data/lib/aspera/persistency_action_once.rb +9 -8
- data/lib/aspera/persistency_folder.rb +13 -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 +27 -23
- data/lib/aspera/rest.rb +73 -74
- data/lib/aspera/rest_call_error.rb +1 -0
- data/lib/aspera/rest_error_analyzer.rb +23 -19
- data/lib/aspera/rest_errors_aspera.rb +43 -40
- data/lib/aspera/secret_hider.rb +74 -0
- data/lib/aspera/ssh.rb +13 -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 +17 -18
- data/lib/aspera/web_auth.rb +17 -15
- data.tar.gz.sig +5 -0
- metadata +119 -35
- metadata.gz.sig +0 -0
- data/bin/dascli +0 -13
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,29 +1,39 @@
|
|
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}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# in 2.5
|
14
|
+
unless Hash.method_defined?(:transform_keys)
|
15
|
+
class Hash
|
16
|
+
def transform_keys
|
17
|
+
return each_with_object({}){|(k,v),memo|memo[yield(k)]=v} if block_given?
|
18
|
+
raise 'missing block'
|
19
|
+
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
# rails
|
23
24
|
unless Hash.method_defined?(:symbolize_keys)
|
24
25
|
class Hash
|
25
26
|
def symbolize_keys
|
26
|
-
return
|
27
|
+
return transform_keys(&:to_sym)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# rails
|
33
|
+
unless Hash.method_defined?(:stringify_keys)
|
34
|
+
class Hash
|
35
|
+
def stringify_keys
|
36
|
+
return transform_keys(&:to_s)
|
27
37
|
end
|
28
38
|
end
|
29
39
|
end
|
data/lib/aspera/id_generator.rb
CHANGED
@@ -1,23 +1,26 @@
|
|
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
|
-
|
12
|
-
object_id
|
13
|
-
|
14
|
-
|
11
|
+
class << self
|
12
|
+
def from_list(object_id)
|
13
|
+
if object_id.is_a?(Array)
|
14
|
+
object_id = object_id.compact.map do |i|
|
15
|
+
i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
|
16
|
+
end.join(ID_SEPARATOR)
|
17
|
+
end
|
18
|
+
raise 'id must be a String' unless object_id.is_a?(String)
|
19
|
+
return object_id.
|
20
|
+
gsub(WINDOWS_PROTECTED_CHAR,PROTECTED_CHAR_REPLACE). # remove windows forbidden chars
|
21
|
+
gsub('.',PROTECTED_CHAR_REPLACE). # keep dot for extension only (nicer)
|
22
|
+
downcase
|
15
23
|
end
|
16
|
-
raise 'id must be a String' unless object_id.is_a?(String)
|
17
|
-
return object_id.
|
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
|
21
24
|
end
|
22
25
|
end
|
23
26
|
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,93 +28,106 @@ 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: [#{url}|#{username}] in #{@all_secrets.keys.join(',')}"
|
109
121
|
when String
|
110
|
-
result
|
122
|
+
result[:secret] = info
|
123
|
+
result[:description] = ''
|
111
124
|
when Hash
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
125
|
+
info=info.symbolize_keys
|
126
|
+
key = identifier(options)
|
127
|
+
plain = SimpleCipher.new(key).decrypt(info[:secret]) rescue info[:secret]
|
128
|
+
result[:secret] = plain
|
129
|
+
result[:description] = info[:description]
|
130
|
+
else raise "#{info.class} is not an expected type"
|
116
131
|
end
|
117
132
|
return result
|
118
133
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'security'
|
3
4
|
|
4
5
|
# enhance the gem to support other keychains
|
@@ -6,7 +7,7 @@ module Security
|
|
6
7
|
class Keychain
|
7
8
|
class << self
|
8
9
|
def by_name(name)
|
9
|
-
keychains_from_output('security list-keychains').
|
10
|
+
keychains_from_output('security list-keychains').find{|kc|kc.filename.end_with?("/#{name}.keychain-db")}
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
@@ -14,20 +15,20 @@ module Security
|
|
14
15
|
class Password
|
15
16
|
class << self
|
16
17
|
# add some login to original method
|
17
|
-
|
18
|
+
alias_method :orig_flags_for_options, :flags_for_options
|
18
19
|
def flags_for_options(options = {})
|
19
|
-
keychain=options.delete(:keychain)
|
20
|
-
url=options.delete(:url)
|
20
|
+
keychain = options.delete(:keychain)
|
21
|
+
url = options.delete(:url)
|
21
22
|
if !url.nil?
|
22
|
-
uri=URI.parse(url)
|
23
|
+
uri = URI.parse(url)
|
23
24
|
raise 'only https' unless uri.scheme.eql?('https')
|
24
|
-
options[:r]='htps'
|
25
|
+
options[:r] = 'htps'
|
25
26
|
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/')
|
27
|
+
options[:s] = uri.host
|
28
|
+
options[:p] = uri.path unless ['','/'].include?(uri.path)
|
29
|
+
options[:P] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
|
29
30
|
end
|
30
|
-
flags=[orig_flags_for_options(options)]
|
31
|
+
flags = [orig_flags_for_options(options)]
|
31
32
|
flags.push(keychain.filename) unless keychain.nil?
|
32
33
|
flags.join(' ')
|
33
34
|
end
|
@@ -40,35 +41,36 @@ module Aspera
|
|
40
41
|
# keychain based on macOS keychain, using `security` cmmand line
|
41
42
|
class MacosSecurity
|
42
43
|
def initialize(name=nil)
|
43
|
-
@keychain=name.nil? ? Security::Keychain.default_keychain : Security::Keychain.by_name(name)
|
44
|
+
@keychain = name.nil? ? Security::Keychain.default_keychain : Security::Keychain.by_name(name)
|
44
45
|
raise "no such keychain #{name}" if @keychain.nil?
|
45
46
|
end
|
46
47
|
|
47
48
|
def set(options)
|
48
49
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
49
|
-
unsupported=options.keys-[
|
50
|
+
unsupported = options.keys - %i[username url secret description]
|
50
51
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
51
|
-
username=options[:username]
|
52
|
+
username = options[:username]
|
52
53
|
raise 'options shall have username' if username.nil?
|
53
|
-
url=options[:url]
|
54
|
+
url = options[:url]
|
54
55
|
raise 'options shall have url' if url.nil?
|
55
|
-
secret=options[:secret]
|
56
|
+
secret = options[:secret]
|
56
57
|
raise 'options shall have secret' if secret.nil?
|
57
58
|
raise 'set not implemented'
|
58
59
|
end
|
59
60
|
|
60
61
|
def get(options)
|
61
62
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
62
|
-
unsupported=options.keys-[
|
63
|
+
unsupported = options.keys - %i[username url]
|
63
64
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
64
|
-
username=options[:username]
|
65
|
+
username = options[:username]
|
65
66
|
raise 'options shall have username' if username.nil?
|
66
|
-
url=options[:url]
|
67
|
+
url = options[:url]
|
67
68
|
raise 'options shall have url' if url.nil?
|
68
|
-
info=Security::InternetPassword.find(keychain: @keychain, url: url, account: username)
|
69
|
+
info = Security::InternetPassword.find(keychain: @keychain, url: url, account: username)
|
69
70
|
raise 'not found' if info.nil?
|
70
|
-
result=options.clone
|
71
|
-
result
|
71
|
+
result = options.clone
|
72
|
+
result[:secret] = info.password
|
73
|
+
result[:description] = info.attributes['icmt']
|
72
74
|
return result
|
73
75
|
end
|
74
76
|
|
@@ -78,11 +80,11 @@ module Aspera
|
|
78
80
|
|
79
81
|
def delete(options)
|
80
82
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
81
|
-
unsupported=options.keys-[
|
83
|
+
unsupported = options.keys - %i[username url]
|
82
84
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
83
|
-
username=options[:username]
|
85
|
+
username = options[:username]
|
84
86
|
raise 'options shall have username' if username.nil?
|
85
|
-
url=options[:url]
|
87
|
+
url = options[:url]
|
86
88
|
raise "delete not implemented #{url}"
|
87
89
|
end
|
88
90
|
end
|