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/private/Settings | |
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/private/Settings')
-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 |
3 files changed, 256 insertions, 17 deletions
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; + } } |