diff options
author | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2020-04-04 00:08:31 +0300 |
---|---|---|
committer | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2020-04-07 23:49:52 +0300 |
commit | 3832ad7e40a6814c11be0776898fcf133be0482f (patch) | |
tree | bc59edb7330e563faa49c275e0c2c628478c4b2f /lib | |
parent | ac332c3c491e813199fb0a3a58a1ceb446cedb04 (diff) |
password expiration support
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/AppInfo/Application.php | 26 | ||||
-rw-r--r-- | lib/Compliance/Expiration.php | 113 | ||||
-rw-r--r-- | lib/Compliance/HistoryCompliance.php (renamed from lib/HistoryCompliance.php) | 5 | ||||
-rw-r--r-- | lib/Compliance/IAuditor.php | 31 | ||||
-rw-r--r-- | lib/Compliance/IEntryControl.php | 36 | ||||
-rw-r--r-- | lib/Compliance/IUpdatable.php | 31 | ||||
-rw-r--r-- | lib/ComplianceService.php | 120 | ||||
-rw-r--r-- | lib/PasswordPolicyConfig.php | 8 | ||||
-rw-r--r-- | lib/Settings.php | 1 |
9 files changed, 362 insertions, 9 deletions
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 8606177..b3e6557 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -25,8 +25,8 @@ declare(strict_types=1); namespace OCA\Password_Policy\AppInfo; use OCA\Password_Policy\Capabilities; +use OCA\Password_Policy\ComplianceService; use OCA\Password_Policy\Generator; -use OCA\Password_Policy\HistoryCompliance; use OCA\Password_Policy\PasswordValidator; use OCP\AppFramework\App; use OCP\EventDispatcher\Event; @@ -36,6 +36,7 @@ use OCP\Security\Events\GenerateSecurePasswordEvent; use OCP\Security\Events\ValidatePasswordPolicyEvent; use OCP\User\Events\BeforePasswordUpdatedEvent; use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\PostLoginEvent; use Symfony\Component\EventDispatcher\GenericEvent; class Application extends App { @@ -80,9 +81,9 @@ class Application extends App { if(!($event instanceof BeforePasswordUpdatedEvent)) { return; } - /** @var HistoryCompliance $historyCompliance */ - $historyCompliance = $container->query(HistoryCompliance::class); - $historyCompliance->audit($event->getUser(), $event->getPassword()); + /** @var ComplianceService $complianceUpdater */ + $complianceUpdater = $container->query(ComplianceService::class); + $complianceUpdater->audit($event->getUser(), $event->getPassword()); } ); $eventDispatcher->addListener( @@ -91,9 +92,20 @@ class Application extends App { if(!($event instanceof PasswordUpdatedEvent)) { return; } - /** @var HistoryCompliance $historyCompliance */ - $historyCompliance = $container->query(HistoryCompliance::class); - $historyCompliance->update($event->getUser(), $event->getPassword()); + /** @var ComplianceService $complianceUpdater */ + $complianceUpdater = $container->query(ComplianceService::class); + $complianceUpdater->update($event->getUser(), $event->getPassword()); + } + ); + $eventDispatcher->addListener( + PostLoginEvent::class, + function (Event $event) use ($container) { + if(!$event instanceof PostLoginEvent) { + return; + } + /** @var ComplianceService $complianceUpdater */ + $complianceUpdater = $container->query(ComplianceService::class); + $complianceUpdater->entryControl($event->getUser(), $event->getPassword(), $event->isTokenLogin()); } ); diff --git a/lib/Compliance/Expiration.php b/lib/Compliance/Expiration.php new file mode 100644 index 0000000..ff50edb --- /dev/null +++ b/lib/Compliance/Expiration.php @@ -0,0 +1,113 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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\Compliance; + +use OC\HintException; +use OCA\Password_Policy\PasswordPolicyConfig; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; +use OCP\IL10N; +use OCP\IUser; +use OCP\IUserManager; +use OCP\PreConditionNotMetException; + +class Expiration implements IUpdatable, IEntryControl { + + /** @var IConfig */ + private $config; + /** @var PasswordPolicyConfig */ + private $policyConfig; + /** @var IUserManager */ + private $userManager; + /** @var IEventDispatcher */ + private $eventDispatcher; + /** @var IL10N */ + private $l; + + public function __construct( + IConfig $config, + PasswordPolicyConfig $policyConfig, + IUserManager $userManager, + IEventDispatcher $eventDispatcher, + IL10N $l + ) { + $this->config = $config; + $this->policyConfig = $policyConfig; + $this->userManager = $userManager; + $this->eventDispatcher = $eventDispatcher; + $this->l = $l; + } + + /** + * @throws PreConditionNotMetException + */ + public function update(IUser $user, string $password): void { + if($this->policyConfig->getExpiryInDays() === 0) { + $this->config->deleteUserValue( + $user->getUID(), + 'password_policy', + 'pwd_last_updated' + ); + return; + } + $this->config->setUserValue( + $user->getUID(), + 'password_policy', + 'pwd_last_updated', + time() + ); + } + + public function entryControl(IUser $user, string $password, bool $isTokenLogin): void { + if($this->policyConfig->getExpiryInDays() !== 0 + && !$isTokenLogin + && $this->isPasswordExpired($user) + ) { + $message = 'Password is expired, please use forgot password method to reset'; + $message_t = $this->l->t('Password is expired, please use forgot password method to reset'); + throw new HintException($message, $message_t); + } + } + + protected function isPasswordExpired(IUser $user) { + $updatedAt = (int)$this->config->getUserValue( + $user->getUID(), + 'password_policy', + 'pwd_last_updated', + 0 + ); + + if($updatedAt === 0) { + $this->update($user, ''); + return false; + } + + $expiryInDays = $this->policyConfig->getExpiryInDays(); + $expiresIn = $updatedAt + $expiryInDays * 24 * 60 * 60; + + return $expiresIn <= time(); + } + +} diff --git a/lib/HistoryCompliance.php b/lib/Compliance/HistoryCompliance.php index 413f0b1..cea7b99 100644 --- a/lib/HistoryCompliance.php +++ b/lib/Compliance/HistoryCompliance.php @@ -22,9 +22,10 @@ declare(strict_types=1); * */ -namespace OCA\Password_Policy; +namespace OCA\Password_Policy\Compliance; use OC\HintException; +use OCA\Password_Policy\PasswordPolicyConfig; use OCP\IConfig; use OCP\IL10N; use OCP\ILogger; @@ -33,7 +34,7 @@ use OCP\IUserSession; use OCP\PreConditionNotMetException; use OCP\Security\IHasher; -class HistoryCompliance { +class HistoryCompliance implements IAuditor, IUpdatable { /** @var string */ protected $uid; diff --git a/lib/Compliance/IAuditor.php b/lib/Compliance/IAuditor.php new file mode 100644 index 0000000..c11eb15 --- /dev/null +++ b/lib/Compliance/IAuditor.php @@ -0,0 +1,31 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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\Compliance; + +use OCP\IUser; + +interface IAuditor { + public function audit(IUser $user, string $password): void; +} diff --git a/lib/Compliance/IEntryControl.php b/lib/Compliance/IEntryControl.php new file mode 100644 index 0000000..febe775 --- /dev/null +++ b/lib/Compliance/IEntryControl.php @@ -0,0 +1,36 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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\Compliance; + + +use OC\HintException; +use OCP\IUser; + +interface IEntryControl { + /** + * @throws HintException + */ + public function entryControl(IUser $user, string $password, bool $isTokenLogin): void; +} diff --git a/lib/Compliance/IUpdatable.php b/lib/Compliance/IUpdatable.php new file mode 100644 index 0000000..9c64ce5 --- /dev/null +++ b/lib/Compliance/IUpdatable.php @@ -0,0 +1,31 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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\Compliance; + +use OCP\IUser; + +interface IUpdatable { + public function update(IUser $user, string $password): void; +} diff --git a/lib/ComplianceService.php b/lib/ComplianceService.php new file mode 100644 index 0000000..bd96f71 --- /dev/null +++ b/lib/ComplianceService.php @@ -0,0 +1,120 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @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 OC\HintException; +use OC\User\LoginException; +use OCA\Password_Policy\Compliance\Expiration; +use OCA\Password_Policy\Compliance\HistoryCompliance; +use OCA\Password_Policy\Compliance\IAuditor; +use OCA\Password_Policy\Compliance\IEntryControl; +use OCA\Password_Policy\Compliance\IUpdatable; +use OCP\AppFramework\IAppContainer; +use OCP\AppFramework\QueryException; +use OCP\IConfig; +use OCP\ILogger; +use OCP\ISession; +use OCP\IUser; +use OCP\IUserSession; + +class ComplianceService { + /** @var IAppContainer */ + protected $container; + /** @var ILogger */ + protected $logger; + + protected const COMPLIANCERS = [ + HistoryCompliance::class, + Expiration::class, + ]; + /** @var IUserSession */ + private $userSession; + /** @var IConfig */ + private $config; + /** @var ISession */ + private $session; + + public function __construct( + IAppContainer $container, + ILogger $logger, + IUserSession $userSession, + IConfig $config, + ISession $session + ) { + $this->container = $container; + $this->logger = $logger; + $this->userSession = $userSession; + $this->config = $config; + $this->session = $session; + } + + public function update(IUser $user, string $password) { + foreach ($this->getInstance(IUpdatable::class) as $instance) { + $instance->update($user, $password); + } + } + + public function audit(IUser $user, string $password) { + foreach ($this->getInstance(IAuditor::class) as $instance) { + $instance->audit($user, $password); + } + } + + /** + * @throws LoginException + */ + public function entryControl(IUser $user, string $password, bool $isTokenLogin) { + foreach ($this->getInstance(IEntryControl::class) as $instance) { + try { + $instance->entryControl($user, $password, $isTokenLogin); + } catch (HintException $e) { + if($this->userSession->isLoggedIn()) { + $this->userSession->logout(); + } + throw new LoginException($e->getHint()); + } + } + } + + /** + * @returns Iterable + */ + protected function getInstance($interface) { + foreach (self::COMPLIANCERS as $compliance) { + try { + $instance = $this->container->query($compliance); + if(!$instance instanceof $interface) { + continue; + } + } catch (QueryException $e) { + //ignore and continue + $this->logger->logException($e, ['level' => ILogger::INFO]); + continue; + } + + yield $instance; + } + } +} diff --git a/lib/PasswordPolicyConfig.php b/lib/PasswordPolicyConfig.php index ffd4f75..80188b9 100644 --- a/lib/PasswordPolicyConfig.php +++ b/lib/PasswordPolicyConfig.php @@ -185,4 +185,12 @@ class PasswordPolicyConfig { ); } + public function getExpiryInDays(): int { + return (int)$this->config->getAppValue( + 'password_policy', + 'expiration', + 0 + ); + } + } diff --git a/lib/Settings.php b/lib/Settings.php index b87dded..60a6a4f 100644 --- a/lib/Settings.php +++ b/lib/Settings.php @@ -46,6 +46,7 @@ class Settings implements ISettings { 'enforceSpecialCharacters' => $this->config->getEnforceSpecialCharacters(), 'enforceHaveIBeenPwned' => $this->config->getEnforceHaveIBeenPwned(), 'historySize' => $this->config->getHistorySize(), + 'expiration' => $this->config->getExpiryInDays(), ]); return $response; |