From 4818652403806f194470194b8c1aeb2f13e75e53 Mon Sep 17 00:00:00 2001 From: Peter Rotich <peter@enhancesoft.com> Date: Tue, 7 Jan 2014 02:28:44 +0000 Subject: [PATCH] Change AuthenticatedUser to abstract class Add ability for backends to set authorization key (authkey) specific to the backend. The authkey is tagged with the "id" of the backend that generated it. --- include/class.auth.php | 194 +++++++++++++++++++++++++++------------ include/class.client.php | 14 +-- include/class.staff.php | 10 +- 3 files changed, 140 insertions(+), 78 deletions(-) diff --git a/include/class.auth.php b/include/class.auth.php index 44d382f73..7d933c771 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 184d11f81..b6f9779fb 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 cc64d80ff..73a2c963f 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'; } -- GitLab