From 3b04873894bef6f344d3e09eb0d6a870bc48ccc6 Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Fri, 24 Jan 2025 01:40:20 -0100 Subject: [PATCH] include circles to acl Signed-off-by: Maxence Lange --- lib/ACL/UserMapping/IUserMapping.php | 2 +- lib/ACL/UserMapping/UserMapping.php | 2 +- lib/ACL/UserMapping/UserMappingManager.php | 90 ++++++++++++++++++-- lib/Command/ListCommand.php | 2 +- lib/Controller/FolderController.php | 8 +- lib/Folder/FolderManager.php | 96 ++++++++++++++++++---- lib/ResponseDefinitions.php | 7 +- 7 files changed, 179 insertions(+), 28 deletions(-) diff --git a/lib/ACL/UserMapping/IUserMapping.php b/lib/ACL/UserMapping/IUserMapping.php index f86d7fbc4..e9f73687e 100644 --- a/lib/ACL/UserMapping/IUserMapping.php +++ b/lib/ACL/UserMapping/IUserMapping.php @@ -9,7 +9,7 @@ namespace OCA\GroupFolders\ACL\UserMapping; interface IUserMapping { - /** @return 'user'|'group'|'dummy' */ + /** @return 'user'|'group'|'dummy'|'circle' */ public function getType(): string; public function getId(): string; diff --git a/lib/ACL/UserMapping/UserMapping.php b/lib/ACL/UserMapping/UserMapping.php index 2a9b2e207..2a0cb36d4 100644 --- a/lib/ACL/UserMapping/UserMapping.php +++ b/lib/ACL/UserMapping/UserMapping.php @@ -12,7 +12,7 @@ class UserMapping implements IUserMapping { private string $displayName; /** - * @param 'user'|'group'|'dummy' $type + * @param 'user'|'group'|'dummy'|'circle' $type */ public function __construct( private string $type, diff --git a/lib/ACL/UserMapping/UserMappingManager.php b/lib/ACL/UserMapping/UserMappingManager.php index 33a607666..c894623a4 100644 --- a/lib/ACL/UserMapping/UserMappingManager.php +++ b/lib/ACL/UserMapping/UserMappingManager.php @@ -8,33 +8,109 @@ namespace OCA\GroupFolders\ACL\UserMapping; +use OCA\Circles\CirclesManager; +use OCA\Circles\Exceptions\CircleNotFoundException; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\Probes\CircleProbe; +use OCP\AutoloadNotAllowedException; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserManager; +use OCP\Server; +use Psr\Container\ContainerExceptionInterface; +use Psr\Log\LoggerInterface; class UserMappingManager implements IUserMappingManager { public function __construct( private IGroupManager $groupManager, private IUserManager $userManager, + private LoggerInterface $logger, ) { } public function getMappingsForUser(IUser $user, bool $userAssignable = true): array { $groupMappings = array_values(array_map(fn (IGroup $group): UserMapping => new UserMapping('group', $group->getGID(), $group->getDisplayName()), $this->groupManager->getUserGroups($user))); + $circleMappings = array_values(array_map(fn (Circle $circle): UserMapping => new UserMapping('circle', $circle->getSingleId(), $circle->getDisplayName()), $this->getUserCircles($user->getUID()))); return array_merge([ new UserMapping('user', $user->getUID(), $user->getDisplayName()), - ], $groupMappings); + ], $groupMappings, $circleMappings); } public function mappingFromId(string $type, string $id): ?IUserMapping { - $mappingObject = ($type === 'group' ? $this->groupManager : $this->userManager)->get($id); - if ($mappingObject) { - $displayName = $mappingObject->getDisplayName(); - /** @var 'user'|'group' $type */ - return new UserMapping($type, $id, $displayName); - } else { + switch ($type) { + case 'group': + $displayName = $this->groupManager->get($id)?->getDisplayName(); + break; + case 'user': + $displayName = $this->userManager->get($id)?->getDisplayName(); + break; + case 'circle': + $displayName = $this->getCircle($id)?->getDisplayName(); + break; + default: + return null; + } + if ($displayName === null) { + return null; + } + + return new UserMapping($type, $id, $displayName); + } + + + + /** + * returns the Circle from its single Id, or NULL if not available + */ + private function getCircle(string $groupId): ?Circle { + $circlesManager = $this->getCirclesManager(); + if ($circlesManager === null) { + return null; + } + + $circlesManager->startSuperSession(); + $probe = new CircleProbe(); + $probe->includeSystemCircles(); + $probe->includeSingleCircles(); + try { + return $circlesManager->getCircle($groupId, $probe); + } catch (CircleNotFoundException) { + } catch (\Exception $e) { + $this->logger->warning('', ['exception' => $e]); + } finally { + $circlesManager->stopSession(); + } + + return null; + } + + /** + * returns list of circles a user is member of + */ + private function getUserCircles(string $userId): array { + $circlesManager = $this->getCirclesManager(); + if ($circlesManager === null) { + return []; + } + + $circlesManager->startSession($circlesManager->getLocalFederatedUser($userId)); + try { + return $circlesManager->probeCircles(); + } catch (\Exception $e) { + $this->logger->warning('', ['exception' => $e]); + } finally { + $circlesManager->stopSession(); + } + + return []; + } + + public function getCirclesManager(): ?CirclesManager { + try { + return Server::get(CirclesManager::class); + } catch (ContainerExceptionInterface|AutoloadNotAllowedException) { return null; } } diff --git a/lib/Command/ListCommand.php b/lib/Command/ListCommand.php index 2b5eafcc8..54c194412 100644 --- a/lib/Command/ListCommand.php +++ b/lib/Command/ListCommand.php @@ -106,7 +106,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int }, array_keys($folder['groups']), array_values($folder['groups'])); $folder['groups'] = implode("\n", $groupStrings); $folder['acl'] = $folder['acl'] ? 'Enabled' : 'Disabled'; - $manageStrings = array_map(fn (array $manage): string => $manage['id'] . ' (' . $manage['type'] . ')', $folder['manage']); + $manageStrings = array_map(fn (array $manage): string => $manage['displayname'] . ' (' . $manage['type'] . ')', $folder['manage']); $folder['manage'] = implode("\n", $manageStrings); return $folder; diff --git a/lib/Controller/FolderController.php b/lib/Controller/FolderController.php index 0293a2019..fba8640f5 100644 --- a/lib/Controller/FolderController.php +++ b/lib/Controller/FolderController.php @@ -30,6 +30,7 @@ /** * @psalm-import-type GroupFoldersGroup from ResponseDefinitions + * @psalm-import-type GroupFoldersCircle from ResponseDefinitions * @psalm-import-type GroupFoldersUser from ResponseDefinitions * @psalm-import-type GroupFoldersFolder from ResponseDefinitions * @psalm-import-type InternalFolderOut from FolderManager @@ -474,7 +475,7 @@ private function folderDataForXML(array $data): array { * * @param int $id The ID of the Groupfolder * @param string $search String to search by - * @return DataResponse, groups: list}, array{}> + * @return DataResponse, groups: list, circles: list}, array{}> * @throws OCSForbiddenException Not allowed to search * * 200: ACL Mappings returned @@ -482,8 +483,7 @@ private function folderDataForXML(array $data): array { #[NoAdminRequired] #[FrontpageRoute(verb: 'GET', url: '/folders/{id}/search')] public function aclMappingSearch(int $id, string $search = ''): DataResponse { - $users = []; - $groups = []; + $users = $groups = $circles = []; if ($this->user === null) { throw new OCSForbiddenException(); @@ -492,11 +492,13 @@ public function aclMappingSearch(int $id, string $search = ''): DataResponse { if ($this->manager->canManageACL($id, $this->user) === true) { $groups = $this->manager->searchGroups($id, $search); $users = $this->manager->searchUsers($id, $search); + $circles = $this->manager->searchCircles($id, $search); } return new DataResponse([ 'users' => $users, 'groups' => $groups, + 'circles' => $circles ]); } } diff --git a/lib/Folder/FolderManager.php b/lib/Folder/FolderManager.php index 971aaaea3..50ddde9f2 100644 --- a/lib/Folder/FolderManager.php +++ b/lib/Folder/FolderManager.php @@ -11,10 +11,11 @@ use OC\Files\Node\Node; use OCA\Circles\CirclesManager; use OCA\Circles\Exceptions\CircleNotFoundException; +use OCA\Circles\Model\Circle; +use OCA\Circles\Model\Member; use OCA\Circles\Model\Probes\CircleProbe; use OCA\GroupFolders\Mount\GroupMountPoint; use OCA\GroupFolders\ResponseDefinitions; -use OCA\GroupFolders\Settings\Admin; use OCP\AutoloadNotAllowedException; use OCP\Constants; use OCP\DB\Exception; @@ -36,6 +37,7 @@ /** * @psalm-import-type GroupFoldersGroup from ResponseDefinitions + * @psalm-import-type GroupFoldersCircle from ResponseDefinitions * @psalm-import-type GroupFoldersUser from ResponseDefinitions * @psalm-import-type GroupFoldersAclManage from ResponseDefinitions * @psalm-import-type GroupFoldersApplicable from ResponseDefinitions @@ -264,16 +266,33 @@ private function getManageAcl(array $mappings): array { ]; } - $group = Server::get(IGroupManager::class)->get($entry['mapping_id']); - if ($group === null) { - return null; + if ($entry['mapping_type'] === 'group') { + $group = Server::get(IGroupManager::class)->get($entry['mapping_id']); + if ($group === null) { + return null; + } + + return [ + 'type' => 'group', + 'id' => $group->getGID(), + 'displayname' => $group->getDisplayName() + ]; } - return [ - 'type' => 'group', - 'id' => $group->getGID(), - 'displayname' => $group->getDisplayName() - ]; + if ($entry['mapping_type'] === 'circle') { + $circle = $this->getCircle($entry['mapping_id']); + if ($circle === null) { + return null; + } + + return [ + 'type' => 'circle', + 'id' => $circle->getSingleId(), + 'displayname' => $circle->getDisplayName() + ]; + } + + return null; }, $mappings))); } @@ -401,6 +420,20 @@ private function getGroups(int $id): array { ], array_values(array_filter($groups))); } + /** + * @throws Exception + * @return list + */ + private function getCircles(int $id): array { + $circles = $this->getAllApplicable()[$id] ?? []; + $circles = array_map(fn (string $singleId): ?Circle => $this->getCircle($singleId), array_keys($circles)); + + return array_map(fn (Circle $circle): array => [ + 'sid' => $circle->getSingleId(), + 'displayname' => $circle->getDisplayName() + ], array_values(array_filter($circles))); + } + /** * Check if the user is able to configure the advanced folder permissions. This * is the case if the user is an admin, has admin permissions for the group folder @@ -460,6 +493,15 @@ public function searchGroups(int $id, string $search = ''): array { return array_values(array_filter($groups, fn (array $group): bool => (stripos($group['gid'], $search) !== false) || (stripos($group['displayname'], $search) !== false))); } + public function searchCircles(int $id, string $search = ''): array { + $circles = $this->getCircles($id); + if ($search === '') { + return $circles; + } + + return array_values(array_filter($circles, fn (array $circle): bool => (stripos($circle['displayname'], $search) !== false))); + } + /** * @throws Exception * @return list @@ -482,6 +524,27 @@ public function searchUsers(int $id, string $search = '', int $limit = 10, int $ } } + foreach ($this->getCircles($id) as $circleData) { + $circle = $this->getCircle($circleData['sid']); + if ($circle === null) { + continue; + } + + foreach ($circle->getInheritedMembers(false) as $member) { + if ($member->getUserType() !== Member::TYPE_USER) { + continue; + } + + $uid = $member->getUserId(); + if (!isset($users[$uid])) { + $users[$uid] = [ + 'uid' => $uid, + 'displayname' => $member->getDisplayName() + ]; + } + } + } + return array_values($users); } @@ -918,9 +981,16 @@ public function getFolderPermissionsForUser(IUser $user, int $folderId): int { * returns if the groupId is in fact the singleId of an existing Circle */ public function isACircle(string $groupId): bool { + return ($this->getCircle($groupId) !== null); + } + + /** + * returns the Circle from its single Id, or NULL if not available + */ + public function getCircle(string $groupId): ?Circle { $circlesManager = $this->getCirclesManager(); if ($circlesManager === null) { - return false; + return null; } $circlesManager->startSuperSession(); @@ -928,9 +998,7 @@ public function isACircle(string $groupId): bool { $probe->includeSystemCircles(); $probe->includeSingleCircles(); try { - $circlesManager->getCircle($groupId, $probe); - - return true; + return $circlesManager->getCircle($groupId, $probe); } catch (CircleNotFoundException) { } catch (\Exception $e) { $this->logger->warning('', ['exception' => $e]); @@ -938,7 +1006,7 @@ public function isACircle(string $groupId): bool { $circlesManager->stopSession(); } - return false; + return null; } public function getCirclesManager(): ?CirclesManager { diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index ddfa678fb..6ab980540 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -23,6 +23,11 @@ * displayname: string, * } * + * @psalm-type GroupFoldersCircle = array{ + * sid: string, + * displayname: string, + * } + * * @psalm-type GroupFoldersUser = array{ * uid: string, * displayname: string, @@ -31,7 +36,7 @@ * @psalm-type GroupFoldersAclManage = array{ * displayname: string, * id: string, - * type: 'user'|'group', + * type: 'user'|'group'|'circle', * } * * @psalm-type GroupFoldersApplicable = array{