Skip to content

Commit

Permalink
include circles to acl
Browse files Browse the repository at this point in the history
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
  • Loading branch information
ArtificialOwl committed Jan 24, 2025
1 parent 447ed37 commit 3b04873
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 28 deletions.
2 changes: 1 addition & 1 deletion lib/ACL/UserMapping/IUserMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion lib/ACL/UserMapping/UserMapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
90 changes: 83 additions & 7 deletions lib/ACL/UserMapping/UserMappingManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Command/ListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions lib/Controller/FolderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -474,16 +475,15 @@ private function folderDataForXML(array $data): array {
*
* @param int $id The ID of the Groupfolder
* @param string $search String to search by
* @return DataResponse<Http::STATUS_OK, array{users: list<GroupFoldersUser>, groups: list<GroupFoldersGroup>}, array{}>
* @return DataResponse<Http::STATUS_OK, array{users: list<GroupFoldersUser>, groups: list<GroupFoldersGroup>, circles: list<GroupFoldersCircle>}, array{}>

Check failure on line 478 in lib/Controller/FolderController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

MoreSpecificReturnType

lib/Controller/FolderController.php:478:13: MoreSpecificReturnType: The declared return type 'OCP\AppFramework\Http\DataResponse<200, array{circles: list<array{displayname: string, sid: string}>, groups: list<array{displayname: string, gid: string}>, users: list<array{displayname: string, uid: string}>}, array<never, never>>' for OCA\GroupFolders\Controller\FolderController::aclMappingSearch is more specific than the inferred return type 'OCP\AppFramework\Http\DataResponse<200, array{circles: array<array-key, mixed>, groups: list<array{displayname: string, gid: string}>, users: list<array{displayname: string, uid: string}>}, array<never, never>>' (see https://psalm.dev/070)
* @throws OCSForbiddenException Not allowed to search
*
* 200: ACL Mappings returned
*/
#[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();
Expand All @@ -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([

Check failure on line 498 in lib/Controller/FolderController.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

LessSpecificReturnStatement

lib/Controller/FolderController.php:498:10: LessSpecificReturnStatement: The type 'OCP\AppFramework\Http\DataResponse<200, array{circles: array<array-key, mixed>, groups: list<array{displayname: string, gid: string}>, users: list<array{displayname: string, uid: string}>}, array<never, never>>' is more general than the declared return type 'OCP\AppFramework\Http\DataResponse<200, array{circles: list<array{displayname: string, sid: string}>, groups: list<array{displayname: string, gid: string}>, users: list<array{displayname: string, uid: string}>}, array<never, never>>' for OCA\GroupFolders\Controller\FolderController::aclMappingSearch (see https://psalm.dev/129)
'users' => $users,
'groups' => $groups,
'circles' => $circles
]);
}
}
96 changes: 82 additions & 14 deletions lib/Folder/FolderManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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)));
}

Expand Down Expand Up @@ -401,6 +420,20 @@ private function getGroups(int $id): array {
], array_values(array_filter($groups)));
}

/**
* @throws Exception
* @return list<GroupFoldersCircle>
*/
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
Expand Down Expand Up @@ -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<GroupFoldersUser>
Expand All @@ -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) {

Check failure on line 534 in lib/Folder/FolderManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedDocblockClass

lib/Folder/FolderManager.php:534:9: UndefinedDocblockClass: Docblock-defined class, interface or enum named OCA\Circles\Model\Member does not exist (see https://psalm.dev/200)

Check failure on line 534 in lib/Folder/FolderManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedClass

lib/Folder/FolderManager.php:534:36: UndefinedClass: Class, interface or enum named OCA\Circles\Model\Member does not exist (see https://psalm.dev/019)
continue;
}

$uid = $member->getUserId();

Check failure on line 538 in lib/Folder/FolderManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedDocblockClass

lib/Folder/FolderManager.php:538:12: UndefinedDocblockClass: Docblock-defined class, interface or enum named OCA\Circles\Model\Member does not exist (see https://psalm.dev/200)
if (!isset($users[$uid])) {
$users[$uid] = [
'uid' => $uid,
'displayname' => $member->getDisplayName()

Check failure on line 542 in lib/Folder/FolderManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedDocblockClass

lib/Folder/FolderManager.php:542:24: UndefinedDocblockClass: Docblock-defined class, interface or enum named OCA\Circles\Model\Member does not exist (see https://psalm.dev/200)
];
}
}
}

return array_values($users);
}

Expand Down Expand Up @@ -918,27 +981,32 @@ 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();
$probe = new CircleProbe();
$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]);
} finally {
$circlesManager->stopSession();
}

return false;
return null;
}

public function getCirclesManager(): ?CirclesManager {
Expand Down
7 changes: 6 additions & 1 deletion lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
* displayname: string,
* }
*
* @psalm-type GroupFoldersCircle = array{
* sid: string,
* displayname: string,
* }
*
* @psalm-type GroupFoldersUser = array{
* uid: string,
* displayname: string,
Expand All @@ -31,7 +36,7 @@
* @psalm-type GroupFoldersAclManage = array{
* displayname: string,
* id: string,
* type: 'user'|'group',
* type: 'user'|'group'|'circle',
* }
*
* @psalm-type GroupFoldersApplicable = array{
Expand Down

0 comments on commit 3b04873

Please sign in to comment.