From dfa2d89ad53ab5823b8b0a65d6c32b0f5326b09c Mon Sep 17 00:00:00 2001 From: Pierre Ozoux Date: Mon, 2 May 2022 14:44:40 +0200 Subject: [PATCH] Adds groups. --- appinfo/routes.php | 3 +- lib/Controller/ASCIMGroup.php | 119 +++++++++++ lib/Controller/GroupController.php | 225 +++++++++++++++++++ lib/Controller/GroupsController.php | 320 ---------------------------- 4 files changed, 346 insertions(+), 321 deletions(-) create mode 100644 lib/Controller/ASCIMGroup.php create mode 100644 lib/Controller/GroupController.php delete mode 100644 lib/Controller/GroupsController.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 0e3f288..f023c1b 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -27,6 +27,7 @@ */ return [ 'resources' => [ - 'user' => ['url' => '/Users'] + 'user' => ['url' => '/Users'], + 'group' => ['url' => '/Groups'] ] ]; diff --git a/lib/Controller/ASCIMGroup.php b/lib/Controller/ASCIMGroup.php new file mode 100644 index 0000000..1ef999c --- /dev/null +++ b/lib/Controller/ASCIMGroup.php @@ -0,0 +1,119 @@ + + * + * @author Arthur Schiwon + * @author Christoph Wurst + * @author Georg Ehrke + * @author Joas Schilling + * @author John Molakvoæ + * @author Roeland Jago Douma + * @author Vincent Petry + * + * @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\SCIMServiceProvider\Controller; + +use OC\Group\Manager; +use OCP\Accounts\IAccountManager; +use OCP\Accounts\PropertyDoesNotExistException; +use OCP\AppFramework\ApiController; +use OCP\AppFramework\Http; +use OCP\Files\NotFoundException; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IRequest; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\L10N\IFactory; +use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse; +use OCP\AppFramework\Http\DataResponse; +use OCP\IUser; +use Psr\Log\LoggerInterface; + +abstract class ASCIMGroup extends ApiController { + /** @var IUserManager */ + protected $userManager; + /** @var IConfig */ + protected $config; + /** @var IGroupManager|Manager */ // FIXME Requires a method that is not on the interface + protected $groupManager; + /** @var IUserSession */ + protected $userSession; + /** @var IAccountManager */ + protected $accountManager; + + public function __construct(string $appName, + IRequest $request, + IUserManager $userManager, + IConfig $config, + IGroupManager $groupManager, + IUserSession $userSession, + IAccountManager $accountManager) { + parent::__construct($appName, $request); + + $this->userManager = $userManager; + $this->config = $config; + $this->groupManager = $groupManager; + $this->userSession = $userSession; + $this->accountManager = $accountManager; + } + + /** + * creates an object with all group data + * + * @param string $groupId + * @param bool $includeScopes + * @return array + * @throws Exception + */ + protected function getSCIMGroup(string $groupId): array { + $groupId = urldecode($groupId); + + // Check the group exists + $group = $this->groupManager->get($groupId); + if ($group === null) { + return []; + } + + $members = array(); + foreach ($this->groupManager->get($groupId)->getUsers() as $member) { + $members[] = [ + 'value' => $member->getUID(), + '$ref' => '/Users/' . $member->getUID(), + 'display' => $member->getDisplayName() + ]; + } + + return [ + 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:Group'], + 'id' => $groupId, + 'displayName' => $group->getDisplayName(), + 'externalId' => '1234', // todo + 'meta' => [ + 'resourceType' => 'Group', + 'location' => '/Groups/' . $groupId, + 'created' => '2022-04-28T18:27:17.783Z', // todo + 'lastModified' => '2022-04-28T18:27:17.783Z' // todo + ], + 'members' => $members + ]; + } +} diff --git a/lib/Controller/GroupController.php b/lib/Controller/GroupController.php new file mode 100644 index 0000000..1dd44a2 --- /dev/null +++ b/lib/Controller/GroupController.php @@ -0,0 +1,225 @@ + + * @author Bjoern Schiessle + * @author Christoph Wurst + * @author Daniel Calviño Sánchez + * @author Daniel Kesselberg + * @author Joas Schilling + * @author John Molakvoæ + * @author Julius Härtl + * @author Lukas Reschke + * @author michag86 + * @author Mikael Hammarin + * @author Morris Jobke + * @author Robin Appelman + * @author Roeland Jago Douma + * @author Sujith Haridasan + * @author Thomas Citharel + * @author Thomas Müller + * @author Tom Needham + * @author Vincent Petry + * + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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, version 3, + * along with this program. If not, see + * + */ +namespace OCA\SCIMServiceProvider\Controller; + +use InvalidArgumentException; +use OC\HintException; +use OC\KnownUser\KnownUserService; +use OCP\Accounts\IAccountManager; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCSController; +use OCP\AppFramework\Http\Response; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IRequest; +use OCP\IURLGenerator; +use OCP\IUserManager; +use OCP\IUserSession; +use OCP\Security\ISecureRandom; +use OCP\EventDispatcher\IEventDispatcher; +use Psr\Log\LoggerInterface; +use OCA\SCIMServiceProvider\Responses\SCIMListResponse; +use OCA\SCIMServiceProvider\Responses\SCIMJSONResponse; +use OCA\SCIMServiceProvider\Responses\SCIMErrorResponse; + + +class GroupController extends ASCIMGroup { + + /** @var IURLGenerator */ + protected $urlGenerator; + /** @var LoggerInterface */ + private $logger; + /** @var ISecureRandom */ + private $secureRandom; + /** @var KnownUserService */ + private $knownUserService; + /** @var IEventDispatcher */ + private $eventDispatcher; + + public function __construct(string $appName, + IRequest $request, + IUserManager $userManager, + IConfig $config, + IGroupManager $groupManager, + IUserSession $userSession, + IAccountManager $accountManager, + IURLGenerator $urlGenerator, + LoggerInterface $logger, + ISecureRandom $secureRandom, + KnownUserService $knownUserService, + IEventDispatcher $eventDispatcher) { + parent::__construct($appName, + $request, + $userManager, + $config, + $groupManager, + $userSession, + $accountManager); + + $this->urlGenerator = $urlGenerator; + $this->logger = $logger; + $this->secureRandom = $secureRandom; + $this->knownUserService = $knownUserService; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @NoCSRFRequired + * + * returns a list of groups and their data + */ + public function index(): SCIMListResponse { + $SCIMGroups = $this->groupManager->search('', null, 0); + $SCIMGroups = array_map(function ($group) { + return $this->getSCIMGroup($group->getGID()); + }, $SCIMGroups); + return new SCIMListResponse($SCIMGroups); + } + + /** + * @NoCSRFRequired + * + * gets group info + * + * @param string $id + * @return SCIMJSONResponse + * @throws Exception + */ + public function show(string $id): SCIMJSONResponse { + $group = $this->getSCIMGroup($id); + // getUserData returns empty array if not enough permissions + if (empty($group)) { + return new SCIMErrorResponse(['message' => 'Group not found'], 404); + } + return new SCIMJSONResponse($group); + } + + /** + * @NoCSRFRequired + * + * @param string $displayName + * @param array $members + * @return SCIMJSONResponse + * @throws Exception + */ + public function create( string $displayName = '', + array $members = []): SCIMJSONResponse { + + $id=urldecode($displayName); + // Validate name + if (empty($id)) { + $this->logger->error('Group name not supplied', ['app' => 'provisioning_api']); + return new SCIMErrorResponse(['message' => 'Invalid group name'], 101); + } + // Check if it exists + if ($this->groupManager->groupExists($id)) { + return new SCIMErrorResponse(['message' => 'Group exists'], 409); + } + $group = $this->groupManager->createGroup($id); + if ($group === null) { + return new SCIMErrorResponse(['message' => 'Not supported by backend'], 103); + } + if ($displayname !== '') { + $group->setDisplayName($displayName); + } + foreach ($members as $member) { + $targetUser = $this->userManager->get($member['value']); + $group->addUser($targetUser); + } + return new SCIMJSONResponse($this->getSCIMGroup($id)); + } + + + /** + * @NoCSRFRequired + * + * @param string $id + * + * @param bool $active + * @param string $displayName + * @param array $emails + * @return DataResponse + * @throws Exception + */ + public function update( string $id, + bool $active, + string $displayName = '', + array $emails = []): SCIMJSONResponse { + $targetUser = $this->userManager->get($id); + if ($targetUser === null) { + return new SCIMErrorResponse(['message' => 'User not found'], 404); + } + foreach ($emails as $email) { + if ($email['primary'] === true) { + $targetUser->setEMailAddress($email['value']); + } + } + if (isset($active)) { + $targetUser->setEnabled($active); + } + return new SCIMJSONResponse($this->getSCIMUser($id)); + + } + + /** + * @NoCSRFRequired + * + * @param string $id + * @return DataResponse + */ + public function destroy(string $id): Response { + $groupId = urldecode($id); + + // Check it exists + if (!$this->groupManager->groupExists($groupId)) { + return new SCIMErrorResponse(['message' => 'Group not found'], 404); + } elseif ($groupId === 'admin' || !$this->groupManager->get($groupId)->delete()) { + // Cannot delete admin group + return new SCIMErrorResponse(['message' => 'Can\'t delete this group, not enough rights or admin group'], 403); + } + $response = new Response(); + $response->setStatus(204); + return $response; + } + +} diff --git a/lib/Controller/GroupsController.php b/lib/Controller/GroupsController.php deleted file mode 100644 index 8336449..0000000 --- a/lib/Controller/GroupsController.php +++ /dev/null @@ -1,320 +0,0 @@ - - * @author Christoph Wurst - * @author Joas Schilling - * @author John Molakvoæ - * @author Julius Härtl - * @author Lukas Reschke - * @author Robin Appelman - * @author Roeland Jago Douma - * @author Tom Needham - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * 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, version 3, - * along with this program. If not, see - * - */ -namespace OCA\SCIMServiceProvider\Controller; - -use OCP\Accounts\IAccountManager; -use OCP\AppFramework\Http\DataResponse; -use OCP\AppFramework\OCSController; -use OCP\IConfig; -use OCP\IGroup; -use OCP\IGroupManager; -use OCP\IRequest; -use OCP\IUser; -use OCP\IUserManager; -use OCP\IUserSession; -use Psr\Log\LoggerInterface; - -class GroupsController extends AUserData { - - /** @var LoggerInterface */ - private $logger; - - public function __construct(string $appName, - IRequest $request, - IUserManager $userManager, - IConfig $config, - IGroupManager $groupManager, - IUserSession $userSession, - IAccountManager $accountManager, - LoggerInterface $logger) { - parent::__construct($appName, - $request, - $userManager, - $config, - $groupManager, - $userSession, - $accountManager - ); - - $this->logger = $logger; - } - - /** - * returns a list of groups - * - * @NoAdminRequired - * - * @param string $search - * @param int $limit - * @param int $offset - * @return DataResponse - */ - public function getGroups(string $search = '', int $limit = null, int $offset = 0): DataResponse { - $groups = $this->groupManager->search($search, $limit, $offset); - $groups = array_map(function ($group) { - /** @var IGroup $group */ - return $group->getGID(); - }, $groups); - - return new DataResponse(['groups' => $groups]); - } - - /** - * returns a list of groups details with ids and displaynames - * - * @NoAdminRequired - * - * @param string $search - * @param int $limit - * @param int $offset - * @return DataResponse - */ - public function getGroupsDetails(string $search = '', int $limit = null, int $offset = 0): DataResponse { - $groups = $this->groupManager->search($search, $limit, $offset); - $groups = array_map(function ($group) { - /** @var IGroup $group */ - return [ - 'id' => $group->getGID(), - 'displayname' => $group->getDisplayName(), - 'usercount' => $group->count(), - 'disabled' => $group->countDisabled(), - 'canAdd' => $group->canAddUser(), - 'canRemove' => $group->canRemoveUser(), - ]; - }, $groups); - - return new DataResponse(['groups' => $groups]); - } - - /** - * @NoAdminRequired - * - * @param string $groupId - * @return DataResponse - * @throws OCSException - * - * @deprecated 14 Use getGroupUsers - */ - public function getGroup(string $groupId): DataResponse { - return $this->getGroupUsers($groupId); - } - - /** - * returns an array of users in the specified group - * - * @NoAdminRequired - * - * @param string $groupId - * @return DataResponse - * @throws OCSException - */ - public function getGroupUsers(string $groupId): DataResponse { - $groupId = urldecode($groupId); - - $user = $this->userSession->getUser(); - $isSubadminOfGroup = false; - - // Check the group exists - $group = $this->groupManager->get($groupId); - if ($group !== null) { - $isSubadminOfGroup = $this->groupManager->getSubAdmin()->isSubAdminOfGroup($user, $group); - } else { - throw new OCSNotFoundException('The requested group could not be found'); - } - - // Check subadmin has access to this group - if ($this->groupManager->isAdmin($user->getUID()) - || $isSubadminOfGroup) { - $users = $this->groupManager->get($groupId)->getUsers(); - $users = array_map(function ($user) { - /** @var IUser $user */ - return $user->getUID(); - }, $users); - $users = array_values($users); - return new DataResponse(['users' => $users]); - } - - throw new OCSForbiddenException(); - } - - /** - * returns an array of users details in the specified group - * - * @NoAdminRequired - * - * @param string $groupId - * @param string $search - * @param int $limit - * @param int $offset - * @return DataResponse - * @throws OCSException - */ - public function getGroupUsersDetails(string $groupId, string $search = '', int $limit = null, int $offset = 0): DataResponse { - $groupId = urldecode($groupId); - $currentUser = $this->userSession->getUser(); - - // Check the group exists - $group = $this->groupManager->get($groupId); - if ($group !== null) { - $isSubadminOfGroup = $this->groupManager->getSubAdmin()->isSubAdminOfGroup($currentUser, $group); - } else { - throw new OCSException('The requested group could not be found', OCSController::RESPOND_NOT_FOUND); - } - - // Check subadmin has access to this group - if ($this->groupManager->isAdmin($currentUser->getUID()) || $isSubadminOfGroup) { - $users = $group->searchUsers($search, $limit, $offset); - - // Extract required number - $usersDetails = []; - foreach ($users as $user) { - try { - /** @var IUser $user */ - $userId = (string)$user->getUID(); - $userData = $this->getUserData($userId); - // Do not insert empty entry - if (!empty($userData)) { - $usersDetails[$userId] = $userData; - } else { - // Logged user does not have permissions to see this user - // only showing its id - $usersDetails[$userId] = ['id' => $userId]; - } - } catch (OCSNotFoundException $e) { - // continue if a users ceased to exist. - } - } - return new DataResponse(['users' => $usersDetails]); - } - - throw new OCSException('The requested group could not be found', OCSController::RESPOND_NOT_FOUND); - } - - /** - * creates a new group - * - * @PasswordConfirmationRequired - * - * @param string $groupid - * @param string $displayname - * @return DataResponse - * @throws OCSException - */ - public function addGroup(string $groupid, string $displayname = ''): DataResponse { - // Validate name - if (empty($groupid)) { - $this->logger->error('Group name not supplied', ['app' => 'provisioning_api']); - throw new OCSException('Invalid group name', 101); - } - // Check if it exists - if ($this->groupManager->groupExists($groupid)) { - throw new OCSException('group exists', 102); - } - $group = $this->groupManager->createGroup($groupid); - if ($group === null) { - throw new OCSException('Not supported by backend', 103); - } - if ($displayname !== '') { - $group->setDisplayName($displayname); - } - return new DataResponse(); - } - - /** - * @PasswordConfirmationRequired - * - * @param string $groupId - * @param string $key - * @param string $value - * @return DataResponse - * @throws OCSException - */ - public function updateGroup(string $groupId, string $key, string $value): DataResponse { - $groupId = urldecode($groupId); - - if ($key === 'displayname') { - $group = $this->groupManager->get($groupId); - if ($group->setDisplayName($value)) { - return new DataResponse(); - } - - throw new OCSException('Not supported by backend', 101); - } else { - throw new OCSException('', OCSController::RESPOND_UNKNOWN_ERROR); - } - } - - /** - * @PasswordConfirmationRequired - * - * @param string $groupId - * @return DataResponse - * @throws OCSException - */ - public function deleteGroup(string $groupId): DataResponse { - $groupId = urldecode($groupId); - - // Check it exists - if (!$this->groupManager->groupExists($groupId)) { - throw new OCSException('', 101); - } elseif ($groupId === 'admin' || !$this->groupManager->get($groupId)->delete()) { - // Cannot delete admin group - throw new OCSException('', 102); - } - - return new DataResponse(); - } - - /** - * @param string $groupId - * @return DataResponse - * @throws OCSException - */ - public function getSubAdminsOfGroup(string $groupId): DataResponse { - // Check group exists - $targetGroup = $this->groupManager->get($groupId); - if ($targetGroup === null) { - throw new OCSException('Group does not exist', 101); - } - - /** @var IUser[] $subadmins */ - $subadmins = $this->groupManager->getSubAdmin()->getGroupsSubAdmins($targetGroup); - // New class returns IUser[] so convert back - $uids = []; - foreach ($subadmins as $user) { - $uids[] = $user->getUID(); - } - - return new DataResponse($uids); - } -}