diff options
author | Christoph Wurst <ChristophWurst@users.noreply.github.com> | 2022-10-13 10:52:03 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-13 10:52:03 +0300 |
commit | 37d82398a887fb05b07fe9b60643fd3e21c4abd3 (patch) | |
tree | 98a6d2103f8f5d285d7b2c538d616d012c9a1a08 | |
parent | bb97a0f14c31bd4478229a33c6af6a02652e7768 (diff) | |
parent | e968520008c5a401605733b16ef98493b3f8fe11 (diff) |
[stable6.4] Add user deletion cleanup
-rw-r--r-- | appinfo/info.xml | 5 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 3 | ||||
-rw-r--r-- | lib/Command/CleanUp.php | 106 | ||||
-rw-r--r-- | lib/Db/TotpSecretMapper.php | 13 | ||||
-rw-r--r-- | lib/Listener/UserDeleted.php | 58 | ||||
-rw-r--r-- | package-lock.json | 4 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | tests/Acceptance/TOTPAcceptanceTest.php | 4 |
8 files changed, 189 insertions, 6 deletions
diff --git a/appinfo/info.xml b/appinfo/info.xml index 1c40268..162821f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -5,7 +5,7 @@ <name>Two-Factor TOTP Provider</name> <summary>TOTP two-factor provider</summary> <description>A Two-Factor-Auth Provider for TOTP (RFC 6238)</description> - <version>6.4.0</version> + <version>6.4.1</version> <licence>agpl</licence> <author>Christoph Wurst</author> <namespace>TwoFactorTOTP</namespace> @@ -23,6 +23,9 @@ <two-factor-providers> <provider>OCA\TwoFactorTOTP\Provider\TotpProvider</provider> </two-factor-providers> + <commands> + <command>OCA\TwoFactorTOTP\Command\CleanUp</command> + </commands> <activity> <settings> <setting>OCA\TwoFactorTOTP\Activity\Setting</setting> diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index ff5f7f4..d5aeffc 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -27,12 +27,14 @@ use OCA\TwoFactorTOTP\Event\DisabledByAdmin; use OCA\TwoFactorTOTP\Event\StateChanged; use OCA\TwoFactorTOTP\Listener\StateChangeActivity; use OCA\TwoFactorTOTP\Listener\StateChangeRegistryUpdater; +use OCA\TwoFactorTOTP\Listener\UserDeleted; use OCA\TwoFactorTOTP\Service\ITotp; use OCA\TwoFactorTOTP\Service\Totp; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\User\Events\UserDeletedEvent; class Application extends App implements IBootstrap { public const APP_ID = 'twofactor_totp'; @@ -49,6 +51,7 @@ class Application extends App implements IBootstrap { $context->registerEventListener(StateChanged::class, StateChangeActivity::class); $context->registerEventListener(StateChanged::class, StateChangeRegistryUpdater::class); $context->registerEventListener(DisabledByAdmin::class, StateChangeActivity::class); + $context->registerEventListener(UserDeletedEvent::class, UserDeleted::class); } public function boot(IBootContext $context): void { diff --git a/lib/Command/CleanUp.php b/lib/Command/CleanUp.php new file mode 100644 index 0000000..a68fb1f --- /dev/null +++ b/lib/Command/CleanUp.php @@ -0,0 +1,106 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2022 Daniel Kesselberg <mail@danielkesselberg.de> + * + * @author Daniel Kesselberg <mail@danielkesselberg.de> + * + * @license AGPL-3.0-or-later + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OCA\TwoFactorTOTP\Command; + +use OCA\TwoFactorTOTP\Db\TotpSecretMapper; +use OCP\DB\Exception; +use OCP\IDBConnection; +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class CleanUp extends Command { + /** @var IDBConnection */ + private $db; + + /** @var IUserManager */ + private $userManager; + + /** @var TotpSecretMapper */ + private $totpSecretMapper; + + public function __construct( + IDBConnection $db, + IUserManager $userManager, + TotpSecretMapper $totpSecretMapper + ) { + parent::__construct(); + + $this->db = $db; + $this->userManager = $userManager; + $this->totpSecretMapper = $totpSecretMapper; + } + + protected function configure(): void { + $this + ->setName('twofactor_totp:cleanup') + ->setDescription('Remove orphaned totp secrets'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + $io->title('Remove totp secrets for deleted users'); + + foreach ($this->findUserIds() as $userId) { + if ($this->userManager->userExists($userId) === false) { + try { + $io->text('Delete secret for uid "' . $userId . '"'); + $this->totpSecretMapper->deleteSecretByUserId($userId); + } catch (Exception $e) { + $io->caution('Error deleting secret: ' . $e->getMessage()); + } + } + } + + $io->success('Orphaned totp secrets removed.'); + + $io->text('Thank you for using Two-Factor TOTP!'); + return 0; + } + + /** + * @throws Exception + */ + private function findUserIds(): array { + $userIds = []; + + $qb = $this->db->getQueryBuilder() + ->selectDistinct('user_id') + ->from($this->totpSecretMapper->getTableName()); + + $result = $qb->executeQuery(); + + while ($row = $result->fetch()) { + $userIds[] = $row['user_id']; + } + + $result->closeCursor(); + + return $userIds; + } +} diff --git a/lib/Db/TotpSecretMapper.php b/lib/Db/TotpSecretMapper.php index 50c605d..6428eee 100644 --- a/lib/Db/TotpSecretMapper.php +++ b/lib/Db/TotpSecretMapper.php @@ -26,6 +26,7 @@ namespace OCA\TwoFactorTOTP\Db; use Doctrine\DBAL\Statement; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\QBMapper; +use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; @@ -61,4 +62,16 @@ class TotpSecretMapper extends QBMapper { } return TotpSecret::fromRow($row); } + + /** + * @param string $uid + * @throws Exception + */ + public function deleteSecretByUserId(string $uid): void { + $qb = $this->db->getQueryBuilder(); + + $qb->delete($this->getTableName()) + ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($uid))); + $qb->executeStatement(); + } } diff --git a/lib/Listener/UserDeleted.php b/lib/Listener/UserDeleted.php new file mode 100644 index 0000000..53631db --- /dev/null +++ b/lib/Listener/UserDeleted.php @@ -0,0 +1,58 @@ +<?php + +declare(strict_types=1); + +/** + * @copyright Copyright (c) 2022 Daniel Kesselberg <mail@danielkesselberg.de> + * + * @author Daniel Kesselberg <mail@danielkesselberg.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\TwoFactorTOTP\Listener; + +use OCA\TwoFactorTOTP\Db\TotpSecretMapper; +use OCP\DB\Exception; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\User\Events\UserDeletedEvent; +use Psr\Log\LoggerInterface; + +class UserDeleted implements IEventListener { + + /** @var TotpSecretMapper */ + private $totpSecretMapper; + + /** @var LoggerInterface */ + private $logger; + + public function __construct(TotpSecretMapper $totpSecretMapper, LoggerInterface $logger) { + $this->totpSecretMapper = $totpSecretMapper; + $this->logger = $logger; + } + + public function handle(Event $event): void { + if ($event instanceof UserDeletedEvent) { + try { + $this->totpSecretMapper->deleteSecretByUserId($event->getUser()->getUID()); + } catch (Exception $e) { + $this->logger->warning($e->getMessage(), ['uid' => $event->getUser()->getUID()]); + } + } + } +} diff --git a/package-lock.json b/package-lock.json index 454de2a..5ce471a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "twofactor_totp", - "version": "6.4.0", + "version": "6.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "twofactor_totp", - "version": "6.4.0", + "version": "6.4.1", "license": "agpl", "dependencies": { "@chenfengyuan/vue-qrcode": "^1.0.2", diff --git a/package.json b/package.json index 5c04a48..36bb5eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "twofactor_totp", "description": "Nextcloud TwoFactor TOTP", - "version": "6.4.0", + "version": "6.4.1", "author": "Christoph Wurst <christoph@winzerhof-wurst.at>", "license": "agpl", "private": true, diff --git a/tests/Acceptance/TOTPAcceptanceTest.php b/tests/Acceptance/TOTPAcceptanceTest.php index 4e01765..0029f14 100644 --- a/tests/Acceptance/TOTPAcceptanceTest.php +++ b/tests/Acceptance/TOTPAcceptanceTest.php @@ -70,7 +70,7 @@ class TOTPAcceptanceTest extends TestCase { // Log in $this->webDriver->findElement(WebDriverBy::id('user'))->sendKeys($this->user->getUID()); $this->webDriver->findElement(WebDriverBy::id('password'))->sendKeys('password'); - $this->webDriver->findElement(WebDriverBy::cssSelector('form[name=login] input[type=submit]'))->click(); + $this->webDriver->findElement(WebDriverBy::cssSelector('form[name=login] [type=submit]'))->click(); // Go to personal settings and TOTP settings $this->webDriver->get('http://localhost:8080/index.php/settings/user/security'); @@ -156,7 +156,7 @@ class TOTPAcceptanceTest extends TestCase { // Log in $this->webDriver->findElement(WebDriverBy::id('user'))->sendKeys($this->user->getUID()); $this->webDriver->findElement(WebDriverBy::id('password'))->sendKeys('password'); - $this->webDriver->findElement(WebDriverBy::cssSelector('form[name=login] input[type=submit]'))->click(); + $this->webDriver->findElement(WebDriverBy::cssSelector('form[name=login] [type=submit]'))->click(); $this->webDriver->wait(20, 200)->until(function (WebDriver $driver) { try { |