diff options
Diffstat (limited to 'apps/files_external/lib/Service/BackendService.php')
-rw-r--r-- | apps/files_external/lib/Service/BackendService.php | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/apps/files_external/lib/Service/BackendService.php b/apps/files_external/lib/Service/BackendService.php new file mode 100644 index 00000000000..3a688ee66e6 --- /dev/null +++ b/apps/files_external/lib/Service/BackendService.php @@ -0,0 +1,342 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ +namespace OCA\Files_External\Service; + +use OCA\Files_External\Config\IConfigHandler; +use OCA\Files_External\ConfigLexicon; +use OCA\Files_External\Lib\Auth\AuthMechanism; +use OCA\Files_External\Lib\Backend\Backend; +use OCA\Files_External\Lib\Config\IAuthMechanismProvider; +use OCA\Files_External\Lib\Config\IBackendProvider; +use OCA\Files_External\Lib\MissingDependency; +use OCP\EventDispatcher\GenericEvent; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IAppConfig; +use OCP\Server; + +/** + * Service class to manage backend definitions + */ +class BackendService { + + /** Visibility constants for VisibilityTrait */ + public const VISIBILITY_NONE = 0; + public const VISIBILITY_PERSONAL = 1; + public const VISIBILITY_ADMIN = 2; + //const VISIBILITY_ALIENS = 4; + + public const VISIBILITY_DEFAULT = 3; // PERSONAL | ADMIN + + /** Priority constants for PriorityTrait */ + public const PRIORITY_DEFAULT = 100; + + /** @var bool */ + private $userMountingAllowed = true; + + /** @var string[] */ + private $userMountingBackends = []; + + /** @var Backend[] */ + private $backends = []; + + /** @var IBackendProvider[] */ + private $backendProviders = []; + + /** @var AuthMechanism[] */ + private $authMechanisms = []; + + /** @var IAuthMechanismProvider[] */ + private $authMechanismProviders = []; + + /** @var callable[] */ + private $configHandlerLoaders = []; + + private $configHandlers = []; + + public function __construct( + protected IAppConfig $appConfig, + ) { + // Load config values + $this->userMountingAllowed = $appConfig->getValueBool('files_external', ConfigLexicon::ALLOW_USER_MOUNTING); + $this->userMountingBackends = explode(',', $appConfig->getValueString('files_external', ConfigLexicon::USER_MOUNTING_BACKENDS)); + + // if no backend is in the list an empty string is in the array and user mounting is disabled + if ($this->userMountingBackends === ['']) { + $this->userMountingAllowed = false; + } + } + + /** + * Register a backend provider + * + * @since 9.1.0 + * @param IBackendProvider $provider + */ + public function registerBackendProvider(IBackendProvider $provider) { + $this->backendProviders[] = $provider; + } + + private function callForRegistrations() { + static $eventSent = false; + if (!$eventSent) { + Server::get(IEventDispatcher::class)->dispatch( + 'OCA\\Files_External::loadAdditionalBackends', + new GenericEvent() + ); + $eventSent = true; + } + } + + private function loadBackendProviders() { + $this->callForRegistrations(); + foreach ($this->backendProviders as $provider) { + $this->registerBackends($provider->getBackends()); + } + $this->backendProviders = []; + } + + /** + * Register an auth mechanism provider + * + * @since 9.1.0 + * @param IAuthMechanismProvider $provider + */ + public function registerAuthMechanismProvider(IAuthMechanismProvider $provider) { + $this->authMechanismProviders[] = $provider; + } + + private function loadAuthMechanismProviders() { + $this->callForRegistrations(); + foreach ($this->authMechanismProviders as $provider) { + $this->registerAuthMechanisms($provider->getAuthMechanisms()); + } + $this->authMechanismProviders = []; + } + + /** + * Register a backend + * + * @deprecated 9.1.0 use registerBackendProvider() + * @param Backend $backend + */ + public function registerBackend(Backend $backend) { + if (!$this->isAllowedUserBackend($backend)) { + $backend->removeVisibility(BackendService::VISIBILITY_PERSONAL); + } + foreach ($backend->getIdentifierAliases() as $alias) { + $this->backends[$alias] = $backend; + } + } + + /** + * @deprecated 9.1.0 use registerBackendProvider() + * @param Backend[] $backends + */ + public function registerBackends(array $backends) { + foreach ($backends as $backend) { + $this->registerBackend($backend); + } + } + /** + * Register an authentication mechanism + * + * @deprecated 9.1.0 use registerAuthMechanismProvider() + * @param AuthMechanism $authMech + */ + public function registerAuthMechanism(AuthMechanism $authMech) { + if (!$this->isAllowedAuthMechanism($authMech)) { + $authMech->removeVisibility(BackendService::VISIBILITY_PERSONAL); + } + foreach ($authMech->getIdentifierAliases() as $alias) { + $this->authMechanisms[$alias] = $authMech; + } + } + + /** + * @deprecated 9.1.0 use registerAuthMechanismProvider() + * @param AuthMechanism[] $mechanisms + */ + public function registerAuthMechanisms(array $mechanisms) { + foreach ($mechanisms as $mechanism) { + $this->registerAuthMechanism($mechanism); + } + } + + /** + * Get all backends + * + * @return Backend[] + */ + public function getBackends() { + $this->loadBackendProviders(); + // only return real identifiers, no aliases + $backends = []; + foreach ($this->backends as $backend) { + $backends[$backend->getIdentifier()] = $backend; + } + return $backends; + } + + /** + * Get all available backends + * + * @return Backend[] + */ + public function getAvailableBackends() { + return array_filter($this->getBackends(), function ($backend) { + $missing = array_filter($backend->checkDependencies(), fn (MissingDependency $dependency) => !$dependency->isOptional()); + return count($missing) === 0; + }); + } + + /** + * @param string $identifier + * @return Backend|null + */ + public function getBackend($identifier) { + $this->loadBackendProviders(); + if (isset($this->backends[$identifier])) { + return $this->backends[$identifier]; + } + return null; + } + + /** + * Get all authentication mechanisms + * + * @return AuthMechanism[] + */ + public function getAuthMechanisms() { + $this->loadAuthMechanismProviders(); + // only return real identifiers, no aliases + $mechanisms = []; + foreach ($this->authMechanisms as $mechanism) { + $mechanisms[$mechanism->getIdentifier()] = $mechanism; + } + return $mechanisms; + } + + /** + * Get all authentication mechanisms for schemes + * + * @param string[] $schemes + * @return AuthMechanism[] + */ + public function getAuthMechanismsByScheme(array $schemes) { + return array_filter($this->getAuthMechanisms(), function ($authMech) use ($schemes) { + return in_array($authMech->getScheme(), $schemes, true); + }); + } + + /** + * @param string $identifier + * @return AuthMechanism|null + */ + public function getAuthMechanism($identifier) { + $this->loadAuthMechanismProviders(); + if (isset($this->authMechanisms[$identifier])) { + return $this->authMechanisms[$identifier]; + } + return null; + } + + /** + * @return bool + */ + public function isUserMountingAllowed() { + return $this->userMountingAllowed; + } + + /** + * Check a backend if a user is allowed to mount it + * + * @param Backend $backend + * @return bool + */ + protected function isAllowedUserBackend(Backend $backend) { + if ($this->userMountingAllowed + && array_intersect($backend->getIdentifierAliases(), $this->userMountingBackends) + ) { + return true; + } + return false; + } + + /** + * Check an authentication mechanism if a user is allowed to use it + * + * @param AuthMechanism $authMechanism + * @return bool + */ + protected function isAllowedAuthMechanism(AuthMechanism $authMechanism) { + return true; // not implemented + } + + /** + * registers a configuration handler + * + * The function of the provided $placeholder is mostly to act a sorting + * criteria, so longer placeholders are replaced first. This avoids + * "$user" overwriting parts of "$userMail" and "$userLang", for example. + * The provided value should not contain the $ prefix, only a-z0-9 are + * allowed. Upper case letters are lower cased, the replacement is case- + * insensitive. + * + * The configHandlerLoader should just instantiate the handler on demand. + * For now all handlers are instantiated when a mount is loaded, independent + * of whether the placeholder is present or not. This may change in future. + * + * @since 16.0.0 + */ + public function registerConfigHandler(string $placeholder, callable $configHandlerLoader) { + $placeholder = trim(strtolower($placeholder)); + if (!(bool)\preg_match('/^[a-z0-9]*$/', $placeholder)) { + throw new \RuntimeException(sprintf( + 'Invalid placeholder %s, only [a-z0-9] are allowed', $placeholder + )); + } + if ($placeholder === '') { + throw new \RuntimeException('Invalid empty placeholder'); + } + if (isset($this->configHandlerLoaders[$placeholder]) || isset($this->configHandlers[$placeholder])) { + throw new \RuntimeException(sprintf('A handler is already registered for %s', $placeholder)); + } + $this->configHandlerLoaders[$placeholder] = $configHandlerLoader; + } + + protected function loadConfigHandlers():void { + $this->callForRegistrations(); + $newLoaded = false; + foreach ($this->configHandlerLoaders as $placeholder => $loader) { + $handler = $loader(); + if (!$handler instanceof IConfigHandler) { + throw new \RuntimeException(sprintf( + 'Handler for %s is not an instance of IConfigHandler', $placeholder + )); + } + $this->configHandlers[$placeholder] = $handler; + $newLoaded = true; + } + $this->configHandlerLoaders = []; + if ($newLoaded) { + // ensure those with longest placeholders come first, + // to avoid substring matches + uksort($this->configHandlers, function ($phA, $phB) { + return strlen($phB) <=> strlen($phA); + }); + } + } + + /** + * @since 16.0.0 + */ + public function getConfigHandlers() { + $this->loadConfigHandlers(); + return $this->configHandlers; + } +} |