Skip to content
Snippets Groups Projects
Commit c7b07445 authored by Peter Rotich's avatar Peter Rotich
Browse files

Add ability to audit failed logins attempts

Separate authentication strike (excessive login attempts) count from timeout
enforcement.
parent 29b26fe0
Branches
Tags
No related merge requests found
...@@ -100,6 +100,18 @@ abstract class AuthenticationBackend { ...@@ -100,6 +100,18 @@ abstract class AuthenticationBackend {
return $backends[$id]; 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) { static function process($username, $password=null, &$errors) {
if (!$username) if (!$username)
...@@ -117,22 +129,32 @@ abstract class AuthenticationBackend { ...@@ -117,22 +129,32 @@ abstract class AuthenticationBackend {
// authentication so that extensions like lockouts and audits // authentication so that extensions like lockouts and audits
// can be supported. // can be supported.
$result = $bk->authenticate($username, $password); $result = $bk->authenticate($username, $password);
if ($result instanceof AuthenticatedUser if ($result instanceof AuthenticatedUser
&& ($bk->login($result, $bk))) && ($bk->login($result, $bk)))
return $result; return $result;
// TODO: Handle permission denied, for instance
elseif ($result instanceof AccessDenied) { elseif ($result instanceof AccessDenied) {
$errors['err'] = $result->reason;
break; 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); 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) { foreach (static::allRegistered() as $bk) {
// All backends are queried here, even if they don't support // All backends are queried here, even if they don't support
...@@ -146,12 +168,18 @@ abstract class AuthenticationBackend { ...@@ -146,12 +168,18 @@ abstract class AuthenticationBackend {
return $result; return $result;
} }
// TODO: Handle permission denied, for instance
elseif ($result instanceof AccessDenied) { elseif ($result instanceof AccessDenied) {
$errors['err'] = $result->reason;
break; 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) { static function searchUsers($query) {
...@@ -202,6 +230,10 @@ abstract class AuthenticationBackend { ...@@ -202,6 +230,10 @@ abstract class AuthenticationBackend {
return null; return null;
} }
protected function audit($result, $credentials) {
return null;
}
abstract function authenticate($username, $password); abstract function authenticate($username, $password);
abstract function login($user, $bk); abstract function login($user, $bk);
abstract static function getUser(); //Validates authenticated users. abstract static function getUser(); //Validates authenticated users.
...@@ -465,9 +497,6 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend { ...@@ -465,9 +497,6 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend {
* This will be an exception in later versions of PHP * This will be an exception in later versions of PHP
*/ */
class AccessDenied { class AccessDenied {
function AccessDenied() {
call_user_func_array(array($this, '__construct'), func_get_args());
}
function __construct($reason) { function __construct($reason) {
$this->reason = $reason; $this->reason = $reason;
} }
...@@ -480,11 +509,11 @@ class AccessDenied { ...@@ -480,11 +509,11 @@ class AccessDenied {
abstract class AuthStrikeBackend extends AuthenticationBackend { abstract class AuthStrikeBackend extends AuthenticationBackend {
function authenticate($username, $password=null) { function authenticate($username, $password=null) {
return static::authStrike($username, $password); return static::authTimeout();
} }
function signOn() { function signOn() {
return static::authStrike('Unknown'); return static::authTimeout();
} }
static function signOut($user) { static function signOut($user) {
...@@ -512,7 +541,17 @@ abstract class AuthStrikeBackend extends AuthenticationBackend { ...@@ -512,7 +541,17 @@ abstract class AuthStrikeBackend extends AuthenticationBackend {
return null; 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 { ...@@ -520,24 +559,36 @@ abstract class AuthStrikeBackend extends AuthenticationBackend {
*/ */
class StaffAuthStrikeBackend extends AuthStrikeBackend { class StaffAuthStrikeBackend extends AuthStrikeBackend {
function authstrike($username, $password=null) { function authTimeout() {
global $ost; global $ost;
$cfg = $ost->getConfig(); $cfg = $ost->getConfig();
$authsession = &$_SESSION['_auth']['staff']; $authsession = &$_SESSION['_auth']['staff'];
if (!$authsession['laststrike'])
return;
if($authsession['laststrike']) { //Veto login due to excessive login attempts.
if((time()-$authsession['laststrike'])<$cfg->getStaffLoginTimeout()) { if((time()-$authsession['laststrike'])<$cfg->getStaffLoginTimeout()) {
$authsession['laststrike'] = time(); //reset timer. $authsession['laststrike'] = time(); //reset timer.
return new AccessDenied('Max. failed login attempts reached'); 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;
}
} }
//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; $authsession['strikes']+=1;
if($authsession['strikes']>$cfg->getStaffMaxLogins()) { if($authsession['strikes']>$cfg->getStaffMaxLogins()) {
$authsession['laststrike']=time(); $authsession['laststrike']=time();
...@@ -567,25 +618,37 @@ StaffAuthenticationBackend::register(StaffAuthStrikeBackend); ...@@ -567,25 +618,37 @@ StaffAuthenticationBackend::register(StaffAuthStrikeBackend);
*/ */
class UserAuthStrikeBackend extends AuthStrikeBackend { class UserAuthStrikeBackend extends AuthStrikeBackend {
function authstrike($username, $password=null) { function authTimeout() {
global $ost; global $ost;
$cfg = $ost->getConfig(); $cfg = $ost->getConfig();
$authsession = &$_SESSION['_auth']['user']; $authsession = &$_SESSION['_auth']['user'];
if (!$authsession['laststrike'])
return;
//Check time for last max failed login attempt strike. //Veto login due to excessive login attempts.
if($authsession['laststrike']) { if ((time()-$authsession['laststrike']) < $cfg->getStaffLoginTimeout()) {
if((time()-$authsession['laststrike'])<$cfg->getClientLoginTimeout()) { $authsession['laststrike'] = time(); //reset timer.
$authsession['laststrike'] = time(); //renew the strike. return new AccessDenied("You've reached maximum failed login attempts allowed.");
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;
}
} }
//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; $authsession['strikes']+=1;
if($authsession['strikes']>$cfg->getClientMaxLogins()) { if($authsession['strikes']>$cfg->getClientMaxLogins()) {
$authsession['laststrike'] = time(); $authsession['laststrike'] = time();
......
...@@ -37,7 +37,7 @@ if($_POST) { ...@@ -37,7 +37,7 @@ if($_POST) {
} }
// Consider single sign-on authentication backends // Consider single sign-on authentication backends
else if (!$thisstaff || !($thisstaff->getId() || $thisstaff->isValid())) { else if (!$thisstaff || !($thisstaff->getId() || $thisstaff->isValid())) {
if (($user = StaffAuthenticationBackend::processSignOn($errors)) if (($user = StaffAuthenticationBackend::processSignOn($errors, false))
&& ($user instanceof StaffSession)) && ($user instanceof StaffSession))
@header("Location: $dest"); @header("Location: $dest");
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment