* Copyright 2001-2010 Strangecode, LLC * * This file is part of The Strangecode Codebase. * * The Strangecode Codebase is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your option) * any later version. * * The Strangecode Codebase is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * The Strangecode Codebase. If not, see . */ /** * Auth_File.inc.php * * The Auth_File class provides a htpasswd file implementation for * authentication. * * @author Quinn Comendant * @version 1.2 */ // Usage example: // $auth = new Auth_File(); // $auth->setParam(array( // 'htpasswd_file' => COMMON_BASE . '/global/site_users.htpasswd', // 'login_timeout' => 21600, // 'idle_timeout' => 3600, // 'login_url' => '/login.php' // )); // Available encryption types for class Auth_SQL. define('AUTH_ENCRYPT_MD5', 'md5'); define('AUTH_ENCRYPT_CRYPT', 'crypt'); define('AUTH_ENCRYPT_SHA1', 'sha1'); define('AUTH_ENCRYPT_PLAINTEXT', 'plaintext'); class Auth_File { // Namespace of this auth object. var $_ns; // Parameters to be specified by setParam(). var $_params = array(); var $_default_params = array( // Full path to htpasswd file. 'htpasswd_file' => null, // The type of encryption to use for passwords stored in the db_table. Use one of the AUTH_ENCRYPT_* types specified above. 'encryption_type' => AUTH_ENCRYPT_CRYPT, // 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. // This applies to admins and users. 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. // This applies to admins and users. In seconds. 3600 seconds = 1 hour. 'idle_timeout' => 3600, // An array of IP blocks that are bypass the remote_ip comparison check. Useful for dynamic IPs or those behind proxy servers. 'trusted_networks' => array(), ); // Associative array of usernames to hashed passwords. var $_users = array(); /** * Constructs a new htpasswd authentication object. * * @access public * * @param optional array $params A hash containing parameters. */ function Auth_File($namespace='') { $this->_ns = $namespace; // Initialize default parameters. $this->setParam($this->_default_params); } /** * 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 */ 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. */ 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; } } /** * Clear any authentication tokens in the current session. A.K.A. logout. * * @access public */ function clear() { $_SESSION['_auth_file'][$this->_ns] = array('authenticated' => false); } /** * 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. */ function set($key, $val) { if (!isset($_SESSION['_auth_file'][$this->_ns]['user_data'])) { $_SESSION['_auth_file'][$this->_ns]['user_data'] = array(); } $_SESSION['_auth_file'][$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. */ function get($key, $default='') { if (isset($_SESSION['_auth_file'][$this->_ns][$key])) { return $_SESSION['_auth_file'][$this->_ns][$key]; } else if (isset($_SESSION['_auth_file'][$this->_ns]['user_data'][$key])) { return $_SESSION['_auth_file'][$this->_ns]['user_data'][$key]; } else { return $default; } } /** * Find out if a set of login credentials are valid. Only supports * htpasswd files with DES passwords right now. * * @access public * * @param string $username The username to check. * @param array $password The password to compare to username. * * @return boolean Whether or not the credentials are valid. */ function authenticate($username, $password) { $app =& App::getInstance(); if ('' == trim($password)) { $app->logMsg(_("No password provided for authentication."), LOG_INFO, __FILE__, __LINE__); return false; } // Load users file. $this->_loadHTPasswdFile(); if (!isset($this->_users[$username])) { $app->logMsg(_("User ID provided does not exist."), LOG_INFO, __FILE__, __LINE__); return false; } if ($this->_encrypt($password, $this->_users[$username]) != $this->_users[$username]) { $app->logMsg(sprintf('Authentication failed for user %s', $username), LOG_INFO, __FILE__, __LINE__); return false; } // Authentication successful! return true; } /** * If user passes authentication create authenticated session. * * @access public * * @param string $username The username to check. * @param array $password The password to compare to username. * * @return boolean Whether or not the credentials are valid. */ function login($username, $password) { $username = mb_strtolower(trim($username)); $this->clear(); if (!$this->authenticate($username, $password)) { // No login: failed authentication! return false; } $_SESSION['_auth_file'][$this->_ns] = array( 'authenticated' => true, 'username' => $username, 'login_datetime' => date('Y-m-d H:i:s'), 'last_access_datetime' => date('Y-m-d H:i:s'), 'remote_ip' => getRemoteAddr() ); // We're logged-in! return true; } /** * Test if user has a currently logged-in session. * - authentication flag set to true * - username not empty * - total logged-in time is not greater than login_timeout * - idle time is not greater than idle_timeout * - remote address is the same as the login remote address. * * @access public */ function isLoggedIn() { $app =& App::getInstance(); // Some users will access from networks with a changing IP number (i.e. behind a proxy server). These users must be allowed entry by adding their IP to the list of trusted_networks. if ($trusted_net = ipInRange(getRemoteAddr(), $this->_params['trusted_networks'])) { $user_in_trusted_network = true; $app->logMsg(sprintf('User %s accessing from trusted network %s', $_SESSION['_auth_file'][$this->_ns]['username'], $trusted_net), LOG_DEBUG, __FILE__, __LINE__); } else if (preg_match('/proxy.aol.com$/i', getRemoteAddr(true))) { $user_in_trusted_network = true; $app->logMsg(sprintf('User %s accessing from trusted network proxy.aol.com', $_SESSION['_auth_file'][$this->_ns]['username']), LOG_DEBUG, __FILE__, __LINE__); } else { $user_in_trusted_network = false; } // Test login with information stored in session. Skip IP matching for users from trusted networks. if (isset($_SESSION['_auth_file'][$this->_ns]) && true === $_SESSION['_auth_file'][$this->_ns]['authenticated'] && !empty($_SESSION['_auth_file'][$this->_ns]['username']) && strtotime($_SESSION['_auth_file'][$this->_ns]['login_datetime']) > time() - $this->_params['login_timeout'] && strtotime($_SESSION['_auth_file'][$this->_ns]['last_access_datetime']) > time() - $this->_params['idle_timeout'] && ($_SESSION['_auth_file'][$this->_ns]['remote_ip'] == getRemoteAddr() || $user_in_trusted_network) ) { // User is authenticated! $_SESSION['_auth_file'][$this->_ns]['last_access_datetime'] = date('Y-m-d H:i:s'); return true; } else if (isset($_SESSION['_auth_file'][$this->_ns]) && true === $_SESSION['_auth_file'][$this->_ns]['authenticated']) { if (strtotime($_SESSION['_auth_file'][$this->_ns]['last_access_datetime']) > time() - 43200) { // Only raise message if last session is less than 12 hours old. $app->raiseMsg(_("Your session has closed. You need to log-in again."), MSG_NOTICE, __FILE__, __LINE__); } // Log the reason for login expiration. $expire_reasons = array(); if (empty($_SESSION['_auth_file'][$this->_ns]['username'])) { $expire_reasons[] = 'username not found'; } if (strtotime($_SESSION['_auth_file'][$this->_ns]['login_datetime']) <= time() - $this->_params['login_timeout']) { $expire_reasons[] = 'login_timeout expired'; } if (strtotime($_SESSION['_auth_file'][$this->_ns]['last_access_datetime']) <= time() - $this->_params['idle_timeout']) { $expire_reasons[] = 'idle_timeout expired'; } if ($_SESSION['_auth_file'][$this->_ns]['remote_ip'] != getRemoteAddr() && !$user_in_trusted_network) { $expire_reasons[] = sprintf('remote_ip not matched (%s != %s)', $_SESSION['_auth_file'][$this->_ns]['remote_ip'], getRemoteAddr()); } $app->logMsg(sprintf('User %s session expired: %s', $_SESSION['_auth_file'][$this->_ns]['username'], join(', ', $expire_reasons)), LOG_INFO, __FILE__, __LINE__); } 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 */ 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.) $app->raiseMsg($message, $type, $file, $line); // Login scripts must have the same 'login' tag for boomerangURL verification/manipulation. $app->setBoomerangURL(absoluteMe(), 'login'); $app->dieURL($this->_params['login_url']); } } /** * Wrapper function for compatibility with lib/Lock.inc.php. * * @param string $username Username to return. * @return string Username, or false if none found. */ function getUsername($username) { if ('' != $username) { return $username; } else { return false; } } /* * Reads the configured htpasswd file into the _users array. * * @access public * @return false on error, true on success. * @author Quinn Comendant * @version 1.0 * @since 18 Apr 2006 18:17:48 */ function _loadHTPasswdFile() { $app =& App::getInstance(); static $users = null; if (!file_exists($this->_params['htpasswd_file'])) { $app->logMsg(sprintf('htpasswd file missing or not specified: %s', $this->_params['htpasswd_file']), LOG_ERR, __FILE__, __LINE__); return false; } if (!isset($users)) { if (false === ($users = file($this->_params['htpasswd_file']))) { $app->logMsg(sprintf('Could not read htpasswd file: %s', $this->_params['htpasswd_file']), LOG_ERR, __FILE__, __LINE__); return false; } } if (is_array($users)) { foreach ($users as $u) { list($user, $pass) = explode(':', $u, 2); $this->_users[trim($user)] = trim($pass); } return true; } return false; } /** * Hash a given password according to the configured encryption * type. * * @param string $password The password to encrypt. * @param string $encrypted_password The currently encrypted password to use as salt, if needed. * * @return string The hashed password. */ function _encrypt($password, $encrypted_password=null) { switch ($this->_params['encryption_type']) { case AUTH_ENCRYPT_PLAINTEXT : return $password; break; case AUTH_ENCRYPT_SHA1 : return sha1($password); break; case AUTH_ENCRYPT_MD5 : return md5($password); break; case AUTH_ENCRYPT_CRYPT : default : return crypt($password, $encrypted_password); break; } } } // end class ?>