diff options
author | Carl Schwan <carl@carlschwan.eu> | 2021-07-22 11:41:29 +0200 |
---|---|---|
committer | Carl Schwan <carl@carlschwan.eu> | 2021-09-29 21:43:31 +0200 |
commit | 6958d8005ae3b86759f49746564bf7238456be52 (patch) | |
tree | aab851e09351c631129e4729aa49c03533ce6180 /lib | |
parent | ee987d74303cb38b864f96660cd2ee6d6552ebfd (diff) | |
download | nextcloud-server-6958d8005ae3b86759f49746564bf7238456be52.tar.gz nextcloud-server-6958d8005ae3b86759f49746564bf7238456be52.zip |
Add admin privilege delegation for admin settings
This makes it possible for selected groups to access some settings
pages.
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 4 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 4 | ||||
-rw-r--r-- | lib/private/App/InfoParser.php | 2 | ||||
-rw-r--r-- | lib/private/AppFramework/DependencyInjection/DIContainer.php | 5 | ||||
-rw-r--r-- | lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php | 47 | ||||
-rw-r--r-- | lib/private/Settings/AuthorizedGroup.php | 50 | ||||
-rw-r--r-- | lib/private/Settings/AuthorizedGroupMapper.php | 125 | ||||
-rw-r--r-- | lib/private/Settings/Manager.php | 98 | ||||
-rw-r--r-- | lib/private/legacy/OC_App.php | 9 | ||||
-rw-r--r-- | lib/public/Settings/IDelegatedSettings.php | 54 | ||||
-rw-r--r-- | lib/public/Settings/IManager.php | 29 |
11 files changed, 394 insertions, 33 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index caddb528d5a..6c887afda97 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -491,6 +491,7 @@ return array( 'OCP\\Security\\VerificationToken\\IVerificationToken' => $baseDir . '/lib/public/Security/VerificationToken/IVerificationToken.php', 'OCP\\Security\\VerificationToken\\InvalidTokenException' => $baseDir . '/lib/public/Security/VerificationToken/InvalidTokenException.php', 'OCP\\Session\\Exceptions\\SessionNotAvailableException' => $baseDir . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', + 'OCP\\Settings\\IDelegatedSettings' => $baseDir . '/lib/public/Settings/IDelegatedSettings.php', 'OCP\\Settings\\IIconSection' => $baseDir . '/lib/public/Settings/IIconSection.php', 'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php', 'OCP\\Settings\\ISettings' => $baseDir . '/lib/public/Settings/ISettings.php', @@ -971,6 +972,7 @@ return array( 'OC\\Core\\Migrations\\Version21000Date20210309185126' => $baseDir . '/core/Migrations/Version21000Date20210309185126.php', 'OC\\Core\\Migrations\\Version21000Date20210309185127' => $baseDir . '/core/Migrations/Version21000Date20210309185127.php', 'OC\\Core\\Migrations\\Version22000Date20210216080825' => $baseDir . '/core/Migrations/Version22000Date20210216080825.php', + 'OC\\Core\\Migrations\\Version23000Date20210721100600' => $baseDir . '/core/Migrations/Version23000Date20210721100600.php', 'OC\\Core\\Migrations\\Version23000Date20210906132259' => $baseDir . '/core/Migrations/Version23000Date20210906132259.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php', @@ -1386,6 +1388,8 @@ return array( 'OC\\Session\\Internal' => $baseDir . '/lib/private/Session/Internal.php', 'OC\\Session\\Memory' => $baseDir . '/lib/private/Session/Memory.php', 'OC\\Session\\Session' => $baseDir . '/lib/private/Session/Session.php', + 'OC\\Settings\\AuthorizedGroup' => $baseDir . '/lib/private/Settings/AuthorizedGroup.php', + 'OC\\Settings\\AuthorizedGroupMapper' => $baseDir . '/lib/private/Settings/AuthorizedGroupMapper.php', 'OC\\Settings\\Manager' => $baseDir . '/lib/private/Settings/Manager.php', 'OC\\Settings\\Section' => $baseDir . '/lib/private/Settings/Section.php', 'OC\\Setup' => $baseDir . '/lib/private/Setup.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 727fe0fc956..fc3b1ca10ca 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -520,6 +520,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Security\\VerificationToken\\IVerificationToken' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/IVerificationToken.php', 'OCP\\Security\\VerificationToken\\InvalidTokenException' => __DIR__ . '/../../..' . '/lib/public/Security/VerificationToken/InvalidTokenException.php', 'OCP\\Session\\Exceptions\\SessionNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Session/Exceptions/SessionNotAvailableException.php', + 'OCP\\Settings\\IDelegatedSettings' => __DIR__ . '/../../..' . '/lib/public/Settings/IDelegatedSettings.php', 'OCP\\Settings\\IIconSection' => __DIR__ . '/../../..' . '/lib/public/Settings/IIconSection.php', 'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php', 'OCP\\Settings\\ISettings' => __DIR__ . '/../../..' . '/lib/public/Settings/ISettings.php', @@ -1000,6 +1001,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Migrations\\Version21000Date20210309185126' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20210309185126.php', 'OC\\Core\\Migrations\\Version21000Date20210309185127' => __DIR__ . '/../../..' . '/core/Migrations/Version21000Date20210309185127.php', 'OC\\Core\\Migrations\\Version22000Date20210216080825' => __DIR__ . '/../../..' . '/core/Migrations/Version22000Date20210216080825.php', + 'OC\\Core\\Migrations\\Version23000Date20210721100600' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210721100600.php', 'OC\\Core\\Migrations\\Version23000Date20210906132259' => __DIR__ . '/../../..' . '/core/Migrations/Version23000Date20210906132259.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php', @@ -1415,6 +1417,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Session\\Internal' => __DIR__ . '/../../..' . '/lib/private/Session/Internal.php', 'OC\\Session\\Memory' => __DIR__ . '/../../..' . '/lib/private/Session/Memory.php', 'OC\\Session\\Session' => __DIR__ . '/../../..' . '/lib/private/Session/Session.php', + 'OC\\Settings\\AuthorizedGroup' => __DIR__ . '/../../..' . '/lib/private/Settings/AuthorizedGroup.php', + 'OC\\Settings\\AuthorizedGroupMapper' => __DIR__ . '/../../..' . '/lib/private/Settings/AuthorizedGroupMapper.php', 'OC\\Settings\\Manager' => __DIR__ . '/../../..' . '/lib/private/Settings/Manager.php', 'OC\\Settings\\Section' => __DIR__ . '/../../..' . '/lib/private/Settings/Section.php', 'OC\\Setup' => __DIR__ . '/../../..' . '/lib/private/Setup.php', diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php index b9ca2d22c1c..9d57ef95688 100644 --- a/lib/private/App/InfoParser.php +++ b/lib/private/App/InfoParser.php @@ -253,7 +253,7 @@ class InfoParser { if (!count($node->children())) { $value = (string)$node; if (!empty($value)) { - $data['@value'] = (string)$node; + $data['@value'] = $value; } } else { $data = array_merge($data, $this->xmlToArray($node)); diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 89d59a471a8..293b9e47b25 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -48,6 +48,7 @@ use OC\AppFramework\Utility\SimpleContainer; use OC\Core\Middleware\TwoFactorMiddleware; use OC\Log\PsrLoggerAdapter; use OC\ServerContainer; +use OC\Settings\AuthorizedGroupMapper; use OCA\WorkflowEngine\Manager; use OCP\AppFramework\Http\IOutput; use OCP\AppFramework\IAppContainer; @@ -246,7 +247,9 @@ class DIContainer extends SimpleContainer implements IAppContainer { $this->getUserId() !== null && $server->getGroupManager()->isAdmin($this->getUserId()), $server->getUserSession()->getUser() !== null && $server->query(ISubAdmin::class)->isSubAdmin($server->getUserSession()->getUser()), $server->getAppManager(), - $server->getL10N('lib') + $server->getL10N('lib'), + $c->get(AuthorizedGroupMapper::class), + $server->get(IUserSession::class) ); $dispatcher->registerMiddleware($securityMiddleware); $dispatcher->registerMiddleware( diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index bd751183604..d162bb54108 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -34,6 +34,7 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/> * */ + namespace OC\AppFramework\Middleware\Security; use OC\AppFramework\Middleware\Security\Exceptions\AppNotEnabledException; @@ -43,6 +44,7 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; use OC\AppFramework\Middleware\Security\Exceptions\SecurityException; use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Utility\ControllerMethodReflector; +use OC\Settings\AuthorizedGroupMapper; use OCP\App\AppPathNotFoundException; use OCP\App\IAppManager; use OCP\AppFramework\Controller; @@ -56,6 +58,7 @@ use OCP\IL10N; use OCP\INavigationManager; use OCP\IRequest; use OCP\IURLGenerator; +use OCP\IUserSession; use OCP\Util; use Psr\Log\LoggerInterface; @@ -88,6 +91,10 @@ class SecurityMiddleware extends Middleware { private $appManager; /** @var IL10N */ private $l10n; + /** @var AuthorizedGroupMapper */ + private $groupAuthorizationMapper; + /** @var IUserSession */ + private $userSession; public function __construct(IRequest $request, ControllerMethodReflector $reflector, @@ -99,7 +106,9 @@ class SecurityMiddleware extends Middleware { bool $isAdminUser, bool $isSubAdmin, IAppManager $appManager, - IL10N $l10n + IL10N $l10n, + AuthorizedGroupMapper $mapper, + IUserSession $userSession ) { $this->navigationManager = $navigationManager; $this->request = $request; @@ -112,12 +121,15 @@ class SecurityMiddleware extends Middleware { $this->isSubAdmin = $isSubAdmin; $this->appManager = $appManager; $this->l10n = $l10n; + $this->groupAuthorizationMapper = $mapper; + $this->userSession = $userSession; } /** * This runs all the security checks before a method call. The * security checks are determined by inspecting the controller method * annotations + * * @param Controller $controller the controller * @param string $methodName the name of the method * @throws SecurityException when a security check fails @@ -140,15 +152,39 @@ class SecurityMiddleware extends Middleware { if (!$this->isLoggedIn) { throw new NotLoggedInException(); } + $authorized = false; + if ($this->reflector->hasAnnotation('AuthorizedAdminSetting')) { + $authorized = $this->isAdminUser; + + if (!$authorized && $this->reflector->hasAnnotation('SubAdminRequired')) { + $authorized = $this->isSubAdmin; + } + + if (!$authorized) { + $settingClasses = explode(';', $this->reflector->getAnnotationParameter('AuthorizedAdminSetting', 'settings')); + $authorizedClasses = $this->groupAuthorizationMapper->findAllClassesForUser($this->userSession->getUser()); + foreach ($settingClasses as $settingClass) { + $authorized = in_array($settingClass, $authorizedClasses, true); + if ($authorized) { + break; + } + } + } + if (!$authorized) { + throw new NotAdminException($this->l10n->t('Logged in user must be an admin, a sub admin or gotten special right to access this setting')); + } + } if ($this->reflector->hasAnnotation('SubAdminRequired') && !$this->isSubAdmin - && !$this->isAdminUser) { + && !$this->isAdminUser + && !$authorized) { throw new NotAdminException($this->l10n->t('Logged in user must be an admin or sub admin')); } if (!$this->reflector->hasAnnotation('SubAdminRequired') && !$this->reflector->hasAnnotation('NoAdminRequired') - && !$this->isAdminUser) { + && !$this->isAdminUser + && !$authorized) { throw new NotAdminException($this->l10n->t('Logged in user must be an admin')); } } @@ -200,19 +236,20 @@ class SecurityMiddleware extends Middleware { /** * If an SecurityException is being caught, ajax requests return a JSON error * response and non ajax requests redirect to the index + * * @param Controller $controller the controller that is being called * @param string $methodName the name of the method that will be called on * the controller * @param \Exception $exception the thrown exception - * @throws \Exception the passed in exception if it can't handle it * @return Response a Response object or null in case that the exception could not be handled + * @throws \Exception the passed in exception if it can't handle it */ public function afterException($controller, $methodName, \Exception $exception): Response { if ($exception instanceof SecurityException) { if ($exception instanceof StrictCookieMissingException) { return new RedirectResponse(\OC::$WEBROOT . '/'); } - if (stripos($this->request->getHeader('Accept'),'html') === false) { + if (stripos($this->request->getHeader('Accept'), 'html') === false) { $response = new JSONResponse( ['message' => $exception->getMessage()], $exception->getCode() diff --git a/lib/private/Settings/AuthorizedGroup.php b/lib/private/Settings/AuthorizedGroup.php new file mode 100644 index 00000000000..d549e3d48f3 --- /dev/null +++ b/lib/private/Settings/AuthorizedGroup.php @@ -0,0 +1,50 @@ +<?php + +/** + * @copyright Copyright (c) 2021 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @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 <https://www.gnu.org/licenses/>. + * + */ + +namespace OC\Settings; + +use OCP\AppFramework\Db\Entity; + +/** + * @method setGroupId(string $groupId) + * @method setClass(string $class) + * @method getGroupId(): string + * @method getClass(): string + */ +class AuthorizedGroup extends Entity implements \JsonSerializable { + + /** @var string $group_id */ + protected $groupId; + + /** @var string $class */ + protected $class; + + public function jsonSerialize(): array { + return [ + 'id' => $this->id, + 'group_id' => $this->groupId, + 'class' => $this->class + ]; + } +} diff --git a/lib/private/Settings/AuthorizedGroupMapper.php b/lib/private/Settings/AuthorizedGroupMapper.php new file mode 100644 index 00000000000..4313ce60580 --- /dev/null +++ b/lib/private/Settings/AuthorizedGroupMapper.php @@ -0,0 +1,125 @@ +<?php +/** + * @copyright Copyright (c) 2021 Carl Schwan <carl@carlschwan.eu> + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @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 <https://www.gnu.org/licenses/>. + * + */ + +namespace OC\Settings; + +use OCP\AppFramework\Db\Entity; +use OCP\AppFramework\Db\QBMapper; +use OCP\DB\Exception; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; + +class AuthorizedGroupMapper extends QBMapper { + public function __construct(IDBConnection $db) { + parent::__construct($db, 'authorized_groups', AuthorizedGroup::class); + } + + /** + * @throws Exception + */ + public function findAllClassesForUser(IUser $user): array { + $qb = $this->db->getQueryBuilder(); + + /** @var IGroupManager $groupManager */ + $groupManager = \OC::$server->get(IGroupManager::class); + $groups = $groupManager->getUserGroups($user); + if (count($groups) === 0) { + return []; + } + + $result = $qb->select('class') + ->from($this->getTableName(), 'auth') + ->where($qb->expr()->in('group_id', array_map(function (IGroup $group) use ($qb) { + return $qb->createNamedParameter($group->getGID()); + }, $groups), IQueryBuilder::PARAM_STR)) + ->executeQuery(); + + $classes = []; + while ($row = $result->fetch()) { + $classes[] = $row['class']; + } + $result->closeCursor(); + return $classes; + } + + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException + * @throws \OCP\DB\Exception + */ + public function find(int $id): AuthorizedGroup { + $queryBuilder = $this->db->getQueryBuilder(); + $queryBuilder->select('*') + ->from($this->getTableName()) + ->where($queryBuilder->expr()->eq('id', $queryBuilder->createNamedParameter($id))); + /** @var AuthorizedGroup $authorizedGroup */ + $authorizedGroup = $this->findEntity($queryBuilder); + return $authorizedGroup; + } + + /** + * Get all the authorizations stored in the database. + * + * @return AuthorizedGroup[] + * @throws \OCP\DB\Exception + */ + public function findAll(): array { + $qb = $this->db->getQueryBuilder(); + $qb->select('*')->from($this->getTableName()); + return $this->findEntities($qb); + } + + public function findByGroupIdAndClass(string $groupId, string $class) { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('group_id', $qb->createNamedParameter($groupId))) + ->andWhere($qb->expr()->eq('class', $qb->createNamedParameter($class))); + return $this->findEntity($qb); + } + + /** + * @return Entity[] + * @throws \OCP\DB\Exception + */ + public function findExistingGroupsForClass(string $class): array { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('class', $qb->createNamedParameter($class))); + return $this->findEntities($qb); + } + + /** + * @throws Exception + */ + public function removeGroup(string $gid) { + $qb = $this->db->getQueryBuilder(); + $qb->delete($this->getTableName()) + ->where($qb->expr()->eq('group_id', $qb->createNamedParameter($gid))) + ->executeStatement(); + } +} diff --git a/lib/private/Settings/Manager.php b/lib/private/Settings/Manager.php index d6b4ce7c080..6c567204253 100644 --- a/lib/private/Settings/Manager.php +++ b/lib/private/Settings/Manager.php @@ -11,6 +11,7 @@ * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author sualko <klaus@jsxc.org> + * @author Carl Schwan <carl@carlschwan.eu> * * @license GNU AGPL version 3 or any later version * @@ -28,23 +29,27 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OC\Settings; use Closure; use OCP\AppFramework\QueryException; +use OCP\Group\ISubAdmin; +use OCP\IGroupManager; use OCP\IL10N; -use OCP\ILogger; use OCP\IServerContainer; use OCP\IURLGenerator; +use OCP\IUser; use OCP\L10N\IFactory; use OCP\Settings\IIconSection; use OCP\Settings\IManager; use OCP\Settings\ISettings; use OCP\Settings\ISubAdminSettings; +use Psr\Log\LoggerInterface; class Manager implements IManager { - /** @var ILogger */ + /** @var LoggerInterface */ private $log; /** @var IL10N */ @@ -59,16 +64,31 @@ class Manager implements IManager { /** @var IServerContainer */ private $container; + /** @var AuthorizedGroupMapper $mapper */ + private $mapper; + + /** @var IGroupManager $groupManager */ + private $groupManager; + + /** @var ISubAdmin $subAdmin */ + private $subAdmin; + public function __construct( - ILogger $log, + LoggerInterface $log, IFactory $l10nFactory, IURLGenerator $url, - IServerContainer $container + IServerContainer $container, + AuthorizedGroupMapper $mapper, + IGroupManager $groupManager, + ISubAdmin $subAdmin ) { $this->log = $log; $this->l10nFactory = $l10nFactory; $this->url = $url; $this->container = $container; + $this->mapper = $mapper; + $this->groupManager = $groupManager; + $this->subAdmin = $subAdmin; } /** @var array */ @@ -106,18 +126,13 @@ class Manager implements IManager { } foreach (array_unique($this->sectionClasses[$type]) as $index => $class) { - try { - /** @var IIconSection $section */ - $section = \OC::$server->query($class); - } catch (QueryException $e) { - $this->log->logException($e, ['level' => ILogger::INFO]); - continue; - } + /** @var IIconSection $section */ + $section = \OC::$server->get($class); $sectionID = $section->getID(); if ($sectionID !== 'connected-accounts' && isset($this->sections[$type][$sectionID])) { - $this->log->logException(new \InvalidArgumentException('Section with the same ID already registered: ' . $sectionID . ', class: ' . $class), ['level' => ILogger::INFO]); + $this->log->info('', ['exception' => new \InvalidArgumentException('Section with the same ID already registered: ' . $sectionID . ', class: ' . $class)]); continue; } @@ -136,8 +151,9 @@ class Manager implements IManager { protected $settings = []; /** - * @param string $type 'admin' or 'personal' - * @param string $setting Class must implement OCP\Settings\ISetting + * @psam-param 'admin'|'personal' $type The type of the setting. + * @param string $setting Class must implement OCP\Settings\ISettings + * @param bool $allowedDelegation * * @return void */ @@ -167,14 +183,14 @@ class Manager implements IManager { try { /** @var ISettings $setting */ - $setting = $this->container->query($class); + $setting = $this->container->get($class); } catch (QueryException $e) { - $this->log->logException($e, ['level' => ILogger::INFO]); + $this->log->info($e->getMessage(), ['exception' => $e]); continue; } if (!$setting instanceof ISettings) { - $this->log->logException(new \InvalidArgumentException('Invalid settings setting registered (' . $class . ')'), ['level' => ILogger::INFO]); + $this->log->info('', ['exception' => new \InvalidArgumentException('Invalid settings setting registered (' . $class . ')')]); continue; } @@ -307,4 +323,52 @@ class Manager implements IManager { ksort($settings); return $settings; } + + public function getAllowedAdminSettings(string $section, IUser $user): array { + $isAdmin = $this->groupManager->isAdmin($user->getUID()); + $isSubAdmin = $this->subAdmin->isSubAdmin($user); + $subAdminOnly = !$isAdmin && $isSubAdmin; + + if ($subAdminOnly) { + // not an admin => look if the user is still authorized to access some + // settings + $subAdminSettingsFilter = function (ISettings $settings) { + return $settings instanceof ISubAdminSettings; + }; + $appSettings = $this->getSettings('admin', $section, $subAdminSettingsFilter); + } elseif ($isAdmin) { + $appSettings = $this->getSettings('admin', $section); + } else { + $authorizedSettingsClasses = $this->mapper->findAllClassesForUser($user); + $authorizedGroupFilter = function (ISettings $settings) use ($authorizedSettingsClasses) { + return in_array(get_class($settings), $authorizedSettingsClasses) === true; + }; + $appSettings = $this->getSettings('admin', $section, $authorizedGroupFilter); + } + + $settings = []; + foreach ($appSettings as $setting) { + if (!isset($settings[$setting->getPriority()])) { + $settings[$setting->getPriority()] = []; + } + $settings[$setting->getPriority()][] = $setting; + } + + ksort($settings); + return $settings; + } + + public function getAllAllowedAdminSettings(IUser $user): array { + $this->getSettings('admin', ''); // Make sure all the settings are loaded + $settings = []; + $authorizedSettingsClasses = $this->mapper->findAllClassesForUser($user); + foreach ($this->settings['admin'] as $section) { + foreach ($section as $setting) { + if (in_array(get_class($setting), $authorizedSettingsClasses) === true) { + $settings[] = $setting; + } + } + } + return $settings; + } } diff --git a/lib/private/legacy/OC_App.php b/lib/private/legacy/OC_App.php index bca0a3dd08e..811703570a2 100644 --- a/lib/private/legacy/OC_App.php +++ b/lib/private/legacy/OC_App.php @@ -61,6 +61,7 @@ use OCP\App\ManagerEvent; use OCP\AppFramework\QueryException; use OCP\Authentication\IAlternativeLogin; use OCP\ILogger; +use OCP\Settings\IManager as ISettingsManager; use Psr\Log\LoggerInterface; /** @@ -223,22 +224,22 @@ class OC_App { if (!empty($info['settings']['admin'])) { foreach ($info['settings']['admin'] as $setting) { - \OC::$server->getSettingsManager()->registerSetting('admin', $setting); + \OC::$server->get(ISettingsManager::class)->registerSetting('admin', $setting); } } if (!empty($info['settings']['admin-section'])) { foreach ($info['settings']['admin-section'] as $section) { - \OC::$server->getSettingsManager()->registerSection('admin', $section); + \OC::$server->get(ISettingsManager::class)->registerSection('admin', $section); } } if (!empty($info['settings']['personal'])) { foreach ($info['settings']['personal'] as $setting) { - \OC::$server->getSettingsManager()->registerSetting('personal', $setting); + \OC::$server->get(ISettingsManager::class)->registerSetting('personal', $setting); } } if (!empty($info['settings']['personal-section'])) { foreach ($info['settings']['personal-section'] as $section) { - \OC::$server->getSettingsManager()->registerSection('personal', $section); + \OC::$server->get(ISettingsManager::class)->registerSection('personal', $section); } } diff --git a/lib/public/Settings/IDelegatedSettings.php b/lib/public/Settings/IDelegatedSettings.php new file mode 100644 index 00000000000..f9bf98668fb --- /dev/null +++ b/lib/public/Settings/IDelegatedSettings.php @@ -0,0 +1,54 @@ +<?php +/** + * @copyright Copyright (c) Nextcloud GmbH + * + * @author Carl Schwan <carl@carlschwan.eu> + * + * @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 OCP\Settings; + +/** + * Special cases of settings that can be allowed to use by member of special + * groups. + * @since 23.0.0 + */ +interface IDelegatedSettings extends ISettings { + /** + * Get the name of the settings to differentiate settings inside a section or + * null if only the section name should be displayed. + * @since 23.0.0 + */ + public function getName(): ?string; + + /** + * Get a list of authorized app config that this setting is allowed to modify. + * The format of the array is the following: + * ```php + * <?php + * [ + * 'app_name' => [ + * '/simple_key/', # value + * '/s[a-z]*ldap/', # regex + * ], + * 'another_app_name => [ ... ], + * ] + * ``` + * @since 23.0.0 + */ + public function getAuthorizedAppConfig(): array; +} diff --git a/lib/public/Settings/IManager.php b/lib/public/Settings/IManager.php index 0d475092704..2ec3fb0fd21 100644 --- a/lib/public/Settings/IManager.php +++ b/lib/public/Settings/IManager.php @@ -23,8 +23,11 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCP\Settings; +use OCP\IUser; + /** * @since 9.1 */ @@ -50,7 +53,7 @@ interface IManager { public const KEY_PERSONAL_SECTION = 'personal-section'; /** - * @param string $type 'admin' or 'personal' + * @param string $type 'admin-section' or 'personal-section' * @param string $section Class must implement OCP\Settings\ISection * @since 14.0.0 */ @@ -58,7 +61,7 @@ interface IManager { /** * @param string $type 'admin' or 'personal' - * @param string $setting Class must implement OCP\Settings\ISetting + * @param string $setting Class must implement OCP\Settings\ISettings * @since 14.0.0 */ public function registerSetting(string $type, string $setting); @@ -66,7 +69,7 @@ interface IManager { /** * returns a list of the admin sections * - * @return array array of ISection[] where key is the priority + * @return array<int, array<int, IIconSection>> array from IConSection[] where key is the priority * @since 9.1.0 */ public function getAdminSections(): array; @@ -84,16 +87,32 @@ interface IManager { * * @param string $section the section id for which to load the settings * @param bool $subAdminOnly only return settings sub admins are supposed to see (since 17.0.0) - * @return array array of IAdmin[] where key is the priority + * @return array<int, array<int, ISettings>> array of ISettings[] where key is the priority * @since 9.1.0 */ public function getAdminSettings($section, bool $subAdminOnly = false): array; /** + * Returns a list of admin settings that the given user can use for the give section + * + * @return array<int, list<ISettings>> The array of admin settings there admin delegation is allowed. + * @since 23.0.0 + */ + public function getAllowedAdminSettings(string $section, IUser $user): array; + + /** + * Returns a list of admin settings that the given user can use. + * + * @return array<int, list<ISettings>> The array of admin settings there admin delegation is allowed. + * @since 23.0.0 + */ + public function getAllAllowedAdminSettings(IUser $user): array; + + /** * returns a list of the personal settings * * @param string $section the section id for which to load the settings - * @return array array of IPersonal[] where key is the priority + * @return array array of ISettings[] where key is the priority * @since 13.0.0 */ public function getPersonalSettings($section): array; |