Location: PHPKode > projects > Aukyla Platform > aukyla/base/Login.php
<?php
/*
     Login.php, this module provides a login mechanism, including user and
                session management
     Copyright (C) 2003-2004 Arend van Beelen, Auton Rijnsburg

     This program 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 2 of the License, or (at your option)
     any later version.

     This program 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 this program; if not, write to the Free Software Foundation, Inc.,
     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA


     For any questions, comments or whatever, you may mail me at: hide@address.com
*/

require_once('Config.php');
require_once('Constants.php');
require_once('Locale.php');
require_once('Messages.php');
require_once('Signal.php');
require_once('String.php');

/**
 * @brief Class used for login management.
 *
 * The Login class handles user and group management. There's a ready to use
 * LoginForm which you may use to provide users a login form. The Login
 * class will automatically notice when this form is submitted and the user
 * will be logged in automatically. Active sessions are also automatically
 * restored every time this class is instantiated. You can call the logout()
 * function when the user should be logged out.
 *
 * The actual verification of the user credentials is done by a LoginHandler
 * class.
 *
 * This class emits the following signals:
 * - Login::UserLoggedIn($username),
 *   emitted when a user successfully logged in.
 * - Login::FailedLoginAttempt($username),
 *   emitted when a user failed to log in.
 * - Login::UserLoggedOut($username),
 *   emitted when a user successfully logged out.
 * - Login::UserTimedOut($username),
 *   emitted when a user's session was removed because it was idle.
 */
class Login
{
	/**
	 * Constructor.
	 *
	 * Does initialization and automatically restores any previous session.
	 * If the user had just filled in a login form, the constructor will try
	 * to log in the user with the credentials he supplied. If this succeeds
	 * a new session is created for him, if it failes the view is reset to
	 * the login form.
	 *
	 * You do not have to instantiate this class yourself, instead you can
	 * use the static functions provided by this class.
	 */
	public function __construct()
	{
		$this->sessionVariables = array();
		$this->username = 'anonymous';

		$this->loginMethod = Config::globals('loginMethod', 'passwd');

		$this->loadHandler($this->loginMethod);

		// restore the current session
		$this->restoreSession();

		global $Login;
		$Login = $this;

		// check whether the user filled in a login form
		if(Config::request('action') == 'Login' && Config::request('button') != i18n('Cancel'))
		{
			self::login(Config::request('username'), Config::request('password'));
		}
		elseif(getenv('AUKYLA_USER') != '' && getenv('AUKYLA_PASSWORD') != '')
		{
			self::login(getenv('AUKYLA_USER'), getenv('AUKYLA_PASSWORD'));
		}
	}

	/**
	 * Destructor.
	 *
	 * Saves session variables to disk.
	 *
	 * @sa saveSessionVariables()
	 */
	public function __destruct()
	{
		$this->saveSessionVariables();
	}

	/**
	 * Logs the user in with the given @p username and @p password.
	 *
	 * @return @p true if the user is successfully logged in, @p false otherwise.
	 *
	 * @note This function is called automatically if the user submits a
	 *       LoginForm.
	 */
	public static function login($username, $password)
	{
		global $Login;

		if($Login->loginHandler->login($username, $password) == true)
		{
			if(isset($_SERVER['REMOTE_ADDR']) &&
			   String::isIpInRange($Login->loginHandler->ipRange(Config::request('username')),
			                       $_SERVER['REMOTE_ADDR']) == false)
			{
				Messages::error(i18n('Sorry, you are not allowed to login from your current location.'));
				Signal::emit('Login::FailedLoginAttempt', Config::request('username'));
				$Login->loginHandler->logout();
				unset($Login->loginHandler);
				$this->loadHandler($Login->loginMethod);
				$this->restoreSession();

				return false;
			}

			$Login->username = $username;
			$Login->createSession($username);
			$config = new Config('', $username);
			Locale::init('base', $config->variable('language', 'en'));
			Messages::confirm(i18n('Logged in successfully.'));
			Signal::emit('Login::UserLoggedIn', $username);

			return true;
		}
		else
		{
			Messages::error(i18n('Sorry, the username or password you provided is not valid.'));
			Signal::emit('Login::FailedLoginAttempt', Config::request('username'));
			if(Config::request('action') == 'Login')
			{
				Config::unsetRequests();
				Config::setRequest('view', 'Login');
			}
			unset($Login->loginHandler);
			$Login->loadHandler($Login->loginMethod);
			$Login->restoreSession();

			return false;
		}
	}

	/**
	 * @internal
	 *
	 * Checks whether the user has requested a logout action and logs the
	 * user out accordingly.
	 */
	public static function checkAction()
	{
		// check whether the user logged out
		if(Config::request('action') == 'Logout')
		{
			Login::logout();
			Config::unsetRequests();
		}
	}

	/**
	 * Returns the username of the currently logged in user. If the username
	 * begins with "anonymous", the user is @b not logged in.
	 *
	 * @return The username of the currently logged in user.
	 */
	public static function username()
	{
		global $Login;

		return $Login->username;
	}

	/**
	 * Returns the full name of the user. If an anonymous username is given, this
	 * defaults to i18n('Anonymous').
	 *
	 * @param username The user to get the full name of. If this is ommitted, the
	 *                 full name of the currently logged in user is given. This
	 *                 parameter was added in Aukyla 1.1.
	 * @return The full name of the user.
	 */
	public static function fullName($username = '')
	{
		global $Login;

		if($username == '')
		{
			$username = $Login->username;
		}

		if(String::startsWith($username, 'anonymous'))
		{
			return i18n('Anonymous');
		}
		else
		{
			return $Login->loginHandler->fullName($username);
		}
	}

	/**
	 * Returns the email address of the user.
	 *
	 * @param username The user to get the email address of. If this is ommitted,
	 *                 the email addresse of the currently logged in user is
	 *                 given. This parameter was added in Aukyla 1.1.
	 * @return The email address of the user. If none is given, this is an empty
	 *         string.
	 */
	public static function userMail($username = '')
	{
		global $Login;

		if($username == '')
		{
			$username = $Login->username;
		}

		if(String::startsWith($username, 'anonymous'))
		{
			return '';
		}
		else
		{
			return $Login->loginHandler->userMail($username);
		}
	}

	/**
	 * Returns the IP range from which a user may login.
	 *
	 * @param username The user to get the allowed IP range of. If this is
	 *                 ommitted, the allowed IP range for the currently logged in
	 *                 user is given.
	 * @return The IP range for which the given user may login.
	 *
	 * @sa String::isIpInRange()
	 *
	 * @since Aukyla 1.1
	 */
	public static function ipRange($username = '')
	{
		global $Login;

		if($username == '')
		{
			$username = $Login->username;
		}

		if(String::startsWith($username, 'anonymous'))
		{
			return '';
		}
		else
		{
			return $Login->loginHandler->ipRange($username);
		}
	}

	/**
	 * Logs the current user out.
	 */
	public static function logout()
	{
		global $Config;
		global $Login;

		if(!String::startsWith($Login->username, 'anonymous'))
		{
			Signal::emit('Login::UserLoggedOut', $Login->username);
			$Login->loginHandler->logout();
			$Login->destroySession();
			$Config->readConfiguration();

			unset($Login->loginHandler);
			$Login->loadHandler($Login->loginMethod);
			$Login->loginHandler->setUsername('anonymous');
		}

		Messages::confirm(i18n('Logged out successfully.'));
	}

	/**
	 * Sets a variable for the length of this session.
	 *
	 * @param variable The name of the variable to set.
	 * @param value    The value of the variable. If you set this to @p false
	 *                 (the default), the given variable will be unset.
	 *
	 * @sa sessionVariable()
	 */
	public static function setSessionVariable($variable, $value = false)
	{
		global $Login;

		if($value === false)
		{
			unset($Login->sessionVariables[$variable]);
		}
		else
		{
			$Login->sessionVariables[$variable] = $value;
		}
	}

	/**
	 * Gets a previously stored session variable.
	 *
	 * @param variable Name of the variable to get.
	 * @param default  The default value for in case the variable could
	 *                 not be found.
	 * @return The value of the variable, or the default value if the
	 *         variable was not found.
	 *
	 * @sa setSessionVariable()
	 */
	public static function sessionVariable($variable, $default = '')
	{
		global $Login;

		if(isset($Login->sessionVariables[$variable]))
		{
			return $Login->sessionVariables[$variable];
		}
		else
		{
			return $default;
		}
	}

	/**
	 * Saves the session variables to disk.
	 *
	 * Usually, you shouldn't have to call this function yourself as it's
	 * called automatically by the destructor.
	 *
	 * @sa sessionVariable()
	 */
	public static function saveSessionVariables()
	{
		global $Login;

		if(sizeof($Login->sessionVariables) == 0)
		{
			return;
		}

		if($fp = fopen(AUKYLA_DIR."/run/login/{$Login->username}", 'w'))
		{
			foreach($Login->sessionVariables as $variable => $value)
			{
				fwrite($fp, "$variable=$value\n");
			}
			fclose($fp);
		}
	}

	/**
	 * Returns all existing users.
	 *
	 * @return An array of strings containing the names of all users.
	 */
	public static function users()
	{
		global $Login;

		return $Login->loginHandler->users();
	}

	/**
	 * Returns all users in a given group.
	 *
	 * @param  group The group in which to look for users.
	 * @return An array of strings containing the names of all users in the given
	 *         group.
	 *
	 * @since Aukyla 1.1
	 */
	public static function usersOfGroup($group)
	{
		global $Login;

		return $Login->loginHandler->usersOfGroup($group);
	}

	/**
	 * Returns all existing groups.
	 *
	 * @return An array of strings containing the names of all groups.
	 */
	public static function groups()
	{
		global $Login;

		return $Login->loginHandler->groups();
	}

	/**
	 * Returns all groups the user is a member of.
	 *
	 * @param username The user to find the member groups of. If this is ommitted,
	 *                 the member groups of the currently logged in user are
	 *                 given. This parameter was added in Aukyla 1.1.
	 * @return An array of strings containing the names of all groups the user is
	 *         a member of.
	 */
	public static function memberGroups($username = '')
	{
		global $Login;

		if($username == '')
		{
			$username = $Login->username;
		}

		return $Login->loginHandler->memberGroups($username);
	}

	/**
	 * Checks whether the user belongs to a certain group.
	 *
	 * @param group The group to check for whether the user is a member.
	 * @return @p true if the user is a member of group, @p false
	 *         otherwise.
	 */
	public static function isMemberOf($group)
	{
		global $Login;

		if(isset($Login->loginHandler) == false)
		{
			return false;
		}

		return (array_search($group, $Login->loginHandler->memberGroups($Login->username), true) !== false);
	}

	/**
	 * @internal
	 *
	 * Creates a new session. A session file is created for storing session
	 * variables and a cookie is set to recognize the user.
	 */
	private function createSession($username)
	{
		global $UNITTEST;

		// generate a session id
		$sessionid = Login::randomPassword(32);

		// set initial session variables
		$this->sessionVariables = array('sessionid' => $sessionid,
		                                'ip' => (isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0'));

		// set cookies in the user's browser
		if(isset($UNITTEST) == false)
		{
			if(headers_sent() == false)
			{
				setcookie('username', $username, 0, '/');
				setcookie('sessionid', $sessionid, 0, '/');
			}
		}
		else
		{
			$_COOKIE['username'] = $username;
			$_COOKIE['sessionid'] = $sessionid;
		}

		if(String::startsWith($username, 'anonymous') == false)
		{
			// make normalBaseURL secure if logged in and we're staying in SSL mode
			if(Config::globals('staySecure') == 'true')
			{
				Config::setGlobal('baseURL', Config::globals('secureBaseURL'));
				Config::setGlobal('downloadURL', Config::globals('secureDownloadURL'));
			}
		}
		$this->loginHandler->setUsername($username);

		$this->username = $username;

		return $sessionid;
	}

	/**
	 * @internal
	 *
	 * Restores a session from disk, using the username and sessionid from
	 * a cookie.
	 */
	private function restoreSession()
	{
		if(isset($_COOKIE['username']) && isset($_COOKIE['sessionid']))
		{
			$username = $_COOKIE['username'];

			// open the session file
			if(!file_exists(AUKYLA_DIR."/run/login/$username") ||
			   ($lines = file(AUKYLA_DIR."/run/login/$username")) === false)
			{
				Messages::error(i18n('Session rejected, possibly timed-out.'));
				$this->createSession('anonymous-'.$this->randomPassword(5));
				return false;
			}

			// check the session id and the ip address and read the session variables
			foreach($lines as $line)
			{
				list($variable, $value) = explode('=', trim($line), 2);
				if(($variable == 'sessionid' && $value != $_COOKIE['sessionid']) ||
				   (isset($_SERVER['REMOTE_ADDR']) && $variable == 'ip' && $value != $_SERVER['REMOTE_ADDR']))
				{
					Messages::error(i18n('Session rejected because of invalid IP address or session ID.'));
					$this->sessionVariables = array();
					$this->createSession('anonymous-'.$this->randomPassword(5));
					return false;
				}

				$this->sessionVariables[$variable] = $value;
			}

			$this->loginHandler->setUsername($username);

			// check if we're dealing with an anonymous user
			if(String::startsWith($username, 'anonymous') == false)
			{
				// make normalBaseURL secure if we're staying in SSL mode
				if(Config::globals('staySecure') == 'true')
				{
					Config::setGlobal('baseURL', Config::globals('secureBaseURL'));
					Config::setGlobal('downloadURL', Config::globals('secureDownloadURL'));
				}
			}

			$this->username = $username;
			$config = new Config('', $username);
			Locale::init('base', $config->variable('language', 'en'));

			return true;
		}
		else
		{
			if(Config::globals('uniqueAnonymousUsers', 'true') == 'true')
			{
				$this->createSession('anonymous-'.$this->randomPassword(5));
			}
			else
			{
				$this->loginHandler->setUsername('anonymous');
			}
			return true; // no session is restored, hence it did not fail
		}
	}

	/**
	 * @internal
	 *
	 * Destroyes the current session.
	 */
	private function destroySession()
	{
		global $UNITTEST;

		$username = $this->loginHandler->username();

		if($username == '')
		{
			return;
		}

		// remove the session file
		unlink(AUKYLA_DIR."/run/login/$username");

		// remove the cookies by setting their expiration time in the past
		if(isset($UNITTEST) == false)
		{
			setcookie('username', '', time() - 3600, '/');
			setcookie('sessionid', '', time() - 3600, '/');
		}
		else
		{
			unset($_COOKIE['username']);
			unset($_COOKIE['sessionid']);
		}

		$this->username = 'anonymous';

		$this->createSession('anonymous-'.$this->randomPassword(5));

		Locale::init('base', Config::globals('language', 'en'));
	}

	/**
	 * @internal
	 *
	 * Loads a LoginHandler class.
	 *
	 * @param name The name of the handler which should be loaded.
	 * @return @p true if the class has been successfully loaded or was
	 *         loaded already, @p false otherwise.
	 */
	private function loadHandler($name)
	{
		$className = self::handlerName($name);
		if(class_exists($className) == false)
		{
			$include = @include_once("LoginHandlers/$className.php");
			if($include == false ||
			   class_exists($className) == false)
			{
				die('Unknown login method selected.');
			}
		}

		$this->loginHandler = new $className;
	}

	/**
	 * @internal
	 *
	 * Returns the name of a login handler class from the handler's name.
	 *
	 * @param name The handler's name.
	 * @return The name of the login handler class. This handler is not
	 *         guaranteed to exist.
	 */
	private static function handlerName($name)
	{
		return str_replace(array('/', '-', '.'), '_', $name).'_LoginHandler';
	}

	/**
	 * Function for generating random passwords or session ID's.
	 *
	 * @param len Length of the password/session ID to return.
	 * @return A highly random string containing upper and lower case
	 *         letters and numbers of the given length.
	 */
	public static function randomPassword($len = 8)
	{
		$pass = '';
		$lchar = 0;
		$char = 0;
		for($i = 0; $i < $len; $i++)
		{
			while($char == $lchar)
			{
				$char = rand(48, 109);
				if($char > 57) $char += 7;
				if($char > 90) $char += 6;
			}
			$pass .= chr($char);
			$lchar = $char;
		}
		return $pass;
	}

	private $sessionVariables;
	private $username;
	private $loginHandler;
	private $loginMethod;
}

// create one global instance of the class and check actions from a previous form
global $Login;
$Login = new Login();
Login::checkAction();

?>
Return current item: Aukyla Platform