class Keychain::Item
@note Methods only need a user’s explicit authorization if they want the
password data, metadata does not need permission. In these cases, the OS should present an alert asking to allow, deny, or always allow the script to access. You need to be careful when using 'always allow' if you are running this code from interactive ruby or the regular interpreter because you could accidentally allow any future script to not require permission to access any password in the keychain.
Represents an entry in the login keychain.
The big assumption that this class makes is that you only ever want to work with a single keychain item; whether it be searching for metadata, getting passwords, or adding a new entry.
In order to be secure, this class will NEVER cache a password; any time that you change a password, it will be written to the keychain immeadiately.
Attributes
@return [Hash]
Public Class Methods
You should initialize objects of this class with the attributes relevant to the item you wish to work with, but you can add or remove attributes via accessors as well. @param [Hash] attributes
# File lib/mr_keychain.rb, line 41 def initialize attributes = {} @attributes = { KSecClass => KSecClassInternetPassword } @attributes.merge! attributes end
Public Instance Methods
Direct access to the attributes hash of the keychain item.
# File lib/mr_keychain.rb, line 28 def [] key @attributes[key] end
Direct access to the attributes hash of the keychain item.
# File lib/mr_keychain.rb, line 33 def []= key, value @attributes[key] = value end
@note This method asks only for the metadata and doesn’t need authorization Returns true if there are any item matching the given attributes. @raise [KeychainException] for unexpected errors @return [true,false]
# File lib/mr_keychain.rb, line 50 def exists? result = Pointer.new :id search = @attributes.merge( KSecMatchLimit => KSecMatchLimitOne, KSecReturnAttributes => true ) case (error_code = SecItemCopyMatching(search, result)) when ErrSecSuccess then true when ErrSecItemNotFound then false else message = SecCopyErrorMessageString(error_code, nil) raise KeychainException, "Error checking keychain item existence: #{message}" end end
Get all the metadata about a keychain item, they will be keyed according Apple’s documentation. @raise [KeychainException] @return [Hash]
# File lib/mr_keychain.rb, line 141 def metadata result = Pointer.new :id search = @attributes.merge( KSecMatchLimit => KSecMatchLimitOne, KSecReturnAttributes => true ) case (error_code = SecItemCopyMatching(search, result)) when ErrSecSuccess then result[0] else message = SecCopyErrorMessageString(error_code, nil) raise KeychainException, "Error getting metadata: #{message}" end end
Update attributes to include all the metadata from the keychain. @raise [KeychainException] @return [Hash]
# File lib/mr_keychain.rb, line 160 def metadata! @attributes = metadata end
@note We ask for an NSData object here in order to get the password. Returns the password for the first match found, raises an error if no keychain item is found.
Blank passwords should come back as an empty string, but that hasn’t been tested. @raise [KeychainException] @return [String] UTF8 encoded password string
# File lib/mr_keychain.rb, line 76 def password result = Pointer.new :id search = @attributes.merge( KSecMatchLimit => KSecMatchLimitOne, KSecReturnData => true ) case (error_code = SecItemCopyMatching(search, result)) when ErrSecSuccess then result[0].to_str else message = SecCopyErrorMessageString(error_code, nil) raise KeychainException, "Error getting password: #{message}" end end
Updates the password associated with the keychain item. If the item does not exist in the keychain it will be added first. @raise [KeychainException] @param [String] new_password a UTF-8 encoded string @return [String] the saved password
# File lib/mr_keychain.rb, line 98 def password= new_password password_data = { KSecValueData => new_password.to_data } if exists? error_code = SecItemUpdate( @attributes, password_data ) else error_code = SecItemAdd( @attributes.merge(password_data), nil ) end case error_code when ErrSecSuccess then password else message = SecCopyErrorMessageString(error_code, nil) raise KeychainException, "Error updating password: #{message}" end end
@todo This method does not really fit with the rest of the API. @note This method does not need authorization unless you are
updating the password.
Updates attributes of the item in the keychain. If the item does not exist yet then this method will raise an exception.
Use a value of nil to remove an attribute. @param [Hash] new_attributes the attributes that you want to update @return [Hash] attributes
# File lib/mr_keychain.rb, line 124 def update! new_attributes result = Pointer.new :id query = @attributes.merge( KSecMatchLimit => KSecMatchLimitOne ) case (error_code = SecItemUpdate(query, new_attributes)) when ErrSecSuccess then metadata! else message = SecCopyErrorMessageString(error_code, nil) raise KeychainException, "Error updating keychain item: #{message}" end end