diff options
author | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2019-02-12 01:18:08 +0300 |
---|---|---|
committer | Arthur Schiwon <blizzz@arthur-schiwon.de> | 2019-02-14 17:20:48 +0300 |
commit | a26bcd8e8fa11870c9192d24c73fbef3ef6112de (patch) | |
tree | b36e72d8eb27e17dd029478fce723b72670abd3c /apps/files_external/lib | |
parent | a80bae398ab2dd5683ca7c80eb7648e5c3dce426 (diff) |
files_external: allow to register config handlers for flexible placeholders
* BackendService (directly accessable via \OC_Server) offers registerConfigHandler
* SimpleSubstitutionTrait brings reusable logic for simple string replacments
* internal $user replacement mechanism was migrated
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
Diffstat (limited to 'apps/files_external/lib')
-rw-r--r-- | apps/files_external/lib/AppInfo/Application.php | 12 | ||||
-rw-r--r-- | apps/files_external/lib/Config/ConfigAdapter.php | 6 | ||||
-rw-r--r-- | apps/files_external/lib/Config/IConfigHandler.php | 39 | ||||
-rw-r--r-- | apps/files_external/lib/Config/SimpleSubstitutionTrait.php | 86 | ||||
-rw-r--r-- | apps/files_external/lib/Config/UserPlaceholderHandler.php | 53 | ||||
-rw-r--r-- | apps/files_external/lib/Service/BackendService.php | 68 | ||||
-rw-r--r-- | apps/files_external/lib/config.php | 36 |
7 files changed, 282 insertions, 18 deletions
diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index 8eebc550d09..5b12377fdb8 100644 --- a/apps/files_external/lib/AppInfo/Application.php +++ b/apps/files_external/lib/AppInfo/Application.php @@ -29,6 +29,7 @@ namespace OCA\Files_External\AppInfo; +use OCA\Files_External\Config\UserPlaceholderHandler; use OCA\Files_External\Lib\Auth\PublicKey\RSAPrivateKey; use OCA\Files_External\Lib\Auth\SMB\KerberosAuth; use \OCP\AppFramework\App; @@ -67,7 +68,12 @@ use OCP\Files\Config\IUserMountCache; */ class Application extends App implements IBackendProvider, IAuthMechanismProvider { - public function __construct(array $urlParams = array()) { + /** + * Application constructor. + * + * @throws \OCP\AppFramework\QueryException + */ + public function __construct(array $urlParams = []) { parent::__construct('files_external', $urlParams); $container = $this->getContainer(); @@ -76,9 +82,13 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide return $c->getServer()->query('UserMountCache'); }); + /** @var BackendService $backendService */ $backendService = $container->query(BackendService::class); $backendService->registerBackendProvider($this); $backendService->registerAuthMechanismProvider($this); + $backendService->registerConfigHandler('user', function() use ($container) { + return $container->query(UserPlaceholderHandler::class); + }); // force-load auth mechanisms since some will register hooks // TODO: obsolete these and use the TokenProvider to get the user's password from the session diff --git a/apps/files_external/lib/Config/ConfigAdapter.php b/apps/files_external/lib/Config/ConfigAdapter.php index 34e96df0441..8d9c7ea8d1b 100644 --- a/apps/files_external/lib/Config/ConfigAdapter.php +++ b/apps/files_external/lib/Config/ConfigAdapter.php @@ -29,7 +29,6 @@ namespace OCA\Files_External\Config; use OC\Files\Storage\Wrapper\Availability; use OCA\Files_External\Migration\StorageMigrator; use OCP\Files\Storage; -use OC\Files\Mount\MountPoint; use OCP\Files\Storage\IStorageFactory; use OCA\Files_External\Lib\PersonalMount; use OCP\Files\Config\IMountProvider; @@ -73,12 +72,11 @@ class ConfigAdapter implements IMountProvider { * * @param StorageConfig $storage * @param IUser $user + * @throws \OCP\AppFramework\QueryException */ private function prepareStorageConfig(StorageConfig &$storage, IUser $user) { foreach ($storage->getBackendOptions() as $option => $value) { - $storage->setBackendOption($option, \OC_Mount_Config::setUserVars( - $user->getUID(), $value - )); + $storage->setBackendOption($option, \OC_Mount_Config::substitutePlaceholdersInConfig($value)); } $objectStore = $storage->getBackendOption('objectstore'); diff --git a/apps/files_external/lib/Config/IConfigHandler.php b/apps/files_external/lib/Config/IConfigHandler.php new file mode 100644 index 00000000000..3e2ec32ba23 --- /dev/null +++ b/apps/files_external/lib/Config/IConfigHandler.php @@ -0,0 +1,39 @@ +<?php +/** + * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +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..9abbfe0df22 --- /dev/null +++ b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php @@ -0,0 +1,86 @@ +<?php +/** + * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +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 + */ + private $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/UserPlaceholderHandler.php b/apps/files_external/lib/Config/UserPlaceholderHandler.php new file mode 100644 index 00000000000..721d3bbe02a --- /dev/null +++ b/apps/files_external/lib/Config/UserPlaceholderHandler.php @@ -0,0 +1,53 @@ +<?php +/** + * @copyright Copyright (c) 2019 Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @author Arthur Schiwon <blizzz@arthur-schiwon.de> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_External\Config; + +use OCP\IUserSession; + +class UserPlaceholderHandler implements IConfigHandler { + use SimpleSubstitutionTrait; + + /** @var IUserSession */ + private $session; + + public function __construct(IUserSession $session) { + $this->session = $session; + $this->placeholder = 'user'; + } + + /** + * @param mixed $optionValue + * @return mixed the same type as $optionValue + * @since 16.0.0 + */ + public function handle($optionValue) { + $user = $this->session->getUser(); + if($user === null) { + return $optionValue; + } + $uid = $user->getUID(); + + return $this->processInput($optionValue, $uid); + } +} diff --git a/apps/files_external/lib/Service/BackendService.php b/apps/files_external/lib/Service/BackendService.php index bd6c525f319..05bda1998da 100644 --- a/apps/files_external/lib/Service/BackendService.php +++ b/apps/files_external/lib/Service/BackendService.php @@ -23,6 +23,7 @@ namespace OCA\Files_External\Service; +use OCA\Files_External\Config\IConfigHandler; use \OCP\IConfig; use \OCA\Files_External\Lib\Backend\Backend; @@ -67,6 +68,11 @@ class BackendService { /** @var IAuthMechanismProvider[] */ private $authMechanismProviders = []; + /** @var callable[] */ + private $configHandlerLoaders = []; + + private $configHandlers = []; + /** * @param IConfig $config */ @@ -280,4 +286,66 @@ class BackendService { 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 { + $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[] = $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; + } } diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 7fb118c2de5..2078abc029e 100644 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -35,6 +35,8 @@ * */ +use OCA\Files_External\Config\IConfigHandler; +use OCA\Files_External\Config\UserPlaceholderHandler; use phpseclib\Crypt\AES; use \OCA\Files_External\AppInfo\Application; use \OCA\Files_External\Lib\Backend\LegacyBackend; @@ -104,7 +106,7 @@ class OC_Mount_Config { $mountPoint = '/'.$uid.'/files'.$storage->getMountPoint(); $mountEntry = self::prepareMountPointEntry($storage, false); foreach ($mountEntry['options'] as &$option) { - $option = self::setUserVars($uid, $option); + $option = self::substitutePlaceholdersInConfig($option); } $mountPoints[$mountPoint] = $mountEntry; } @@ -113,7 +115,7 @@ class OC_Mount_Config { $mountPoint = '/'.$uid.'/files'.$storage->getMountPoint(); $mountEntry = self::prepareMountPointEntry($storage, true); foreach ($mountEntry['options'] as &$option) { - $option = self::setUserVars($uid, $option); + $option = self::substitutePlaceholdersInConfig($uid, $option); } $mountPoints[$mountPoint] = $mountEntry; } @@ -199,18 +201,26 @@ class OC_Mount_Config { * @param string $user user value * @param string|array $input * @return string + * @deprecated use self::substitutePlaceholdersInConfig($input) */ public static function setUserVars($user, $input) { - if (is_array($input)) { - foreach ($input as &$value) { - if (is_string($value)) { - $value = str_replace('$user', $user, $value); - } - } - } else { - if (is_string($input)) { - $input = str_replace('$user', $user, $input); - } + $handler = self::$app->getContainer()->query(UserPlaceholderHandler::class); + return $handler->handle($input); + } + + /** + * @param mixed $input + * @return mixed + * @throws \OCP\AppFramework\QueryException + * @since 16.0.0 + */ + public static function substitutePlaceholdersInConfig($input) { + /** @var BackendService $backendService */ + $backendService = self::$app->getContainer()->query(BackendService::class); + /** @var IConfigHandler[] $handlers */ + $handlers = $backendService->getConfigHandlers(); + foreach ($handlers as $handler) { + $input = $handler->handle($input); } return $input; } @@ -229,7 +239,7 @@ class OC_Mount_Config { return StorageNotAvailableException::STATUS_SUCCESS; } foreach ($options as &$option) { - $option = self::setUserVars(OCP\User::getUser(), $option); + $option = self::substitutePlaceholdersInConfig($option); } if (class_exists($class)) { try { |