<?php require(INCLUDE_DIR.'class.ostsession.php'); require(INCLUDE_DIR.'class.usersession.php'); class AuthenticatedUser { // How the user was authenticated var $backend; // Get basic information function getId() {} function getUsername() {} } interface AuthDirectorySearch { /** * Indicates if the backend can be used to search for user information. * Lookup is performed to find user information based on a unique * identifier. */ function lookup($id); /** * Indicates if the backend supports searching for usernames. This is * distinct from information lookup in that lookup is intended to lookup * information based on a unique identifier */ function search($query); } /** * Authentication backend * * Authentication provides the basis of abstracting the link between the * login page with a username and password and the staff member, * administrator, or client using the system. * * The system works by allowing the AUTH_BACKENDS setting from * ost-config.php to determine the list of authentication backends or * providers and also specify the order they should be evaluated in. * * The authentication backend should define a authenticate() method which * receives a username and optional password. If the authentication * succeeds, an instance deriving from <User> should be returned. */ class AuthenticationBackend { static private $registry = array(); static $name; static $id; /* static */ static function register($class) { if (is_string($class)) $class = new $class(); // XXX: Raise error if $class::id is already in the registry static::$registry[$class::$id] = $class; } static function allRegistered() { return static::$registry; } static function getBackend($id) { return static::$registry[$id]; } /* static */ function process($username, $password=null, &$errors) { if (!$username) return false; $backend = static::_getAllowedBackends($username); foreach (static::$registry as $bk) { if ($backend && $bk->supportsAuthentication() && $bk::$id != $backend) // User cannot be authenticated against this backend continue; // All backends are queried here, even if they don't support // authentication so that extensions like lockouts and audits // can be supported. $result = $bk->authenticate($username, $password); if ($result instanceof AuthenticatedUser) { static::_login($result, $username, $bk); $result->backend = $bk; return $result; } // TODO: Handle permission denied, for instance elseif ($result instanceof AccessDenied) { $errors['err'] = $result->reason; break; } } $info = array('username'=>$username, 'password'=>$password); Signal::send('auth.login.failed', null, $info); } function singleSignOn(&$errors) { global $ost; foreach (static::$registry as $bk) { // All backends are queried here, even if they don't support // authentication so that extensions like lockouts and audits // can be supported. $result = $bk->signOn(); if ($result instanceof AuthenticatedUser) { // Ensure staff members are allowed to be authenticated // against this backend if ($result instanceof Staff && !static::_isBackendAllowed($result, $bk)) continue; static::_login($result, $result->getUserName(), $bk); $result->backend = $bk; return $result; } // TODO: Handle permission denied, for instance elseif ($result instanceof AccessDenied) { $errors['err'] = $result->reason; break; } } } static function searchUsers($query) { $users = array(); foreach (static::$registry as $bk) { if ($bk instanceof AuthDirectorySearch) { $users += $bk->search($query); } } return $users; } function _isBackendAllowed($staff, $bk) { $sql = 'SELECT backend FROM '.STAFF_TABLE .' WHERE staff_id='.db_input($staff->getId()); $backend = db_result(db_query($sql)); return !$backend || strcasecmp($bk::$id, $backend) === 0; } function _getAllowedBackends($username) { $username = trim($_POST['userid']); $sql = 'SELECT backend FROM '.STAFF_TABLE .' WHERE username='.db_input($username) .' OR email='.db_input($username); return db_result(db_query($sql)); } function _login($user, $username, $bk) { global $ost; if ($user instanceof Staff) { //Log debug info. $ost->logDebug('Staff login', sprintf("%s logged in [%s], via %s", $user->getUserName(), $_SERVER['REMOTE_ADDR'], get_class($bk))); //Debug. $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() ' .' WHERE staff_id='.db_input($user->getId()); db_query($sql); //Now set session crap and lets roll baby! $_SESSION['_staff'] = array(); //clear. $_SESSION['_staff']['userID'] = $username; $user->refreshSession(); //set the hash. $_SESSION['TZ_OFFSET'] = $user->getTZoffset(); $_SESSION['TZ_DST'] = $user->observeDaylight(); } //Regenerate session id. $sid = session_id(); //Current id session_regenerate_id(true); // Destroy old session ID - needed for PHP version < 5.1.0 // DELME: remove when we move to php 5.3 as min. requirement. if(($session=$ost->getSession()) && is_object($session) && $sid!=session_id()) $session->destroy($sid); Signal::send('auth.login.succeeded', $user); $user->cancelResetTokens(); } /** * Fetches the friendly name of the backend */ function getName() { return static::$name; } /** * Indicates if the backed supports authentication. Useful if the * backend is used for logging or lockout only */ function supportsAuthentication() { return true; } /** * Indicates if the backend supports changing a user's password. This * would be done in two fashions. Either the currently-logged in user * want to change its own password or a user requests to have their * password reset. This requires an administrative privilege which this * backend might not possess, so it's defined in supportsPasswordReset() */ function supportsPasswordChange() { return false; } function supportsPasswordReset() { return false; } /* abstract */ function authenticate($username, $password) { return false; } /* abstract */ function signOn() { return false; } } class RemoteAuthenticationBackend { var $create_unknown_user = false; } /** * This will be an exception in later versions of PHP */ class AccessDenied { function AccessDenied() { call_user_func_array(array($this, '__construct'), func_get_args()); } function __construct($reason) { $this->reason = $reason; } } /** * Simple authentication backend which will lock the login form after a * configurable number of attempts */ class AuthLockoutBackend extends AuthenticationBackend { function authenticate($username, $password=null) { global $cfg, $ost; if($_SESSION['_staff']['laststrike']) { if((time()-$_SESSION['_staff']['laststrike'])<$cfg->getStaffLoginTimeout()) { $_SESSION['_staff']['laststrike'] = time(); //reset timer. return new AccessDenied('Max. failed login attempts reached'); } else { //Timeout is over. //Reset the counter for next round of attempts after the timeout. $_SESSION['_staff']['laststrike']=null; $_SESSION['_staff']['strikes']=0; } } $_SESSION['_staff']['strikes']+=1; if($_SESSION['_staff']['strikes']>$cfg->getStaffMaxLogins()) { $_SESSION['_staff']['laststrike']=time(); $alert='Excessive login attempts by a staff member?'."\n". 'Username: '.$username."\n" .'IP: '.$_SERVER['REMOTE_ADDR']."\n" .'TIME: '.date('M j, Y, g:i a T')."\n\n" .'Attempts #'.$_SESSION['_staff']['strikes']."\n" .'Timeout: '.($cfg->getStaffLoginTimeout()/60)." minutes \n\n"; $ost->logWarning('Excessive login attempts ('.$username.')', $alert, $cfg->alertONLoginError()); return new AccessDenied('Forgot your login info? Contact Admin.'); //Log every other failed login attempt as a warning. } elseif($_SESSION['_staff']['strikes']%2==0) { $alert='Username: '.$username."\n" .'IP: '.$_SERVER['REMOTE_ADDR']."\n" .'TIME: '.date('M j, Y, g:i a T')."\n\n" .'Attempts #'.$_SESSION['_staff']['strikes']; $ost->logWarning('Failed staff login attempt ('.$username.')', $alert, false); } } function supportsAuthentication() { return false; } } AuthenticationBackend::register(AuthLockoutBackend); class osTicketAuthentication extends AuthenticationBackend { static $name = "Local Authenication"; static $id = "local"; function authenticate($username, $password) { if (($user = new StaffSession($username)) && $user->getId() && $user->check_passwd($password)) { //update last login && password reset stuff. $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() '; if($user->isPasswdResetDue() && !$user->isAdmin()) $sql.=',change_passwd=1'; $sql.=' WHERE staff_id='.db_input($user->getId()); db_query($sql); return $user; } } } AuthenticationBackend::register(osTicketAuthentication); ?>