diff options
Diffstat (limited to 'app/Model')
-rw-r--r-- | app/Model/Acl.php | 2 | ||||
-rw-r--r-- | app/Model/Authentication.php | 117 | ||||
-rw-r--r-- | app/Model/User.php | 65 |
3 files changed, 161 insertions, 23 deletions
diff --git a/app/Model/Acl.php b/app/Model/Acl.php index 95056de6..a47886bb 100644 --- a/app/Model/Acl.php +++ b/app/Model/Acl.php @@ -17,7 +17,7 @@ class Acl extends Base * @var array */ private $public_acl = array( - 'auth' => array('login', 'check'), + 'auth' => array('login', 'check', 'captcha'), 'task' => array('readonly'), 'board' => array('readonly'), 'webhook' => '*', diff --git a/app/Model/Authentication.php b/app/Model/Authentication.php index 31969b57..f09312bd 100644 --- a/app/Model/Authentication.php +++ b/app/Model/Authentication.php @@ -5,6 +5,7 @@ namespace Model; use Core\Request; use SimpleValidator\Validator; use SimpleValidator\Validators; +use Gregwar\Captcha\CaptchaBuilder; /** * Authentication model @@ -75,18 +76,52 @@ class Authentication extends Base */ public function authenticate($username, $password) { - // Try first the database auth and then LDAP if activated - if ($this->backend('database')->authenticate($username, $password)) { + if ($this->user->isLocked($username)) { + $this->container['logger']->error('Account locked: '.$username); + return false; + } + else if ($this->backend('database')->authenticate($username, $password)) { + $this->user->resetFailedLogin($username); return true; } else if (LDAP_AUTH && $this->backend('ldap')->authenticate($username, $password)) { + $this->user->resetFailedLogin($username); return true; } + $this->handleFailedLogin($username); return false; } /** + * Return true if the captcha must be shown + * + * @access public + * @param string $username + * @return boolean + */ + public function hasCaptcha($username) + { + return $this->user->getFailedLogin($username) >= BRUTEFORCE_CAPTCHA; + } + + /** + * Handle failed login + * + * @access public + * @param string $username + */ + public function handleFailedLogin($username) + { + $this->user->incrementFailedLogin($username); + + if ($this->user->getFailedLogin($username) >= BRUTEFORCE_LOCKDOWN) { + $this->container['logger']->critical('Locking account: '.$username); + $this->user->lock($username, BRUTEFORCE_LOCKDOWN_DURATION); + } + } + + /** * Validate user login form * * @access public @@ -95,27 +130,12 @@ class Authentication extends Base */ public function validateForm(array $values) { - $v = new Validator($values, array( - new Validators\Required('username', t('The username is required')), - new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), - new Validators\Required('password', t('The password is required')), - )); - - $result = $v->execute(); - $errors = $v->getErrors(); + list($result, $errors) = $this->validateFormCredentials($values); if ($result) { - if ($this->authenticate($values['username'], $values['password'])) { - - // Setup the remember me feature - if (! empty($values['remember_me'])) { - - $credentials = $this->backend('rememberMe') - ->create($this->userSession->getId(), Request::getIpAddress(), Request::getUserAgent()); - - $this->backend('rememberMe')->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']); - } + if ($this->validateFormCaptcha($values) && $this->authenticate($values['username'], $values['password'])) { + $this->createRememberMeSession($values); } else { $result = false; @@ -123,9 +143,62 @@ class Authentication extends Base } } + return array($result, $errors); + } + + /** + * Validate credentials syntax + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateFormCredentials(array $values) + { + $v = new Validator($values, array( + new Validators\Required('username', t('The username is required')), + new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), + new Validators\Required('password', t('The password is required')), + )); + return array( - $result, - $errors + $v->execute(), + $v->getErrors(), ); } + + /** + * Validate captcha + * + * @access public + * @param array $values Form values + * @return boolean + */ + public function validateFormCaptcha(array $values) + { + if ($this->hasCaptcha($values['username'])) { + $builder = new CaptchaBuilder; + $builder->setPhrase($this->session['captcha']); + return $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : ''); + } + + return true; + } + + /** + * Create remember me session if necessary + * + * @access private + * @param array $values Form values + */ + private function createRememberMeSession(array $values) + { + if (! empty($values['remember_me'])) { + + $credentials = $this->backend('rememberMe') + ->create($this->userSession->getId(), Request::getIpAddress(), Request::getUserAgent()); + + $this->backend('rememberMe')->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']); + } + } } diff --git a/app/Model/User.php b/app/Model/User.php index b6804abc..8daef3f2 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -365,6 +365,71 @@ class User extends Base } /** + * Get the number of failed login for the user + * + * @access public + * @param string $username + * @return integer + */ + public function getFailedLogin($username) + { + return (int) $this->db->table(self::TABLE)->eq('username', $username)->findOneColumn('nb_failed_login'); + } + + /** + * Reset to 0 the counter of failed login + * + * @access public + * @param string $username + * @return boolean + */ + public function resetFailedLogin($username) + { + return $this->db->table(self::TABLE)->eq('username', $username)->update(array('nb_failed_login' => 0, 'lock_expiration_date' => 0)); + } + + /** + * Increment failed login counter + * + * @access public + * @param string $username + * @return boolean + */ + public function incrementFailedLogin($username) + { + return $this->db->execute('UPDATE '.self::TABLE.' SET nb_failed_login=nb_failed_login+1 WHERE username=?', array($username)) !== false; + } + + /** + * Check if the account is locked + * + * @access public + * @param string $username + * @return boolean + */ + public function isLocked($username) + { + return $this->db->table(self::TABLE) + ->eq('username', $username) + ->neq('lock_expiration_date', 0) + ->gte('lock_expiration_date', time()) + ->exists(); + } + + /** + * Lock the account for the specified duration + * + * @access public + * @param string $username Username + * @param integer $duration Duration in minutes + * @return boolean + */ + public function lock($username, $duration = 15) + { + return $this->db->table(self::TABLE)->eq('username', $username)->update(array('lock_expiration_date' => time() + $duration * 60)); + } + + /** * Common validation rules * * @access private |