Alphred
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Todo
  • Download

Namespaces

  • Alphred
  • None

Classes

  • Alphred
  1 <?php
  2 /**
  3  * Keychain classes for Alphred
  4  *
  5  * PHP version 5
  6  *
  7  * @package    Alphred
  8  * @copyright  Shawn Patrick Rice 2014
  9  * @license    http://opensource.org/licenses/MIT  MIT
 10  * @version    1.0.0
 11  * @author     Shawn Patrick Rice <rice@shawnrice.org>
 12  * @link       http://www.github.com/shawnrice/alphred
 13  * @link       http://shawnrice.github.io/alphred
 14  * @since      File available since Release 1.0.0
 15  *
 16  */
 17 
 18 namespace Alphred;
 19 
 20 /**
 21  * Enables easy access to parts of the Keychain for secure password storage / retrieval
 22  *
 23  * Uses the `security` command in order to add / retrieve / delete passwords. Note: we use
 24  * only the "generic" password functions and not the "internet" password functions.
 25  *
 26  * @see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/security.1.html security(1)
 27  *
 28  */
 29 class Keychain {
 30 
 31     /**
 32      * Throws an exception if you try to instantiate it
 33      *
 34      * @throws UseOnlyAsStatic if you try to institate a Globals object
 35      */
 36     public function __construct() {
 37         throw new UseOnlyAsStatic( 'The Keychain class is to be used statically only.', 2 );
 38     }
 39 
 40     /**
 41      * Saves a password to the keychain
 42      *
 43      * @throws PasswordExists           (indirectly )when trying to add a password that already exists
 44      *                                              without specifying 'update'
 45      *
 46      * @param  string   $account        the name of the account
 47      * @param  string   $password       the new password
 48      * @param  boolean  $update         whether or not to update an old password (defaults to `true`)
 49      * @param  string   $service            optional: defaults to the bundleid of the workflow (if set)
 50      *
 51      * @return boolean                  whether or not it was successful (usually true)
 52      */
 53     public static function save_password( $account, $password, $update = true, $service = null ) {
 54         if ( $update ) {
 55             $update = ' -U';
 56         } else {
 57             $update = '';
 58         }
 59         return self::call_security( 'add-generic-password', $service, $account, "{$update} -w '{$password}'" );
 60     }
 61 
 62 
 63     /**
 64      * Retrieves a password from the keychain
 65      *
 66      * @throws InvalidKeychainAccount   on an empty account
 67      *
 68      * @param  string $account          the name of an account
 69      * @param  string $service          optional: defaults to the bundleid of the workflow (if set)
 70      *
 71      * @return string                   the password
 72      */
 73     public static function find_password( $account, $service = null ) {
 74         // Make sure that the account is something other than whitespace
 75         if ( empty( trim( $account ) ) ) {
 76             throw new InvalidKeychainAccount( 'You must specify an account to get a password', 3 );
 77         }
 78 
 79         return self::call_security( 'find-generic-password', $service, $account, '-w' );
 80     }
 81 
 82 
 83     /**
 84      * Deletes a password from the keychain
 85      *
 86      * @param  string $account          the name of the account
 87      * @param  string $service          optional: defaults to the bundleid of the workflow (if set)
 88      * @return boolean                  success of command
 89      */
 90     public static function delete_password( $account, $service = null ) {
 91         if ( empty( trim( $account ) ) ) {
 92             throw new InvalidKeychainAccount(
 93                 'The action you just attempted will delete the entire keychain; please specify the account', 3
 94             );
 95         }
 96         return self::call_security( 'delete-generic-password', $service, $account, '' );
 97     }
 98 
 99 
100     /**
101      * Interfaces directly with the `security` command
102      *
103      * @throws PasswordExists           when trying to add a password that already exists without specifying 'update'
104      * @throws PasswordNotFound         when trying to find a password that does not exist
105      * @throws UnknownSecurityException when something weird happens
106      *
107      * @param  string   $action     one of 'add-', 'delete-', or 'find-generic-password'
108      * @param  string   $service    the "owner" of the action; usually the bundle id
109      * @param  string   $account    the "account" of the password
110      * @param  string   $args       extra arguments for the security command
111      * @return string|boolean       either a found password or true
112      */
113     private function call_security( $action, $service, $account, $args ) {
114         if ( ! in_array( $action, [ 'add-generic-password', 'delete-generic-password', 'find-generic-password' ] ) ) {
115             throw new InvalidSecurityAction( "{$action} is not valid.", 4 );
116 
117             // So, if, for some reason, the thing is caught, we can't really go on. So we'll exit anyway.
118             return false;
119         }
120         $service = self::set_service( $service );
121 
122         // Note: $args needs to be escaped in the function that calls this one
123         $command = "security {$action} -s '{$service}' -a '{$account}'  {$args}";
124         exec( $command, $output, $return_code );
125         if ( 45 == $return_code ) {
126             // raise exception because password already exists
127             throw new PasswordExists( 'Password Already Exists, did you mean to update it?', 2 );
128         } else if ( 44 == $return_code ) {
129             // raise exception because password does not exist
130             throw new PasswordNotFound( "Password for '{$account}' does not exist", 3 );
131         } else if ( 0 == $return_code ) {
132             // Do nothing here. For now.
133             // @todo Do something here.
134         } else {
135             throw new UnknownSecurityException(
136                 'An unanticipated error has happened when trying to call the security command', 4
137             );
138         }
139 
140         if ( 'find-generic-password' === $action ) {
141             /**
142              * @todo Test that this is exactly what we need to return
143              */
144             return $output[0];
145         }
146         return true;
147     }
148 
149     /**
150      * Sets the service appropriately, usually to the bundle id of the workflow
151      */
152     private function set_service( $service ) {
153 
154         // The service has not been set, so let's set it to the bundle id of the workflow
155         if ( is_null( $service ) ) {
156             if ( Globals::bundle() ) {
157                 $service = Globals::bundle();
158             }
159         }
160         return $service;
161     }
162 
163 
164 }
Alphred API documentation generated by ApiGen