lib/sym/app/keychain.rb
require 'sym'
require 'sym/app'
require 'sym/errors'
module Sym
module App
#
# This class forms and shells several commands that wrap Mac OS-X +security+ command.
# They provide access to storing generic passwords in the KeyChain Access.
#
class KeyChain
class << self
attr_accessor :user, :kind, :sub_section
def get(value)
self.new(value).find
end
def configure
yield self
end
def validate!
raise ArgumentError.new(
'User is not defined. Either set $USER in environment, or directly on the class.') unless self.user
end
end
configure do
self.kind = 'sym'
self.user = ENV.fetch('USER', nil)
self.sub_section = 'generic-password'
end
attr_accessor :key_name, :opts, :stderr_disabled
def initialize(key_name, opts = {})
self.key_name = key_name
self.opts = opts
self.class.validate!
opts[:trace] ? stderr_on : stderr_off
end
def add(password)
delete rescue nil
sleep 0.1
execute command(:add, " -T /usr/bin/security -w '#{password}' ")
end
def find
execute command(:find, ' -g -w ')
end
def delete
execute command(:delete)
end
def execute(command)
command += ' 2>/dev/null' if stderr_disabled
puts "> #{command.yellow}" if opts[:verbose]
output = `#{command}`
result = $?
unless result.success?
warn "> ERROR running command:\n> $ #{output.red}" if !stderr_disabled && opts[:verbose]
raise Sym::Errors::KeyChainCommandError.new("Command error: #{result}, command: #{command}")
end
output.chomp
rescue Errno::ENOENT => e
raise Sym::Errors::KeyChainCommandError.new("Command error: #{e.message}, command: #{command}")
end
def stderr_off
self.stderr_disabled = true
end
def stderr_on
self.stderr_disabled = false
end
private
def command(action, extras = nil)
out = base_command(action)
out << extras if extras
out = out.join
# Do not actually ever run these commands on non MacOSX
out = "echo Run this –\"#{out}\", on #{Sym::App.this_os}?\nAre you sure?" unless Sym::App.osx?
out
end
def base_command(action)
[
"/usr/bin/security #{action}-#{self.class.sub_section} ",
"-a #{self.class.user} ",
"-D #{self.class.kind} ",
"-s #{self.key_name} "
]
end
end
end
end
#
# Usage: add-generic-password [-a account] [-s service] [-w password] [options...] [-A|--trace appPath] [keychain]
# -a Specify account name (required)
# -c Specify item creator (optional four-character code)
# -C Specify item type (optional four-character code)
# -D Specify kind (default is "application password")
# -G Specify generic attribute (optional)
# -j Specify comment string (optional)
# -l Specify label (if omitted, service name is used as default label)
# -s Specify service name (required)
# -p Specify password to be added (legacy option, equivalent to -w)
# -w Specify password to be added
# -A Allow any application to access this item without warning (insecure, not recommended!)
# --trace Specify an application which may access this item (multiple --trace options are allowed)
# -U Update item if it already exists (if omitted, the item cannot already exist)
#
# Usage: find-generic-password [-a account] [-s service] [options...] [-g] [keychain...]
# -a Match "account" string
# -c Match "creator" (four-character code)
# -C Match "type" (four-character code)
# -D Match "kind" string
# -G Match "value" string (generic attribute)
# -j Match "comment" string
# -l Match "label" string
# -s Match "service" string
# -g Display the password for the item found
# -w Display only the password on stdout
# If no keychains are specified to search, the default search list is used.
# Find a generic password item.
#
# Usage: delete-generic-password [-a account] [-s service] [options...] [keychain...]
# -a Match "account" string
# -c Match "creator" (four-character code)
# -C Match "type" (four-character code)
# -D Match "kind" string
# -G Match "value" string (generic attribute)
# -j Match "comment" string
# -l Match "label" string
# -s Match "service" string
# If no keychains are specified to search, the default search list is used.
# Delete a generic password item.