Class: Aspera::Keychain::MacosSecurity::Keychain
- Inherits:
-
Object
- Object
- Aspera::Keychain::MacosSecurity::Keychain
- Defined in:
- lib/aspera/keychain/macos_security.rb
Overview
keychain based on macOS keychain, using ‘security` command line
Constant Summary collapse
- SECURITY_UTILITY =
'security'
- DOMAINS =
%i[user system common dynamic].freeze
- LIST_OPTIONS =
{ domain: :c }
- ADD_PASS_OPTIONS =
{ account: :a, creator: :c, type: :C, domain: :d, kind: :D, value: :G, comment: :j, label: :l, path: :p, port: :P, protocol: :r, server: :s, service: :s, auth: :t, password: :w, getpass: :g }.freeze
Instance Attribute Summary collapse
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Class Method Summary collapse
- .by_name(name) ⇒ Object
- .default ⇒ Object
- .execute(command, options = nil, supported = nil, last_opt = nil) ⇒ Object
- .key_chains(output) ⇒ Object
- .list(options = {}) ⇒ Object
- .login ⇒ Object
Instance Method Summary collapse
- #decode_hex_blob(string) ⇒ Object
-
#initialize(path) ⇒ Keychain
constructor
A new instance of Keychain.
- #password(operation, pass_type, options) ⇒ Object
Constructor Details
#initialize(path) ⇒ Keychain
Returns a new instance of Keychain.
90 91 92 |
# File 'lib/aspera/keychain/macos_security.rb', line 90 def initialize(path) @path = path end |
Instance Attribute Details
#path ⇒ Object (readonly)
Returns the value of attribute path.
88 89 90 |
# File 'lib/aspera/keychain/macos_security.rb', line 88 def path @path end |
Class Method Details
.by_name(name) ⇒ Object
84 85 86 |
# File 'lib/aspera/keychain/macos_security.rb', line 84 def by_name(name) list.find{|kc|kc.path.end_with?("/#{name}.keychain-db")} end |
.default ⇒ Object
71 72 73 |
# File 'lib/aspera/keychain/macos_security.rb', line 71 def default key_chains(execute('default-keychain')).first end |
.execute(command, options = nil, supported = nil, last_opt = nil) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/aspera/keychain/macos_security.rb', line 40 def execute(command, =nil, supported=nil, last_opt=nil) url = &.delete(:url) if !url.nil? uri = URI.parse(url) Aspera.assert(uri.scheme.eql?('https')){'only https'} [:protocol] = 'htps' # cspell: disable-line raise 'host required in URL' if uri.host.nil? [:server] = uri.host [:path] = uri.path unless ['', '/'].include?(uri.path) [:port] = uri.port unless uri.port.eql?(443) && !url.include?(':443/') end command_line = [SECURITY_UTILITY, command] &.each do |k, v| Aspera.assert(supported.key?(k)){"unknown option: #{k}"} next if v.nil? command_line.push("-#{supported[k]}") command_line.push(v.shellescape) unless v.empty? end command_line.push(last_opt) unless last_opt.nil? Log.log.debug{"executing>>#{command_line.join(' ')}"} stdout, stderr, status = Open3.capture3(*command_line) Log.log.debug{"status=#{status}, stderr=#{stderr}"} Log.log.trace1{"stdout=#{stdout}"} raise "#{SECURITY_UTILITY} failed: #{status.exitstatus} : #{stderr}" unless status.success? return stdout end |
.key_chains(output) ⇒ Object
67 68 69 |
# File 'lib/aspera/keychain/macos_security.rb', line 67 def key_chains(output) output.split("\n").collect { |line| new(line.strip.gsub(/^"|"$/, '')) } end |
.list(options = {}) ⇒ Object
79 80 81 82 |
# File 'lib/aspera/keychain/macos_security.rb', line 79 def list(={}) Aspera.assert_values([:domain], DOMAINS, exception_class: ArgumentError){'domain'} unless [:domain].nil? key_chains(execute('list-key_chains', , LIST_OPTIONS)) end |
.login ⇒ Object
75 76 77 |
# File 'lib/aspera/keychain/macos_security.rb', line 75 def login key_chains(execute('login-keychain')).first end |
Instance Method Details
#decode_hex_blob(string) ⇒ Object
94 95 96 |
# File 'lib/aspera/keychain/macos_security.rb', line 94 def decode_hex_blob(string) [string].pack('H*').force_encoding('UTF-8') end |
#password(operation, pass_type, options) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/aspera/keychain/macos_security.rb', line 98 def password(operation, pass_type, ) Aspera.assert_values(operation, %i[add find delete]){'operation'} Aspera.assert_values(pass_type, %i[generic internet]){'pass_type'} Aspera.assert_type(, Hash) missing = (operation.eql?(:add) ? %i[account service password] : %i[label]) - .keys Aspera.assert(missing.empty?){"missing options: #{missing}"} [:getpass] = '' if operation.eql?(:find) output = self.class.execute("#{operation}-#{pass_type}-password", , ADD_PASS_OPTIONS, @path) raise output.gsub(/^.*: /, '') if output.start_with?('security: ') return unless operation.eql?(:find) attributes = {} output.split("\n").each do |line| case line when /^keychain: "(.+)"/ # ignore when /0x00000007 .+="(.+)"/ attributes['label'] = Regexp.last_match(1) when /"(\w{4})".+="(.+)"/ attributes[Regexp.last_match(1)] = Regexp.last_match(2) when /"(\w{4})"<blob>=0x([[:xdigit:]]+)/ attributes[Regexp.last_match(1)] = decode_hex_blob(Regexp.last_match(2)) when /^password: "(.+)"/ attributes['password'] = Regexp.last_match(1) when /^password: 0x([[:xdigit:]]+)/ attributes['password'] = decode_hex_blob(Regexp.last_match(1)) end end return attributes end |