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.
@@ -1,91 +1,161 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'security'
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 Security
7
- class Keychain
8
- class << self
9
- def by_name(name)
10
- keychains_from_output('security list-keychains').find{|kc|kc.filename.end_with?("/#{name}.keychain-db")}
11
- end
12
- end
13
- end
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
- class Password
16
- class << self
17
- # add some login to original method
18
- alias_method :orig_flags_for_options, :flags_for_options
19
- def flags_for_options(options = {})
20
- keychain = options.delete(:keychain)
21
- url = options.delete(:url)
22
- if !url.nil?
23
- uri = URI.parse(url)
24
- raise 'only https' unless uri.scheme.eql?('https')
25
- options[:r] = 'htps'
26
- raise 'host required in URL' if uri.host.nil?
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/')
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
- module Aspera
40
- module Keychain
41
- # keychain based on macOS keychain, using `security` cmmand line
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 secret description]
131
+ unsupported = options.keys - %i[label username password url description]
51
132
  raise "unsupported options: #{unsupported}" unless unsupported.empty?
52
- username = options[:username]
53
- raise 'options shall have username' if username.nil?
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[username url]
139
+ unsupported = options.keys - %i[label]
64
140
  raise "unsupported options: #{unsupported}" unless unsupported.empty?
65
- username = options[:username]
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.password
73
- result[:description] = info.attributes['icmt']
144
+ result[:secret] = info['password']
145
+ result[:description] = info['icmt']
74
146
  return result
75
147
  end
76
148
 
77
149
  def list
78
- raise 'list not implemented'
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[username url]
156
+ unsupported = options.keys - %i[label]
84
157
  raise "unsupported options: #{unsupported}" unless unsupported.empty?
85
- username = options[:username]
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
- File.chmod(0400,persist_filepath)
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
- File.chmod(0700,@folder)
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
- # we want to return URIs, so add dummy scheme
116
- uri_list.push(URI.parse("proxy://#{addr_port}"))
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
- uri = self.class.build_uri(@params[:base_url])
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.9.0
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-09-14 00:00:00.000000000 Z
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: security
131
+ name: symmetric-encryption
132
132
  requirement: !ruby/object:Gem::Requirement
133
133
  requirements:
134
134
  - - "~>"
135
135
  - !ruby/object:Gem::Version
136
- version: '0.0'
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: '0.0'
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