diff --git a/include/class.auth.php b/include/class.auth.php index 44d382f732e2c7d4e78da2ffedd5c8d299082124..7d933c771cff4cf01e70e05c5673f6ce63b0b071 100644 --- a/include/class.auth.php +++ b/include/class.auth.php @@ -3,14 +3,22 @@ require(INCLUDE_DIR.'class.ostsession.php'); require(INCLUDE_DIR.'class.usersession.php'); -interface AuthenticatedUser { +abstract class AuthenticatedUser { + //Authorization key returned by the backend used to authorize the user + private $authkey; // Get basic information - function getId(); - function getUsername(); - function setBackend($bk); - function getBackend(); - function getRole(); + abstract function getId(); + abstract function getUsername(); + abstract function getRole(); + + function setAuthKey($key) { + $this->authkey = $key; + } + + function getAuthKey() { + return $this->authkey; + } } interface AuthDirectorySearch { @@ -72,7 +80,11 @@ abstract class AuthenticationBackend { } static function getBackend($id) { - return static::$registry[$id]; + + if ($id + && ($backends = static::allRegistered()) + && isset($backends[$id])) + return $backends[$id]; } static function process($username, $password=null, &$errors) { @@ -94,11 +106,8 @@ abstract class AuthenticationBackend { $result = $bk->authenticate($username, $password); if ($result instanceof AuthenticatedUser - && (static::login($result, $bk))) { - $result->setBackend($bk); - + && (static::login($result, $bk))) return $result; - } // TODO: Handle permission denied, for instance elseif ($result instanceof AccessDenied) { $errors['err'] = $result->reason; @@ -121,9 +130,8 @@ abstract class AuthenticationBackend { if ($result instanceof AuthenticatedUser) { //Perform further Object specific checks and the actual login if (!static::login($result, $bk)) - continue + continue; - $result->setBackend($bk); return $result; } // TODO: Handle permission denied, for instance @@ -181,7 +189,7 @@ abstract class AuthenticationBackend { abstract function authenticate($username, $password); abstract function login($user, $bk); abstract function getAllowedBackends($userid); - + abstract protected function getAuthKey($user); } class RemoteAuthenticationBackend { @@ -226,32 +234,39 @@ abstract class StaffAuthenticationBackend extends AuthenticationBackend { return array_filter($backends); } - function login($user, $bk) { + function login($staff, $bk) { global $ost; - if (!($user instanceof Staff)) + if (!$bk || !($staff instanceof Staff)) return false; // Ensure staff is allowed for realz to be authenticated via the backend. - if (!static::isBackendAllowed($user, $bk)) + if (!static::isBackendAllowed($staff, $bk) + || !($authkey=$bk->getAuthKey($staff))) return false; //Log debug info. $ost->logDebug('Staff login', - sprintf("%s logged in [%s], via %s", $user->getUserName(), + sprintf("%s logged in [%s], via %s", $staff->getUserName(), $_SERVER['REMOTE_ADDR'], get_class($bk))); //Debug. $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() ' - .' WHERE staff_id='.db_input($user->getId()); + .' WHERE staff_id='.db_input($staff->getId()); db_query($sql); + + //Tag the authkey. + $authkey = $bk::$id.':'.$authkey; + //Now set session crap and lets roll baby! - $_SESSION['_staff'] = array(); //clear. - $_SESSION['_staff']['userID'] = $user->getUserName(); + $_SESSION['_auth']['staff'] = array(); //clear. + $_SESSION['_auth']['staff']['id'] = $staff->getId(); + $_SESSION['_auth']['staff']['key'] = $authkey; - $user->refreshSession(); //set the hash. + $staff->setAuthKey($authkey); + $staff->refreshSession(); //set the hash. - $_SESSION['TZ_OFFSET'] = $user->getTZoffset(); - $_SESSION['TZ_DST'] = $user->observeDaylight(); + $_SESSION['TZ_OFFSET'] = $staff->getTZoffset(); + $_SESSION['TZ_DST'] = $staff->observeDaylight(); //Regenerate session id. $sid = session_id(); //Current id @@ -262,12 +277,16 @@ abstract class StaffAuthenticationBackend extends AuthenticationBackend { && $sid!=session_id()) $session->destroy($sid); - Signal::send('auth.login.succeeded', $user); + Signal::send('auth.login.succeeded', $staff); - $user->cancelResetTokens(); + $staff->cancelResetTokens(); return true; } + + protected function getAuthKey($staff) { + return null; + } } abstract class UserAuthenticationBackend extends AuthenticationBackend { @@ -290,19 +309,29 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend { function login($user, $bk) { global $ost; - if (!($user instanceof TicketUser)) + if (!$user || !$bk + || !$bk::$id //Must have ID + || !($authkey = $bk->getAuthKey($user))) return false; - $_SESSION['_client'] = array(); //clear. - $_SESSION['_client']['userID'] = $user->getEmail(); //Email - //$_SESSION['_client']['key'] = $ticket->getExtId(); //Ticket ID --acts as password when used with email. See above. - $_SESSION['_client']['token'] = $user->getSessionToken(); + //Tag the authkey. + $authkey = $bk::$id.':'.$authkey; + //Set the session goodies + $_SESSION['_auth']['user'] = array(); //clear. + $_SESSION['_auth']['user']['id'] = $user->getId(); + $_SESSION['_auth']['user']['key'] = $authkey; $_SESSION['TZ_OFFSET'] = $ost->getConfig()->getTZoffset(); $_SESSION['TZ_DST'] = $ost->getConfig()->observeDaylightSaving(); + + //The backend used decides the format of the auth key. + // XXX: encrypt to hide the bk?? + $user->setAuthKey($authkey); + $user->refreshSession(); //set the hash. + //Log login info... - $msg=sprintf('%s/%s logged in [%s]', - $user->getEmail(), $user->getId(), $_SERVER['REMOTE_ADDR']); + $msg=sprintf('%s (%s) logged in [%s]', + $user->getUserName(), $user->getId(), $_SERVER['REMOTE_ADDR']); $ost->logDebug('User login', $msg); //Regenerate session ID. @@ -314,6 +343,11 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend { return true; } + + protected function getAuthKey($user) { + return null; + } + } /** @@ -354,6 +388,10 @@ abstract class AuthStrikeBackend extends AuthenticationBackend { return array(); } + function getAuthKey($user) { + return null; + } + abstract function authStrike($username, $password=null); } @@ -367,35 +405,35 @@ class StaffAuthStrikeBackend extends AuthStrikeBackend { $cfg = $ost->getConfig(); - if($_SESSION['_staff']['laststrike']) { - if((time()-$_SESSION['_staff']['laststrike'])<$cfg->getStaffLoginTimeout()) { - $_SESSION['_staff']['laststrike'] = time(); //reset timer. + if($_SESSION['_auth']['staff']['laststrike']) { + if((time()-$_SESSION['_auth']['staff']['laststrike'])<$cfg->getStaffLoginTimeout()) { + $_SESSION['_auth']['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['_auth']['staff']['laststrike']=null; + $_SESSION['_auth']['staff']['strikes']=0; } } - $_SESSION['_staff']['strikes']+=1; - if($_SESSION['_staff']['strikes']>$cfg->getStaffMaxLogins()) { - $_SESSION['_staff']['laststrike']=time(); + $_SESSION['_auth']['staff']['strikes']+=1; + if($_SESSION['_auth']['staff']['strikes']>$cfg->getStaffMaxLogins()) { + $_SESSION['_auth']['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" + .'Attempts #'.$_SESSION['_auth']['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) { + } elseif($_SESSION['_auth']['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']; + .'Attempts #'.$_SESSION['_auth']['staff']['strikes']; $ost->logWarning('Failed staff login attempt ('.$username.')', $alert, false); } } @@ -412,30 +450,31 @@ class UserAuthStrikeBackend extends AuthStrikeBackend { $cfg = $ost->getConfig(); + $_SESSION['_auth']['user'] = array(); //Check time for last max failed login attempt strike. - if($_SESSION['_client']['laststrike']) { - if((time()-$_SESSION['_client']['laststrike'])<$cfg->getClientLoginTimeout()) { - $_SESSION['_client']['laststrike'] = time(); //renew the strike. + if($_SESSION['_auth']['user']['laststrike']) { + if((time()-$_SESSION['_auth']['user']['laststrike'])<$cfg->getClientLoginTimeout()) { + $_SESSION['_auth']['user']['laststrike'] = time(); //renew the strike. return new AccessDenied('You\'ve reached maximum failed login attempts allowed.'); } else { //Timeout is over. //Reset the counter for next round of attempts after the timeout. - $_SESSION['_client']['laststrike'] = null; - $_SESSION['_client']['strikes'] = 0; + $_SESSION['_auth']['user']['laststrike'] = null; + $_SESSION['_auth']['user']['strikes'] = 0; } } - $_SESSION['_client']['strikes']+=1; - if($_SESSION['_client']['strikes']>$cfg->getClientMaxLogins()) { - $_SESSION['_client']['laststrike'] = time(); + $_SESSION['_auth']['user']['strikes']+=1; + if($_SESSION['_auth']['user']['strikes']>$cfg->getClientMaxLogins()) { + $_SESSION['_auth']['user']['laststrike'] = time(); $alert='Excessive login attempts by a user.'."\n". 'Login: '.$username.': '.$password."\n". 'IP: '.$_SERVER['REMOTE_ADDR']."\n".'Time:'.date('M j, Y, g:i a T')."\n\n". - 'Attempts #'.$_SESSION['_client']['strikes']; + 'Attempts #'.$_SESSION['_auth']['user']['strikes']; $ost->logError('Excessive login attempts (user)', $alert, ($cfg->alertONLoginError())); return new AccessDenied('Access Denied'); - } elseif($_SESSION['_client']['strikes']%2==0) { //Log every other failed login attempt as a warning. + } elseif($_SESSION['_auth']['user']['strikes']%2==0) { //Log every other failed login attempt as a warning. $alert='Login: '.$username.': '.$password."\n".'IP: '.$_SERVER['REMOTE_ADDR']. - "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_client']['strikes']; + "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_auth']['user']['strikes']; $ost->logWarning('Failed login attempt (user)', $alert); } @@ -462,6 +501,15 @@ class osTicketAuthentication extends StaffAuthenticationBackend { return $user; } } + + protected function getAuthKey($staff) { + + if(!($staff instanceof Staff)) + return null; + + return $staff->getUsername(); //FIXME: + } + } StaffAuthenticationBackend::register(osTicketAuthentication); @@ -470,11 +518,41 @@ class AuthTokenAuthentication extends UserAuthenticationBackend { static $id = "authtoken"; + function signOn() { - if ($_GET['auth'] && ($user=self::__authtoken($_GET['auth']))) - return $user; + $user = null; + if ($_GET['auth']) + $user = self::__authtoken($_GET['auth']); + // Support old ticket based tokens. + elseif ($_GET['t'] && $_GET['e'] && $_GET['a']) { + if (($ticket = Ticket::lookupByExtId($_GET['t'], $_GET['e'])) + // Using old ticket auth code algo - hardcoded here because it + // will be removed in ticket class in the upcoming rewrite + && !strcasecmp($_GET['a'], md5($ticket->getId() . $_GET['e'] . SECRET_SALT)) + && ($client = $ticket->getClient())) + $user = new ClientSession($client); + } + + return $user; + } + + protected function getAuthKey($user) { + + if (!$this->supportsAuthentication() + || !$user + || !($user instanceof EndUser)) + return null; + + //Generate authkey based the type of ticket user + // It's required to validate users going forward. + $authkey = sprintf('%s%dt%dh%s', //XXX: Placeholder + $user->isOwner() ? 'o':'c', + $user->getId(), + $user->getTicketID(), + md5($user->getUsername().$this->id)); + return $authkey; } static private function __authtoken($token) { @@ -482,7 +560,7 @@ class AuthTokenAuthentication extends UserAuthenticationBackend { switch ($token[0]) { case 'c': //Collaborator c+[token] if (($c = Collaborator::lookupByAuthToken($token))) - return new TicketUser($c); //Decorator + return new ClientSession($c); //Decorator break; case 'o': //Ticket owner o+[token] break; diff --git a/include/class.client.php b/include/class.client.php index 184d11f81cb4037be88b2bc46d4d451443c5ccee..b6f9779fb7f96301b2a6e426956a7c357cba6f0e 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -247,9 +247,8 @@ class Client { * */ -class TicketUser implements AuthenticatedUser { +class EndUser extends AuthenticatedUser { - protected $backend; protected $user; function __construct($user) { @@ -261,8 +260,9 @@ class TicketUser implements AuthenticatedUser { */ function __call($name, $args) { + if(!$this->user - || !is_callable($this->user, $name)) + || !is_callable(array($this->user, $name))) return false; return $args @@ -282,14 +282,6 @@ class TicketUser implements AuthenticatedUser { return ($this->user && $this->user instanceof Client); } - function setBackend($bk) { - $this->backend = $bk; - } - - function getBackend() { - return $this->backend; - } - function getUserName() { //XXX: Revisit when real usernames are introduced or when email // requirement is removed. diff --git a/include/class.staff.php b/include/class.staff.php index cc64d80ffc3fe9b88087f22344cf17e8d6170397..73a2c963f26de91c499f59f99258ef93c7d3d330 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -22,7 +22,7 @@ include_once(INCLUDE_DIR.'class.passwd.php'); include_once(INCLUDE_DIR.'class.user.php'); include_once(INCLUDE_DIR.'class.auth.php'); -class Staff implements AuthenticatedUser { +class Staff extends AuthenticatedUser { var $ht; var $id; @@ -97,14 +97,6 @@ class Staff implements AuthenticatedUser { // AuthenticatedUser implementation... // TODO: Move to an abstract class that extends Staff - function setBackend($bk) { - - } - - function getBackend() { - - } - function getRole() { return 'staff'; }