diff options
author | Roeland Jago Douma <roeland@famdouma.nl> | 2020-04-21 16:46:51 +0300 |
---|---|---|
committer | Roeland Jago Douma <roeland@famdouma.nl> | 2020-04-24 17:34:47 +0300 |
commit | d4250cfac40e6d43ddc050f2cd49ebd74ab288b2 (patch) | |
tree | 4cbb43b7d742d494139b87eb1cc2023fb78a1f6e /lib | |
parent | b617166503a1dcf4587876d199c2bebfc03c6132 (diff) |
Disable account after failed login attempts
Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/AppInfo/Application.php | 7 | ||||
-rw-r--r-- | lib/FailedLoginCompliance.php | 88 | ||||
-rw-r--r-- | lib/Listener/FailedLoginListener.php | 49 | ||||
-rw-r--r-- | lib/Listener/SuccesfullLoginListener.php | 48 | ||||
-rw-r--r-- | lib/PasswordPolicyConfig.php | 10 | ||||
-rw-r--r-- | lib/Settings.php | 1 |
6 files changed, 203 insertions, 0 deletions
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 183d461..7d6c68e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -27,8 +27,11 @@ namespace OCA\Password_Policy\AppInfo; use OCA\Password_Policy\Capabilities; use OCA\Password_Policy\ComplianceService; use OCA\Password_Policy\Generator; +use OCA\Password_Policy\Listener\FailedLoginListener; +use OCA\Password_Policy\Listener\SuccesfullLoginListener; use OCA\Password_Policy\PasswordValidator; use OCP\AppFramework\App; +use OCP\Authentication\Events\LoginFailedEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\ILogger; @@ -37,6 +40,7 @@ use OCP\Security\Events\ValidatePasswordPolicyEvent; use OCP\User\Events\BeforePasswordUpdatedEvent; use OCP\User\Events\BeforeUserLoggedInEvent; use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\UserLoggedInEvent; use Symfony\Component\EventDispatcher\GenericEvent; class Application extends App { @@ -109,6 +113,9 @@ class Application extends App { } ); + $eventDispatcher->addServiceListener(LoginFailedEvent::class, FailedLoginListener::class); + $eventDispatcher->addServiceListener(UserLoggedInEvent::class, SuccesfullLoginListener::class); + // TODO: remove these two legacy event listeners $symfonyDispatcher = $server->getEventDispatcher(); $symfonyDispatcher->addListener( diff --git a/lib/FailedLoginCompliance.php b/lib/FailedLoginCompliance.php new file mode 100644 index 0000000..618fead --- /dev/null +++ b/lib/FailedLoginCompliance.php @@ -0,0 +1,88 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Password_Policy; + +use OCP\IConfig; +use OCP\IUser; +use OCP\IUserManager; + +class FailedLoginCompliance { + + /** @var IConfig */ + private $config; + + /** @var IUserManager */ + private $userManager; + + /** @var PasswordPolicyConfig */ + private $passwordPolicyConfig; + + public function __construct( + IConfig $config, + IUserManager $userManager, + PasswordPolicyConfig $passwordPolicyConfig) { + $this->config = $config; + $this->userManager = $userManager; + $this->passwordPolicyConfig = $passwordPolicyConfig; + } + + public function onFailedLogin(string $uid) { + $user = $this->userManager->get($uid); + + if (!($user instanceof IUser)) { + return; + } + + if ($user->isEnabled() === false) { + // Just ignore this user then + return; + } + + $allowedAttempts = $this->passwordPolicyConfig->getMaximumLoginAttempts(); + + $attempts = $this->getAttempts($uid); + $attempts++; + + if ($attempts >= $allowedAttempts) { + $this->setAttempts($uid, 0); + $user->setEnabled(false); + return; + } + + $this->setAttempts($uid, $attempts); + } + + public function onSucessfullLogin(IUser $user) { + $this->setAttempts($user->getUID(), 0); + } + + private function getAttempts(string $uid): int { + return (int)$this->config->getUserValue($uid, 'password_policy', 'failedLoginAttempts', 0); + } + + private function setAttempts(string $uid, int $attempts): void { + return $this->config->setUserValue($uid, 'password_policy', 'failedLoginAttempts', $attempts); + } +} diff --git a/lib/Listener/FailedLoginListener.php b/lib/Listener/FailedLoginListener.php new file mode 100644 index 0000000..33a9d4d --- /dev/null +++ b/lib/Listener/FailedLoginListener.php @@ -0,0 +1,49 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Password_Policy\Listener; + +use OCA\Password_Policy\FailedLoginCompliance; +use OCP\Authentication\Events\LoginFailedEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; + +class FailedLoginListener implements IEventListener { + + /** @var FailedLoginCompliance */ + private $compliance; + + public function __construct(FailedLoginCompliance $compliance) { + $this->compliance = $compliance; + } + + public function handle(Event $event): void { + if (!($event instanceof LoginFailedEvent)) { + return; + } + + $this->compliance->onFailedLogin($event->getUid()); + } + +} diff --git a/lib/Listener/SuccesfullLoginListener.php b/lib/Listener/SuccesfullLoginListener.php new file mode 100644 index 0000000..55a93b0 --- /dev/null +++ b/lib/Listener/SuccesfullLoginListener.php @@ -0,0 +1,48 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Password_Policy\Listener; + +use OCA\Password_Policy\FailedLoginCompliance; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\UserLoggedInEvent; + +class SuccesfullLoginListener implements IEventListener { + /** @var FailedLoginCompliance */ + private $compliance; + + public function __construct(FailedLoginCompliance $compliance) { + $this->compliance = $compliance; + } + + public function handle(Event $event): void { + if (!($event instanceof UserLoggedInEvent)) { + return; + } + + $this->compliance->onSucessfullLogin($event->getUser()); + } + +} diff --git a/lib/PasswordPolicyConfig.php b/lib/PasswordPolicyConfig.php index 80188b9..a86ab1a 100644 --- a/lib/PasswordPolicyConfig.php +++ b/lib/PasswordPolicyConfig.php @@ -193,4 +193,14 @@ class PasswordPolicyConfig { ); } + /** + * @return int if 0 then there is no limit + */ + public function getMaximumLoginAttempts(): int { + return (int)$this->config->getAppValue( + 'password_policy', + 'maximumLoginAttempts', + 0 + ); + } } diff --git a/lib/Settings.php b/lib/Settings.php index 60a6a4f..7ae59e3 100644 --- a/lib/Settings.php +++ b/lib/Settings.php @@ -47,6 +47,7 @@ class Settings implements ISettings { 'enforceHaveIBeenPwned' => $this->config->getEnforceHaveIBeenPwned(), 'historySize' => $this->config->getHistorySize(), 'expiration' => $this->config->getExpiryInDays(), + 'maximumLoginAttempts' => $this->config->getMaximumLoginAttempts(), ]); return $response; |