aspera-cli 4.9.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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