<?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();
?>