Location: PHPKode > projects > SolarPHP > solar-system-1.1.1/solar/source/solar/Solar/Auth/Adapter.php
<?php
/**
 * 
 * Abstract authentication adapter.
 * 
 * @category Solar
 * 
 * @package Solar_Auth
 * 
 * @author Paul M. Jones <hide@address.com>
 * 
 * @license http://opensource.org/licenses/bsd-license.php BSD
 * 
 * @version $Id: Adapter.php 4582 2010-06-02 21:25:46Z pmjones $
 * 
 */
abstract class Solar_Auth_Adapter extends Solar_Base {
    
    /**
     * 
     * Default configuration values.
     * 
     * @config int expire Authentication lifetime in seconds; zero is
     *   forever.  Default is 14400 (4 hours). If this value is greater than
     *   the non-zero PHP ini setting for `session.cookie_lifetime`, it will
     *   throw an exception.
     * 
     * @config int idle Maximum allowed idle time in seconds; zero is
     *   forever.  Default is 1440 (24 minutes). If this value is greater than
     *   the the PHP ini setting for `session.gc_maxlifetime`, it will throw
     *   an exception.
     * 
     * @config bool allow Whether or not to allow automatic login/logout at start()
     *   time. Default true.
     * 
     * @config dependency cache A Solar_Cache dependency to store user data. Default is
     *   to create a Solar_Cache_Adapter_Session object internal to this 
     *   instance.
     * 
     * @config string source The source for auth credentials, 'get' (via the
     *   for GET request vars) or 'post' (via the POST request vars).
     *   Default is 'post'.
     * 
     * @config string source_handle Username key in the credential array source,
     *   default 'handle'.
     * 
     * @config string source_passwd Password key in the credential array source,
     *   default 'passwd'.
     * 
     * @config string source_redirect Element key in the credential array source to indicate
     *   where to redirect on successful login or logout, default 'redirect'.
     * 
     * @config string source_process Element key in the credential array source to indicate
     *   how to process the request, default 'process'.
     * 
     * @config string process_login The source_process element value indicating a login request;
     *   default is the 'PROCESS_LOGIN' locale key value.
     * 
     * @config string process_logout The source_process element value indicating a logout request;
     *   default is the 'PROCESS_LOGOUT' locale key value.
     * 
     * @config callback login_callback A callback to execute as part of the login process,
     * whether or not login was successful.
     * 
     * @config callback logout_callback A callback to execute as part of the logout process.
     * 
     * @var array
     * 
     */
    protected $_Solar_Auth_Adapter = array(
        'expire'         => 14400,
        'idle'           => 1440,
        'allow'          => true,
        'cache' => array(
            'adapter' => 'Solar_Cache_Adapter_Session',
            'prefix'  => 'Solar_Auth_Adapter',
        ),
        'source'         => 'post',
        'source_handle'  => 'handle',
        'source_passwd'  => 'passwd',
        'source_redirect' => 'redirect',
        'source_process' => 'process',
        'process_login'  => null,
        'process_logout' => null,
        'login_callback'  => null,
        'logout_callback' => null,
    );
    
    /**
     * 
     * Details on the current request.
     * 
     * @var Solar_Request
     * 
     */
    protected $_request;
    
    /**
     * 
     * A cache object to retain the current user information.
     * 
     * @var Solar_Cache_Adapter
     * 
     */
    protected $_cache;
    
    /**
     * 
     * The source of auth credentials, either 'get' or 'post'.
     * 
     * @var string
     * 
     */
    protected $_source;
    
    /**
     * 
     * The user-provided plaintext handle, if any.
     * 
     * @var string
     * 
     */
    protected $_handle;
    
    /**
     * 
     * The user-provided plaintext password, if any.
     * 
     * @var string
     * 
     */
    protected $_passwd;
    
    /**
     * 
     * The current error code string.
     * 
     * @var string
     * 
     */
    protected $_err;
    
    /**
     * 
     * Whether or not to allow automatic login/logout at start() time.
     * 
     * @var bool
     * 
     */
    public $allow = true;
    
    /**
     * 
     * Magic "public" properties that are actually stored in the cache.
     * 
     * The available magic properties are ...
     * 
     * - status:  (int)  The status code of the current user authentication. The string
     *   codes are ...
     *   
     *     - Solar_Auth::ANON (or empty): The user is anonymous/unauthenticated (no attempt to authenticate)
     *     
     *     - Solar_Auth::EXPIRED: The max time for authentication has expired
     *     
     *     - Solar_Auth::IDLED: The authenticated user has been idle for too long
     *     
     *     - Solar_Auth::VALID: The user is authenticated and has not timed out
     *     
     *     - Solar_Auth::WRONG: The user attempted authentication but failed
     *   
     * - initial:  (int)  The Unix time at which the handle was initially authenticated.
     * 
     * - active:  (string)  The Unix time at which the authenticated handle was last 
     *   valid.
     * 
     * - handle:  (string) The currently authenticated user handle.
     * 
     * - email:  (string) The email address of the currently authenticated user. May 
     *   or may not be populated by the adapter.
     * 
     * - moniker:  (string) The "display name" or "full name" of the currently 
     *   authenticated user.  May or may not be populated by the adapter.
     * 
     * - uri:  (string) The URI for the currently authenticated user. May or may not 
     *   be populated by the adapter.
     * 
     * - uid:  (mixed) The user ID (usually numeric) for the currently authenticated 
     *   user.  May or may not be populated by the adapter.
     * 
     * @var array
     * 
     * @see __get()
     * 
     * @see __set()
     * 
     */
    protected $_magic = array(
        'status',
        'initial',
        'active',
        'handle',
        'email',
        'moniker',
        'uri',
        'uid',
    );
    
    /**
     * 
     * Modifies $this->_config after it has been built.
     * 
     * @return void
     * 
     */
    protected function _postConfig()
    {
        parent::_postConfig();
        
        // check max life before garbage collection on server vs. idle time
        $gc_maxlife = ini_get('session.gc_maxlifetime');
        if ($gc_maxlife < $this->_config['idle']) {
            throw $this->_exception('ERR_PHP_SESSION_IDLE', array(
                'session.gc_maxlifetime' => $gc_maxlife,
                'solar_auth_idle'      => $this->_config['idle'],
            ));
        }
        
        // check life at client vs. exipire time;
        // if life at client is zero, cookie never expires.
        $cookie_life = ini_get('session.cookie_lifetime');
        if ($cookie_life > 0 && $cookie_life < $this->_config['expire']) {
            throw $this->_exception('ERR_PHP_SESSION_EXPIRE', array(
                'session.cookie_lifetime' => $cookie_life,
                'solar_auth_expire'       => $this->_config['expire'],
            ));
        }
        
        // make sure we have process values
        if (empty($this->_config['process_login'])) {
            $this->_config['process_login'] = $this->locale('PROCESS_LOGIN');
        }
        
        if (empty($this->_config['process_logout'])) {
            $this->_config['process_logout'] = $this->locale('PROCESS_LOGOUT');
        }
        
        // make sure the source is either 'get' or 'post'.
        $is_get_or_post = $this->_config['source'] == 'get' 
                       || $this->_config['source'] == 'post';
        
        if (! $is_get_or_post) {
            // default to post
            $this->_config['source'] = 'post';
        }
    }
    
    /**
     * 
     * Post-construction tasks to complete object construction.
     * 
     * @return void
     * 
     */
    protected function _postConstruct()
    {
        parent::_postConstruct();
        
        // get the current request environment
        $this->_request = Solar_Registry::get('request');
        
        // set per config
        $this->allow = (bool) $this->_config['allow'];
        
        // cache dependency injection
        $this->_cache = Solar::dependency(
            'Solar_Cache',
            $this->_config['cache']
        );
    }
    
    /**
     * 
     * Magic get for pseudo-public properties as defined by [[Solar_Auth_Adapter::$_magic]].
     * 
     * @param string $key The name of the property to get.
     * 
     * @return mixed
     * 
     * @see $_magic
     * 
     */
    public function __get($key)
    {
        if (! in_array($key, $this->_magic)) {
            throw $this->_exception('ERR_NO_SUCH_PROPERTY', array(
                'class'    => get_class($this),
                'property' => $key,
            ));
        }
        
        $val = $this->_cache->fetch($key);
        
        // special behavior for 'status'
        if ($key == 'status' && ! $val) {
            $val = Solar_Auth::ANON;
        }
        
        return $val;
    }
    
    /**
     * 
     * Magic set for pseudo-public properties as defined by [[Solar_Auth_Adapter::$_magic]].
     * 
     * @param string $key The name of the property to set.
     * 
     * @param mixed $val The value for the property.
     * 
     * @return void
     * 
     * @see $_magic
     * 
     */
    public function __set($key, $val)
    {
        if (! in_array($key, $this->_magic)) {
            throw $this->_exception('ERR_NO_SUCH_PROPERTY', array(
                'class'    => get_class($this),
                'property' => $key,
            ));
        }
        
        $this->_cache->save($key, $val);
    }
    
    /**
     * 
     * Starts authentication.
     * 
     * @return void
     * 
     */
    public function start()
    {
        // update idle and expire times no matter what
        $this->updateIdleExpire();
        
        // allow auto-processing?
        if (! $this->isAllowed()) {
            return;
        }
        
        // auto-login?
        if (! $this->isValid() && $this->isLoginRequest()) {
            return $this->processLogin();
        }
        
        // auto-logout?
        if ($this->isValid() && $this->isLogoutRequest()) {
            return $this->processLogout();
        }
    }
    
    /**
     * 
     * Redirects to another URI after valid authentication.
     * 
     * Looks at the value of the 'redirect' source key, and sets a 'Location:'
     * header from it.  Note that this will end any further processing on this
     * page-load.
     * 
     * If the 'redirect' key is empty or not present, will not redirect, and
     * processing will continue.
     * 
     * @return void
     * 
     */
    protected function _redirect()
    {
        $method = strtolower($this->_config['source']);
        $href = $this->_request->$method($this->_config['source_redirect']);
        if ($href) {
            $response = Solar_Registry::get('response');
            $response->redirectNoCache($href);
            exit(0);
        }
    }
    
    /**
     * 
     * Updates idle and expire times, invalidating authentication if
     * they are exceeded.
     * 
     * Note that if your script runs more than 1 second, it is possible
     * that multiple calls to this method may result in the authentication
     * expiring in the middle of the script.  As such, if you only need
     * to check that the user is logged in, call $this->isValid().
     * 
     * @return bool Whether or not authentication is still valid.
     * 
     */
    public function updateIdleExpire()
    {
        // is the current user already authenticated?
        if ($this->isValid()) {
            
            // Check if authentication has expired
            $tmp = $this->initial + $this->_config['expire'];
            if ($this->_config['expire'] > 0 && $tmp < time()) {
                // past the expiration time
                // flash forward the status text, and return
                $this->reset(Solar_Auth::EXPIRED);
                return false;
            }
    
            // Check if user has been idle for too long
            $tmp = $this->active + $this->_config['idle'];
            if ($this->_config['idle'] > 0 && $tmp < time()) {
                // past the idle time
                // flash forward the status text, and return
                $this->reset(Solar_Auth::IDLED);
                return false;
            }
            
            // not expired, not idled, so update the active time
            $this->active = time();
            return true;
            
        }
        
        return false;
    }
    
    /**
     * 
     * Tells whether the current authentication is valid.
     * 
     * @return bool Whether or not authentication is still valid.
     * 
     */
    public function isValid()
    {
        return $this->status == Solar_Auth::VALID;
    }
    
    /**
     * 
     * Tells whether authentication processing is allowed.
     * 
     * @return bool Whether or not authentication processing is allowed.
     * 
     */
    public function isAllowed()
    {
        return (bool) $this->allow;
    }
    
    /**
     * 
     * Resets any authentication data in the cache.
     * 
     * Typically used for idling, expiration, and logout.  Calls
     * [[php::session_regenerate_id() | ]] to clear previous session, if any
     * exists.
     * 
     * @param string $status A Solar_Auth status string; default is Solar_Auth::ANON.
     * 
     * @param array $info If status is Solar_Auth::VALID, populate properties with this
     * user data, with keys for 'handle', 'email', 'moniker', 'uri', and 
     * 'uid'.  If a key is empty or does not exist, its value is set to null.
     * 
     * @return void
     * 
     */
    public function reset($status = Solar_Auth::ANON, $info = array())
    {
        // baseline user information
        $base = array(
           'handle'  => null,
           'moniker' => null,
           'email'   => null,
           'uri'     => null,
           'uid'     => null,
        );
        
        // reset the status
        $this->status = strtoupper($status);
        
        // change properties
        if ($this->status == Solar_Auth::VALID) {
            // update the timers, leave user info alone
            $now = time();
            $this->initial = $now;
            $this->active  = $now;
        } else {
            // clear the timers *and* the user info
            $this->initial = null;
            $this->active  = null;
            $info = null;
        }
        
        // set the user-info properties
        $info = array_merge($base, (array) $info);
        $this->handle  = $info['handle'];
        $this->moniker = $info['moniker'];
        $this->email   = $info['email'];
        $this->uri     = $info['uri'];
        $this->uid     = $info['uid'];
        
        // reset the session id and delete previous session, but only
        // if a session is actually in place
        if (session_id() !== '' && ! headers_sent()) {
            session_regenerate_id();
        }
        
        // cache any messages
        $this->_cache->save('status_text', $this->locale($this->status));
    }
    
    /**
     * 
     * Retrieve the status text from the cache and then deletes it, making it
     * act like a read-once session flash value.
     * 
     * @return string The status text.
     * 
     */
    public function getStatusText()
    {
        $val = $this->_cache->fetch('status_text');
        $this->_cache->delete('status_text');
        return $val;
    }
    
    /**
     * 
     * Tells if the current page load appears to be the result of
     * an attempt to log in.
     * 
     * @return bool
     * 
     */
    public function isLoginRequest()
    {
        $method = strtolower($this->_config['source']);
        $process = $this->_request->$method($this->_config['source_process']);
        return ! $this->_request->isCsrf()
             && $process == $this->_config['process_login'];
    }
    
    /**
     * 
     * Tells if the current page load appears to be the result of
     * an attempt to log out.
     * 
     * @return bool
     * 
     */
    public function isLogoutRequest()
    {
        $method = strtolower($this->_config['source']);
        $process = $this->_request->$method($this->_config['source_process']);
        return ! $this->_request->isCsrf()
            && $process == $this->_config['process_logout'];
    }
    
    /**
     * 
     * Processes login attempts and sets user credentials.
     * 
     * @return bool True if the login was successful, false if not.
     * 
     */
    public function processLogin()
    {
        // clear out current error and user data.
        $this->_err = null;
        $this->reset();
        
        // load the user-provided handle and password
        $this->_loadCredentials();
        
        // adapter-specific login processing
        $result = $this->_processLogin();
        
        // did it work?
        if (is_array($result)) {
            // successful login, treat result as user info
            $this->reset(Solar_Auth::VALID, $result);
        } elseif (is_string($result)) {
            // failed login, treat result as error code
            $this->reset($result);
        } else {
            // failed login, generic error code
            $this->reset(Solar_Auth::WRONG);
        }
        
        // callback?
        if ($this->_config['login_callback']) {
            call_user_func(
                $this->_config['login_callback'],
                $this
            );
        }
        
        // did it work?
        if ($this->isValid()) {
            // attempt to redirect.
            $this->_redirect();
        }
        
        // done!
        return $this->status == Solar_Auth::VALID;
    }
    
    /**
     * 
     * Loads the user credentials (handle and passwd) from the request source.
     * 
     * @return void
     * 
     */
    protected function _loadCredentials()
    {
        // where do the handle and passwd come from?
        $method = strtolower($this->_config['source']);
        
        // retrieve the handle and passwd
        $this->_handle = $this->_request->$method($this->_config['source_handle']);
        $this->_passwd = $this->_request->$method($this->_config['source_passwd']);
    }
    
    /**
     * 
     * Adapter-specific login processing.
     * 
     * @return mixed An array of user information if valid; if not valid, a
     * string error code or empty value.
     * 
     */
    protected function _processLogin()
    {
        // you should implement this in an adapter, but the default is to 
        // not-validate login attempts.
        return false;
    }
    
    /**
     * 
     * Processes logout attempts.
     * 
     * @return void
     * 
     */
    public function processLogout()
    {
        // process logout
        $code = $this->_processLogout();
        
        // change status
        $this->reset($code);
        
        // callback?
        if ($this->_config['logout_callback']) {
            call_user_func(
                $this->_config['logout_callback'],
                $this
            );
        }
        
        // logout always works, so see if a redirect is needed
        $this->_redirect();
    }
    
    /**
     * 
     * Adapter-specific logout processing.
     * 
     * @return string A status code string for reset().
     * 
     */
    protected function _processLogout()
    {
        return Solar_Auth::ANON;
    }
}
Return current item: SolarPHP