diff --git a/include/class.auth.php b/include/class.auth.php index 09708971fe24750cb1c8db3fd69215554f345381..ff8b54466e493ebb40ae48723c3668fd9a280d15 100644 --- a/include/class.auth.php +++ b/include/class.auth.php @@ -100,6 +100,18 @@ abstract class AuthenticationBackend { return $backends[$id]; } + /* + * Allow the backend to do login audit depending on the result + * This is mainly used to track failed login attempts + */ + static function authAudit($result, $credentials=null) { + + if (!$result) return; + + foreach (static::allRegistered() as $bk) + $bk->audit($result, $credentials); + } + static function process($username, $password=null, &$errors) { if (!$username) @@ -117,22 +129,32 @@ abstract class AuthenticationBackend { // authentication so that extensions like lockouts and audits // can be supported. $result = $bk->authenticate($username, $password); - if ($result instanceof AuthenticatedUser && ($bk->login($result, $bk))) return $result; - // TODO: Handle permission denied, for instance elseif ($result instanceof AccessDenied) { - $errors['err'] = $result->reason; break; } } - $info = array('username'=>$username, 'password'=>$password); + if (!$result) + $result = new AccessDenied('Access denied'); + + if ($result && $result instanceof AccessDenied) + $errors['err'] = $result->reason; + + $info = array('username' => $username, 'password' => $password); Signal::send('auth.login.failed', null, $info); + self::authAudit($result, $info); } - function processSignOn(&$errors) { + /* + * Attempt to process non-interactive sign-on e.g HTTP-Passthrough + * + * $forcedAuth - indicate if authentication is required. + * + */ + function processSignOn(&$errors, $forcedAuth=true) { foreach (static::allRegistered() as $bk) { // All backends are queried here, even if they don't support @@ -146,12 +168,18 @@ abstract class AuthenticationBackend { return $result; } - // TODO: Handle permission denied, for instance elseif ($result instanceof AccessDenied) { - $errors['err'] = $result->reason; break; } } + + if (!$result && $forcedAuth) + $result = new AccessDenied('Unknown user'); + + if ($result && $result instanceof AccessDenied) + $errors['err'] = $result->reason; + + self::authAudit($result); } static function searchUsers($query) { @@ -202,6 +230,10 @@ abstract class AuthenticationBackend { return null; } + protected function audit($result, $credentials) { + return null; + } + abstract function authenticate($username, $password); abstract function login($user, $bk); abstract static function getUser(); //Validates authenticated users. @@ -465,9 +497,6 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend { * 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; } @@ -480,11 +509,11 @@ class AccessDenied { abstract class AuthStrikeBackend extends AuthenticationBackend { function authenticate($username, $password=null) { - return static::authStrike($username, $password); + return static::authTimeout(); } function signOn() { - return static::authStrike('Unknown'); + return static::authTimeout(); } static function signOut($user) { @@ -512,7 +541,17 @@ abstract class AuthStrikeBackend extends AuthenticationBackend { return null; } - abstract function authStrike($username, $password=null); + //Provides audit facility for logins attempts + function audit($result, $credentials) { + + //Count failed login attempts as a strike. + if ($result instanceof AccessDenied) + return static::authStrike($credentials); + + } + + abstract function authStrike($credentials); + abstract function authTimeout(); } /* @@ -520,24 +559,36 @@ abstract class AuthStrikeBackend extends AuthenticationBackend { */ class StaffAuthStrikeBackend extends AuthStrikeBackend { - function authstrike($username, $password=null) { + function authTimeout() { global $ost; $cfg = $ost->getConfig(); $authsession = &$_SESSION['_auth']['staff']; + if (!$authsession['laststrike']) + return; - if($authsession['laststrike']) { - if((time()-$authsession['laststrike'])<$cfg->getStaffLoginTimeout()) { - $authsession['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. - $authsession['laststrike']=null; - $authsession['strikes']=0; - } + //Veto login due to excessive login attempts. + if((time()-$authsession['laststrike'])<$cfg->getStaffLoginTimeout()) { + $authsession['laststrike'] = time(); //reset timer. + return new AccessDenied('Max. failed login attempts reached'); } + //Timeout is over. + //Reset the counter for next round of attempts after the timeout. + $authsession['laststrike']=null; + $authsession['strikes']=0; + } + + function authstrike($credentials) { + global $ost; + + $cfg = $ost->getConfig(); + + $authsession = &$_SESSION['_auth']['staff']; + + $username = $credentials['username']; + $authsession['strikes']+=1; if($authsession['strikes']>$cfg->getStaffMaxLogins()) { $authsession['laststrike']=time(); @@ -567,25 +618,37 @@ StaffAuthenticationBackend::register('StaffAuthStrikeBackend'); */ class UserAuthStrikeBackend extends AuthStrikeBackend { - function authstrike($username, $password=null) { + function authTimeout() { global $ost; $cfg = $ost->getConfig(); $authsession = &$_SESSION['_auth']['user']; + if (!$authsession['laststrike']) + return; - //Check time for last max failed login attempt strike. - if($authsession['laststrike']) { - if((time()-$authsession['laststrike'])<$cfg->getClientLoginTimeout()) { - $authsession['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. - $authsession['laststrike'] = null; - $authsession['strikes'] = 0; - } + //Veto login due to excessive login attempts. + if ((time()-$authsession['laststrike']) < $cfg->getStaffLoginTimeout()) { + $authsession['laststrike'] = time(); //reset timer. + return new AccessDenied("You've reached maximum failed login attempts allowed."); } + //Timeout is over. + //Reset the counter for next round of attempts after the timeout. + $authsession['laststrike']=null; + $authsession['strikes']=0; + } + + function authstrike($credentials) { + global $ost; + + $cfg = $ost->getConfig(); + + $authsession = &$_SESSION['_auth']['user']; + + $username = $credentials['username']; + $password = $credentials['password']; + $authsession['strikes']+=1; if($authsession['strikes']>$cfg->getClientMaxLogins()) { $authsession['laststrike'] = time(); diff --git a/scp/login.php b/scp/login.php index 840716c65deda81e7b87026450ea3bfd6fd51d93..211bb70989f8fbe002fded97c9e59fa873b81ab1 100644 --- a/scp/login.php +++ b/scp/login.php @@ -37,7 +37,7 @@ if($_POST) { } // Consider single sign-on authentication backends else if (!$thisstaff || !($thisstaff->getId() || $thisstaff->isValid())) { - if (($user = StaffAuthenticationBackend::processSignOn($errors)) + if (($user = StaffAuthenticationBackend::processSignOn($errors, false)) && ($user instanceof StaffSession)) @header("Location: $dest"); }