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 {
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();
......
......@@ -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");
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment