commit 836eda3c7ca6909883f936544da74be0b8a44d73
parent 12f438d60d0cdd3db8ecde2c91028b13c78e5efe
Author: Jan Dankert <develop@jandankert.de>
Date: Sat, 28 Nov 2020 00:53:41 +0100
New: Lock password after a number of login fails.
Diffstat:
14 files changed, 144 insertions(+), 4 deletions(-)
diff --git a/modules/cms/action/login/LoginLoginAction.class.php b/modules/cms/action/login/LoginLoginAction.class.php
@@ -6,9 +6,9 @@ use cms\action\Method;
use cms\action\RequestParams;
use cms\auth\Auth;
use cms\auth\AuthRunner;
-use cms\auth\InternalAuth;
use cms\base\Configuration;
use cms\base\DB;
+use cms\base\Startup;
use cms\model\User;
use language\Language;
use language\Messages;
@@ -153,6 +153,35 @@ class LoginLoginAction extends LoginAction implements Method {
$this->addErrorFor( null,Messages::LOGIN_FAILED, ['name' => $loginName] );
$this->addValidationError('login_name' , '');
$this->addValidationError('login_password', '');
+
+ // Increase fail counter
+ $user = User::loadWithName($loginName,User::AUTH_TYPE_INTERNAL);
+
+ $isLocked = $user->passwordLockedUntil && $user->passwordLockedUntil > Startup::getStartTime();
+
+ if ( ! $isLocked ) {
+
+ $user->increaseFailedPasswordCounter();
+
+ $lockAfter = Configuration::subset(['security','password'])->get('lock_after_fail_count',10);
+ if ( $lockAfter && $user->passwordFailedCount % $lockAfter == 0 ) {
+ $factor = pow(2, intval($user->passwordFailedCount/$lockAfter) - 1 ) ;
+ $lockedDuration = Configuration::subset(['security','password'])->get('lock_duration',120) * $factor * 60;
+
+ $lockedUntil = Startup::getStartTime() + $lockedDuration;
+ $user->passwordLockedUntil = $lockedUntil;
+ $user->persist();
+
+ if ( $user->mail ) {
+ $mail = new Mail( $user->mail,Messages::MAIL_PASSWORD_LOCKED_SUBJECT,Messages::MAIL_PASSWORD_LOCKED);
+ $mail->setVar('username',$user->name);
+ $mail->setVar('name',$user->getName() );
+ $mail->setVar('until',date( \cms\base\Language::lang(Messages::DATE_FORMAT ), $lockedUntil ) );
+ $mail->send();
+ }
+ }
+ }
+
return;
}
diff --git a/modules/cms/auth/InternalAuth.class.php b/modules/cms/auth/InternalAuth.class.php
@@ -4,6 +4,7 @@ namespace cms\auth;
use cms\base\Configuration;
use cms\base\DB as Db;
+use cms\base\Startup;
use cms\model\User;
use LogicException;
use security\Password;
@@ -40,6 +41,11 @@ SQL
return null;
}
+ $lockedUntil = $row_user['password_locked_until'];
+ if ( $lockedUntil && $lockedUntil > Startup::getStartTime() ) {
+ return Auth::STATUS_FAILED; // Password is locked
+ }
+
// Pruefen ob Kennwort mit Datenbank uebereinstimmt.
if (!Password::check(User::pepperPassword($password), $row_user['password_hash'], $row_user['password_algo'])) {
return Auth::STATUS_FAILED;
diff --git a/modules/cms/model/User.class.php b/modules/cms/model/User.class.php
@@ -76,6 +76,10 @@ class User extends ModelBase
public $issuer = null;
public $type = User::AUTH_TYPE_INTERNAL;
+ public $passwordFailedCount = 0;
+ public $passwordLockedUntil = 0;
+
+
// Konstruktor
public function __construct( $userid='' )
{
@@ -421,6 +425,8 @@ SQL
$this->totp = ($row['totp']==1);
$this->passwordExpires = $row['password_expires'];
$this->passwordAlgo = $row['password_algo'];
+ $this->passwordLockedUntil = $row['password_locked_until'];
+ $this->passwordFailedCount = $row['password_fail_count' ];
$this->type = $row['auth_type'];
$this->issuer = $row['issuer'];
@@ -486,7 +492,9 @@ SQL
timezone = {timezone},
is_admin = {isAdmin},
totp = {totp},
- hotp = {hotp}
+ hotp = {hotp},
+ password_fail_count = {fail_count},
+ password_locked_until = {locked_until}
WHERE id={userid}
SQL
);
@@ -502,7 +510,9 @@ SQL
$sql->setBoolean( 'totp' ,$this->totp );
$sql->setBoolean( 'hotp' ,$this->hotp );
$sql->setInt ( 'userid' ,$this->userid );
-
+ $sql->setInt ( 'fail_count' ,$this->passwordFailedCount );
+ $sql->setInt ( 'locked_until',$this->passwordLockedUntil );
+
// Datenbankabfrage ausfuehren
$sql->query();
}
@@ -1144,6 +1154,12 @@ SQL
}
+ public function increaseFailedPasswordCounter() {
+ $this->passwordFailedCount++;
+ $this->save();
+ }
+
+
public function getId()
{
return $this->userid;
diff --git a/modules/cms/update/Update.class.php b/modules/cms/update/Update.class.php
@@ -12,7 +12,7 @@ use logger\Logger;
class Update
{
// This is the required DB version:
- const SUPPORTED_VERSION = 22;
+ const SUPPORTED_VERSION = 23;
// -----------------------^^-----------------------------
const STATUS_UPDATE_PROGRESS = 0;
diff --git a/modules/cms/update/version/DBVersion000023.class.php b/modules/cms/update/version/DBVersion000023.class.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace cms\update\version;
+
+use database\DbVersion;
+use database\Column;
+
+/**
+ * Security enhancements:
+ * - log login tries
+ * - add new fail counter
+ *
+ * @author Jan Dankert
+ *
+ */
+class DBVersion000023 extends DbVersion
+{
+ /**
+ *
+ */
+ public function update()
+ {
+ $table = $this->table('user');
+
+ $table->column('password_locked_until' )->type(Column::TYPE_INT )->nullable()->add();
+ $table->column('password_fail_count' )->type(Column::TYPE_INT )->defaultValue(0 )->add();
+ }
+}
+
diff --git a/modules/language/Language_CN.class.php b/modules/language/Language_CN.class.php
@@ -560,6 +560,12 @@ public function get() { return [
We want to inform you that you just logged in with your username ${username} on the device ${browser} (${platform}).
If you did not do this, please change your password.',
+'MAIL_PASSWORD_LOCKED_SUBJECT'=>'Your password is locked',
+'MAIL_PASSWORD_LOCKED'=>'Hello ${name},
+
+you have tried to login a few times with your user account ${username}.
+
+Due to security resons your account is locked until ${until}.',
'MAIL_SUBJECT_MAIL_CHANGE_CODE'=>'Change of your E-Mail adress',
'MAIL_SUBJECT_PASSWORD_COMMIT_CODE'=>'Password change request',
'MAIL_SUBJECT_PASSWORD_NEW'=>'New password',
diff --git a/modules/language/Language_DE.class.php b/modules/language/Language_DE.class.php
@@ -560,6 +560,12 @@ public function get() { return [
Sie haben sich mit Ihrem Benutzernamen ${username} auf dem Gerät ${browser} (${platform}) neu angemeldet. Dies dient rein zu Ihrer Information.
Sofern Sie dieses nicht waren, ändern Sie bitte umgehend Ihr Kennwort.',
+'MAIL_PASSWORD_LOCKED_SUBJECT'=>'Kennwort gesperrt',
+'MAIL_PASSWORD_LOCKED'=>'Guten Tag ${name},
+
+Sie haben mehrfach vergeblich versucht, sich mit Ihrem Benutzernamen ${username} anzumelden.
+
+Aus Sicherheitsgründen ist Ihre Kennung bis ${until} gesperrt.',
'MAIL_SUBJECT_MAIL_CHANGE_CODE'=>'Änderung Ihrer E-Mail-Adresse',
'MAIL_SUBJECT_PASSWORD_COMMIT_CODE'=>'Bestaetigung fuer Kennwortänderung',
'MAIL_SUBJECT_PASSWORD_NEW'=>'Neues Kennwort',
diff --git a/modules/language/Language_EN.class.php b/modules/language/Language_EN.class.php
@@ -560,6 +560,12 @@ public function get() { return [
We want to inform you that you just logged in with your username ${username} on the device ${browser} (${platform}).
If you did not do this, please change your password.',
+'MAIL_PASSWORD_LOCKED_SUBJECT'=>'Your password is locked',
+'MAIL_PASSWORD_LOCKED'=>'Hello ${name},
+
+you have tried to login a few times with your user account ${username}.
+
+Due to security resons your account is locked until ${until}.',
'MAIL_SUBJECT_MAIL_CHANGE_CODE'=>'Change of your E-Mail adress',
'MAIL_SUBJECT_PASSWORD_COMMIT_CODE'=>'Password change request',
'MAIL_SUBJECT_PASSWORD_NEW'=>'New password',
diff --git a/modules/language/Language_ES.class.php b/modules/language/Language_ES.class.php
@@ -560,6 +560,12 @@ public function get() { return [
We want to inform you that you just logged in with your username ${username} on the device ${browser} (${platform}).
If you did not do this, please change your password.',
+'MAIL_PASSWORD_LOCKED_SUBJECT'=>'Your password is locked',
+'MAIL_PASSWORD_LOCKED'=>'Hello ${name},
+
+you have tried to login a few times with your user account ${username}.
+
+Due to security resons your account is locked until ${until}.',
'MAIL_SUBJECT_MAIL_CHANGE_CODE'=>'Change of your E-Mail adress',
'MAIL_SUBJECT_PASSWORD_COMMIT_CODE'=>'Demande de changement de mot de passe',
'MAIL_SUBJECT_PASSWORD_NEW'=>'Nouveau mot de passe',
diff --git a/modules/language/Language_FR.class.php b/modules/language/Language_FR.class.php
@@ -560,6 +560,12 @@ public function get() { return [
We want to inform you that you just logged in with your username ${username} on the device ${browser} (${platform}).
If you did not do this, please change your password.',
+'MAIL_PASSWORD_LOCKED_SUBJECT'=>'Your password is locked',
+'MAIL_PASSWORD_LOCKED'=>'Hello ${name},
+
+you have tried to login a few times with your user account ${username}.
+
+Due to security resons your account is locked until ${until}.',
'MAIL_SUBJECT_MAIL_CHANGE_CODE'=>'Change of your E-Mail adress',
'MAIL_SUBJECT_PASSWORD_COMMIT_CODE'=>'Demande de changement de mot de passe',
'MAIL_SUBJECT_PASSWORD_NEW'=>'Nouveau mot de passe',
diff --git a/modules/language/Language_IT.class.php b/modules/language/Language_IT.class.php
@@ -560,6 +560,12 @@ public function get() { return [
We want to inform you that you just logged in with your username ${username} on the device ${browser} (${platform}).
If you did not do this, please change your password.',
+'MAIL_PASSWORD_LOCKED_SUBJECT'=>'Your password is locked',
+'MAIL_PASSWORD_LOCKED'=>'Hello ${name},
+
+you have tried to login a few times with your user account ${username}.
+
+Due to security resons your account is locked until ${until}.',
'MAIL_SUBJECT_MAIL_CHANGE_CODE'=>'Change of your E-Mail adress',
'MAIL_SUBJECT_PASSWORD_COMMIT_CODE'=>'Contraseña de la petición del cambio de la contraseña nueva',
'MAIL_SUBJECT_PASSWORD_NEW'=>'New password',
diff --git a/modules/language/Language_RU.class.php b/modules/language/Language_RU.class.php
@@ -560,6 +560,12 @@ public function get() { return [
We want to inform you that you just logged in with your username ${username} on the device ${browser} (${platform}).
If you did not do this, please change your password.',
+'MAIL_PASSWORD_LOCKED_SUBJECT'=>'Your password is locked',
+'MAIL_PASSWORD_LOCKED'=>'Hello ${name},
+
+you have tried to login a few times with your user account ${username}.
+
+Due to security resons your account is locked until ${until}.',
'MAIL_SUBJECT_MAIL_CHANGE_CODE'=>'Change of your E-Mail adress',
'MAIL_SUBJECT_PASSWORD_COMMIT_CODE'=>'Запрос на изменение пароля',
'MAIL_SUBJECT_PASSWORD_NEW'=>'Новый пароль',
diff --git a/modules/language/Messages.class.php b/modules/language/Messages.class.php
@@ -556,6 +556,8 @@ class Messages {
const MAIL_PASSWORD_CHANGE_SUCCESS = 'MAIL_PASSWORD_CHANGE_SUCCESS';
const MAIL_NEW_LOGIN_SUBJECT = 'MAIL_NEW_LOGIN_SUBJECT';
const MAIL_NEW_LOGIN_TEXT = 'MAIL_NEW_LOGIN_TEXT';
+ const MAIL_PASSWORD_LOCKED_SUBJECT = 'MAIL_PASSWORD_LOCKED_SUBJECT';
+ const MAIL_PASSWORD_LOCKED = 'MAIL_PASSWORD_LOCKED';
const MAIL_SUBJECT_MAIL_CHANGE_CODE = 'MAIL_SUBJECT_MAIL_CHANGE_CODE';
const MAIL_SUBJECT_PASSWORD_COMMIT_CODE = 'MAIL_SUBJECT_PASSWORD_COMMIT_CODE';
const MAIL_SUBJECT_PASSWORD_NEW = 'MAIL_SUBJECT_PASSWORD_NEW';
diff --git a/modules/language/language.yml b/modules/language/language.yml
@@ -2846,6 +2846,22 @@ MAIL_NEW_LOGIN_TEXT:
We want to inform you that you just logged in with your username ${username} on the device ${browser} (${platform}).
If you did not do this, please change your password.
+MAIL_PASSWORD_LOCKED_SUBJECT:
+ de: Kennwort gesperrt
+ en: Your password is locked
+MAIL_PASSWORD_LOCKED:
+ de: |
+ Guten Tag ${name},
+
+ Sie haben mehrfach vergeblich versucht, sich mit Ihrem Benutzernamen ${username} anzumelden.
+
+ Aus Sicherheitsgründen ist Ihre Kennung bis ${until} gesperrt.
+ en: |
+ Hello ${name},
+
+ you have tried to login a few times with your user account ${username}.
+
+ Due to security resons your account is locked until ${until}.
MAIL_SUBJECT_MAIL_CHANGE_CODE:
de: Änderung Ihrer E-Mail-Adresse
en: Change of your E-Mail adress