* @version 1.0 * @since 04 Mar 2009 21:59:03 */ class Auth_Simple { // Namespace of this auth object. protected $_ns; // Parameters to be configured by setParam. protected $_params = array(); protected $_default_params = array( // The URL to the login script. 'login_url' => '/', // The maximum amount of time a user is allowed to be logged in. They will be forced to login again if they expire. // In seconds. 21600 seconds = 6 hours. 'login_timeout' => 21600, // The maximum amount of time a user is allowed to be idle before their session expires. They will be forced to login again if they expire. // In seconds. 3600 seconds = 1 hour. 'idle_timeout' => 3600, ); /** * Constructs a new authentication object. * * @access public * @param optional array $params A hash containing parameters. */ public function __construct($namespace='') { $app =& App::getInstance(); $this->_ns = $namespace; // Initialize default parameters. $this->setParam($this->_default_params); // Get create tables config from global context. if (!is_null($app->getParam('db_create_tables'))) { $this->setParam(array('create_table' => $app->getParam('db_create_tables'))); } if (!isset($_SESSION['_auth_simple'][$this->_ns])) { $this->clear(); } } /** * Set the params of an auth object. * * @param array $params Array of parameter keys and value to set. * @return bool true on success, false on failure */ public function setParam($params) { if (isset($params) && is_array($params)) { // Merge new parameters with old overriding only those passed. $this->_params = array_merge($this->_params, $params); } } /** * Return the value of a parameter, if it exists. * * @access public * @param string $param Which parameter to return. * @return mixed Configured parameter value. */ public function getParam($param) { $app =& App::getInstance(); if (isset($this->_params[$param])) { return $this->_params[$param]; } else { $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__); return null; } } /** * Sets a variable into a registered auth session. * * @access public * @param mixed $key Which value to set. * @param mixed $val Value to set variable to. */ public function set($key, $val) { if (!isset($_SESSION['_auth_simple'][$this->_ns]['user_data'])) { $_SESSION['_auth_simple'][$this->_ns]['user_data'] = array(); } $_SESSION['_auth_simple'][$this->_ns]['user_data'][$key] = $val; } /** * Returns a specified value from a registered auth session. * * @access public * @param mixed $key Which value to return. * @param mixed $default Value to return if key not found in user_data. * @return mixed Value stored in session. */ public function get($key, $default='') { if (isset($_SESSION['_auth_simple'][$this->_ns][$key])) { return $_SESSION['_auth_simple'][$this->_ns][$key]; } else if (isset($_SESSION['_auth_simple'][$this->_ns]['user_data'][$key])) { return $_SESSION['_auth_simple'][$this->_ns]['user_data'][$key]; } else { return $default; } } /** * Clear any authentication tokens in the current session. A.K.A. logout. * * @access public */ public function clear() { $_SESSION['_auth_simple'][$this->_ns] = array('authenticated' => false); } /* * Runs a full login sequence: clear old session, validate auth, create session. * * @access public * @param int $user_id User ID. * @return mixed False on failure, true on success. * @author Quinn Comendant * @version 1.0 * @since 04 Mar 2009 21:07:33 */ public function login($user_id, $password, $callback) { global $acct; $app =& App::getInstance(); $this->clear(); if (!is_callable($callback)) { $app->logMsg(sprintf('Not callable: %s', getDump($callable)), LOG_ERR, __FILE__, __LINE__); return false; } if (!call_user_func($callback, $user_id, $password)) { $app->logMsg(sprintf('Authentication failed for user_id: %s', $user_id), LOG_NOTICE, __FILE__, __LINE__); return false; } $app->logMsg(sprintf('Authentication successful for user_id: %s', $user_id), LOG_INFO, __FILE__, __LINE__); return $this->createSession($user_id); } /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 04 Mar 2009 21:07:33 */ public function createSession($user_id) { $app =& App::getInstance(); // Register authenticated session. $_SESSION['_auth_simple'][$this->_ns] = array( 'authenticated' => true, 'user_id' => $user_id, 'login_datetime' => date('Y-m-d H:i:s'), 'last_access_datetime' => date('Y-m-d H:i:s'), 'remote_ip' => getRemoteAddr(), ); return true; } /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 04 Mar 2009 21:10:41 */ public function isLoggedIn($update_last_access_datetime=true) { $app =& App::getInstance(); // Test login with information stored in session. Skip IP matching for users from trusted networks. if (isset($_SESSION['_auth_simple'][$this->_ns]['authenticated']) && true === $_SESSION['_auth_simple'][$this->_ns]['authenticated'] && !empty($_SESSION['_auth_simple'][$this->_ns]['user_id']) && strtotime($_SESSION['_auth_simple'][$this->_ns]['login_datetime']) > time() - $this->_params['login_timeout'] && strtotime($_SESSION['_auth_simple'][$this->_ns]['last_access_datetime']) > time() - $this->_params['idle_timeout'] ) { // User is authenticated! if ($update_last_access_datetime) { $_SESSION['_auth_simple'][$this->_ns]['last_access_datetime'] = date('Y-m-d H:i:s'); } $seconds_until_login_timeout = max(0, $this->_params['login_timeout'] - $result['seconds_since_last_login']); $seconds_until_idle_timeout = max(0, $this->_params['idle_timeout'] - $result['seconds_since_last_access']); $session_expiry_seconds = min($seconds_until_login_timeout, $seconds_until_idle_timeout); $app->logMsg(sprintf('Returning true login status for user_id %s (session expires in %s seconds)', $_SESSION['_auth_simple'][$this->_ns]['user_id'], $session_expiry_seconds), LOG_DEBUG, __FILE__, __LINE__); return $session_expiry_seconds; } else if (isset($_SESSION['_auth_simple'][$this->_ns]['authenticated']) && true === $_SESSION['_auth_simple'][$this->_ns]['authenticated']) { // User is authenticated, but login has expired. if (strtotime($_SESSION['_auth_simple'][$this->_ns]['last_access_datetime']) > time() - 43200) { // Only raise message if last session is less than 12 hours old. // TODO: Can we move this message to requireLogin() so it doesn't display unless the user expects it to? // $app->raiseMsg(_("Your session has expired. You need to log-in again."), MSG_NOTICE, __FILE__, __LINE__); } // Log the reason for login expiration. $expire_reasons = array(); if (empty($_SESSION['_auth_simple'][$this->_ns]['user_id'])) { $expire_reasons[] = 'user_id not found'; } if (strtotime($_SESSION['_auth_simple'][$this->_ns]['login_datetime']) <= time() - $this->_params['login_timeout']) { $expire_reasons[] = 'login_timeout expired'; } if (strtotime($_SESSION['_auth_simple'][$this->_ns]['last_access_datetime']) <= time() - $this->_params['idle_timeout']) { $expire_reasons[] = 'idle_timeout expired'; } $app->logMsg(sprintf('user_id %s session expired: %s', $this->get('user_id'), join(', ', $expire_reasons)), LOG_INFO, __FILE__, __LINE__); } // User is not authenticated. $this->clear(); return false; } /** * Redirect user to login page if they are not logged in. * * @param string $message The text description of a message to raise. * @param int $type The type of message: MSG_NOTICE, * MSG_SUCCESS, MSG_WARNING, or MSG_ERR. * @param string $file __FILE__. * @param string $line __LINE__. * @access public */ public function requireLogin($message='', $type=MSG_NOTICE, $file=null, $line=null) { $app =& App::getInstance(); if (!$this->isLoggedIn()) { // Display message for requiring login. (RaiseMsg will ignore empty strings.) if ('' != $message) { $app->raiseMsg($message, $type, $file, $line); } // Login scripts must have the same 'login' tag for boomerangURL verification/manipulation. $app->setBoomerangURL(getenv('REQUEST_URI'), 'login'); $app->dieURL($this->_params['login_url']); } } /** * Stub method to fulfill requirements by Version() * * @param string $null * @return string Empty string */ public function getUsername($null=null) { return ''; } }