diff --git a/core/AppInfo/Application.php b/core/AppInfo/Application.php index 9dd642cb33740..c3e7ee77c941f 100644 --- a/core/AppInfo/Application.php +++ b/core/AppInfo/Application.php @@ -22,6 +22,8 @@ use OC\Core\Listener\BeforeTemplateRenderedListener; use OC\Core\Listener\PasswordUpdatedListener; use OC\Core\Notification\CoreNotifier; +use OC\DirectEditing\Listeners\UserDeletedTokenCleanupListener as UserDeletedDirectEditingTokenCleanupListener; +use OC\DirectEditing\Listeners\UserDisabledTokenCleanupListener as UserDisabledDirectEditingTokenCleanupListener; use OC\TagManager; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -33,6 +35,7 @@ use OCP\DB\Events\AddMissingPrimaryKeyEvent; use OCP\User\Events\BeforeUserDeletedEvent; use OCP\User\Events\PasswordUpdatedEvent; +use OCP\User\Events\UserChangedEvent; use OCP\User\Events\UserDeletedEvent; use OCP\Util; @@ -86,6 +89,10 @@ public function register(IRegistrationContext $context): void { $context->registerConfigLexicon(ConfigLexicon::class); $context->registerCapability(Capabilities::class); + + // Direct Editing + $context->registerEventListener(UserDeletedEvent::class, UserDeletedDirectEditingTokenCleanupListener::class); + $context->registerEventListener(UserChangedEvent::class, UserDisabledDirectEditingTokenCleanupListener::class); } public function boot(IBootContext $context): void { diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index c8bab9ba0da0a..d37a9eb7434ca 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1611,6 +1611,8 @@ 'OC\\Diagnostics\\Query' => $baseDir . '/lib/private/Diagnostics/Query.php', 'OC\\Diagnostics\\QueryLogger' => $baseDir . '/lib/private/Diagnostics/QueryLogger.php', 'OC\\Diagnostics\\TLogSlowOperation' => $baseDir . '/lib/private/Diagnostics/TLogSlowOperation.php', + 'OC\\DirectEditing\\Listeners\\UserDeletedTokenCleanupListener' => $baseDir . '/lib/private/DirectEditing/Listeners/UserDeletedTokenCleanupListener.php', + 'OC\\DirectEditing\\Listeners\\UserDisabledTokenCleanupListener' => $baseDir . '/lib/private/DirectEditing/Listeners/UserDisabledTokenCleanupListener.php', 'OC\\DirectEditing\\Manager' => $baseDir . '/lib/private/DirectEditing/Manager.php', 'OC\\DirectEditing\\Token' => $baseDir . '/lib/private/DirectEditing/Token.php', 'OC\\EmojiHelper' => $baseDir . '/lib/private/EmojiHelper.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 61769759115c4..6a78dde5ac02a 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1652,6 +1652,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Diagnostics\\Query' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/Query.php', 'OC\\Diagnostics\\QueryLogger' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/QueryLogger.php', 'OC\\Diagnostics\\TLogSlowOperation' => __DIR__ . '/../../..' . '/lib/private/Diagnostics/TLogSlowOperation.php', + 'OC\\DirectEditing\\Listeners\\UserDeletedTokenCleanupListener' => __DIR__ . '/../../..' . '/lib/private/DirectEditing/Listeners/UserDeletedTokenCleanupListener.php', + 'OC\\DirectEditing\\Listeners\\UserDisabledTokenCleanupListener' => __DIR__ . '/../../..' . '/lib/private/DirectEditing/Listeners/UserDisabledTokenCleanupListener.php', 'OC\\DirectEditing\\Manager' => __DIR__ . '/../../..' . '/lib/private/DirectEditing/Manager.php', 'OC\\DirectEditing\\Token' => __DIR__ . '/../../..' . '/lib/private/DirectEditing/Token.php', 'OC\\EmojiHelper' => __DIR__ . '/../../..' . '/lib/private/EmojiHelper.php', diff --git a/lib/private/DirectEditing/Listeners/UserDeletedTokenCleanupListener.php b/lib/private/DirectEditing/Listeners/UserDeletedTokenCleanupListener.php new file mode 100644 index 0000000000000..1fc7860a90687 --- /dev/null +++ b/lib/private/DirectEditing/Listeners/UserDeletedTokenCleanupListener.php @@ -0,0 +1,49 @@ + + */ +class UserDeletedTokenCleanupListener implements IEventListener { + public function __construct( + private Manager $manager, + private LoggerInterface $logger, + ) { + } + + #[\Override] + public function handle(Event $event): void { + if (!($event instanceof UserDeletedEvent)) { + // Unrelated + return; + } + + /** + * Catch any exception during this process as any failure here shouldn't block the + * user deletion. + */ + try { + $uid = $event->getUser()->getUID(); + $this->manager->invalidateTokensForUser($uid); + } catch (Throwable $e) { + $this->logger->error('Could not clean up direct editing tokens after user deletion: ' . $e->getMessage(), [ + 'exception' => $e, + ]); + } + } +} diff --git a/lib/private/DirectEditing/Listeners/UserDisabledTokenCleanupListener.php b/lib/private/DirectEditing/Listeners/UserDisabledTokenCleanupListener.php new file mode 100644 index 0000000000000..9284f1d437d67 --- /dev/null +++ b/lib/private/DirectEditing/Listeners/UserDisabledTokenCleanupListener.php @@ -0,0 +1,54 @@ + + */ +class UserDisabledTokenCleanupListener implements IEventListener { + public function __construct( + private Manager $manager, + private LoggerInterface $logger, + ) { + } + + #[\Override] + public function handle(Event $event): void { + if (!($event instanceof UserChangedEvent)) { + // Unrelated + return; + } + + if ($event->getFeature() !== 'enabled' || $event->getValue()) { + // not disabled + return; + } + + /** + * Catch any exception during this process + * as any failure here shouldn't block the disabling the user. + */ + try { + $uid = $event->getUser()->getUID(); + $this->manager->invalidateTokensForUser($uid); + } catch (Throwable $e) { + $this->logger->error('Could not clean up direct editing tokens when disabling user: ' . $e->getMessage(), [ + 'exception' => $e, + ]); + } + } +} diff --git a/lib/private/DirectEditing/Manager.php b/lib/private/DirectEditing/Manager.php index 154002ef340cb..c849e4a5cf95a 100644 --- a/lib/private/DirectEditing/Manager.php +++ b/lib/private/DirectEditing/Manager.php @@ -209,6 +209,13 @@ public function cleanup(): int { return $query->executeStatement(); } + public function invalidateTokensForUser(string $uid): int { + $query = $this->connection->getQueryBuilder(); + $query->delete(self::TABLE_TOKENS) + ->where($query->expr()->eq('user_id', $query->createNamedParameter($uid))); + return $query->executeStatement(); + } + public function refreshToken(string $token): bool { $query = $this->connection->getQueryBuilder(); $query->update(self::TABLE_TOKENS)