From a26bcd8e8fa11870c9192d24c73fbef3ef6112de Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Mon, 11 Feb 2019 23:18:08 +0100 Subject: [PATCH 1/5] 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 --- .../lib/AppInfo/Application.php | 12 ++- .../lib/Config/ConfigAdapter.php | 6 +- .../lib/Config/IConfigHandler.php | 39 +++++++++ .../lib/Config/SimpleSubstitutionTrait.php | 86 +++++++++++++++++++ .../lib/Config/UserPlaceholderHandler.php | 53 ++++++++++++ .../lib/Service/BackendService.php | 68 +++++++++++++++ apps/files_external/lib/config.php | 36 +++++--- .../Config/UserPlaceholderHandlerTest.php | 81 +++++++++++++++++ .../tests/Service/BackendServiceTest.php | 75 ++++++++++++---- 9 files changed, 423 insertions(+), 33 deletions(-) create mode 100644 apps/files_external/lib/Config/IConfigHandler.php create mode 100644 apps/files_external/lib/Config/SimpleSubstitutionTrait.php create mode 100644 apps/files_external/lib/Config/UserPlaceholderHandler.php create mode 100644 apps/files_external/tests/Config/UserPlaceholderHandlerTest.php diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index 8eebc550d0..5b12377fdb 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 34e96df044..8d9c7ea8d1 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 0000000000..3e2ec32ba2 --- /dev/null +++ b/apps/files_external/lib/Config/IConfigHandler.php @@ -0,0 +1,39 @@ + + * + * @author Arthur Schiwon + * + * @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 . + * + */ + +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 0000000000..9abbfe0df2 --- /dev/null +++ b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php @@ -0,0 +1,86 @@ + + * + * @author Arthur Schiwon + * + * @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 . + * + */ + +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 0000000000..721d3bbe02 --- /dev/null +++ b/apps/files_external/lib/Config/UserPlaceholderHandler.php @@ -0,0 +1,53 @@ + + * + * @author Arthur Schiwon + * + * @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 . + * + */ + +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 bd6c525f31..05bda1998d 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 7fb118c2de..2078abc029 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 { diff --git a/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php b/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php new file mode 100644 index 0000000000..d44a146cc8 --- /dev/null +++ b/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php @@ -0,0 +1,81 @@ + + * + * @author Arthur Schiwon + * + * @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 . + * + */ + +namespace OCA\files_external\tests\Config; + +use OCA\Files_External\Config\UserPlaceholderHandler; +use OCP\IUser; +use OCP\IUserSession; + +class UserPlaceholderHandlerTest extends \Test\TestCase { + /** @var IUser|\PHPUnit_Framework_MockObject_MockObject */ + protected $user; + + /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + protected $session; + + /** @var UserPlaceholderHandler */ + protected $handler; + + public function setUp() { + parent::setUp(); + + $this->user = $this->createMock(IUser::class); + $this->user->expects($this->any()) + ->method('getUid') + ->willReturn('alice'); + $this->session = $this->createMock(IUserSession::class); + + $this->handler = new UserPlaceholderHandler($this->session); + } + + protected function setUser() { + $this->session->expects($this->any()) + ->method('getUser') + ->willReturn($this->user); + } + + public function optionProvider() { + return [ + ['/foo/bar/$user/foobar', '/foo/bar/alice/foobar'], + [['/foo/bar/$user/foobar'], ['/foo/bar/alice/foobar']], + [['/FOO/BAR/$USER/FOOBAR'], ['/FOO/BAR/alice/FOOBAR']], + ]; + } + + /** + * @dataProvider optionProvider + */ + public function testHandle($option, $expected) { + $this->setUser(); + $this->assertSame($expected, $this->handler->handle($option)); + } + + /** + * @dataProvider optionProvider + */ + public function testHandleNoUser($option) { + $this->assertSame($option, $this->handler->handle($option)); + } + +} diff --git a/apps/files_external/tests/Service/BackendServiceTest.php b/apps/files_external/tests/Service/BackendServiceTest.php index e8a3181658..1e3b6b5875 100644 --- a/apps/files_external/tests/Service/BackendServiceTest.php +++ b/apps/files_external/tests/Service/BackendServiceTest.php @@ -23,31 +23,27 @@ */ namespace OCA\Files_External\Tests\Service; +use OCA\Files_External\Config\IConfigHandler; 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\Service\BackendService; +use OCA\Files_External\Service\BackendService; use OCP\IConfig; -use OCP\IL10N; class BackendServiceTest extends \Test\TestCase { - /** @var \OCP\IConfig */ + /** @var \OCP\IConfig|\PHPUnit_Framework_MockObject_MockObject */ protected $config; - /** @var \OCP\IL10N */ - protected $l10n; - protected function setUp() { $this->config = $this->createMock(IConfig::class); - $this->l10n = $this->createMock(IL10N::class); } /** * @param string $class * - * @return \OCA\Files_External\Lib\Backend\Backend + * @return \OCA\Files_External\Lib\Backend\Backend|\PHPUnit_Framework_MockObject_MockObject */ protected function getBackendMock($class) { $backend = $this->getMockBuilder(Backend::class) @@ -61,7 +57,7 @@ class BackendServiceTest extends \Test\TestCase { /** * @param string $class * - * @return \OCA\Files_External\Lib\Auth\AuthMechanism + * @return \OCA\Files_External\Lib\Auth\AuthMechanism|\PHPUnit_Framework_MockObject_MockObject */ protected function getAuthMechanismMock($class) { $backend = $this->getMockBuilder(AuthMechanism::class) @@ -73,10 +69,11 @@ class BackendServiceTest extends \Test\TestCase { } public function testRegisterBackend() { - $service = new BackendService($this->config, $this->l10n); + $service = new BackendService($this->config); $backend = $this->getBackendMock('\Foo\Bar'); + /** @var \OCA\Files_External\Lib\Backend\Backend|\PHPUnit_Framework_MockObject_MockObject $backendAlias */ $backendAlias = $this->getMockBuilder(Backend::class) ->disableOriginalConstructor() ->getMock(); @@ -100,11 +97,12 @@ class BackendServiceTest extends \Test\TestCase { } public function testBackendProvider() { - $service = new BackendService($this->config, $this->l10n); + $service = new BackendService($this->config); $backend1 = $this->getBackendMock('\Foo\Bar'); $backend2 = $this->getBackendMock('\Bar\Foo'); + /** @var IBackendProvider|\PHPUnit_Framework_MockObject_MockObject $providerMock */ $providerMock = $this->createMock(IBackendProvider::class); $providerMock->expects($this->once()) ->method('getBackends') @@ -118,11 +116,12 @@ class BackendServiceTest extends \Test\TestCase { } public function testAuthMechanismProvider() { - $service = new BackendService($this->config, $this->l10n); + $service = new BackendService($this->config); $backend1 = $this->getAuthMechanismMock('\Foo\Bar'); $backend2 = $this->getAuthMechanismMock('\Bar\Foo'); + /** @var IAuthMechanismProvider|\PHPUnit_Framework_MockObject_MockObject $providerMock */ $providerMock = $this->createMock(IAuthMechanismProvider::class); $providerMock->expects($this->once()) ->method('getAuthMechanisms') @@ -136,18 +135,20 @@ class BackendServiceTest extends \Test\TestCase { } public function testMultipleBackendProviders() { - $service = new BackendService($this->config, $this->l10n); + $service = new BackendService($this->config); $backend1a = $this->getBackendMock('\Foo\Bar'); $backend1b = $this->getBackendMock('\Bar\Foo'); $backend2 = $this->getBackendMock('\Dead\Beef'); + /** @var IBackendProvider|\PHPUnit_Framework_MockObject_MockObject $provider1Mock */ $provider1Mock = $this->createMock(IBackendProvider::class); $provider1Mock->expects($this->once()) ->method('getBackends') ->willReturn([$backend1a, $backend1b]); $service->registerBackendProvider($provider1Mock); + /** @var IBackendProvider|\PHPUnit_Framework_MockObject_MockObject $provider2Mock */ $provider2Mock = $this->createMock(IBackendProvider::class); $provider2Mock->expects($this->once()) ->method('getBackends') @@ -169,7 +170,7 @@ class BackendServiceTest extends \Test\TestCase { ['files_external', 'user_mounting_backends', '', 'identifier:\User\Mount\Allowed,identifier_alias'] ])); - $service = new BackendService($this->config, $this->l10n); + $service = new BackendService($this->config); $backendAllowed = $this->getBackendMock('\User\Mount\Allowed'); $backendAllowed->expects($this->never()) @@ -193,7 +194,7 @@ class BackendServiceTest extends \Test\TestCase { } public function testGetAvailableBackends() { - $service = new BackendService($this->config, $this->l10n); + $service = new BackendService($this->config); $backendAvailable = $this->getBackendMock('\Backend\Available'); $backendAvailable->expects($this->once()) @@ -216,5 +217,49 @@ class BackendServiceTest extends \Test\TestCase { $this->assertArrayNotHasKey('identifier:\Backend\NotAvailable', $availableBackends); } + public function invalidConfigPlaceholderProvider() { + return [ + [['@user']], + [['hællo']], + [['spa ce']], + [['yo\o']], + [['']], + [['xxyoloxx', 'invÆlid']], + [['tautology', 'tautology']], + [['tautology2', 'TAUTOLOGY2']], + ]; + } + + /** + * @dataProvider invalidConfigPlaceholderProvider + * @expectedException \RuntimeException + */ + public function testRegisterConfigHandlerInvalid(array $placeholders) { + $service = new BackendService($this->config); + $mock = $this->createMock(IConfigHandler::class); + $cb = function () use ($mock) { return $mock; }; + foreach ($placeholders as $placeholder) { + $service->registerConfigHandler($placeholder, $cb); + } + } + + public function testConfigHandlers() { + $service = new BackendService($this->config); + $mock = $this->createMock(IConfigHandler::class); + $mock->expects($this->exactly(3)) + ->method('handle'); + $cb = function () use ($mock) { return $mock; }; + $service->registerConfigHandler('one', $cb); + $service->registerConfigHandler('2', $cb); + $service->registerConfigHandler('Three', $cb); + + /** @var IConfigHandler[] $handlers */ + $handlers = $service->getConfigHandlers(); + + foreach ($handlers as $handler) { + $handler->handle('Something'); + } + } + } From 792bcb82ae5149c86afcd4d550e3a22d60d330f7 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 13 Feb 2019 00:14:56 +0100 Subject: [PATCH 2/5] add LDAP ConfigHandler for external storages and "$home" var * handler registered upon OCA\\Files_External::loadAdditionalBackends event as user_ldap is loaded before files_external * new configuration field "ldapExtStorageHomeAttribute" (not in GUI yet) Signed-off-by: Arthur Schiwon --- .../lib/AppInfo/Application.php | 3 +- apps/files_external/lib/Lib/Storage/FTP.php | 2 - .../lib/Service/BackendService.php | 2 +- apps/user_ldap/appinfo/app.php | 4 + .../composer/composer/autoload_classmap.php | 2 + .../composer/composer/autoload_static.php | 2 + apps/user_ldap/lib/AppInfo/Application.php | 16 ++++ apps/user_ldap/lib/Configuration.php | 3 + apps/user_ldap/lib/Connection.php | 2 + .../lib/Exceptions/AttributeNotSet.php | 26 +++++++ .../lib/Handler/ExtStorageConfigHandler.php | 74 +++++++++++++++++++ apps/user_ldap/lib/User/Manager.php | 1 + apps/user_ldap/lib/User/User.php | 49 ++++++++++++ apps/user_ldap/tests/ConfigurationTest.php | 2 + apps/user_ldap/tests/User/UserTest.php | 52 ++++++++++++- 15 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 apps/user_ldap/lib/Exceptions/AttributeNotSet.php create mode 100644 apps/user_ldap/lib/Handler/ExtStorageConfigHandler.php diff --git a/apps/files_external/lib/AppInfo/Application.php b/apps/files_external/lib/AppInfo/Application.php index 5b12377fdb..01de6f1e40 100644 --- a/apps/files_external/lib/AppInfo/Application.php +++ b/apps/files_external/lib/AppInfo/Application.php @@ -94,7 +94,8 @@ class Application extends App implements IBackendProvider, IAuthMechanismProvide // TODO: obsolete these and use the TokenProvider to get the user's password from the session $this->getAuthMechanisms(); - // app developers: do NOT depend on this! it will disappear with oC 9.0! + // don't remove this, as app loading order might be a side effect and + // querying the service from the server not reliable \OC::$server->getEventDispatcher()->dispatch( 'OCA\\Files_External::loadAdditionalBackends' ); diff --git a/apps/files_external/lib/Lib/Storage/FTP.php b/apps/files_external/lib/Lib/Storage/FTP.php index dc4ab9cb0e..db2ae9cf29 100644 --- a/apps/files_external/lib/Lib/Storage/FTP.php +++ b/apps/files_external/lib/Lib/Storage/FTP.php @@ -44,8 +44,6 @@ class FTP extends StreamWrapper{ private $secure; private $root; - private static $tempFiles=array(); - public function __construct($params) { if (isset($params['host']) && isset($params['user']) && isset($params['password'])) { $this->host=$params['host']; diff --git a/apps/files_external/lib/Service/BackendService.php b/apps/files_external/lib/Service/BackendService.php index 05bda1998d..3c66e8eb3c 100644 --- a/apps/files_external/lib/Service/BackendService.php +++ b/apps/files_external/lib/Service/BackendService.php @@ -328,7 +328,7 @@ class BackendService { 'Handler for %s is not an instance of IConfigHandler', $placeholder )); } - $this->configHandlers[] = $handler; + $this->configHandlers[$placeholder] = $handler; $newLoaded = true; } $this->configHandlerLoaders = []; diff --git a/apps/user_ldap/appinfo/app.php b/apps/user_ldap/appinfo/app.php index f371ef9efb..5afd928301 100644 --- a/apps/user_ldap/appinfo/app.php +++ b/apps/user_ldap/appinfo/app.php @@ -34,6 +34,8 @@ return new OCA\User_LDAP\GroupPluginManager(); }); +$app = new \OCA\User_LDAP\AppInfo\Application(); + $helper = new \OCA\User_LDAP\Helper(\OC::$server->getConfig()); $configPrefixes = $helper->getServerConfigurationPrefixes(true); if(count($configPrefixes) > 0) { @@ -67,6 +69,8 @@ if(count($configPrefixes) > 0) { OC::$server->getEventDispatcher()->dispatch('OCA\\User_LDAP\\User\\User::postLDAPBackendAdded'); \OC::$server->getGroupManager()->addBackend($groupBackend); + + $app->registerBackendDependents(); } \OCP\Util::connectHook( diff --git a/apps/user_ldap/composer/composer/autoload_classmap.php b/apps/user_ldap/composer/composer/autoload_classmap.php index e25b7ee312..fadbc701ec 100644 --- a/apps/user_ldap/composer/composer/autoload_classmap.php +++ b/apps/user_ldap/composer/composer/autoload_classmap.php @@ -23,12 +23,14 @@ return array( 'OCA\\User_LDAP\\ConnectionFactory' => $baseDir . '/../lib/ConnectionFactory.php', 'OCA\\User_LDAP\\Controller\\ConfigAPIController' => $baseDir . '/../lib/Controller/ConfigAPIController.php', 'OCA\\User_LDAP\\Controller\\RenewPasswordController' => $baseDir . '/../lib/Controller/RenewPasswordController.php', + 'OCA\\User_LDAP\\Exceptions\\AttributeNotSet' => $baseDir . '/../lib/Exceptions/AttributeNotSet.php', 'OCA\\User_LDAP\\Exceptions\\ConstraintViolationException' => $baseDir . '/../lib/Exceptions/ConstraintViolationException.php', 'OCA\\User_LDAP\\Exceptions\\NotOnLDAP' => $baseDir . '/../lib/Exceptions/NotOnLDAP.php', 'OCA\\User_LDAP\\FilesystemHelper' => $baseDir . '/../lib/FilesystemHelper.php', 'OCA\\User_LDAP\\GroupPluginManager' => $baseDir . '/../lib/GroupPluginManager.php', 'OCA\\User_LDAP\\Group_LDAP' => $baseDir . '/../lib/Group_LDAP.php', 'OCA\\User_LDAP\\Group_Proxy' => $baseDir . '/../lib/Group_Proxy.php', + 'OCA\\User_LDAP\\Handler\\ExtStorageConfigHandler' => $baseDir . '/../lib/Handler/ExtStorageConfigHandler.php', 'OCA\\User_LDAP\\Helper' => $baseDir . '/../lib/Helper.php', 'OCA\\User_LDAP\\IGroupLDAP' => $baseDir . '/../lib/IGroupLDAP.php', 'OCA\\User_LDAP\\ILDAPGroupPlugin' => $baseDir . '/../lib/ILDAPGroupPlugin.php', diff --git a/apps/user_ldap/composer/composer/autoload_static.php b/apps/user_ldap/composer/composer/autoload_static.php index 23819055be..d40df6e483 100644 --- a/apps/user_ldap/composer/composer/autoload_static.php +++ b/apps/user_ldap/composer/composer/autoload_static.php @@ -38,12 +38,14 @@ class ComposerStaticInitUser_LDAP 'OCA\\User_LDAP\\ConnectionFactory' => __DIR__ . '/..' . '/../lib/ConnectionFactory.php', 'OCA\\User_LDAP\\Controller\\ConfigAPIController' => __DIR__ . '/..' . '/../lib/Controller/ConfigAPIController.php', 'OCA\\User_LDAP\\Controller\\RenewPasswordController' => __DIR__ . '/..' . '/../lib/Controller/RenewPasswordController.php', + 'OCA\\User_LDAP\\Exceptions\\AttributeNotSet' => __DIR__ . '/..' . '/../lib/Exceptions/AttributeNotSet.php', 'OCA\\User_LDAP\\Exceptions\\ConstraintViolationException' => __DIR__ . '/..' . '/../lib/Exceptions/ConstraintViolationException.php', 'OCA\\User_LDAP\\Exceptions\\NotOnLDAP' => __DIR__ . '/..' . '/../lib/Exceptions/NotOnLDAP.php', 'OCA\\User_LDAP\\FilesystemHelper' => __DIR__ . '/..' . '/../lib/FilesystemHelper.php', 'OCA\\User_LDAP\\GroupPluginManager' => __DIR__ . '/..' . '/../lib/GroupPluginManager.php', 'OCA\\User_LDAP\\Group_LDAP' => __DIR__ . '/..' . '/../lib/Group_LDAP.php', 'OCA\\User_LDAP\\Group_Proxy' => __DIR__ . '/..' . '/../lib/Group_Proxy.php', + 'OCA\\User_LDAP\\Handler\\ExtStorageConfigHandler' => __DIR__ . '/..' . '/../lib/Handler/ExtStorageConfigHandler.php', 'OCA\\User_LDAP\\Helper' => __DIR__ . '/..' . '/../lib/Helper.php', 'OCA\\User_LDAP\\IGroupLDAP' => __DIR__ . '/..' . '/../lib/IGroupLDAP.php', 'OCA\\User_LDAP\\ILDAPGroupPlugin' => __DIR__ . '/..' . '/../lib/ILDAPGroupPlugin.php', diff --git a/apps/user_ldap/lib/AppInfo/Application.php b/apps/user_ldap/lib/AppInfo/Application.php index 59d7cdb492..7e0c353448 100644 --- a/apps/user_ldap/lib/AppInfo/Application.php +++ b/apps/user_ldap/lib/AppInfo/Application.php @@ -23,7 +23,9 @@ namespace OCA\User_LDAP\AppInfo; +use OCA\Files_External\Service\BackendService; use OCA\User_LDAP\Controller\RenewPasswordController; +use OCA\User_LDAP\Handler\ExtStorageConfigHandler; use OCA\User_LDAP\ILDAPWrapper; use OCA\User_LDAP\LDAP; use OCP\AppFramework\App; @@ -57,4 +59,18 @@ class Application extends App { return new LDAP(); }); } + + public function registerBackendDependents() { + $container = $this->getContainer(); + + $container->getServer()->getEventDispatcher()->addListener( + 'OCA\\Files_External::loadAdditionalBackends', + function() use ($container) { + $storagesBackendService = $container->query(BackendService::class); + $storagesBackendService->registerConfigHandler('home', function () use ($container) { + return $container->query(ExtStorageConfigHandler::class); + }); + } + ); + } } diff --git a/apps/user_ldap/lib/Configuration.php b/apps/user_ldap/lib/Configuration.php index c912d30b49..ee77702a09 100644 --- a/apps/user_ldap/lib/Configuration.php +++ b/apps/user_ldap/lib/Configuration.php @@ -106,6 +106,7 @@ class Configuration { 'turnOnPasswordChange' => false, 'ldapDynamicGroupMemberURL' => null, 'ldapDefaultPPolicyDN' => null, + 'ldapExtStorageHomeAttribute' => null, ); /** @@ -477,6 +478,7 @@ class Configuration { 'ldap_dynamic_group_member_url' => '', 'ldap_default_ppolicy_dn' => '', 'ldap_user_avatar_rule' => 'default', + 'ldap_ext_storage_home_attribute' => '', ); } @@ -537,6 +539,7 @@ class Configuration { 'ldap_experienced_admin' => 'ldapExperiencedAdmin', 'ldap_dynamic_group_member_url' => 'ldapDynamicGroupMemberURL', 'ldap_default_ppolicy_dn' => 'ldapDefaultPPolicyDN', + 'ldap_ext_storage_home_attribute' => 'ldapExtStorageHomeAttribute', 'ldapIgnoreNamingRules' => 'ldapIgnoreNamingRules', // sysconfig ); return $array; diff --git a/apps/user_ldap/lib/Connection.php b/apps/user_ldap/lib/Connection.php index 7becf311a2..ba393dffc1 100644 --- a/apps/user_ldap/lib/Connection.php +++ b/apps/user_ldap/lib/Connection.php @@ -60,6 +60,8 @@ use OCP\ILogger; * @property string ldapQuotaAttribute * @property string ldapQuotaDefault * @property string ldapEmailAttribute + * @property string ldapExtStorageHomeAttribute + * @property string homeFolderNamingRule */ class Connection extends LDAPUtility { private $ldapConnectionRes = null; diff --git a/apps/user_ldap/lib/Exceptions/AttributeNotSet.php b/apps/user_ldap/lib/Exceptions/AttributeNotSet.php new file mode 100644 index 0000000000..540b65c182 --- /dev/null +++ b/apps/user_ldap/lib/Exceptions/AttributeNotSet.php @@ -0,0 +1,26 @@ + + * + * @author Arthur Schiwon + * + * @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 . + * + */ + +namespace OCA\User_LDAP\Exceptions; + +class AttributeNotSet extends \RuntimeException {} diff --git a/apps/user_ldap/lib/Handler/ExtStorageConfigHandler.php b/apps/user_ldap/lib/Handler/ExtStorageConfigHandler.php new file mode 100644 index 0000000000..98a3cc7126 --- /dev/null +++ b/apps/user_ldap/lib/Handler/ExtStorageConfigHandler.php @@ -0,0 +1,74 @@ + + * + * @author Arthur Schiwon + * + * @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 . + * + */ + +namespace OCA\User_LDAP\Handler; + +use OCA\Files_External\Config\IConfigHandler; +use OCA\Files_External\Config\SimpleSubstitutionTrait; +use OCA\User_LDAP\User_Proxy; +use OCP\IUserSession; + +class ExtStorageConfigHandler implements IConfigHandler { + use SimpleSubstitutionTrait; + + /** @var IUserSession */ + private $session; + + public function __construct(IUserSession $session) { + $this->placeholder = 'home'; + $this->session = $session; + } + + /** + * @param mixed $optionValue + * @return mixed the same type as $optionValue + * @since 16.0.0 + * @throws \Exception + */ + public function handle($optionValue) { + $user = $this->session->getUser(); + if($user === null) { + return $optionValue; + } + + $backend = $user->getBackend(); + if(!$backend instanceof User_Proxy) { + return $optionValue; + } + + $access = $backend->getLDAPAccess($user->getUID()); + if(!$access) { + return $optionValue; + } + + $attribute = $access->connection->ldapExtStorageHomeAttribute; + if(empty($attribute)) { + return $optionValue; + } + + $ldapUser = $access->userManager->get($user->getUID()); + $extHome = $ldapUser->getExtStorageHome(); + + return $this->processInput($optionValue, $extHome); + } +} diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php index 6185c0da45..046b42551b 100644 --- a/apps/user_ldap/lib/User/Manager.php +++ b/apps/user_ldap/lib/User/Manager.php @@ -176,6 +176,7 @@ class Manager { $this->access->getConnection()->ldapEmailAttribute, $this->access->getConnection()->ldapUserDisplayName, $this->access->getConnection()->ldapUserDisplayName2, + $this->access->getConnection()->ldapExtStorageHomeAttribute, ]; $homeRule = $this->access->getConnection()->homeFolderNamingRule; diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php index 0d8f993746..d68d8b35d1 100644 --- a/apps/user_ldap/lib/User/User.php +++ b/apps/user_ldap/lib/User/User.php @@ -32,6 +32,7 @@ namespace OCA\User_LDAP\User; use OCA\User_LDAP\Access; use OCA\User_LDAP\Connection; +use OCA\User_LDAP\Exceptions\AttributeNotSet; use OCA\User_LDAP\FilesystemHelper; use OCA\User_LDAP\LogWrapper; use OCP\IAvatarManager; @@ -244,6 +245,13 @@ class User { } $this->connection->writeToCache($cacheKey, $groups); + //external storage var + $attr = strtolower($this->connection->ldapExtStorageHomeAttribute); + if(isset($ldapEntry[$attr])) { + $this->updateExtStorageHome($ldapEntry[$attr][0]); + } + unset($attr); + //Avatar /** @var Connection $connection */ $connection = $this->access->getConnection(); @@ -616,6 +624,47 @@ class User { return false; } + /** + * @throws AttributeNotSet + * @throws \OC\ServerNotAvailableException + * @throws \OCP\PreConditionNotMetException + */ + public function getExtStorageHome():string { + $value = $this->config->getUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', ''); + if ($value !== '') { + return $value; + } + + $value = $this->updateExtStorageHome(); + if ($value !== '') { + return $value; + } + + throw new AttributeNotSet(sprintf( + 'external home storage attribute yield no value for %s', $this->getUsername() + )); + } + + /** + * @throws \OCP\PreConditionNotMetException + * @throws \OC\ServerNotAvailableException + */ + public function updateExtStorageHome(string $valueFromLDAP = null):string { + if($valueFromLDAP === null) { + $extHomeValues = $this->access->readAttribute($this->getDN(), $this->connection->ldapExtStorageHomeAttribute); + } else { + $extHomeValues = [$valueFromLDAP]; + } + if ($extHomeValues && isset($extHomeValues[0])) { + $extHome = $extHomeValues[0]; + $this->config->setUserValue($this->getUsername(), 'user_ldap', 'extStorageHome', $extHome); + return $extHome; + } else { + $this->config->deleteUserValue($this->getUsername(), 'user_ldap', 'extStorageHome'); + return ''; + } + } + /** * called by a post_login hook to handle password expiry * diff --git a/apps/user_ldap/tests/ConfigurationTest.php b/apps/user_ldap/tests/ConfigurationTest.php index ab1312860f..6e45f16386 100644 --- a/apps/user_ldap/tests/ConfigurationTest.php +++ b/apps/user_ldap/tests/ConfigurationTest.php @@ -97,6 +97,8 @@ class ConfigurationTest extends \Test\TestCase { 'set avatar rule, default' => ['ldapUserAvatarRule', 'default', 'default'], 'set avatar rule, none' => ['ldapUserAvatarRule', 'none', 'none'], 'set avatar rule, data attribute' => ['ldapUserAvatarRule', 'data:jpegPhoto', 'data:jpegPhoto'], + + 'set external storage home attribute' => ['ldapExtStorageHomeAttribute', 'homePath', 'homePath'], ); } diff --git a/apps/user_ldap/tests/User/UserTest.php b/apps/user_ldap/tests/User/UserTest.php index 6ff9defe47..f99100789d 100644 --- a/apps/user_ldap/tests/User/UserTest.php +++ b/apps/user_ldap/tests/User/UserTest.php @@ -789,6 +789,50 @@ class UserTest extends \Test\TestCase { $this->user->update(); } + public function extStorageHomeDataProvider() { + return [ + [ 'myFolder', null ], + [ '', null, false ], + [ 'myFolder', 'myFolder' ], + ]; + } + + /** + * @dataProvider extStorageHomeDataProvider + */ + public function testUpdateExtStorageHome(string $expected, string $valueFromLDAP = null, bool $isSet = true) { + if($valueFromLDAP === null) { + $this->connection->expects($this->once()) + ->method('__get') + ->willReturnMap([ + ['ldapExtStorageHomeAttribute', 'homeDirectory'], + ]); + + $return = []; + if($isSet) { + $return[] = $expected; + } + $this->access->expects($this->once()) + ->method('readAttribute') + ->with($this->dn, 'homeDirectory') + ->willReturn($return); + } + + if($expected !== '') { + $this->config->expects($this->once()) + ->method('setUserValue') + ->with($this->uid, 'user_ldap', 'extStorageHome', $expected); + } else { + $this->config->expects($this->once()) + ->method('deleteUserValue') + ->with($this->uid, 'user_ldap', 'extStorageHome'); + } + + $actual = $this->user->updateExtStorageHome($valueFromLDAP); + $this->assertSame($expected, $actual); + + } + public function testUpdateNoRefresh() { $this->config->expects($this->at(0)) ->method('getUserValue') @@ -867,15 +911,16 @@ class UserTest extends \Test\TestCase { } public function testProcessAttributes() { - $requiredMethods = array( + $requiredMethods = [ 'markRefreshTime', 'updateQuota', 'updateEmail', 'composeAndStoreDisplayName', 'storeLDAPUserName', 'getHomePath', - 'updateAvatar' - ); + 'updateAvatar', + 'updateExtStorageHome', + ]; /** @var User|\PHPUnit_Framework_MockObject_MockObject $userMock */ $userMock = $this->getMockBuilder(User::class) @@ -914,6 +959,7 @@ class UserTest extends \Test\TestCase { strtolower($this->connection->ldapQuotaAttribute) => ['4096'], strtolower($this->connection->ldapEmailAttribute) => ['alice@wonderland.org'], strtolower($this->connection->ldapUserDisplayName) => ['Aaaaalice'], + strtolower($this->connection->ldapExtStorageHomeAttribute) => ['homeDirectory'], 'uid' => [$this->uid], 'homedirectory' => ['Alice\'s Folder'], 'memberof' => ['cn=groupOne', 'cn=groupTwo'], From f8a133d39e50c47dcda5685857baf465dea30104 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 13 Feb 2019 12:21:51 +0100 Subject: [PATCH 3/5] reject mounts with unsubstituted placeholders as incompletely configured Signed-off-by: Arthur Schiwon --- apps/files_external/lib/config.php | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 2078abc029..86be517bf0 100644 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -240,6 +240,20 @@ class OC_Mount_Config { } foreach ($options as &$option) { $option = self::substitutePlaceholdersInConfig($option); + if(!self::arePlaceholdersSubstituted($option)) { + \OC::$server->getLogger()->error( + 'A placeholder was not substituted: {option} for mount type {class}', + [ + 'app' => 'files_external', + 'option' => $option, + 'class' => $class, + ] + ); + throw new StorageNotAvailableException( + 'Mount configuration incomplete', + StorageNotAvailableException::STATUS_INCOMPLETE_CONF + ); + } } if (class_exists($class)) { try { @@ -264,6 +278,22 @@ class OC_Mount_Config { return StorageNotAvailableException::STATUS_ERROR; } + public static function arePlaceholdersSubstituted($option):bool { + $result = true; + if(is_array($option)) { + foreach ($option as $optionItem) { + if(is_array($optionItem)) { + $result = $result && self::arePlaceholdersSubstituted($option); + } + } + } else if (is_string($option)) { + if (strpos($option, '$') !== false) { + $result = false; + } + } + return $result; + } + /** * Read the mount points in the config file into an array * From 685c13e091e25b6df0b93285446e61bdc4d7315b Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Wed, 13 Feb 2019 12:42:41 +0100 Subject: [PATCH 4/5] add GUI option to set the $home placeholder Signed-off-by: Arthur Schiwon --- apps/user_ldap/js/wizard/wizardTabAdvanced.js | 15 ++++++++++++++- apps/user_ldap/templates/settings.php | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/user_ldap/js/wizard/wizardTabAdvanced.js b/apps/user_ldap/js/wizard/wizardTabAdvanced.js index 9302104b1f..1545147f64 100644 --- a/apps/user_ldap/js/wizard/wizardTabAdvanced.js +++ b/apps/user_ldap/js/wizard/wizardTabAdvanced.js @@ -120,7 +120,11 @@ OCA = OCA || {}; home_folder_naming_rule: { $element: $('#home_folder_naming_rule'), setMethod: 'setHomeFolderAttribute' - } + }, + ldap_ext_storage_home_attribute: { + $element: $('#ldap_ext_storage_home_attribute'), + setMethod: 'setExternalStorageHomeAttribute' + }, }; this.setManagedItems(items); }, @@ -326,6 +330,15 @@ OCA = OCA || {}; this.setElementValue(this.managedItems.ldap_email_attr.$element, attribute); }, + /** + * sets the external storage home attribute + * + * @param {string} attribute + */ + setExternalStorageHomeAttribute: function(attribute) { + this.setElementValue(this.managedItems.ldap_ext_storage_home_attribute.$element, attribute); + }, + /** * sets the quota attribute * diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 6d69d0aeb1..7c41363c63 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -108,6 +108,7 @@ style('user_ldap', 'settings');

+

From 173836b95af7cdc87cb603c5d98494b02242a949 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 15 Feb 2019 22:41:29 +0100 Subject: [PATCH 5/5] typos in doc, no code changes Signed-off-by: Arthur Schiwon --- apps/files_external/lib/Config/SimpleSubstitutionTrait.php | 4 ++-- apps/files_external/lib/Service/BackendService.php | 5 +++-- apps/files_external/lib/config.php | 1 + .../tests/Config/UserPlaceholderHandlerTest.php | 2 +- apps/files_external/tests/Service/BackendServiceTest.php | 1 + 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/files_external/lib/Config/SimpleSubstitutionTrait.php b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php index 9abbfe0df2..05bd529f1c 100644 --- a/apps/files_external/lib/Config/SimpleSubstitutionTrait.php +++ b/apps/files_external/lib/Config/SimpleSubstitutionTrait.php @@ -1,6 +1,6 @@ + * @copyright Copyright (c) 2019 Arthur Schiwon * * @author Arthur Schiwon * @@ -31,7 +31,7 @@ namespace OCA\Files_External\Config; */ trait SimpleSubstitutionTrait { /** - * @var string the placeholder without @ prefix + * @var string the placeholder without $ prefix * @since 16.0.0 */ private $placeholder; diff --git a/apps/files_external/lib/Service/BackendService.php b/apps/files_external/lib/Service/BackendService.php index 3c66e8eb3c..bd4d2bffa7 100644 --- a/apps/files_external/lib/Service/BackendService.php +++ b/apps/files_external/lib/Service/BackendService.php @@ -4,6 +4,7 @@ * * @author Morris Jobke * @author Robin McCorkell + * @author Arthur Schiwon * * @license AGPL-3.0 * @@ -292,8 +293,8 @@ class BackendService { * * 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 + * "$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. * diff --git a/apps/files_external/lib/config.php b/apps/files_external/lib/config.php index 86be517bf0..65e8ae387b 100644 --- a/apps/files_external/lib/config.php +++ b/apps/files_external/lib/config.php @@ -3,6 +3,7 @@ * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Andreas Fischer + * @author Arthur Schiwon * @author Bart Visscher * @author Björn Schießle * @author Frank Karlitschek diff --git a/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php b/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php index d44a146cc8..b6103c20b4 100644 --- a/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php +++ b/apps/files_external/tests/Config/UserPlaceholderHandlerTest.php @@ -1,6 +1,6 @@ + * @copyright Copyright (c) 2019 Arthur Schiwon * * @author Arthur Schiwon * diff --git a/apps/files_external/tests/Service/BackendServiceTest.php b/apps/files_external/tests/Service/BackendServiceTest.php index 1e3b6b5875..71990553e8 100644 --- a/apps/files_external/tests/Service/BackendServiceTest.php +++ b/apps/files_external/tests/Service/BackendServiceTest.php @@ -220,6 +220,7 @@ class BackendServiceTest extends \Test\TestCase { public function invalidConfigPlaceholderProvider() { return [ [['@user']], + [['$user']], [['hællo']], [['spa ce']], [['yo\o']],