- <?php
- /**
- * PHLY - PHp LibrarY
- *
- * PHLY is a library of PHP classes designed with the following intentions:
- * - Loosely coupled; dependencies should be few, and no base class should be
- * necessary.
- * - Extendible; all classes should be easily extendible. This may be via
- * observers, interfaces, adapters, etc.. The base class should solve 80% of
- * usage, and allow extensions to the class to fill in the remainder.
- * - Designed for PHP5 and up; all classes should make use of PHP5's features.
- * - Documented; all classes should minimally have excellent API-level
- * documentation, with use cases in the class docblock.
- * - Tested; all classes should have (passing) unit tests accompanying them.
- * - Open source and commercial friendly; all classes should use a
- * commercial-friendly open source license. The BSD license is one such
- * example.
- *
- * @license New BSD, http://www.opensource.org/licenses/bsd-license.php
- * @package Phly
- * @copyright 2006 - Present, Matthew Weier O'Phinney
- */
-
- /**
- * Exceptions
- */
- require_once 'Phly/Auth/Exception.php';
-
- /**
- * Authentication class
- *
- * Pluggable authentication class. Credentials are checked against the provided
- * callback (passed to the constructor), and valid users then receive an
- * authentication session. The authentication session includes the following
- * keys:
- * - authenticated; always set to true
- * - username; set to the username provided at login, unless the validation
- * callback returns a non boolean true value, in which case that value will be
- * used.
- * - loginTime; timestamp of initial login
- * - timeStamp; timestamp of most recent page visit
- * - userData; a second argument to the constructor may be passed, a callback to
- * retrieve user data. If so provided, any return value is stored in this key.
- * This might contain ACL permissions, additional demographic information, etc
- * - sessionData; any extra session data you wish to associate with the
- * authentication session.
- *
- * The above properties, and all properties in the sessionData array, may be
- * accessed as object properties. (Note: make sure your sessionData keys don't
- * collide with those in the main auth array.)
- *
- * You may also configure the authentication class. Simply use the
- * {@link config()} method, setting one of the {@link $_config} keys as desired;
- * see {@link $_config} for some sample keys and default values.
- *
- * Examples:
- * <code>
- * class MyAuth
- * {
- * public static function isValid($username, $password)
- * {
- * $users = parse_ini_file('./users.ini', true);
- * if (isset($users[$username])
- * && (md5($password) == $users[$username]['password']))
- * {
- * return true;
- * }
- *
- * return false;
- * }
- *
- * public static function getUserData($username)
- * {
- * $users = parse_ini_file('./users.ini', true);
- * if (isset($users[$username])) {
- * $user = $users[$username];
- * unset($user['password']);
- * return $user;
- * }
- *
- * return array();
- * }
- * }
- *
- * require_once 'Phly/Auth.php';
- * $auth = new Phly_Auth(array('MyAuth', 'isValid'), array('MyAuth', 'getUserData'));
- * $auth->start();
- *
- * if (!$auth->isValid) {
- * // decide what to do with unauthenticated user
- * } else {
- * echo 'Welcome back, ' . $auth->username . '!';
- * }
- * </code>
- *
- * @category Phly
- * @subcategory Phly_Auth
- * @subpackage Phly_Auth
- * @copyright 2006 - Present, Matthew Weier O'Phinney
- * @author Matthew Weier O'Phinney <mweierophinney@gmail.com>
- * @version @release-version@
- */
- class Phly_Auth
- {
- /**
- * Configuration array, with keys:
- * - form_username (default: username)
- * - form_password (default: password)
- * - form_submit (default: login; a form variable that should be set
- * indicating that the form submitted is a login form)
- * - password_hash (default: md5; callback to use to hash password)
- * - session_var (default: _auth; name of session key holding authentication
- * session)
- * - session_idle (default: null; maximum number of seconds allowed between
- * requests before requiring a new login)
- * - session_length (default: null; maximum number of seconds a session is
- * valid)
- * - use_get (default: false; flag; whether or not to check for login form
- * variables in the $_GET array)
- *
- * @var array
- * @access protected
- */
- protected $_config = array(
- 'form_username' => 'username',
- 'form_password' => 'password',
- 'form_submit' => 'login',
- 'password_hash' => 'md5',
- 'session_var' => '_auth',
- 'session_idle' => null,
- 'session_length'=> null,
- 'use_get' => false
- );
-
- /**
- * Authentication validation callback
- * @var mixed
- * @access protected
- */
- protected $_validator;
-
- /**
- * getData callback (for retrieving user data)
- * @var mixed
- * @access protected
- */
- protected $_getData = null;
-
- /**
- * Constructor
- *
- * Creates authentication object using passed validation callback and
- * optional getData callback (for retrieving user data).
- *
- * @access public
- * @param mixed $isValidCallback Authentication callback
- * @param mixed $getDataCallback Optional callback for retrieving user data
- * @return void
- * @throws Phly_Auth_Exception
- */
- public function __construct($isValidCallback, $getDataCallback = null)
- {
- if (!is_callable($isValidCallback)) {
- throw new Phly_Auth_Exception('Invalid validation callback');
- }
-
- $this->_validator = $isValidCallback;
-
- if ((null !== $getDataCallback)) {
- if (!is_callable($getDataCallback)) {
- throw new Phly_Auth_Exception('Invalid data retrieval callback');
- }
-
- $this->_getData = $getDataCallback;
- }
- }
-
- /**
- * Configuration
- *
- * Allows storing/retrieving authentication options.
- *
- * Passing no values returns the entire {@link $_config}.
- *
- * Passing a single string value returns the value associated with that key
- * in {@link $_config}.
- *
- * Passing two values associates the second value with the key specified in
- * the first in {@link $_config}.
- *
- * If all else fails, returns null.
- *
- * @access public
- * @param string $key Optional; configuration key
- * @param mixed $value Optional; value to store in $key
- * @return mixed
- */
- public function config()
- {
- $argc = func_num_args();
- if (0 == $argc) {
- return $this->_config;
- }
-
- if (1 == $argc) {
- $key = func_get_arg(0);
- if (isset($this->_config[$key])) {
- return $this->_config[$key];
- }
- return null;
- }
-
- if (2 == $argc) {
- $key = func_get_arg(0);
- $value = func_get_arg(1);
- if (is_string($key)) {
- $this->_config[$key] = $value;
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Starts session, if not already done, updating auth session timestamp if
- * present.
- *
- * @access public
- * @return void
- */
- public function start()
- {
- @session_start();
- }
-
- /**
- * Checks whether a user has authenticated.
- *
- * First checks to see if the user exists in the session; then attempts to
- * log a person in.
- *
- * @access public
- * @return boolean
- */
- public function isValid()
- {
- $sessionVar = $this->config('session_var');
- if (isset($_SESSION[$sessionVar])
- && isset($_SESSION[$sessionVar]['authenticated'])
- && $_SESSION[$sessionVar]['authenticated'])
- {
- // We have a valid session; now check to see if it's expired
- $time = time();
-
- // Have we exceeded maximum session length?
- $sessionLength = $this->config('session_length');
- if (null !== $sessionLength) {
- if (($time - $this->loginTime) > $sessionLength) {
- $this->logout();
- return false;
- }
- }
-
- // Have we idled too long?
- $sessionIdle = $this->config('session_idle');
- if (null !== $sessionIdle) {
- if (($time - $this->timestamp) > $sessionIdle) {
- $this->logout();
- return false;
- }
- }
-
- // Update session timestamp
- $_SESSION[$sessionVar]['timestamp'] = $time;
-
- return true;
- }
-
- return $this->_login();
- }
-
- /**
- * Attempt to login a user
- *
- * Attempts to login a user via $_POST (or $_GET if 'use_get' config value
- * is set to true).
- *
- * Uses the config values:
- * - form_username: username element of form
- * - form_password: password element of form
- * - form_submit: name of submit button of form (or any other key that
- * should be utilized)
- *
- * Then applies 'password_hash' config callback to the password, and sends
- * username and password to isValid() method of container.
- *
- * @access protected
- * @return boolean
- * @throws Phly_Auth_Exception if unable to populate session data
- */
- protected function _login()
- {
- if ('cli' == php_sapi_name()) {
- global $_POST;
- }
- $vars = $_POST;
- if ($this->config('use_get')) {
- if ('cli' == php_sapi_name()) {
- global $_GET;
- }
- $vars = $_GET;
- }
-
- $usernameVar = $this->config('form_username');
- $passwordVar = $this->config('form_password');
- $loginVar = $this->config('form_submit');
-
- if (isset($vars[$loginVar])) {
- if (isset($vars[$usernameVar])) {
- $username = $vars[$usernameVar];
- }
- if (isset($vars[$passwordVar])) {
- $password = call_user_func($this->config('password_hash'), $vars[$passwordVar]);
- }
-
- if (isset($username) && isset($password)) {
- if ($uid = call_user_func_array($this->_validator, array($username, $password)))
- {
- if (true !== $uid) {
- $username = $uid;
- }
- try {
- $sessVar = $this->config('session_var');
- $_SESSION[$sessVar] = array(
- 'authenticated' => true,
- 'username' => $username,
- 'loginTime' => time(),
- 'timestamp' => time(),
- 'sessionData' => array()
- );
-
- if (null !== $this->_getData) {
- $_SESSION[$sessVar]['userData'] = call_user_func($this->_getData, $username);
- }
- } catch (Exception $e) {
- throw new Phly_Auth_Exception('Error populating session: ' . $e->getMessage());
- }
-
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Logout a user
- *
- * Logs out a user by unsetting the authentication session.
- *
- * @access public
- * @return void
- */
- public function logout()
- {
- unset($_SESSION[$this->config('session_var')]);
- }
-
- /**
- * Retrieve a value from the session auth array
- *
- * Attempts to retrieve a value from the session auth array or the
- * sessionData array of the session auth array. If the value exists, it is
- * returned; otherwise, false is returned.
- *
- * @access public
- * @param string $key
- * @return mixed
- */
- public function __get($key)
- {
- $sessVar = $this->config('session_var');
- if (isset($_SESSION[$sessVar])
- && isset($_SESSION[$sessVar][$key]))
- {
- return $_SESSION[$sessVar][$key];
- } elseif (isset($_SESSION[$sessVar])
- && isset($_SESSION[$sessVar]['sessionData'])
- && isset($_SESSION[$sessVar]['sessionData'][$key]))
- {
- return $_SESSION[$sessVar]['sessionData'][$key];
- }
-
- return false;
- }
-
- /**
- * Set data in the sessionData array of the auth session
- *
- * @access public
- * @param string $key
- * @param mixed $value
- * @return boolean
- */
- public function __set($key, $value)
- {
- $sessVar = $this->config('session_var');
- if (isset($_SESSION[$sessVar])) {
- if (!isset($_SESSION[$sessVar]['sessionData'])) {
- $_SESSION[$sessVar]['sessionData'] = array(
- $key => $value
- );
- } else {
- $_SESSION[$sessVar]['sessionData'][$key] = $value;
- }
-
- return true;
- }
-
- return false;
- }
-
- /**
- * Get session data from an authentication session
- *
- * Returns all session data stored in the sessionData array of the auth
- * session.
- *
- * To access individual values from the array, access them as class
- * properties.
- *
- * @access public
- * @return false|array
- */
- public function getSessionData()
- {
- $sessVar = $this->config('session_var');
-
- if (isset($_SESSION[$sessVar])
- && isset($_SESSION[$sessVar]['sessionData']))
- {
- return $_SESSION[$sessVar]['sessionData'];
- }
-
- return false;
- }
- }