diff options
Diffstat (limited to 'apps/files_external/lib/Config')
-rw-r--r-- | apps/files_external/lib/Config/ConfigAdapter.php | 171 | ||||
-rw-r--r-- | apps/files_external/lib/Config/ExternalMountPoint.php | 34 | ||||
-rw-r--r-- | apps/files_external/lib/Config/IConfigHandler.php | 22 | ||||
-rw-r--r-- | apps/files_external/lib/Config/SimpleSubstitutionTrait.php | 69 | ||||
-rw-r--r-- | apps/files_external/lib/Config/SystemMountPoint.php | 15 | ||||
-rw-r--r-- | apps/files_external/lib/Config/UserContext.php | 61 | ||||
-rw-r--r-- | apps/files_external/lib/Config/UserPlaceholderHandler.php | 25 |
7 files changed, 397 insertions, 0 deletions
diff --git a/apps/files_external/lib/Config/ConfigAdapter.php b/apps/files_external/lib/Config/ConfigAdapter.php new file mode 100644 index 00000000000..a46c0fd5c66 --- /dev/null +++ b/apps/files_external/lib/Config/ConfigAdapter.php @@ -0,0 +1,171 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Config; + +use OC\Files\Cache\Storage; +use OC\Files\Storage\FailedStorage; +use OC\Files\Storage\Wrapper\Availability; +use OC\Files\Storage\Wrapper\KnownMtime; +use OCA\Files_External\Lib\PersonalMount; +use OCA\Files_External\Lib\StorageConfig; +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\IMountProvider; +use OCP\Files\Mount\IMountPoint; +use OCP\Files\ObjectStore\IObjectStore; +use OCP\Files\Storage\IConstructableStorage; +use OCP\Files\Storage\IStorage; +use OCP\Files\Storage\IStorageFactory; +use OCP\Files\StorageNotAvailableException; +use OCP\IUser; +use OCP\Server; +use Psr\Clock\ClockInterface; +use Psr\Log\LoggerInterface; + +/** + * Make the old files_external config work with the new public mount config api + */ +class ConfigAdapter implements IMountProvider { + public function __construct( + private UserStoragesService $userStoragesService, + private UserGlobalStoragesService $userGlobalStoragesService, + private ClockInterface $clock, + ) { + } + + /** + * @param class-string $class + * @return class-string<IObjectStore> + * @throws \InvalidArgumentException + * @psalm-taint-escape callable + */ + private function validateObjectStoreClassString(string $class): string { + if (!\is_subclass_of($class, IObjectStore::class)) { + throw new \InvalidArgumentException('Invalid object store'); + } + return $class; + } + + /** + * Process storage ready for mounting + * + * @throws QueryException + */ + private function prepareStorageConfig(StorageConfig &$storage, IUser $user): void { + foreach ($storage->getBackendOptions() as $option => $value) { + $storage->setBackendOption($option, MountConfig::substitutePlaceholdersInConfig($value, $user->getUID())); + } + + $objectStore = $storage->getBackendOption('objectstore'); + if ($objectStore) { + $objectClass = $this->validateObjectStoreClassString($objectStore['class']); + $storage->setBackendOption('objectstore', new $objectClass($objectStore)); + } + + $storage->getAuthMechanism()->manipulateStorageConfig($storage, $user); + $storage->getBackend()->manipulateStorageConfig($storage, $user); + } + + /** + * Construct the storage implementation + * + * @param StorageConfig $storageConfig + */ + private function constructStorage(StorageConfig $storageConfig): IStorage { + $class = $storageConfig->getBackend()->getStorageClass(); + if (!is_a($class, IConstructableStorage::class, true)) { + Server::get(LoggerInterface::class)->warning('Building a storage not implementing IConstructableStorage is deprecated since 31.0.0', ['class' => $class]); + } + $storage = new $class($storageConfig->getBackendOptions()); + + // auth mechanism should fire first + $storage = $storageConfig->getBackend()->wrapStorage($storage); + $storage = $storageConfig->getAuthMechanism()->wrapStorage($storage); + + return $storage; + } + + /** + * Get all mountpoints applicable for the user + * + * @return IMountPoint[] + */ + 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) { + try { + $this->prepareStorageConfig($storageConfig, $user); + return $this->constructStorage($storageConfig); + } catch (\Exception $e) { + // propagate exception into filesystem + return new FailedStorage(['exception' => $e]); + } + }, $storageConfigs); + + + Storage::getGlobalCache()->loadForStorageIds(array_map(function (IStorage $storage) { + return $storage->getId(); + }, $storages)); + + $availableStorages = array_map(function (IStorage $storage, StorageConfig $storageConfig): IStorage { + try { + $availability = $storage->getAvailability(); + if (!$availability['available'] && !Availability::shouldRecheck($availability)) { + $storage = new FailedStorage([ + 'exception' => new StorageNotAvailableException('Storage with mount id ' . $storageConfig->getId() . ' is not available') + ]); + } + } catch (\Exception $e) { + // propagate exception into filesystem + $storage = new FailedStorage(['exception' => $e]); + } + return $storage; + }, $storages, $storageConfigs); + + $mounts = array_map(function (StorageConfig $storageConfig, IStorage $storage) use ($user, $loader) { + $storage->setOwner($user->getUID()); + if ($storageConfig->getType() === StorageConfig::MOUNT_TYPE_PERSONAL) { + return new PersonalMount( + $this->userStoragesService, + $storageConfig, + $storageConfig->getId(), + new KnownMtime([ + 'storage' => $storage, + 'clock' => $this->clock, + ]), + '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), + null, + $loader, + $storageConfig->getMountOptions(), + $storageConfig->getId() + ); + } else { + return new SystemMountPoint( + $storageConfig, + $storage, + '/' . $user->getUID() . '/files' . $storageConfig->getMountPoint(), + null, + $loader, + $storageConfig->getMountOptions(), + $storageConfig->getId() + ); + } + }, $storageConfigs, $availableStorages); + + $this->userStoragesService->resetUser(); + $this->userGlobalStoragesService->resetUser(); + + return $mounts; + } +} diff --git a/apps/files_external/lib/Config/ExternalMountPoint.php b/apps/files_external/lib/Config/ExternalMountPoint.php new file mode 100644 index 00000000000..97569ed2913 --- /dev/null +++ b/apps/files_external/lib/Config/ExternalMountPoint.php @@ -0,0 +1,34 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Config; + +use OC\Files\Mount\MountPoint; +use OCA\Files_External\Lib\Auth\Password\SessionCredentials; +use OCA\Files_External\Lib\StorageConfig; + +class ExternalMountPoint extends MountPoint { + + public function __construct( + protected StorageConfig $storageConfig, + $storage, + $mountpoint, + $arguments = null, + $loader = null, + $mountOptions = null, + $mountId = null, + ) { + parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId, ConfigAdapter::class); + } + + public function getMountType() { + return ($this->storageConfig->getAuthMechanism() instanceof SessionCredentials) ? 'external-session' : 'external'; + } + + public function getStorageConfig(): StorageConfig { + return $this->storageConfig; + } +} diff --git a/apps/files_external/lib/Config/IConfigHandler.php b/apps/files_external/lib/Config/IConfigHandler.php new file mode 100644 index 00000000000..9e8283cc58b --- /dev/null +++ b/apps/files_external/lib/Config/IConfigHandler.php @@ -0,0 +1,22 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Config; + +/** + * Interface IConfigHandler + * + * @package OCA\Files_External\Config + * @since 16.0.0 + */ +interface IConfigHandler { + /** + * @param mixed $optionValue + * @return mixed the same type as $optionValue + * @since 16.0.0 + */ + public function handle($optionValue); +} diff --git a/apps/files_external/lib/Config/SimpleSubstitutionTrait.php b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php new file mode 100644 index 00000000000..85a76054fa8 --- /dev/null +++ b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php @@ -0,0 +1,69 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Config; + +/** + * Trait SimpleSubstitutionTrait + * + * @package OCA\Files_External\Config + * @since 16.0.0 + */ +trait SimpleSubstitutionTrait { + /** + * @var string the placeholder without $ prefix + * @since 16.0.0 + */ + protected $placeholder; + + /** @var string */ + protected $sanitizedPlaceholder; + + /** + * @param mixed $optionValue + * @param string $replacement + * @return mixed + * @since 16.0.0 + */ + private function processInput($optionValue, string $replacement) { + $this->checkPlaceholder(); + if (is_array($optionValue)) { + foreach ($optionValue as &$value) { + $value = $this->substituteIfString($value, $replacement); + } + } else { + $optionValue = $this->substituteIfString($optionValue, $replacement); + } + return $optionValue; + } + + /** + * @throws \RuntimeException + */ + protected function checkPlaceholder(): void { + $this->sanitizedPlaceholder = trim(strtolower($this->placeholder)); + if (!(bool)\preg_match('/^[a-z0-9]*$/', $this->sanitizedPlaceholder)) { + throw new \RuntimeException(sprintf( + 'Invalid placeholder %s, only [a-z0-9] are allowed', $this->sanitizedPlaceholder + )); + } + if ($this->sanitizedPlaceholder === '') { + throw new \RuntimeException('Invalid empty placeholder'); + } + } + + /** + * @param mixed $value + * @param string $replacement + * @return mixed + */ + protected function substituteIfString($value, string $replacement) { + if (is_string($value)) { + return str_ireplace('$' . $this->sanitizedPlaceholder, $replacement, $value); + } + return $value; + } +} diff --git a/apps/files_external/lib/Config/SystemMountPoint.php b/apps/files_external/lib/Config/SystemMountPoint.php new file mode 100644 index 00000000000..af0bf792140 --- /dev/null +++ b/apps/files_external/lib/Config/SystemMountPoint.php @@ -0,0 +1,15 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_External\Config; + +use OCP\Files\Mount\IShareOwnerlessMount; +use OCP\Files\Mount\ISystemMountPoint; + +class SystemMountPoint extends ExternalMountPoint implements ISystemMountPoint, IShareOwnerlessMount { +} diff --git a/apps/files_external/lib/Config/UserContext.php b/apps/files_external/lib/Config/UserContext.php new file mode 100644 index 00000000000..fb5c79a9329 --- /dev/null +++ b/apps/files_external/lib/Config/UserContext.php @@ -0,0 +1,61 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Config; + +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IManager as ShareManager; + +class UserContext { + + /** @var string */ + private $userId; + + public function __construct( + private IUserSession $session, + private ShareManager $shareManager, + private IRequest $request, + private IUserManager $userManager, + ) { + } + + public function getSession(): IUserSession { + return $this->session; + } + + public function setUserId(string $userId): void { + $this->userId = $userId; + } + + protected function getUserId(): ?string { + if ($this->userId !== null) { + return $this->userId; + } + if ($this->session->getUser() !== null) { + return $this->session->getUser()->getUID(); + } + try { + $shareToken = $this->request->getParam('token'); + $share = $this->shareManager->getShareByToken($shareToken); + return $share->getShareOwner(); + } catch (ShareNotFound $e) { + } + + return null; + } + + protected function getUser(): ?IUser { + $userId = $this->getUserId(); + if ($userId !== null) { + return $this->userManager->get($userId); + } + return null; + } +} diff --git a/apps/files_external/lib/Config/UserPlaceholderHandler.php b/apps/files_external/lib/Config/UserPlaceholderHandler.php new file mode 100644 index 00000000000..d158e6923c1 --- /dev/null +++ b/apps/files_external/lib/Config/UserPlaceholderHandler.php @@ -0,0 +1,25 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_External\Config; + +class UserPlaceholderHandler extends UserContext implements IConfigHandler { + use SimpleSubstitutionTrait; + + /** + * @param mixed $optionValue + * @return mixed the same type as $optionValue + * @since 16.0.0 + */ + public function handle($optionValue) { + $this->placeholder = 'user'; + $uid = $this->getUserId(); + if ($uid === null) { + return $optionValue; + } + return $this->processInput($optionValue, $uid); + } +} |