From 89ff5415ddca467a445786da1c1b21ed619e4f15 Mon Sep 17 00:00:00 2001 From: Jonathan Treffler Date: Tue, 26 Nov 2024 18:19:05 +0100 Subject: [PATCH] split principal types into different subclasses, added principal factory, implemented principal friendlyNames and FullHierarchyNames, added parentOrganizationId attribute to organizations --- lib/Command/BaseCommand.php | 2 + .../CreateOrganizationFolderMember.php | 4 +- .../ResourceMember/CreateResourceMember.php | 4 +- lib/Controller/ResourceMemberController.php | 7 ++- lib/Db/OrganizationFolderMember.php | 47 +++++++++++------ lib/Db/OrganizationFolderMemberMapper.php | 33 +++++++++++- lib/Db/ResourceMember.php | 41 +++++++++++---- lib/Db/ResourceMemberMapper.php | 33 +++++++++++- lib/Model/GroupPrincipal.php | 36 +++++++++++++ lib/Model/Organization.php | 5 ++ lib/Model/Principal.php | 32 ++++++------ lib/Model/PrincipalFactory.php | 34 +++++++++++++ lib/Model/RolePrincipal.php | 50 +++++++++++++++++++ lib/Model/UserPrincipal.php | 36 +++++++++++++ lib/Service/ResourceService.php | 4 +- 15 files changed, 321 insertions(+), 47 deletions(-) create mode 100644 lib/Model/GroupPrincipal.php create mode 100644 lib/Model/PrincipalFactory.php create mode 100644 lib/Model/RolePrincipal.php create mode 100644 lib/Model/UserPrincipal.php diff --git a/lib/Command/BaseCommand.php b/lib/Command/BaseCommand.php index 93cc1be..3e2ce20 100644 --- a/lib/Command/BaseCommand.php +++ b/lib/Command/BaseCommand.php @@ -11,6 +11,7 @@ use OCA\OrganizationFolders\Service\ResourceService; use OCA\OrganizationFolders\Service\ResourceMemberService; use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager; use OCA\OrganizationFolders\Interface\TableSerializable; +use OCA\OrganizationFolders\Model\PrincipalFactory; abstract class BaseCommand extends Base { @@ -21,6 +22,7 @@ abstract class BaseCommand extends Base { protected readonly ResourceService $resourceService, protected readonly ResourceMemberService $resourceMemberService, protected readonly OrganizationProviderManager $organizationProviderManager, + protected readonly PrincipalFactory $principalFactory, ) { parent::__construct(); } diff --git a/lib/Command/OrganizationFolderMember/CreateOrganizationFolderMember.php b/lib/Command/OrganizationFolderMember/CreateOrganizationFolderMember.php index 872538e..64fe65e 100644 --- a/lib/Command/OrganizationFolderMember/CreateOrganizationFolderMember.php +++ b/lib/Command/OrganizationFolderMember/CreateOrganizationFolderMember.php @@ -31,11 +31,13 @@ class CreateOrganizationFolderMember extends BaseCommand { $principalType = PrincipalType::fromNameOrValue($input->getOption('principal-type')); $principalId = $input->getOption('principal-id'); + $principal = $this->principalFactory->buildPrincipal($principalType, $principalId); + try { $member = $this->organizationFolderMemberService->create( organizationFolderId: $organizationFolderId, permissionLevel: $permissionLevel, - principal: new Principal($principalType, $principalId), + principal: $principal, ); $this->writeTableInOutputFormat($input, $output, [$this->formatTableSerializable($member)]); diff --git a/lib/Command/ResourceMember/CreateResourceMember.php b/lib/Command/ResourceMember/CreateResourceMember.php index 4864917..3d22ba1 100644 --- a/lib/Command/ResourceMember/CreateResourceMember.php +++ b/lib/Command/ResourceMember/CreateResourceMember.php @@ -31,11 +31,13 @@ class CreateResourceMember extends BaseCommand { $principalType = PrincipalType::fromNameOrValue($input->getOption('principal-type')); $principalId = $input->getOption('principal-id'); + $principal = $this->principalFactory->buildPrincipal($principalType, $principalId); + try { $member = $this->resourceMemberService->create( resourceId: $resourceId, permissionLevel: $permissionLevel, - principal: new Principal($principalType, $principalId), + principal: $principal, ); $this->writeTableInOutputFormat($input, $output, [$this->formatTableSerializable($member)]); diff --git a/lib/Controller/ResourceMemberController.php b/lib/Controller/ResourceMemberController.php index 2e86134..9cf6018 100644 --- a/lib/Controller/ResourceMemberController.php +++ b/lib/Controller/ResourceMemberController.php @@ -2,7 +2,6 @@ namespace OCA\OrganizationFolders\Controller; -use OCA\OrganizationFolders\Model\Principal; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\Attribute\NoAdminRequired; @@ -11,6 +10,7 @@ use OCA\OrganizationFolders\Service\ResourceService; use OCA\OrganizationFolders\Service\ResourceMemberService; use OCA\OrganizationFolders\Enum\PrincipalType; use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel; +use OCA\OrganizationFolders\Model\PrincipalFactory; class ResourceMemberController extends BaseController { use Errors; @@ -18,6 +18,7 @@ class ResourceMemberController extends BaseController { public function __construct( private ResourceMemberService $service, private ResourceService $resourceService, + private PrincipalFactory $principalFactory, private string $userId, ) { parent::__construct(); @@ -46,10 +47,12 @@ class ResourceMemberController extends BaseController { $this->denyAccessUnlessGranted(['UPDATE_MEMBERS'], $resource); + $principal = $this->principalFactory->buildPrincipal(PrincipalType::fromNameOrValue($principalType), $principalId); + $resourceMember = $this->service->create( resourceId: $resourceId, permissionLevel: ResourceMemberPermissionLevel::fromNameOrValue($permissionLevel), - principal: new Principal(PrincipalType::fromNameOrValue($principalType), $principalId), + principal: $principal, ); return $resourceMember; diff --git a/lib/Db/OrganizationFolderMember.php b/lib/Db/OrganizationFolderMember.php index 24c12d0..8f57445 100644 --- a/lib/Db/OrganizationFolderMember.php +++ b/lib/Db/OrganizationFolderMember.php @@ -14,8 +14,11 @@ use OCA\OrganizationFolders\Model\Principal; class OrganizationFolderMember extends Entity implements JsonSerializable, TableSerializable { protected $organizationFolderId; protected $permissionLevel; - protected $principalType; - protected $principalId; + + /** + * @var Principal + */ + protected $principal; protected $createdTimestamp; protected $lastUpdatedTimestamp; @@ -26,20 +29,33 @@ class OrganizationFolderMember extends Entity implements JsonSerializable, Table $this->addType('createdTimestamp','integer'); $this->addType('lastUpdatedTimestamp','integer'); } - - public function getPrincipal(): Principal { - return new Principal(PrincipalType::from($this->principalType), $this->principalId); - } public function setPrincipal(Principal $principal) { - $principalType = $principal->getType(); - if($principalType === PrincipalType::GROUP || $principalType === PrincipalType::ROLE) { - $this->setPrincipalType($principalType->value); - } else { - throw new \Exception("individual users are not allowed as organization folder members"); - } + if($principal->getType() === PrincipalType::GROUP || $principal->getType() === PrincipalType::ROLE) { + if(!isset($this->principal) || $this->principal->getType() !== $principal->getType()) { + $this->markFieldUpdated("principalType"); + $principalTypeUpdated = true; + } - $this->setPrincipalId($principal->getId()); + if(!isset($this->principal) || $this->principal->getId() !== $principal->getId()) { + $this->markFieldUpdated("principalId"); + $principalIdUpdated = true; + } + + if($principalTypeUpdated || $principalIdUpdated) { + $this->principal = $principal; + } + } else { + throw new \Exception("individual users are not allowed as organization folder members"); + } + } + + public function getPrincipalType(): int { + return $this->principal?->getType()->value; + } + + public function getPrincipalId(): string|null { + return $this->principal?->getId(); } public function setPermissionLevel(int $permissionLevel) { @@ -72,8 +88,9 @@ class OrganizationFolderMember extends Entity implements JsonSerializable, Table 'Id' => $this->id, 'Organization Folder Id' => $this->organizationFolderId, 'Permission Level' => OrganizationFolderMemberPermissionLevel::from($this->permissionLevel)->name, - 'Principal Type' => PrincipalType::from($this->principalType)->name, - 'Principal Id' => $this->principalId, + 'Principal Type' => $this->principal?->getType()->name, + 'Principal Id' => $this->principal?->getId(), + 'Principal Friendly Name' => $this->principal?->getFriendlyName(), 'Created' => $this->createdTimestamp, 'LastUpdated' => $this->lastUpdatedTimestamp, ]; diff --git a/lib/Db/OrganizationFolderMemberMapper.php b/lib/Db/OrganizationFolderMemberMapper.php index 11f3a86..5856935 100644 --- a/lib/Db/OrganizationFolderMemberMapper.php +++ b/lib/Db/OrganizationFolderMemberMapper.php @@ -8,13 +8,44 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use OCA\OrganizationFolders\Enum\PrincipalType; +use OCA\OrganizationFolders\Model\PrincipalFactory; + class OrganizationFolderMemberMapper extends QBMapper { public const ORGANIZATIONFOLDER_MEMBERS_TABLE = "organizationfolders_members"; - public function __construct(IDBConnection $db) { + public function __construct( + protected PrincipalFactory $principalFactory, + IDBConnection $db, + ) { parent::__construct($db, self::ORGANIZATIONFOLDER_MEMBERS_TABLE, OrganizationFolderMember::class); } + /** + * + * @param array $row the row which should be converted to an entity + * @return OrganizationFolderMember the entity + * @psalm-return OrganizationFolderMember the entity + */ + protected function mapRowToEntity(array $row): OrganizationFolderMember { + $member = new OrganizationFolderMember(); + + $member->setId($row["id"]); + $member->setOrganizationFolderId($row["organization_folder_id"]); + $member->setPermissionLevel($row["permission_level"]); + + $principalType = PrincipalType::from($row["principal_type"]); + $principal = $this->principalFactory->buildPrincipal($principalType, $row["principal_id"]); + $member->setPrincipal($principal); + + $member->setCreatedTimestamp($row["created_timestamp"]); + $member->setLastUpdatedTimestamp($row["last_updated_timestamp"]); + + $member->resetUpdatedFields(); + + return $member; + } + /** * @param int $id * @return Entity|OrganizationFolderMember diff --git a/lib/Db/ResourceMember.php b/lib/Db/ResourceMember.php index c0ebcc0..c1dd651 100644 --- a/lib/Db/ResourceMember.php +++ b/lib/Db/ResourceMember.php @@ -14,8 +14,11 @@ use OCA\OrganizationFolders\Model\Principal; class ResourceMember extends Entity implements JsonSerializable, TableSerializable { protected $resourceId; protected $permissionLevel; - protected $principalType; - protected $principalId; + + /** + * @var Principal + */ + protected $principal; protected $createdTimestamp; protected $lastUpdatedTimestamp; @@ -26,14 +29,29 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab $this->addType('createdTimestamp','integer'); $this->addType('lastUpdatedTimestamp','integer'); } - - public function getPrincipal(): Principal { - return new Principal(PrincipalType::from($this->principalType), $this->principalId); - } public function setPrincipal(Principal $principal) { - $this->setPrincipalType($principal->getType()->value); - $this->setPrincipalId($principal->getId()); + if(!isset($this->principal) || $this->principal->getType() !== $principal->getType()) { + $this->markFieldUpdated("principalType"); + $principalTypeUpdated = true; + } + + if(!isset($this->principal) || $this->principal->getId() !== $principal->getId()) { + $this->markFieldUpdated("principalId"); + $principalIdUpdated = true; + } + + if($principalTypeUpdated || $principalIdUpdated) { + $this->principal = $principal; + } + } + + public function getPrincipalType(): int { + return $this->principal?->getType()->value; + } + + public function getPrincipalId(): string|null { + return $this->principal?->getId(); } public function setPermissionLevel(int $permissionLevel) { @@ -55,7 +73,7 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab 'id' => $this->id, 'resourceId' => $this->resourceId, 'permissionLevel' => $this->permissionLevel, - 'principal' => $this->getPrincipal(), + 'principal' => $this->principal, 'createdTimestamp' => $this->createdTimestamp, 'lastUpdatedTimestamp' => $this->lastUpdatedTimestamp, ]; @@ -66,8 +84,9 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab 'Id' => $this->id, 'Resource Id' => $this->resourceId, 'Permission Level' => ResourceMemberPermissionLevel::from($this->permissionLevel)->name, - 'Principal Type' => PrincipalType::from($this->principalType)->name, - 'Principal Id' => $this->principalId, + 'Principal Type' => $this->principal?->getType()->name, + 'Principal Id' => $this->principal?->getId(), + 'Principal Friendly Name' => $this->principal?->getFriendlyName(), 'Created' => $this->createdTimestamp, 'LastUpdated' => $this->lastUpdatedTimestamp, ]; diff --git a/lib/Db/ResourceMemberMapper.php b/lib/Db/ResourceMemberMapper.php index 475210f..9db79c0 100644 --- a/lib/Db/ResourceMemberMapper.php +++ b/lib/Db/ResourceMemberMapper.php @@ -8,13 +8,44 @@ use OCP\AppFramework\Db\QBMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use OCA\OrganizationFolders\Enum\PrincipalType; +use OCA\OrganizationFolders\Model\PrincipalFactory; + class ResourceMemberMapper extends QBMapper { public const RESOURCE_MEMBERS_TABLE = "organizationfolders_resource_members"; - public function __construct(IDBConnection $db) { + public function __construct( + protected PrincipalFactory $principalFactory, + IDBConnection $db, + ) { parent::__construct($db, self::RESOURCE_MEMBERS_TABLE, ResourceMember::class); } + /** + * + * @param array $row the row which should be converted to an entity + * @return ResourceMember the entity + * @psalm-return ResourceMember the entity + */ + protected function mapRowToEntity(array $row): ResourceMember { + $resourceMember = new ResourceMember(); + + $resourceMember->setId($row["id"]); + $resourceMember->setResourceId($row["resource_id"]); + $resourceMember->setPermissionLevel($row["permission_level"]); + + $principalType = PrincipalType::from($row["principal_type"]); + $principal = $this->principalFactory->buildPrincipal($principalType, $row["principal_id"]); + $resourceMember->setPrincipal($principal); + + $resourceMember->setCreatedTimestamp($row["created_timestamp"]); + $resourceMember->setLastUpdatedTimestamp($row["last_updated_timestamp"]); + + $resourceMember->resetUpdatedFields(); + + return $resourceMember; + } + /** * @param int $id * @return Entity|ResourceMember diff --git a/lib/Model/GroupPrincipal.php b/lib/Model/GroupPrincipal.php new file mode 100644 index 0000000..a8d2031 --- /dev/null +++ b/lib/Model/GroupPrincipal.php @@ -0,0 +1,36 @@ +group = $this->groupManager->get($id); + $this->valid = !is_null($this->group); + } catch (\Exception $e) { + $this->valid = false; + } + } + + public function getType(): PrincipalType { + return PrincipalType::GROUP; + } + + public function getId(): string { + return $this->id; + } + + public function getFriendlyName(): string { + return $this->group?->getDisplayName(); + } +} \ No newline at end of file diff --git a/lib/Model/Organization.php b/lib/Model/Organization.php index cf45767..6005931 100644 --- a/lib/Model/Organization.php +++ b/lib/Model/Organization.php @@ -9,6 +9,7 @@ class Organization implements \JsonSerializable, TableSerializable { private int $id, private string $friendlyName, private string $membersGroup, + private ?int $parentOrganizationId = null, ) { } @@ -24,6 +25,10 @@ class Organization implements \JsonSerializable, TableSerializable { return $this->membersGroup; } + public function getParentOrganizationId(): ?int { + return $this->parentOrganizationId; + } + public function jsonSerialize(): array { return [ 'id' => $this->id, diff --git a/lib/Model/Principal.php b/lib/Model/Principal.php index bddf0c2..eff1d3f 100644 --- a/lib/Model/Principal.php +++ b/lib/Model/Principal.php @@ -4,26 +4,30 @@ namespace OCA\OrganizationFolders\Model; use OCA\OrganizationFolders\Enum\PrincipalType; -class Principal implements \JsonSerializable { - public function __construct( - private PrincipalType $type, - private string $id, - ) { - // check if id fits format - } +abstract class Principal implements \JsonSerializable { + protected bool $valid; - public function getType(): PrincipalType { - return $this->type; - } + abstract public function getType(): PrincipalType; - public function getId(): string { - return $this->id; + abstract public function getId(): string; + + abstract public function getFriendlyName(): string; + + /** + * @return array + * @psalm-return string[] + */ + public function getFullHierarchyNames(): array { + return [$this->getFriendlyName()]; } public function jsonSerialize(): array { return [ - 'type' => $this->type->value, - 'id' => $this->id, + 'type' => $this->getType(), + 'id' => $this->getId(), + 'valid' => $this->valid, + 'friendlyName' => $this->getFriendlyName(), + 'fullHierarchyNames' => $this->getFullHierarchyNames(), ]; } } \ No newline at end of file diff --git a/lib/Model/PrincipalFactory.php b/lib/Model/PrincipalFactory.php new file mode 100644 index 0000000..4af22ab --- /dev/null +++ b/lib/Model/PrincipalFactory.php @@ -0,0 +1,34 @@ +userManager, $id); + } else if ($type === PrincipalType::GROUP) { + return new GroupPrincipal($this->groupManager, $id); + } else if ($type === PrincipalType::ROLE) { + [$organizationProviderId, $roleId] = explode(":", $id, 2); + if(!(isset($organizationProviderId) && isset($roleId))) { + throw new \Exception("Invalid id format for principal of type role"); + } + return new RolePrincipal($this->organizationProviderManager, $organizationProviderId, $roleId); + } else { + throw new \Exception("invalid PrincipalType"); + } + } +} \ No newline at end of file diff --git a/lib/Model/RolePrincipal.php b/lib/Model/RolePrincipal.php new file mode 100644 index 0000000..e2577b4 --- /dev/null +++ b/lib/Model/RolePrincipal.php @@ -0,0 +1,50 @@ +role = $this->organizationProviderManager->getOrganizationProvider($providerId)->getRole($roleId); + $this->valid = !is_null($this->role); + } catch (\Exception $e) { + $this->valid = false; + } + } + + public function getType(): PrincipalType { + return PrincipalType::ROLE; + } + + public function getId(): string { + return $this->providerId . ":" . $this->roleId; + } + + public function getFriendlyName(): string { + return $this->role->getFriendlyName(); + } + + public function getFullHierarchyNames(): array { + $result = []; + $result[] = $this->getFriendlyName(); + + $organizationProvider = $this->organizationProviderManager->getOrganizationProvider($this->providerId); + $organization = $organizationProvider->getOrganization($this->role->getOrganizationId()); + $result[] = $organization->getFriendlyName(); + + while($organization->getParentOrganizationId() && $organization = $organizationProvider->getOrganization($organization->getParentOrganizationId())) { + $result[] = $organization->getFriendlyName(); + } + + return array_reverse($result); + } +} \ No newline at end of file diff --git a/lib/Model/UserPrincipal.php b/lib/Model/UserPrincipal.php new file mode 100644 index 0000000..20d726a --- /dev/null +++ b/lib/Model/UserPrincipal.php @@ -0,0 +1,36 @@ +user = $this->userManager->get($id); + $this->valid = !is_null($this->user); + } catch (\Exception $e) { + $this->valid = false; + } + } + + public function getType(): PrincipalType { + return PrincipalType::USER; + } + + public function getId(): string { + return $this->id; + } + + public function getFriendlyName(): string { + return $this->user?->getDisplayName(); + } +} \ No newline at end of file diff --git a/lib/Service/ResourceService.php b/lib/Service/ResourceService.php index 25c4618..4ca2991 100644 --- a/lib/Service/ResourceService.php +++ b/lib/Service/ResourceService.php @@ -17,6 +17,7 @@ use OCA\OrganizationFolders\Db\FolderResource; use OCA\OrganizationFolders\Db\ResourceMapper; use OCA\OrganizationFolders\Model\OrganizationFolder; use \OCA\OrganizationFolders\Model\Principal; +use OCA\OrganizationFolders\Model\PrincipalFactory; use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel; use OCA\OrganizationFolders\Enum\PrincipalType; use OCA\OrganizationFolders\Errors\InvalidResourceType; @@ -35,6 +36,7 @@ class ResourceService { protected OrganizationProviderManager $organizationProviderManager, protected OrganizationFolderService $organizationFolderService, protected ContainerInterface $container, + protected PrincipalFactory $principalFactory, ) { } @@ -215,7 +217,7 @@ class ResourceService { $inheritingPrincipals = []; foreach($inheritingGroups as $inheritingGroup) { - $inheritingPrincipals[] = new Principal(PrincipalType::GROUP, $inheritingGroup); + $inheritingPrincipals[] = $this->principalFactory->buildPrincipal(PrincipalType::GROUP, $inheritingGroup); } return $this->recursivelySetFolderResourceALCs($topLevelFolderResources, "", $inheritingPrincipals);