Skip to content
Snippets Groups Projects
Commit a4d21341 authored by Jared Hancock's avatar Jared Hancock
Browse files

Merge pull request #559 from protich/issue/549


Add ability to audit failed logins attempts

Reviewed-By: default avatarJared Hancock <jared@osticket.com>
parents 411e03cc c7b07445
No related branches found
No related tags found
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