aspera-cli 4.9.0 → 4.10.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 +749 -667
- data/docs/test_env.conf +6 -4
- data/examples/dascli +9 -4
- data/examples/faspex4.rb +1 -1
- data/examples/node.rb +1 -1
- data/examples/server.rb +1 -1
- data/lib/aspera/cli/info.rb +2 -2
- data/lib/aspera/cli/main.rb +1 -1
- data/lib/aspera/cli/manager.rb +6 -4
- data/lib/aspera/cli/plugin.rb +5 -1
- data/lib/aspera/cli/plugins/aoc.rb +1 -1
- data/lib/aspera/cli/plugins/config.rb +100 -84
- data/lib/aspera/cli/plugins/faspex.rb +3 -3
- data/lib/aspera/cli/plugins/faspex5.rb +5 -1
- data/lib/aspera/cli/plugins/node.rb +24 -4
- data/lib/aspera/cli/plugins/preview.rb +1 -1
- data/lib/aspera/cli/transfer_agent.rb +3 -2
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +19 -1
- data/lib/aspera/fasp/agent_direct.rb +4 -0
- data/lib/aspera/fasp/agent_httpgw.rb +147 -88
- data/lib/aspera/fasp/installation.rb +4 -4
- data/lib/aspera/fasp/parameters.rb +13 -1
- data/lib/aspera/fasp/parameters.yaml +5 -2
- data/lib/aspera/fasp/resume_policy.rb +1 -1
- data/lib/aspera/keychain/encrypted_hash.rb +36 -98
- data/lib/aspera/keychain/macos_security.rb +127 -57
- data/lib/aspera/persistency_folder.rb +3 -2
- data/lib/aspera/proxy_auto_config.rb +12 -5
- data/lib/aspera/rest.rb +19 -10
- data.tar.gz.sig +0 -0
- metadata +5 -33
- metadata.gz.sig +0 -0
@@ -1,91 +1,161 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# https://github.com/fastlane-community/security
|
4
|
+
require 'aspera/cli/info'
|
4
5
|
|
5
6
|
# enhance the gem to support other keychains
|
6
|
-
module
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
7
|
+
module Aspera
|
8
|
+
module Keychain
|
9
|
+
module MacosSecurity
|
10
|
+
# keychain based on macOS keychain, using `security` cmmand line
|
11
|
+
class Keychain
|
12
|
+
DOMAINS = %i[user system common dynamic].freeze
|
13
|
+
LIST_OPTIONS={
|
14
|
+
domain: :c
|
15
|
+
}
|
16
|
+
ADD_PASS_OPTIONS={
|
17
|
+
account: :a,
|
18
|
+
creator: :c,
|
19
|
+
type: :C,
|
20
|
+
domain: :d,
|
21
|
+
kind: :D,
|
22
|
+
value: :G,
|
23
|
+
comment: :j,
|
24
|
+
label: :l,
|
25
|
+
path: :p,
|
26
|
+
port: :P,
|
27
|
+
protocol: :r,
|
28
|
+
server: :s,
|
29
|
+
service: :s,
|
30
|
+
auth: :t,
|
31
|
+
password: :w,
|
32
|
+
getpass: :g
|
33
|
+
}.freeze
|
34
|
+
class << self
|
35
|
+
def execute(command,options=nil,supported=nil,lastopt=nil)
|
36
|
+
url = options&.delete(:url)
|
37
|
+
if !url.nil?
|
38
|
+
uri = URI.parse(url)
|
39
|
+
raise 'only https' unless uri.scheme.eql?('https')
|
40
|
+
options[:protocol] = 'htps'
|
41
|
+
raise 'host required in URL' if uri.host.nil?
|
42
|
+
options[:server] = uri.host
|
43
|
+
options[:path] = uri.path unless ['','/'].include?(uri.path)
|
44
|
+
options[:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/')
|
45
|
+
end
|
46
|
+
cmd=['security',command]
|
47
|
+
options&.each do |k,v|
|
48
|
+
raise "unknown option: #{k}" unless supported.has_key?(k)
|
49
|
+
next if v.nil?
|
50
|
+
cmd.push("-#{supported[k]}")
|
51
|
+
cmd.push(v.shellescape) unless v.empty?
|
52
|
+
end
|
53
|
+
cmd.push(lastopt) unless lastopt.nil?
|
54
|
+
Log.log.debug("executing>>#{cmd.join(' ')}")
|
55
|
+
result=%x(#{cmd.join(' ')} 2>&1)
|
56
|
+
Log.log.debug("result>>[#{result}]")
|
57
|
+
return result
|
58
|
+
end
|
14
59
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
options
|
28
|
-
|
29
|
-
|
60
|
+
def keychains(output)
|
61
|
+
output.split("\n").collect { |line| new(line.strip.gsub(/^"|"$/, '')) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def default
|
65
|
+
keychains(execute('default-keychain')).first
|
66
|
+
end
|
67
|
+
|
68
|
+
def login
|
69
|
+
keychains(execute('login-keychain')).first
|
70
|
+
end
|
71
|
+
|
72
|
+
def list(options={})
|
73
|
+
raise ArgumentError,"Invalid domain #{options[:domain]}, expected one of: #{DOMAINS}" unless options[:domain].nil? || DOMAINS.include?(options[:domain])
|
74
|
+
keychains(execute('list-keychains',options,LIST_OPTIONS))
|
75
|
+
end
|
76
|
+
|
77
|
+
def by_name(name)
|
78
|
+
list.find{|kc|kc.path.end_with?("/#{name}.keychain-db")}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
attr_reader :path
|
82
|
+
|
83
|
+
def initialize(path)
|
84
|
+
@path = path
|
85
|
+
end
|
86
|
+
|
87
|
+
def decode_hex_blob(string)
|
88
|
+
[string].pack('H*').force_encoding('UTF-8')
|
89
|
+
end
|
90
|
+
|
91
|
+
def password(operation,passtype,options)
|
92
|
+
raise "wrong operation: #{operation}" unless %i[add find delete].include?(operation)
|
93
|
+
raise "wrong passtype: #{passtype}" unless %i[generic internet].include?(passtype)
|
94
|
+
raise 'options shall be Hash' unless options.is_a?(Hash)
|
95
|
+
missing=(operation.eql?(:add) ? %i[account service password] : %i[label])-options.keys
|
96
|
+
raise "missing options: #{missing}" unless missing.empty?
|
97
|
+
options[:getpass]='' if operation.eql?(:find)
|
98
|
+
output=self.class.execute("#{operation}-#{passtype}-password",options,ADD_PASS_OPTIONS,@path)
|
99
|
+
raise output.gsub(/^.*: /,'') if output.start_with?('security: ')
|
100
|
+
return nil unless operation.eql?(:find)
|
101
|
+
attributes = {}
|
102
|
+
output.split("\n").each do |line|
|
103
|
+
case line
|
104
|
+
when /^keychain: "(.+)"/
|
105
|
+
# ignore
|
106
|
+
when /0x00000007 .+="(.+)"/
|
107
|
+
attributes['label'] = Regexp.last_match(1)
|
108
|
+
when /"(\w{4})".+="(.+)"/
|
109
|
+
attributes[Regexp.last_match(1)] = Regexp.last_match(2)
|
110
|
+
when /"(\w{4})"<blob>=0x([[:xdigit:]]+)/
|
111
|
+
attributes[Regexp.last_match(1)] = decode_hex_blob(Regexp.last_match(2))
|
112
|
+
when /^password: "(.+)"/
|
113
|
+
attributes['password'] = Regexp.last_match(1)
|
114
|
+
when /^password: 0x([[:xdigit:]]+)/
|
115
|
+
attributes['password'] = decode_hex_blob(Regexp.last_match(1))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
return attributes
|
30
119
|
end
|
31
|
-
flags = [orig_flags_for_options(options)]
|
32
|
-
flags.push(keychain.filename) unless keychain.nil?
|
33
|
-
flags.join(' ')
|
34
120
|
end
|
35
121
|
end
|
36
|
-
end
|
37
|
-
end
|
38
122
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
class MacosSecurity
|
43
|
-
def initialize(name=nil)
|
44
|
-
@keychain = name.nil? ? Security::Keychain.default_keychain : Security::Keychain.by_name(name)
|
123
|
+
class MacosSystem
|
124
|
+
def initialize(name=nil,password=nil)
|
125
|
+
@keychain = name.nil? ? MacosSecurity::Keychain.default_keychain : MacosSecurity::Keychain.by_name(name)
|
45
126
|
raise "no such keychain #{name}" if @keychain.nil?
|
46
127
|
end
|
47
128
|
|
48
129
|
def set(options)
|
49
130
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
50
|
-
unsupported = options.keys - %i[username url
|
131
|
+
unsupported = options.keys - %i[label username password url description]
|
51
132
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
52
|
-
|
53
|
-
|
54
|
-
url = options[:url]
|
55
|
-
raise 'options shall have url' if url.nil?
|
56
|
-
secret = options[:secret]
|
57
|
-
raise 'options shall have secret' if secret.nil?
|
58
|
-
raise 'set not implemented'
|
133
|
+
@keychain.password(:add,:generic, service: options[:label],
|
134
|
+
account: options[:username] || 'none', password: options[:password], comment: options[:description])
|
59
135
|
end
|
60
136
|
|
61
137
|
def get(options)
|
62
138
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
63
|
-
unsupported = options.keys - %i[
|
139
|
+
unsupported = options.keys - %i[label]
|
64
140
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
65
|
-
|
66
|
-
raise 'options shall have username' if username.nil?
|
67
|
-
url = options[:url]
|
68
|
-
raise 'options shall have url' if url.nil?
|
69
|
-
info = Security::InternetPassword.find(keychain: @keychain, url: url, account: username)
|
141
|
+
info = @keychain.password(:find,:generic,label: options[:label])
|
70
142
|
raise 'not found' if info.nil?
|
71
143
|
result = options.clone
|
72
|
-
result[:secret] = info
|
73
|
-
result[:description] = info
|
144
|
+
result[:secret] = info['password']
|
145
|
+
result[:description] = info['icmt']
|
74
146
|
return result
|
75
147
|
end
|
76
148
|
|
77
149
|
def list
|
78
|
-
|
150
|
+
# the only way to list is `dump-keychain` which triggers security alert
|
151
|
+
raise 'list not implemented, use macos keychain app'
|
79
152
|
end
|
80
153
|
|
81
154
|
def delete(options)
|
82
155
|
raise 'options shall be Hash' unless options.is_a?(Hash)
|
83
|
-
unsupported = options.keys - %i[
|
156
|
+
unsupported = options.keys - %i[label]
|
84
157
|
raise "unsupported options: #{unsupported}" unless unsupported.empty?
|
85
|
-
|
86
|
-
raise 'options shall have username' if username.nil?
|
87
|
-
url = options[:url]
|
88
|
-
raise "delete not implemented #{url}"
|
158
|
+
raise 'delete not implemented, use macos keychain app'
|
89
159
|
end
|
90
160
|
end
|
91
161
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'aspera/log'
|
5
|
+
require 'aspera/environment'
|
5
6
|
|
6
7
|
# search: persistency_folder PersistencyFolder
|
7
8
|
|
@@ -38,7 +39,7 @@ module Aspera
|
|
38
39
|
Log.log.debug("persistency saving: #{persist_filepath}")
|
39
40
|
File.delete(persist_filepath) if File.exist?(persist_filepath)
|
40
41
|
File.write(persist_filepath,value)
|
41
|
-
|
42
|
+
Environment.restrict_file_access(persist_filepath)
|
42
43
|
@cache[object_id] = value
|
43
44
|
end
|
44
45
|
|
@@ -68,7 +69,7 @@ module Aspera
|
|
68
69
|
def id_to_filepath(object_id)
|
69
70
|
raise 'object_id: only String supported' unless object_id.is_a?(String)
|
70
71
|
FileUtils.mkdir_p(@folder)
|
71
|
-
|
72
|
+
Environment.restrict_file_access(@folder)
|
72
73
|
return File.join(@folder,"#{object_id}#{FILE_SUFFIX}")
|
73
74
|
#.gsub(/[^a-z]+/,FILE_FIELD_SEPARATOR)
|
74
75
|
end
|
@@ -52,13 +52,15 @@ END_OF_JAVASCRIPT
|
|
52
52
|
|
53
53
|
public
|
54
54
|
|
55
|
+
attr_writer :proxy_user,:proxy_pass
|
56
|
+
|
55
57
|
# @param proxy_auto_config the proxy auto config script to be evaluated
|
56
58
|
def initialize(proxy_auto_config)
|
57
59
|
# user provided javascript with FindProxyForURL function
|
58
60
|
@proxy_auto_config = proxy_auto_config
|
59
61
|
# avoid multiple execution, this does not support load balancing
|
60
62
|
@cache = {}
|
61
|
-
@pac_functions = nil
|
63
|
+
@proxy_user=@proxy_pass=@pac_functions = nil
|
62
64
|
end
|
63
65
|
|
64
66
|
def register_uri_generic
|
@@ -106,23 +108,28 @@ END_OF_JAVASCRIPT
|
|
106
108
|
case parts.shift
|
107
109
|
when 'DIRECT'
|
108
110
|
raise 'DIRECT has no param' unless parts.empty?
|
111
|
+
Log.log.debug('ignoring proxy DIRECT')
|
109
112
|
when 'PROXY'
|
110
113
|
addr_port = parts.shift
|
111
114
|
raise 'PROXY shall have one param' unless addr_port.is_a?(String) && parts.empty?
|
112
115
|
begin
|
113
116
|
# PAC proxy addresses are <host>:<port>
|
114
117
|
if /:[0-9]+$/.match?(addr_port)
|
115
|
-
|
116
|
-
|
118
|
+
uri=URI.parse("proxy://#{addr_port}")
|
119
|
+
# ruby v>2.6 allows
|
120
|
+
uri.user=@proxy_user
|
121
|
+
uri.password=@proxy_pass
|
122
|
+
uri_list.push(uri)
|
117
123
|
else
|
118
124
|
Log.log.warn("PAC: PROXY must be <address>:<port>, ignoring #{addr_port}")
|
119
125
|
end
|
120
|
-
rescue StandardError
|
121
|
-
Log.log.warn("PAC: cannot parse #{addr_port}")
|
126
|
+
rescue StandardError => e
|
127
|
+
Log.log.warn("PAC: cannot parse #{addr_port} #{e}")
|
122
128
|
end
|
123
129
|
else Log.log.warn("PAC: ignoring proxy type #{parts.first}: not supported")
|
124
130
|
end
|
125
131
|
end
|
132
|
+
Log.log.debug("Proxies: #{uri_list}")
|
126
133
|
return uri_list
|
127
134
|
end
|
128
135
|
end
|
data/lib/aspera/rest.rb
CHANGED
@@ -37,7 +37,9 @@ module Aspera
|
|
37
37
|
user_agent: 'Ruby',
|
38
38
|
download_partial_suffix: '.http_partial',
|
39
39
|
# a lambda which takes the Net::HTTP as arg, use this to change parameters
|
40
|
-
session_cb: nil
|
40
|
+
session_cb: nil,
|
41
|
+
proxy_user: nil,
|
42
|
+
proxy_pass: nil
|
41
43
|
}
|
42
44
|
|
43
45
|
class << self
|
@@ -75,6 +77,21 @@ module Aspera
|
|
75
77
|
end
|
76
78
|
return uri
|
77
79
|
end
|
80
|
+
|
81
|
+
def start_http_session(base_url)
|
82
|
+
uri = build_uri(base_url)
|
83
|
+
# this honors http_proxy env var
|
84
|
+
http_session = Net::HTTP.new(uri.host, uri.port)
|
85
|
+
http_session.proxy_user=proxy_user
|
86
|
+
http_session.proxy_pass=proxy_pass
|
87
|
+
http_session.use_ssl = uri.scheme.eql?('https')
|
88
|
+
http_session.set_debug_output($stdout) if debug
|
89
|
+
# set http options in callback, such as timeout and cert. verification
|
90
|
+
session_cb&.call(http_session)
|
91
|
+
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
92
|
+
http_session.start
|
93
|
+
return http_session
|
94
|
+
end
|
78
95
|
end
|
79
96
|
|
80
97
|
private
|
@@ -82,15 +99,7 @@ module Aspera
|
|
82
99
|
# create and start keep alive connection on demand
|
83
100
|
def http_session
|
84
101
|
if @http_session.nil?
|
85
|
-
|
86
|
-
# this honors http_proxy env var
|
87
|
-
@http_session = Net::HTTP.new(uri.host, uri.port)
|
88
|
-
@http_session.use_ssl = uri.scheme.eql?('https')
|
89
|
-
@http_session.set_debug_output($stdout) if self.class.debug
|
90
|
-
# set http options in callback, such as timeout and cert. verification
|
91
|
-
self.class.session_cb&.call(@http_session)
|
92
|
-
# manually start session for keep alive (if supported by server, else, session is closed every time)
|
93
|
-
@http_session.start
|
102
|
+
@http_session=self.class.start_http_session(@params[:base_url])
|
94
103
|
end
|
95
104
|
return @http_session
|
96
105
|
end
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aspera-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Laurent Martin
|
@@ -35,7 +35,7 @@ cert_chain:
|
|
35
35
|
ZjkOWbUc1aLIsfaQFHWyNfisY9X2RgkFHjX0p5493wnoA7aWh52MUhc145npFh8z
|
36
36
|
v4P9xwkT02Shkert4B4iwNvVjoAUGk+J4090svZCroAyXBjon5LV7MJ4fyw=
|
37
37
|
-----END CERTIFICATE-----
|
38
|
-
date: 2022-
|
38
|
+
date: 2022-12-01 00:00:00.000000000 Z
|
39
39
|
dependencies:
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
41
|
name: execjs
|
@@ -128,19 +128,19 @@ dependencies:
|
|
128
128
|
- !ruby/object:Gem::Version
|
129
129
|
version: '2.0'
|
130
130
|
- !ruby/object:Gem::Dependency
|
131
|
-
name:
|
131
|
+
name: symmetric-encryption
|
132
132
|
requirement: !ruby/object:Gem::Requirement
|
133
133
|
requirements:
|
134
134
|
- - "~>"
|
135
135
|
- !ruby/object:Gem::Version
|
136
|
-
version: '
|
136
|
+
version: '4.6'
|
137
137
|
type: :runtime
|
138
138
|
prerelease: false
|
139
139
|
version_requirements: !ruby/object:Gem::Requirement
|
140
140
|
requirements:
|
141
141
|
- - "~>"
|
142
142
|
- !ruby/object:Gem::Version
|
143
|
-
version: '
|
143
|
+
version: '4.6'
|
144
144
|
- !ruby/object:Gem::Dependency
|
145
145
|
name: terminal-table
|
146
146
|
requirement: !ruby/object:Gem::Requirement
|
@@ -197,20 +197,6 @@ dependencies:
|
|
197
197
|
- - "~>"
|
198
198
|
- !ruby/object:Gem::Version
|
199
199
|
version: '1.2'
|
200
|
-
- !ruby/object:Gem::Dependency
|
201
|
-
name: websocket-client-simple
|
202
|
-
requirement: !ruby/object:Gem::Requirement
|
203
|
-
requirements:
|
204
|
-
- - "~>"
|
205
|
-
- !ruby/object:Gem::Version
|
206
|
-
version: '0.3'
|
207
|
-
type: :runtime
|
208
|
-
prerelease: false
|
209
|
-
version_requirements: !ruby/object:Gem::Requirement
|
210
|
-
requirements:
|
211
|
-
- - "~>"
|
212
|
-
- !ruby/object:Gem::Version
|
213
|
-
version: '0.3'
|
214
200
|
- !ruby/object:Gem::Dependency
|
215
201
|
name: xml-simple
|
216
202
|
requirement: !ruby/object:Gem::Requirement
|
@@ -225,20 +211,6 @@ dependencies:
|
|
225
211
|
- - "~>"
|
226
212
|
- !ruby/object:Gem::Version
|
227
213
|
version: '1.0'
|
228
|
-
- !ruby/object:Gem::Dependency
|
229
|
-
name: grpc
|
230
|
-
requirement: !ruby/object:Gem::Requirement
|
231
|
-
requirements:
|
232
|
-
- - "~>"
|
233
|
-
- !ruby/object:Gem::Version
|
234
|
-
version: '1.0'
|
235
|
-
type: :development
|
236
|
-
prerelease: false
|
237
|
-
version_requirements: !ruby/object:Gem::Requirement
|
238
|
-
requirements:
|
239
|
-
- - "~>"
|
240
|
-
- !ruby/object:Gem::Version
|
241
|
-
version: '1.0'
|
242
214
|
- !ruby/object:Gem::Dependency
|
243
215
|
name: mimemagic
|
244
216
|
requirement: !ruby/object:Gem::Requirement
|
metadata.gz.sig
CHANGED
Binary file
|