From 63fc0dd4f6a4453eb438c04a4ca6ac4a6fbc2c41 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 1 Aug 2026 09:07:07 +0200 Subject: [PATCH] fix(DirectEditing): cleanup tokens on user deletion ... and for disabled users Signed-off-by: Max --- core/AppInfo/Application.php | 7 +++ lib/composer/composer/autoload_classmap.php | 2 + lib/composer/composer/autoload_static.php | 2 + .../UserDeletedTokenCleanupListener.php | 49 +++++++++++++++++ .../UserDisabledTokenCleanupListener.php | 54 +++++++++++++++++++ lib/private/DirectEditing/Manager.php | 7 +++ 6 files changed, 121 insertions(+) create mode 100644 lib/private/DirectEditing/Listeners/UserDeletedTokenCleanupListener.php create mode 100644 lib/private/DirectEditing/Listeners/UserDisabledTokenCleanupListener.php diff --git a/core/AppInfo/Application.php b/core/AppInfo/Application.php index 6d55bfb7c0299..0a151c70353c3 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\OCM\OCMDiscoveryHandler; use OC\TagManager; use OCP\AppFramework\App; @@ -34,6 +36,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; @@ -88,6 +91,10 @@ public function register(IRegistrationContext $context): void { $context->registerWellKnownHandler(OCMDiscoveryHandler::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 949c43d1005c3..6dd29be07ab78 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1668,6 +1668,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 f1a11b0e24599..4580d20a2607e 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1709,6 +1709,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)