diff --git a/apps/files_external/lib/Config/ConfigAdapter.php b/apps/files_external/lib/Config/ConfigAdapter.php index 042c364bc6718..a657d244a7ef2 100644 --- a/apps/files_external/lib/Config/ConfigAdapter.php +++ b/apps/files_external/lib/Config/ConfigAdapter.php @@ -16,9 +16,9 @@ use OCA\Files_External\MountConfig; use OCA\Files_External\Service\UserGlobalStoragesService; use OCA\Files_External\Service\UserStoragesService; -use OCP\AppFramework\QueryException; use OCP\Files\Config\IAuthoritativeMountProvider; use OCP\Files\Config\IMountProvider; +use OCP\Files\Config\IPartialMountProvider; use OCP\Files\Mount\IMountPoint; use OCP\Files\ObjectStore\IObjectStore; use OCP\Files\Storage\IConstructableStorage; @@ -27,13 +27,15 @@ use OCP\Files\StorageNotAvailableException; use OCP\IUser; use OCP\Server; +use Override; use Psr\Clock\ClockInterface; +use Psr\Container\ContainerExceptionInterface; use Psr\Log\LoggerInterface; /** * Make the old files_external config work with the new public mount config api */ -class ConfigAdapter implements IMountProvider, IAuthoritativeMountProvider { +class ConfigAdapter implements IMountProvider, IAuthoritativeMountProvider, IPartialMountProvider { public function __construct( private UserStoragesService $userStoragesService, private UserGlobalStoragesService $userGlobalStoragesService, @@ -57,7 +59,7 @@ private function validateObjectStoreClassString(string $class): string { /** * Process storage ready for mounting * - * @throws QueryException + * @throws ContainerExceptionInterface */ private function prepareStorageConfig(StorageConfig &$storage, IUser $user): void { foreach ($storage->getBackendOptions() as $option => $value) { @@ -81,8 +83,6 @@ public function constructStorageForUser(IUser $user, StorageConfig $storage) { /** * Construct the storage implementation - * - * @param StorageConfig $storageConfig */ private function constructStorage(StorageConfig $storageConfig): IStorage { $class = $storageConfig->getBackend()->getStorageClass(); @@ -99,17 +99,12 @@ private function constructStorage(StorageConfig $storageConfig): IStorage { } /** - * Get all mountpoints applicable for the user - * - * @return IMountPoint[] + * @param list $storageConfigs + * @return array + * @throws ContainerExceptionInterface */ - public function getMountsForUser(IUser $user, IStorageFactory $loader) { - $this->userStoragesService->setUser($user); - $this->userGlobalStoragesService->setUser($user); - - $storageConfigs = $this->userGlobalStoragesService->getAllStoragesForUser(); - - $storages = array_map(function (StorageConfig $storageConfig) use ($user) { + private function getAvailableStorages(array $storageConfigs, IUser $user): array { + $storages = array_map(function (StorageConfig $storageConfig) use ($user): IStorage { try { return $this->constructStorageForUser($user, $storageConfig); } catch (\Exception $e) { @@ -123,7 +118,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { return $storage->getId(); }, $storages)); - $availableStorages = array_map(function (IStorage $storage, StorageConfig $storageConfig): IStorage { + return array_map(function (IStorage $storage, StorageConfig $storageConfig): IStorage { try { $availability = $storage->getAvailability(); if (!$availability['available'] && !Availability::shouldRecheck($availability)) { @@ -137,6 +132,19 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { } return $storage; }, $storages, $storageConfigs); + } + + /** + * Get all mountpoints applicable for the user + * + * @return IMountPoint[] + */ + public function getMountsForUser(IUser $user, IStorageFactory $loader): array { + $this->userStoragesService->setUser($user); + $this->userGlobalStoragesService->setUser($user); + + $storageConfigs = $this->userGlobalStoragesService->getAllStoragesForUser(); + $availableStorages = $this->getAvailableStorages($storageConfigs, $user); $mounts = array_map(function (StorageConfig $storageConfig, IStorage $storage) use ($user, $loader) { $storage->setOwner($user->getUID()); @@ -173,4 +181,74 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) { return $mounts; } + + #[Override] + public function getMountsForPath(string $path, bool $forChildren, array $mountProviderArgs, IStorageFactory $loader): array { + if (empty($mountProviderArgs)) { + return []; + } + + $userId = null; + $user = null; + foreach ($mountProviderArgs as $mountProviderArg) { + if ($userId === null) { + $user = $mountProviderArg->mountInfo->getUser(); + $userId = $user->getUID(); + } elseif ($userId !== $mountProviderArg->mountInfo->getUser()->getUID()) { + throw new \LogicException('Mounts must belong to the same user!'); + } + } + + if (!$forChildren) { + // override path with mount point when fetching without children + $path = $mountProviderArgs[0]->mountInfo->getMountPoint(); // TODO: not sure what this is doing, is this getting the parent path? + } + + $this->userStoragesService->setUser($user); + $this->userGlobalStoragesService->setUser($user); + + $storageConfigs = $this->userGlobalStoragesService->getAllStoragesForUserWithPath($path, $forChildren); + $availableStorages = $this->getAvailableStorages($storageConfigs, $user); + + $mounts = []; + + $i = 0; + foreach ($storageConfigs as $storageConfig) { + $storage = $availableStorages[$i]; + $i++; + $storage->setOwner($user->getUID()); + $mountPoint = '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(); + if ($storageConfig->getType() === StorageConfig::MOUNT_TYPE_PERSONAL) { + $mounts[$mountPoint] = new PersonalMount( + $this->userStoragesService, + $storageConfig, + $storageConfig->getId(), + new KnownMtime([ + 'storage' => $storage, + 'clock' => $this->clock, + ]), + $mountPoint, + null, + $loader, + $storageConfig->getMountOptions(), + $storageConfig->getId() + ); + } else { + $mounts[$mountPoint] = new SystemMountPoint( + $storageConfig, + $storage, + $mountPoint, + null, + $loader, + $storageConfig->getMountOptions(), + $storageConfig->getId() + ); + } + } + + $this->userStoragesService->resetUser(); + $this->userGlobalStoragesService->resetUser(); + + return $mounts; + } } diff --git a/apps/files_external/lib/Service/DBConfigService.php b/apps/files_external/lib/Service/DBConfigService.php index e9d4cbc89e763..e17790d201381 100644 --- a/apps/files_external/lib/Service/DBConfigService.php +++ b/apps/files_external/lib/Service/DBConfigService.php @@ -18,6 +18,17 @@ * * @psalm-type ApplicableConfig = array{type: int, value: string} * @psalm-type StorageConfigData = array{type: int, priority: int, applicable: list, config: array, options: array, ...} + * @psalm-type ExternalMountInfo = array{ + * mount_id: int, + * mount_point: string, + * storage_backend: string, + * auth_backend: string, + * priority: int, + * type: self::MOUNT_TYPE_ADMIN|self::MOUNT_TYPE_PERSONAL, + * applicable: list, + * config: array, + * options: array, + * } */ class DBConfigService { public const MOUNT_TYPE_ADMIN = 1; @@ -35,6 +46,9 @@ public function __construct( ) { } + /** + * @return ?ExternalMountInfo + */ public function getMountById(int $mountId): ?array { $builder = $this->connection->getQueryBuilder(); $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type']) @@ -51,16 +65,16 @@ public function getMountById(int $mountId): ?array { /** * Get all configured mounts * - * @return array + * @return list */ - public function getAllMounts() { + public function getAllMounts(): array { $builder = $this->connection->getQueryBuilder(); $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type']) ->from('external_mounts'); return $this->getMountsFromQuery($query); } - public function getMountsForUser($userId, $groupIds) { + public function getMountsForUser(string $userId, 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') @@ -83,18 +97,34 @@ public function getMountsForUser($userId, $groupIds) { return $this->getMountsFromQuery($query); } - /** - * @param list $groupIds - * @return list + /* + * @return list */ - public function getMountsForGroups(array $groupIds): array { + public function getMountsForUserAndPath(string $userId, array $groupIds, string $path, bool $forChildren): array { + $path = str_replace('/' . $userId . '/files', '', $path); $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)), + ->where($builder->expr()->orX( + $builder->expr()->andX( // global mounts + $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_GLOBAL, IQueryBuilder::PARAM_INT)), + $builder->expr()->isNull('a.value'), + $forChildren ? $builder->expr()->like('m.mount_point', $builder->createNamedParameter($this->connection->escapeLikeParameter($path) . '/%', IQueryBuilder::PARAM_STR)) + : $builder->expr()->eq('m.mount_point', $builder->createNamedParameter($path, IQueryBuilder::PARAM_STR)), + ), + $builder->expr()->andX( // mounts for user + $builder->expr()->eq('a.type', $builder->createNamedParameter(self::APPLICABLE_TYPE_USER, IQueryBuilder::PARAM_INT)), + $builder->expr()->eq('a.value', $builder->createNamedParameter($userId)), + $forChildren ? $builder->expr()->like('m.mount_point', $builder->createNamedParameter($this->connection->escapeLikeParameter($path) . '/%', IQueryBuilder::PARAM_STR)) + : $builder->expr()->eq('m.mount_point', $builder->createNamedParameter($path, IQueryBuilder::PARAM_STR)), + ), + $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)), + $forChildren ? $builder->expr()->like('m.mount_point', $builder->createNamedParameter($this->connection->escapeLikeParameter($path) . '/%', IQueryBuilder::PARAM_STR)) + : $builder->expr()->eq('m.mount_point', $builder->createNamedParameter($path, IQueryBuilder::PARAM_STR)), + ), )); return $this->getMountsFromQuery($query); @@ -151,9 +181,9 @@ protected function modifyMountsOnDelete(string $applicableId, int $applicableTyp /** * Get admin defined mounts * - * @return array + * @return list */ - public function getAdminMounts() { + public function getAdminMounts(): array { $builder = $this->connection->getQueryBuilder(); $query = $builder->select(['mount_id', 'mount_point', 'storage_backend', 'auth_backend', 'priority', 'type']) ->from('external_mounts') @@ -161,7 +191,7 @@ public function getAdminMounts() { return $this->getMountsFromQuery($query); } - protected function getForQuery(IQueryBuilder $builder, $type, $value) { + protected function getForQuery(IQueryBuilder $builder, int $type, ?string $value): IQueryBuilder { $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')) @@ -181,9 +211,9 @@ protected function getForQuery(IQueryBuilder $builder, $type, $value) { * * @param int $type any of the self::APPLICABLE_TYPE_ constants * @param string|null $value user_id, group_id or null for global mounts - * @return array + * @return list */ - public function getMountsFor($type, $value) { + public function getMountsFor(int $type, ?string $value): array { $builder = $this->connection->getQueryBuilder(); $query = $this->getForQuery($builder, $type, $value); @@ -195,9 +225,9 @@ public function getMountsFor($type, $value) { * * @param int $type any of the self::APPLICABLE_TYPE_ constants * @param string|null $value user_id, group_id or null for global mounts - * @return array + * @return list */ - public function getAdminMountsFor($type, $value) { + public function getAdminMountsFor(int $type, ?string $value): array { $builder = $this->connection->getQueryBuilder(); $query = $this->getForQuery($builder, $type, $value); $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_ADMIN, IQueryBuilder::PARAM_INT))); @@ -210,9 +240,9 @@ public function getAdminMountsFor($type, $value) { * * @param int $type any of the self::APPLICABLE_TYPE_ constants * @param string[] $values user_ids or group_ids - * @return array + * @return list */ - public function getAdminMountsForMultiple($type, array $values) { + public function getAdminMountsForMultiple(int $type, array $values): array { $builder = $this->connection->getQueryBuilder(); $params = array_map(function ($value) use ($builder) { return $builder->createNamedParameter($value, IQueryBuilder::PARAM_STR); @@ -233,9 +263,9 @@ public function getAdminMountsForMultiple($type, array $values) { * * @param int $type any of the self::APPLICABLE_TYPE_ constants * @param string|null $value user_id, group_id or null for global mounts - * @return array + * @return list */ - public function getUserMountsFor($type, $value) { + public function getUserMountsFor(int $type, ?string $value): array { $builder = $this->connection->getQueryBuilder(); $query = $this->getForQuery($builder, $type, $value); $query->andWhere($builder->expr()->eq('m.type', $builder->expr()->literal(self::MOUNT_TYPE_PERSONAL, IQueryBuilder::PARAM_INT))); @@ -246,14 +276,10 @@ public function getUserMountsFor($type, $value) { /** * Add a mount to the database * - * @param string $mountPoint - * @param string $storageBackend - * @param string $authBackend - * @param int $priority - * @param int $type self::MOUNT_TYPE_ADMIN or self::MOUNT_TYPE_PERSONAL + * @param self::MOUNT_TYPE_ADMIN|self::MOUNT_TYPE_PERSONAL $type * @return int the id of the new mount */ - public function addMount($mountPoint, $storageBackend, $authBackend, $priority, $type) { + public function addMount(string $mountPoint, string $storageBackend, string $authBackend, int $priority, int $type): int { if (!$priority) { $priority = 100; } @@ -272,10 +298,8 @@ public function addMount($mountPoint, $storageBackend, $authBackend, $priority, /** * Remove a mount from the database - * - * @param int $mountId */ - public function removeMount($mountId) { + public function removeMount(int $mountId): void { $builder = $this->connection->getQueryBuilder(); $query = $builder->delete('external_mounts') ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))); @@ -297,11 +321,7 @@ public function removeMount($mountId) { $query->executeStatement(); } - /** - * @param int $mountId - * @param string $newMountPoint - */ - public function setMountPoint($mountId, $newMountPoint) { + public function setMountPoint(int $mountId, string $newMountPoint): void { $builder = $this->connection->getQueryBuilder(); $query = $builder->update('external_mounts') @@ -311,11 +331,7 @@ public function setMountPoint($mountId, $newMountPoint) { $query->executeStatement(); } - /** - * @param int $mountId - * @param string $newAuthBackend - */ - public function setAuthBackend($mountId, $newAuthBackend) { + public function setAuthBackend(int $mountId, string $newAuthBackend): void { $builder = $this->connection->getQueryBuilder(); $query = $builder->update('external_mounts') @@ -325,12 +341,7 @@ public function setAuthBackend($mountId, $newAuthBackend) { $query->executeStatement(); } - /** - * @param int $mountId - * @param string $key - * @param string $value - */ - public function setConfig($mountId, $key, $value) { + public function setConfig(int $mountId, string $key, string $value): void { if ($key === 'password') { $value = $this->encryptValue($value); } @@ -355,12 +366,7 @@ public function setConfig($mountId, $key, $value) { } } - /** - * @param int $mountId - * @param string $key - * @param string $value - */ - public function setOption($mountId, $key, $value) { + public function setOption(int $mountId, string $key, string $value): void { try { $builder = $this->connection->getQueryBuilder(); $builder->insert('external_options') @@ -381,7 +387,7 @@ public function setOption($mountId, $key, $value) { } } - public function addApplicable($mountId, $type, $value) { + public function addApplicable(int $mountId, int $type, ?string $value): void { try { $builder = $this->connection->getQueryBuilder(); $builder->insert('external_applicable') @@ -397,7 +403,7 @@ public function addApplicable($mountId, $type, $value) { } } - public function removeApplicable($mountId, $type, $value) { + public function removeApplicable(int $mountId, int $type, ?string $value): void { $builder = $this->connection->getQueryBuilder(); $query = $builder->delete('external_applicable') ->where($builder->expr()->eq('mount_id', $builder->createNamedParameter($mountId, IQueryBuilder::PARAM_INT))) @@ -414,10 +420,12 @@ public function removeApplicable($mountId, $type, $value) { /** * @return list + * @throws Exception */ private function getMountsFromQuery(IQueryBuilder $query): array { $result = $query->executeQuery(); - $mounts = $result->fetchAllAssociative(); + /** @var \Generator $mounts */ + $mounts = $result->iterateAssociative(); $uniqueMounts = []; foreach ($mounts as $mount) { $id = $mount['mount_id']; @@ -427,7 +435,7 @@ private function getMountsFromQuery(IQueryBuilder $query): array { } $uniqueMounts = array_values($uniqueMounts); - $mountIds = array_map(function ($mount) { + $mountIds = array_map(function (array $mount): int { return $mount['mount_id']; }, $uniqueMounts); $mountIds = array_values(array_unique($mountIds)); @@ -436,8 +444,10 @@ private function getMountsFromQuery(IQueryBuilder $query): array { $config = $this->getConfigForMounts($mountIds); $options = $this->getOptionsForMounts($mountIds); - return array_map(function ($mount, $applicable, $config, $options) { - $mount['type'] = (int)$mount['type']; + return array_map(function (array $mount, array $applicable, array $config, array $options): array { + $mountType = (int)$mount['type']; + assert($mountType === self::MOUNT_TYPE_ADMIN || $mountType === self::MOUNT_TYPE_PERSONAL); + $mount['type'] = $mountType; $mount['priority'] = (int)$mount['priority']; $mount['applicable'] = $applicable; $mount['config'] = $config; @@ -449,7 +459,6 @@ private function getMountsFromQuery(IQueryBuilder $query): array { /** * Get mount options from a table grouped by mount id * - * @param string $table * @param string[] $fields * @param int[] $mountIds * @return array> [$mountId => [['field1' => $value1, ...], ...], ...] @@ -460,9 +469,7 @@ private function selectForMounts(string $table, array $fields, array $mountIds): } $builder = $this->connection->getQueryBuilder(); $fields[] = 'mount_id'; - $placeHolders = array_map(function ($id) use ($builder) { - return $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT); - }, $mountIds); + $placeHolders = array_map(fn ($id) => $builder->createPositionalParameter($id, IQueryBuilder::PARAM_INT), $mountIds); $query = $builder->select($fields) ->from($table) ->where($builder->expr()->in('mount_id', $placeHolders)); @@ -486,26 +493,28 @@ private function selectForMounts(string $table, array $fields, array $mountIds): /** * @param int[] $mountIds - * @return array> [$id => [['type' => $type, 'value' => $value], ...], ...] + * @return array> [$id => [['type' => $type, 'value' => $value], ...], ...] */ public function getApplicableForMounts(array $mountIds): array { - return $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds); + /** @var array> $result */ + $result = $this->selectForMounts('external_applicable', ['type', 'value'], $mountIds); + return $result; } /** * @param int[] $mountIds - * @return array [$id => ['key1' => $value1, ...], ...] + * @return array [$id => ['key1' => $value1, ...], ...] */ - public function getConfigForMounts($mountIds) { + public function getConfigForMounts(array $mountIds): array { $mountConfigs = $this->selectForMounts('external_config', ['key', 'value'], $mountIds); return array_map([$this, 'createKeyValueMap'], $mountConfigs); } /** * @param int[] $mountIds - * @return array [$id => ['key1' => $value1, ...], ...] + * @return array [$id => ['key1' => $value1, ...], ...] */ - public function getOptionsForMounts($mountIds) { + public function getOptionsForMounts(array $mountIds): array { $mountOptions = $this->selectForMounts('external_options', ['key', 'value'], $mountIds); $optionsMap = array_map([$this, 'createKeyValueMap'], $mountOptions); return array_map(function (array $options) { @@ -516,10 +525,10 @@ public function getOptionsForMounts($mountIds) { } /** - * @param array $keyValuePairs [['key'=>$key, 'value=>$value], ...] + * @param list $keyValuePairs [['key'=>$key, 'value=>$value], ...] * @return array ['key1' => $value1, ...] */ - private function createKeyValueMap(array $keyValuePairs) { + private function createKeyValueMap(array $keyValuePairs): array { $decryptedPairts = array_map(function ($pair) { if ($pair['key'] === 'password') { $pair['value'] = $this->decryptValue($pair['value']); @@ -536,14 +545,14 @@ private function createKeyValueMap(array $keyValuePairs) { return array_combine($keys, $values); } - private function encryptValue($value) { + private function encryptValue(string $value): string { return $this->crypto->encrypt($value); } - private function decryptValue($value) { + private function decryptValue(string $value): string { try { return $this->crypto->decrypt($value); - } catch (\Exception $e) { + } catch (\Exception) { return $value; } } diff --git a/apps/files_external/lib/Service/StoragesService.php b/apps/files_external/lib/Service/StoragesService.php index c61c19aa3915b..cfbc091334649 100644 --- a/apps/files_external/lib/Service/StoragesService.php +++ b/apps/files_external/lib/Service/StoragesService.php @@ -120,10 +120,9 @@ protected function readConfig() { * * @param int $id storage id * - * @return StorageConfig * @throws NotFoundException if the storage with the given id was not found */ - public function getStorage(int $id) { + public function getStorage(int $id): StorageConfig { $mount = $this->dbConfig->getMountById($id); if (!is_array($mount)) { diff --git a/apps/files_external/lib/Service/UserGlobalStoragesService.php b/apps/files_external/lib/Service/UserGlobalStoragesService.php index c2b22344b3b0f..65fff40af9048 100644 --- a/apps/files_external/lib/Service/UserGlobalStoragesService.php +++ b/apps/files_external/lib/Service/UserGlobalStoragesService.php @@ -58,18 +58,18 @@ protected function readDBConfig() { return array_merge($userMounts, $groupMounts, $globalMounts); } - public function addStorage(StorageConfig $newStorage) { + public function addStorage(StorageConfig $newStorage): never { throw new \DomainException('UserGlobalStoragesService writing disallowed'); } - public function updateStorage(StorageConfig $updatedStorage) { + public function updateStorage(StorageConfig $updatedStorage): never { throw new \DomainException('UserGlobalStoragesService writing disallowed'); } /** * @param integer $id */ - public function removeStorage($id) { + public function removeStorage($id): never { throw new \DomainException('UserGlobalStoragesService writing disallowed'); } @@ -164,16 +164,33 @@ public function getAllStoragesForUser(?IUser $user = null) { } $groupIds = $this->groupManager->getUserGroupIds($user); $mounts = $this->dbConfig->getMountsForUser($user->getUID(), $groupIds); - $configs = array_map([$this, 'getStorageConfigFromDBMount'], $mounts); - $configs = array_filter($configs, function ($config) { - return $config instanceof StorageConfig; - }); + $configs = array_map($this->getStorageConfigFromDBMount(...), $mounts); + $configs = array_filter($configs, static fn ($config) => $config instanceof StorageConfig); - $keys = array_map(function (StorageConfig $config) { - return $config->getId(); - }, $configs); + $keys = array_map(static fn (StorageConfig $config) => $config->getId(), $configs); $storages = array_combine($keys, $configs); - return array_filter($storages, [$this, 'validateStorage']); + return array_filter($storages, $this->validateStorage(...)); + } + + + /** + * @return StorageConfig[] + */ + public function getAllStoragesForUserWithPath(string $path, bool $forChildren): array { + $user = $this->getUser(); + + if (is_null($user)) { + return []; + } + + $groupIds = $this->groupManager->getUserGroupIds($user); + $mounts = $this->dbConfig->getMountsForUserAndPath($user->getUID(), $groupIds, $path, $forChildren); + $configs = array_map($this->getStorageConfigFromDBMount(...), $mounts); + $configs = array_filter($configs, static fn ($config) => $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/UserStoragesService.php b/apps/files_external/lib/Service/UserStoragesService.php index 21746deee5821..d03ceb52b1959 100644 --- a/apps/files_external/lib/Service/UserStoragesService.php +++ b/apps/files_external/lib/Service/UserStoragesService.php @@ -116,11 +116,11 @@ public function updateStorage(StorageConfig $updatedStorage) { * * @return int BackendService::VISIBILITY_* constants */ - public function getVisibilityType() { + public function getVisibilityType(): int { return BackendService::VISIBILITY_PERSONAL; } - protected function isApplicable(StorageConfig $config) { + protected function isApplicable(StorageConfig $config): bool { return ($config->getApplicableUsers() === [$this->getUser()->getUID()]) && $config->getType() === StorageConfig::MOUNT_TYPE_PERSONAL; } diff --git a/apps/files_external/tests/Config/ConfigAdapterTest.php b/apps/files_external/tests/Config/ConfigAdapterTest.php new file mode 100644 index 0000000000000..421459dae50d7 --- /dev/null +++ b/apps/files_external/tests/Config/ConfigAdapterTest.php @@ -0,0 +1,226 @@ +setId($data['id']); + } + $storage->setMountPoint($data['mountPoint']); + $data['backend'] = $this->backendService->getBackend($data['backendIdentifier']); + if (!isset($data['backend'])) { + throw new \Exception('oops, no backend'); + } + $data['authMechanism'] = $this->backendService->getAuthMechanism($data['authMechanismIdentifier']); + if (!isset($data['authMechanism'])) { + throw new \Exception('oops, no auth mechanism'); + } + $storage->setId(StorageConfig::MOUNT_TYPE_PERSONAL); + $storage->setApplicableUsers([$this->user->getUID()]); + $storage->setBackend($data['backend']); + $storage->setAuthMechanism($data['authMechanism']); + $storage->setBackendOptions($data['backendOptions']); + $storage->setPriority($data['priority']); + if (isset($data['mountOptions'])) { + $storage->setMountOptions($data['mountOptions']); + } + return $storage; + } + + protected function getBackendMock($class = SMB::class, $storageClass = \OCA\Files_External\Lib\Storage\SMB::class) { + $backend = $this->createMock(Backend::class); + $backend->method('getStorageClass') + ->willReturn($storageClass); + $backend->method('getIdentifier') + ->willReturn('identifier:' . $class); + return $backend; + } + + protected function getAuthMechMock($scheme = 'null', $class = NullMechanism::class) { + $authMech = $this->createMock(AuthMechanism::class); + $authMech->method('getScheme') + ->willReturn($scheme); + $authMech->method('getIdentifier') + ->willReturn('identifier:' . $class); + + return $authMech; + } + + public function setUp(): void { + // prepare BackendService mock + $this->backendService = $this->createMock(BackendService::class); + + $authMechanisms = [ + 'identifier:\Auth\Mechanism' => $this->getAuthMechMock('null', '\Auth\Mechanism'), + 'identifier:\Other\Auth\Mechanism' => $this->getAuthMechMock('null', '\Other\Auth\Mechanism'), + 'identifier:\OCA\Files_External\Lib\Auth\NullMechanism' => $this->getAuthMechMock(), + ]; + $this->backendService->method('getAuthMechanism') + ->willReturnCallback(function ($class) use ($authMechanisms) { + if (isset($authMechanisms[$class])) { + return $authMechanisms[$class]; + } + return null; + }); + $this->backendService->method('getAuthMechanismsByScheme') + ->willReturnCallback(function ($schemes) use ($authMechanisms) { + return array_filter($authMechanisms, function ($authMech) use ($schemes) { + return in_array($authMech->getScheme(), $schemes, true); + }); + }); + + $backends = [ + 'identifier:\OCA\Files_External\Lib\Backend\DAV' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\DAV', '\OC\Files\Storage\DAV'), + 'identifier:\OCA\Files_External\Lib\Backend\SMB' => $this->getBackendMock('\OCA\Files_External\Lib\Backend\SMB', '\OCA\Files_External\Lib\Storage\SMB'), + ]; + $this->backendService->method('getBackend') + ->willReturnCallback(function ($backendClass) use ($backends) { + if (isset($backends[$backendClass])) { + return $backends[$backendClass]; + } + return null; + }); + $this->backendService->method('getAuthMechanisms') + ->willReturn($authMechanisms); + $this->backendService->method('getBackends') + ->willReturn($backends); + + $this->userStoragesService = Server::get(UserStoragesService::class); + $this->userGlobalStoragesService = Server::get(UserGlobalStoragesService::class); + $this->adapter = new ConfigAdapter($this->userStoragesService, $this->userGlobalStoragesService, $this->createMock(ClockInterface::class)); + + $this->user = $this->createMock(IUser::class); + $this->user->method('getUID')->willReturn('user1'); + + $this->userStoragesService->setUser($this->user); + + $storageConfig = $this->makeStorageConfig([ + 'mountPoint' => '/mountpoint', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', + 'backendOptions' => [ + 'option1' => 'value1', + 'option2' => 'value2', + 'password' => 'testPassword', + ], + 'priority' => 15, + 'mountOptions' => [ + 'preview' => false, + ] + ]); + $this->storageIds[] = $this->userStoragesService->addStorage($storageConfig)->getId(); + + $storageConfig = $this->makeStorageConfig([ + 'mountPoint' => '/subfolder/mountpoint', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', + 'backendOptions' => [ + 'option1' => 'value1', + 'option2' => 'value2', + 'password' => 'testPassword', + ], + 'priority' => 15, + 'mountOptions' => [ + 'preview' => false, + ] + ]); + $this->storageIds[] = $this->userStoragesService->addStorage($storageConfig)->getId(); + + $storageConfig = $this->makeStorageConfig([ + 'mountPoint' => '/subfolder/subfolder/mountpoint', + 'backendIdentifier' => 'identifier:\OCA\Files_External\Lib\Backend\SMB', + 'authMechanismIdentifier' => 'identifier:\Auth\Mechanism', + 'backendOptions' => [ + 'option1' => 'value1', + 'option2' => 'value2', + 'password' => 'testPassword', + ], + 'priority' => 15, + 'mountOptions' => [ + 'preview' => false, + ] + ]); + $this->storageIds[] = $this->userStoragesService->addStorage($storageConfig)->getId(); + } + + public function tearDown(): void { + $this->user = $this->createMock(IUser::class); + $this->user->method('getUID')->willReturn('user1'); + $this->userStoragesService->setUser($this->user); + foreach ($this->storageIds as $storageId) { + $this->userStoragesService->removeStorage($storageId); + } + } + + public static function pathsProvider(): \Generator { + yield ['/user1/files/subfolder', 2]; + yield ['/user1/files/subfolder/subfolder', 1]; + yield ['/user1/files/nothing', 0]; + yield ['/user1/files/mountpoint', 1]; + } + + #[DataProvider(methodName: 'pathsProvider')] + public function testPartialMountpointWithChildren(string $path, int $count): void { + $mountFileInfo = $this->createMock(ICachedMountFileInfo::class); + $mountFileInfo->method('getUser')->willReturn($this->user); + $cacheEntry = $this->createMock(ICacheEntry::class); + + $result = $this->adapter->getMountsForPath($path, true, [ + new IMountProviderArgs($mountFileInfo, $cacheEntry), + ], $this->createMock(StorageFactory::class)); + + $this->assertCount($count, $result); + } + + public function testPartialMountpointExact(): void { + $mountFileInfo = $this->createMock(ICachedMountFileInfo::class); + $mountFileInfo->method('getUser')->willReturn($this->user); + $mountFileInfo->method('getMountPoint')->willReturn('/user1/files/subfolder/subfolder'); + $cacheEntry = $this->createMock(ICacheEntry::class); + + $result = $this->adapter->getMountsForPath('/user1/files/subfolder/subfolder', true, [ + new IMountProviderArgs($mountFileInfo, $cacheEntry), + ], $this->createMock(StorageFactory::class)); + + $this->assertCount(1, $result); + } +}