From d64f7eb93950513e9d9992cf55b2871cd051ccc1 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 21 Nov 2025 18:42:13 +0100 Subject: [PATCH] feat: listen to user/group events and update external storage mounts Signed-off-by: Robin Appelman --- .../lib/AppInfo/Application.php | 14 +++-- .../lib/Service/DBConfigService.php | 40 +++++++++++++- .../lib/Service/GlobalStoragesService.php | 28 ++++++++++ .../lib/Service/MountCacheService.php | 54 ++++++++++++++++++- .../lib/Service/UserGlobalStoragesService.php | 1 - 5 files changed, 128 insertions(+), 9 deletions(-) diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index 08a96e6265c..c54c2eea389 100644 --- a/apps/files_external/lib/AppInfo/Application.php +++ b/apps/files_external/lib/AppInfo/Application.php @@ -45,7 +45,6 @@ use OCA\Files_External\Lib\Config\IAuthMechanismProvider; use OCA\Files_External\Lib\Config\IBackendProvider; use OCA\Files_External\Listener\GroupDeletedListener; use OCA\Files_External\Listener\LoadAdditionalListener; -use OCA\Files_External\Listener\StorePasswordListener; use OCA\Files_External\Listener\UserDeletedListener; use OCA\Files_External\Service\BackendService; use OCA\Files_External\Service\MountCacheService; @@ -55,10 +54,12 @@ use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; use OCP\AppFramework\QueryException; use OCP\Files\Config\IMountProviderCollection; +use OCP\Group\Events\BeforeGroupDeletedEvent; use OCP\Group\Events\GroupDeletedEvent; -use OCP\User\Events\PasswordUpdatedEvent; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; +use OCP\User\Events\UserCreatedEvent; use OCP\User\Events\UserDeletedEvent; -use OCP\User\Events\UserLoggedInEvent; /** * @package OCA\Files_External\AppInfo @@ -79,11 +80,14 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); $context->registerEventListener(GroupDeletedEvent::class, GroupDeletedListener::class); $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class); - $context->registerEventListener(UserLoggedInEvent::class, StorePasswordListener::class); - $context->registerEventListener(PasswordUpdatedEvent::class, StorePasswordListener::class); $context->registerEventListener(StorageCreatedEvent::class, MountCacheService::class); $context->registerEventListener(StorageDeletedEvent::class, MountCacheService::class); $context->registerEventListener(StorageUpdatedEvent::class, MountCacheService::class); + $context->registerEventListener(BeforeGroupDeletedEvent::class, MountCacheService::class); + $context->registerEventListener(UserCreatedEvent::class, MountCacheService::class); + $context->registerEventListener(UserAddedEvent::class, MountCacheService::class); + $context->registerEventListener(UserRemovedEvent::class, MountCacheService::class); + $context->registerConfigLexicon(ConfigLexicon::class); } diff --git a/apps/files_external/lib/Service/DBConfigService.php b/apps/files_external/lib/Service/DBConfigService.php index f08f3442a47..207bb1b79b2 100644 --- a/apps/files_external/lib/Service/DBConfigService.php +++ b/apps/files_external/lib/Service/DBConfigService.php @@ -15,6 +15,8 @@ use OCP\Security\ICrypto; /** * Stores the mount config in the database + * + * @psalm-type StorageConfigData = array{type: int, priority: int, applicable: list, config: array, options: array} */ class DBConfigService { public const MOUNT_TYPE_ADMIN = 1; @@ -80,6 +82,39 @@ class DBConfigService { return $this->getMountsFromQuery($query); } + /** + * @param list $groupIds + * @return list + */ + public function getMountsForGroups(array $groupIds): array { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type']) + ->from('external_mounts', 'm') + ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id')) + ->where($builder->expr()->andX( // mounts for group + $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GROUP, IQueryBuilder::PARAM_INT)), + $builder->expr()->in('a.value', $builder->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY)), + )); + + return $this->getMountsFromQuery($query); + } + + /** + * @return list + */ + public function getGlobalMounts(): array { + $builder = $this->connection->getQueryBuilder(); + $query = $builder->select(['m.mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'm.type']) + ->from('external_mounts', 'm') + ->innerJoin('m', 'external_applicable', 'a', $builder->expr()->eq('m.mount_id', 'a.mount_id')) + ->where($builder->expr()->andX( // global mounts + $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)), + $builder->expr()->isNull('a.value'), + ), ); + + return $this->getMountsFromQuery($query); + } + public function modifyMountsOnUserDelete(string $uid): void { $this->modifyMountsOnDelete($uid, self::APPLICABLE_TYPE_USER); } @@ -376,7 +411,10 @@ class DBConfigService { $query->executeStatement(); } - private function getMountsFromQuery(IQueryBuilder $query) { + /** + * @return list + */ + private function getMountsFromQuery(IQueryBuilder $query): array { $result = $query->executeQuery(); $mounts = $result->fetchAllAssociative(); $uniqueMounts = []; diff --git a/apps/files_external/lib/Service/GlobalStoragesService.php b/apps/files_external/lib/Service/GlobalStoragesService.php index 0358a597ff8..2694058c968 100644 --- a/apps/files_external/lib/Service/GlobalStoragesService.php +++ b/apps/files_external/lib/Service/GlobalStoragesService.php @@ -13,6 +13,7 @@ use OCA\Files_External\Event\StorageDeletedEvent; use OCA\Files_External\Event\StorageUpdatedEvent; use OCA\Files_External\Lib\StorageConfig; use OCA\Files_External\MountConfig; +use OCP\IGroup; /** * Service class to manage global external storage @@ -169,4 +170,31 @@ class GlobalStoragesService extends StoragesService { return array_combine($keys, $configs); } + + /** + * Gets all storages for the group, not including any global storages + * @return StorageConfig[] + */ + public function getAllStoragesForGroup(IGroup $group): array { + $mounts = $this->dbConfig->getMountsForGroups([$group->getGID()]); + $configs = array_map($this->getStorageConfigFromDBMount(...), $mounts); + $configs = array_filter($configs, static fn (?StorageConfig $config): bool => $config instanceof StorageConfig); + $keys = array_map(static fn (StorageConfig $config) => $config->getId(), $configs); + + $storages = array_combine($keys, $configs); + return array_filter($storages, $this->validateStorage(...)); + } + + /** + * @return StorageConfig[] + */ + public function getAllGlobalStorages(): array { + $mounts = $this->dbConfig->getGlobalMounts(); + + $configs = array_map($this->getStorageConfigFromDBMount(...), $mounts); + $configs = array_filter($configs, static fn (?StorageConfig $config): bool => $config instanceof StorageConfig); + $keys = array_map(static fn (StorageConfig $config) => $config->getId(), $configs); + $storages = array_combine($keys, $configs); + return array_filter($storages, $this->validateStorage(...)); + } } diff --git a/apps/files_external/lib/Service/MountCacheService.php b/apps/files_external/lib/Service/MountCacheService.php index 3e536ce9993..924691ba5fa 100644 --- a/apps/files_external/lib/Service/MountCacheService.php +++ b/apps/files_external/lib/Service/MountCacheService.php @@ -20,16 +20,19 @@ use OCP\EventDispatcher\Event as T; use OCP\EventDispatcher\IEventListener; use OCP\Files\Cache\ICacheEntry; use OCP\Files\Config\IUserMountCache; -use OCP\Files\IMimeTypeLoader; +use OCP\Group\Events\BeforeGroupDeletedEvent; +use OCP\Group\Events\UserAddedEvent; +use OCP\Group\Events\UserRemovedEvent; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserManager; +use OCP\User\Events\UserCreatedEvent; /** * Listens to config events and update the mounts for the applicable users * - * @template-implements IEventListener + * @template-implements IEventListener */ class MountCacheService implements IEventListener { public function __construct( @@ -37,6 +40,7 @@ class MountCacheService implements IEventListener { private readonly ConfigAdapter $configAdapter, private readonly IUserManager $userManager, private readonly IGroupManager $groupManager, + private readonly GlobalStoragesService $storagesService, ) { } @@ -50,6 +54,18 @@ class MountCacheService implements IEventListener { if ($event instanceof StorageUpdatedEvent) { $this->handleUpdatedStorage($event->getOldConfig(), $event->getNewConfig()); } + if ($event instanceof UserAddedEvent) { + $this->handleUserAdded($event->getGroup(), $event->getUser()); + } + if ($event instanceof UserRemovedEvent) { + $this->handleUserRemoved($event->getGroup(), $event->getUser()); + } + if ($event instanceof BeforeGroupDeletedEvent) { + $this->handleGroupDeleted($event->getGroup()); + } + if ($event instanceof UserCreatedEvent) { + $this->handleUserCreated($event->getUser()); + } } @@ -153,4 +169,38 @@ class MountCacheService implements IEventListener { $storage->getId(), ); } + + private function handleUserRemoved(IGroup $group, IUser $user): void { + $storages = $this->storagesService->getAllStoragesForGroup($group); + foreach ($storages as $storage) { + if (!in_array($user->getUID(), $storage->getApplicableUsers())) { + $this->userMountCache->removeMount($storage->getMountPointForUser($user)); + } + } + } + + private function handleUserAdded(IGroup $group, IUser $user): void { + $storages = $this->storagesService->getAllStoragesForGroup($group); + foreach ($storages as $storage) { + $this->registerForUser($user, $storage); + } + } + + private function handleGroupDeleted(IGroup $group): void { + $storages = $this->storagesService->getAllStoragesForGroup($group); + foreach ($storages as $storage) { + foreach ($group->searchUsers('') as $user) { + if (!in_array($user->getUID(), $storage->getApplicableUsers())) { + $this->userMountCache->removeMount($storage->getMountPointForUser($user)); + } + } + } + } + + private function handleUserCreated(IUser $user): void { + $storages = $this->storagesService->getAllGlobalStorages(); + foreach ($storages as $storage) { + $this->registerForUser($user, $storage); + } + } } diff --git a/apps/files_external/lib/Service/UserGlobalStoragesService.php b/apps/files_external/lib/Service/UserGlobalStoragesService.php index 2607472e969..c2b22344b3b 100644 --- a/apps/files_external/lib/Service/UserGlobalStoragesService.php +++ b/apps/files_external/lib/Service/UserGlobalStoragesService.php @@ -9,7 +9,6 @@ namespace OCA\Files_External\Service; use OCA\Files_External\Lib\StorageConfig; use OCP\EventDispatcher\IEventDispatcher; -use OCP\Files\Config\IUserMountCache; use OCP\IAppConfig; use OCP\IGroupManager; use OCP\IUser;