From 22c06b5689e1d30b51e04c0e2cfff510476e0058 Mon Sep 17 00:00:00 2001 From: Jonathan Treffler Date: Wed, 6 Nov 2024 19:58:57 +0100 Subject: [PATCH] finalized ACL management --- lib/Service/OrganizationFolderService.php | 18 +++- lib/Service/ResourceMemberService.php | 27 ++++- lib/Service/ResourceService.php | 126 +++++++++++++++------- 3 files changed, 125 insertions(+), 46 deletions(-) diff --git a/lib/Service/OrganizationFolderService.php b/lib/Service/OrganizationFolderService.php index 5524e7c..19953c5 100644 --- a/lib/Service/OrganizationFolderService.php +++ b/lib/Service/OrganizationFolderService.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace OCA\OrganizationFolders\Service; +use Psr\Container\ContainerInterface; + use OCP\AppFramework\Db\TTransactional; use OCP\IDBConnection; @@ -31,7 +33,7 @@ class OrganizationFolderService { protected PathManager $pathManager, protected GroupfolderManager $groupfolderManager, protected ACLManager $aclManager, - protected ResourceService $resourceService, + protected ContainerInterface $container, ) { } @@ -96,6 +98,8 @@ class OrganizationFolderService { organizationId: $organizationId, ); + $this->applyPermissions($groupfolderId); + return $organizationFolder; }, $this->db); } @@ -107,7 +111,7 @@ class OrganizationFolderService { ?string $organizationProviderId = null, ?int $organizationId = null ): OrganizationFolder { - return $this->atomic(function () use ($id, $name, $quota, $organizationProviderId, $organizationId) { + $this->atomic(function () use ($id, $name, $quota, $organizationProviderId, $organizationId) { if(isset($name)) { $this->folderManager->renameFolder($id, $name); } @@ -136,9 +140,11 @@ class OrganizationFolderService { $this->tagService->update($id, "organization_provider", $organizationProviderId); $this->tagService->update($id, "organization_id", (string)$organization->getId()); } - - return $this->find($id); }, $this->db); + + $this->applyPermissions($id); + + return $this->find($id); } public function applyPermissions(int $id) { @@ -149,7 +155,9 @@ class OrganizationFolderService { $this->setGroupsAsGroupfolderMembers($organizationFolder->getId(), $memberGroups); $this->setRootFolderACLs($organizationFolder, $memberGroups); - return $this->resourceService->setAllFolderResourceAclsInOrganizationFolder($organizationFolder, $memberGroups); + /** @var ResourceService */ + $resourceService = $this->container->get(ResourceService::class); + return $resourceService->setAllFolderResourceAclsInOrganizationFolder($organizationFolder, $memberGroups); } protected function getMemberGroups(OrganizationFolder $organizationFolder) { diff --git a/lib/Service/ResourceMemberService.php b/lib/Service/ResourceMemberService.php index 8d87a2b..0253281 100644 --- a/lib/Service/ResourceMemberService.php +++ b/lib/Service/ResourceMemberService.php @@ -17,7 +17,9 @@ use OCA\OrganizationFolders\Enum\MemberType; class ResourceMemberService { public function __construct( - private ResourceMemberMapper $mapper + protected ResourceMemberMapper $mapper, + protected ResourceService $resourceService, + protected OrganizationFolderService $organizationFolderService, ) { } @@ -48,8 +50,11 @@ class ResourceMemberService { MemberType $type, string $principal ): ResourceMember { + $resource = $this->resourceService->find($resourceId); + $member = new ResourceMember(); - $member->setResourceId($resourceId); + + $member->setResourceId($resource->getId()); $member->setPermissionLevel($permissionLevel->value); $member->setType($type->value); @@ -58,7 +63,11 @@ class ResourceMemberService { $member->setCreatedTimestamp(time()); $member->setLastUpdatedTimestamp(time()); - return $this->mapper->insert($member); + $member = $this->mapper->insert($member); + + $this->organizationFolderService->applyPermissions($resource->getOrganizationFolderId()); + + return $member; } public function update(int $id, ?MemberPermissionLevel $permissionLevel = null, ?MemberType $type = null, ?string $principal = null): ResourceMember { @@ -81,7 +90,12 @@ class ResourceMemberService { $member->setLastUpdatedTimestamp(time()); } - return $this->mapper->update($member); + $member = $this->mapper->update($member); + + $resource = $this->resourceService->find($member->getResourceId()); + $this->organizationFolderService->applyPermissions($resource->getOrganizationFolderId()); + + return $member; } catch (Exception $e) { $this->handleException($e); } @@ -90,7 +104,12 @@ class ResourceMemberService { public function delete(int $id): ResourceMember { try { $member = $this->mapper->find($id); + $this->mapper->delete($member); + + $resource = $this->resourceService->find($member->getResourceId()); + $this->organizationFolderService->applyPermissions($resource->getOrganizationFolderId()); + return $member; } catch (Exception $e) { $this->handleException($e); diff --git a/lib/Service/ResourceService.php b/lib/Service/ResourceService.php index e31ff4b..a1cd335 100644 --- a/lib/Service/ResourceService.php +++ b/lib/Service/ResourceService.php @@ -4,6 +4,8 @@ namespace OCA\OrganizationFolders\Service; use Exception; +use Psr\Container\ContainerInterface; + use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; @@ -29,8 +31,9 @@ class ResourceService { protected PathManager $pathManager, protected ACLManager $aclManager, protected UserMappingManager $userMappingManager, - protected ResourceMemberService $resourceMemberService, protected OrganizationProviderManager $organizationProviderManager, + protected OrganizationFolderService $organizationFolderService, + protected ContainerInterface $container, ) { } @@ -120,6 +123,8 @@ class ResourceService { $resource = $this->mapper->insert($resource); + $this->organizationFolderService->applyPermissions($organizationFolderId); + return $resource; } else { throw new ResourceNameNotUnique(); @@ -187,14 +192,27 @@ class ResourceService { $resource->setLastUpdatedTimestamp(time()); } - return $this->mapper->update($resource); + $resource = $this->mapper->update($resource); + + $this->organizationFolderService->applyPermissions($resource->getOrganizationFolderId()); + + return $resource; + // TODO: improve error handing: if db update fails roll back changes in the filesystem } public function setAllFolderResourceAclsInOrganizationFolder(OrganizationFolder $organizationFolder, array $inheritingGroups) { $topLevelFolderResources = $this->findAll($organizationFolder->getId(), null, ["type" => "folder"]); - return $this->recursivelySetFolderResourceALCs($topLevelFolderResources, "", $inheritingGroups); + $inheritingPrincipals = []; + foreach($inheritingGroups as $inheritingGroup) { + $inheritingPrincipals[] = [ + "type" => "group", + "groupId" => $inheritingGroup, + ]; + } + + return $this->recursivelySetFolderResourceALCs($topLevelFolderResources, "", $inheritingPrincipals); } /** @@ -207,23 +225,41 @@ class ResourceService { * @param array $inheritingGroups * @psalm-param string[] $inheritingGroups */ - public function recursivelySetFolderResourceALCs(array $folderResources, string $path, array $inheritingGroups) { + public function recursivelySetFolderResourceALCs(array $folderResources, string $path, array $inheritingPrincipals) { foreach($folderResources as $folderResource) { $resourceFileId = $folderResource->getFileId(); $acls = []; // inherit ACLs - foreach($inheritingGroups as $inheritingGroup) { - $acls[] = new Rule( - userMapping: $this->userMappingManager->mappingFromId("group", $inheritingGroup), - fileId: $resourceFileId, - mask: 31, - permissions: $folderResource->getInheritedAclPermission(), - ); + foreach($inheritingPrincipals as $inheritingPrincipal) { + if($inheritingPrincipal["type"] === "group") { + $acls[] = new Rule( + userMapping: $this->userMappingManager->mappingFromId("group", $inheritingPrincipal["groupId"]), + fileId: $resourceFileId, + mask: 31, + permissions: $folderResource->getInheritedAclPermission(), + ); + } else if($inheritingPrincipal["type"] === "user") { + $acls[] = new Rule( + userMapping: $this->userMappingManager->mappingFromId("user", $inheritingPrincipal["userId"]), + fileId: $resourceFileId, + mask: 31, + permissions: $folderResource->getInheritedAclPermission(), + ); + } + } + + // inherited principals will affect resources further down, if they have any permissions at this level + if($folderResource->getInheritedAclPermission() !== 0) { + $nextInheritingPrincipals = $inheritingPrincipals; + } else { + $nextInheritingPrincipals = []; } // member ACLs - $resourceMembers = $this->resourceMemberService->findAll($folderResource->getId()); + /** @var ResourceService */ + $resourceMemberService = $this->container->get(ResourceMemberService::class); + $resourceMembers = $resourceMemberService->findAll($folderResource->getId()); foreach($resourceMembers as $resourceMember) { if($resourceMember->getPermissionLevel() === MemberPermissionLevel::MANAGER->value) { $resourceMemberPermissions = $folderResource->getManagersAclPermission(); @@ -233,36 +269,52 @@ class ResourceService { throw new Exception("invalid resource member permission level"); } - if($resourceMember->getType() === MemberType::USER->value) { - $mapping = $this->userMappingManager->mappingFromId("user", $resourceMember->getPrincipal()); - } else if($resourceMember->getType() === MemberType::GROUP->value) { - $mapping = $this->userMappingManager->mappingFromId("group", $resourceMember->getPrincipal()); - } else if($resourceMember->getType() === MemberType::ROLE->value) { - ['organizationProviderId' => $organizationProviderId, 'roleId' => $roleId] = $resourceMember->getParsedPrincipal(); + if($resourceMemberPermissions !== 0) { + if($resourceMember->getType() === MemberType::USER->value) { + $mapping = $this->userMappingManager->mappingFromId("user", $resourceMember->getPrincipal()); + $nextInheritingPrincipals[] = [ + "type" => "user", + "userId" => $resourceMember->getPrincipal(), + ]; + } else if($resourceMember->getType() === MemberType::GROUP->value) { + $mapping = $this->userMappingManager->mappingFromId("group", $resourceMember->getPrincipal()); + $nextInheritingPrincipals[] = [ + "type" => "group", + "groupId" => $resourceMember->getPrincipal(), + ]; + } else if($resourceMember->getType() === MemberType::ROLE->value) { + ['organizationProviderId' => $organizationProviderId, 'roleId' => $roleId] = $resourceMember->getParsedPrincipal(); - $organizationProvider = $this->organizationProviderManager->getOrganizationProvider($organizationProviderId); - $role = $organizationProvider->getRole($roleId); - $mapping = $this->userMappingManager->mappingFromId("group", $role->getMembersGroup()); - } else { - throw new Exception("invalid resource member type"); - } + $organizationProvider = $this->organizationProviderManager->getOrganizationProvider($organizationProviderId); + $role = $organizationProvider->getRole($roleId); + $mapping = $this->userMappingManager->mappingFromId("group", $role->getMembersGroup()); + $nextInheritingPrincipals[] = [ + "type" => "group", + "groupId" => $role->getMembersGroup(), + ]; + } else { + throw new Exception("invalid resource member type"); + } - if(is_null($mapping)) { - // TODO: skip member instead of crashing - throw new Exception(message: "invalid mapping, likely non-existing group"); + if(is_null($mapping)) { + // TODO: skip member instead of crashing + throw new Exception(message: "invalid mapping, likely non-existing group"); + } + + $acls[] = new Rule( + userMapping: $mapping, + fileId: $resourceFileId, + mask: 31, + permissions: $resourceMemberPermissions, + ); } - - $acls[] = new Rule( - userMapping: $mapping, - fileId: $resourceFileId, - mask: 31, - permissions: $resourceMemberPermissions, - ); } $this->aclManager->overwriteACLsForFileId($resourceFileId, $acls); - // TODO: recurse sub-resources + // recurse sub-resources + $subFolderResources = $this->getSubResources($folderResource, ["type" => "folder"]); + $this->recursivelySetFolderResourceALCs($subFolderResources, $path . $folderResource->getName() . "/", $nextInheritingPrincipals); } } @@ -287,8 +339,8 @@ class ResourceService { /** * get all direct sub-resources */ - public function getSubResources(Resource $resource) { - return $this->findAll($resource->getOrganizationFolderId(), $resource->getId()); + public function getSubResources(Resource $resource, array $filters = []) { + return $this->findAll($resource->getOrganizationFolderId(), $resource->getId(), $filters); } /**