0
0
Fork 0
mirror of https://github.com/verdigado/organization_folders.git synced 2024-12-06 11:22:41 +01:00

split principal types into different subclasses, added principal factory, implemented principal friendlyNames and FullHierarchyNames, added parentOrganizationId attribute to organizations

This commit is contained in:
Jonathan Treffler 2024-11-26 18:19:05 +01:00
parent 225072bff7
commit 89ff5415dd
15 changed files with 321 additions and 47 deletions

View file

@ -11,6 +11,7 @@ use OCA\OrganizationFolders\Service\ResourceService;
use OCA\OrganizationFolders\Service\ResourceMemberService; use OCA\OrganizationFolders\Service\ResourceMemberService;
use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager; use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager;
use OCA\OrganizationFolders\Interface\TableSerializable; use OCA\OrganizationFolders\Interface\TableSerializable;
use OCA\OrganizationFolders\Model\PrincipalFactory;
abstract class BaseCommand extends Base { abstract class BaseCommand extends Base {
@ -21,6 +22,7 @@ abstract class BaseCommand extends Base {
protected readonly ResourceService $resourceService, protected readonly ResourceService $resourceService,
protected readonly ResourceMemberService $resourceMemberService, protected readonly ResourceMemberService $resourceMemberService,
protected readonly OrganizationProviderManager $organizationProviderManager, protected readonly OrganizationProviderManager $organizationProviderManager,
protected readonly PrincipalFactory $principalFactory,
) { ) {
parent::__construct(); parent::__construct();
} }

View file

@ -31,11 +31,13 @@ class CreateOrganizationFolderMember extends BaseCommand {
$principalType = PrincipalType::fromNameOrValue($input->getOption('principal-type')); $principalType = PrincipalType::fromNameOrValue($input->getOption('principal-type'));
$principalId = $input->getOption('principal-id'); $principalId = $input->getOption('principal-id');
$principal = $this->principalFactory->buildPrincipal($principalType, $principalId);
try { try {
$member = $this->organizationFolderMemberService->create( $member = $this->organizationFolderMemberService->create(
organizationFolderId: $organizationFolderId, organizationFolderId: $organizationFolderId,
permissionLevel: $permissionLevel, permissionLevel: $permissionLevel,
principal: new Principal($principalType, $principalId), principal: $principal,
); );
$this->writeTableInOutputFormat($input, $output, [$this->formatTableSerializable($member)]); $this->writeTableInOutputFormat($input, $output, [$this->formatTableSerializable($member)]);

View file

@ -31,11 +31,13 @@ class CreateResourceMember extends BaseCommand {
$principalType = PrincipalType::fromNameOrValue($input->getOption('principal-type')); $principalType = PrincipalType::fromNameOrValue($input->getOption('principal-type'));
$principalId = $input->getOption('principal-id'); $principalId = $input->getOption('principal-id');
$principal = $this->principalFactory->buildPrincipal($principalType, $principalId);
try { try {
$member = $this->resourceMemberService->create( $member = $this->resourceMemberService->create(
resourceId: $resourceId, resourceId: $resourceId,
permissionLevel: $permissionLevel, permissionLevel: $permissionLevel,
principal: new Principal($principalType, $principalId), principal: $principal,
); );
$this->writeTableInOutputFormat($input, $output, [$this->formatTableSerializable($member)]); $this->writeTableInOutputFormat($input, $output, [$this->formatTableSerializable($member)]);

View file

@ -2,7 +2,6 @@
namespace OCA\OrganizationFolders\Controller; namespace OCA\OrganizationFolders\Controller;
use OCA\OrganizationFolders\Model\Principal;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\NoAdminRequired;
@ -11,6 +10,7 @@ use OCA\OrganizationFolders\Service\ResourceService;
use OCA\OrganizationFolders\Service\ResourceMemberService; use OCA\OrganizationFolders\Service\ResourceMemberService;
use OCA\OrganizationFolders\Enum\PrincipalType; use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel; use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel;
use OCA\OrganizationFolders\Model\PrincipalFactory;
class ResourceMemberController extends BaseController { class ResourceMemberController extends BaseController {
use Errors; use Errors;
@ -18,6 +18,7 @@ class ResourceMemberController extends BaseController {
public function __construct( public function __construct(
private ResourceMemberService $service, private ResourceMemberService $service,
private ResourceService $resourceService, private ResourceService $resourceService,
private PrincipalFactory $principalFactory,
private string $userId, private string $userId,
) { ) {
parent::__construct(); parent::__construct();
@ -46,10 +47,12 @@ class ResourceMemberController extends BaseController {
$this->denyAccessUnlessGranted(['UPDATE_MEMBERS'], $resource); $this->denyAccessUnlessGranted(['UPDATE_MEMBERS'], $resource);
$principal = $this->principalFactory->buildPrincipal(PrincipalType::fromNameOrValue($principalType), $principalId);
$resourceMember = $this->service->create( $resourceMember = $this->service->create(
resourceId: $resourceId, resourceId: $resourceId,
permissionLevel: ResourceMemberPermissionLevel::fromNameOrValue($permissionLevel), permissionLevel: ResourceMemberPermissionLevel::fromNameOrValue($permissionLevel),
principal: new Principal(PrincipalType::fromNameOrValue($principalType), $principalId), principal: $principal,
); );
return $resourceMember; return $resourceMember;

View file

@ -14,8 +14,11 @@ use OCA\OrganizationFolders\Model\Principal;
class OrganizationFolderMember extends Entity implements JsonSerializable, TableSerializable { class OrganizationFolderMember extends Entity implements JsonSerializable, TableSerializable {
protected $organizationFolderId; protected $organizationFolderId;
protected $permissionLevel; protected $permissionLevel;
protected $principalType;
protected $principalId; /**
* @var Principal
*/
protected $principal;
protected $createdTimestamp; protected $createdTimestamp;
protected $lastUpdatedTimestamp; protected $lastUpdatedTimestamp;
@ -27,19 +30,32 @@ class OrganizationFolderMember extends Entity implements JsonSerializable, Table
$this->addType('lastUpdatedTimestamp','integer'); $this->addType('lastUpdatedTimestamp','integer');
} }
public function getPrincipal(): Principal { public function setPrincipal(Principal $principal) {
return new Principal(PrincipalType::from($this->principalType), $this->principalId); if($principal->getType() === PrincipalType::GROUP || $principal->getType() === PrincipalType::ROLE) {
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;
}
} else {
throw new \Exception("individual users are not allowed as organization folder members");
}
} }
public function setPrincipal(Principal $principal) { public function getPrincipalType(): int {
$principalType = $principal->getType(); return $this->principal?->getType()->value;
if($principalType === PrincipalType::GROUP || $principalType === PrincipalType::ROLE) { }
$this->setPrincipalType($principalType->value);
} else {
throw new \Exception("individual users are not allowed as organization folder members");
}
$this->setPrincipalId($principal->getId()); public function getPrincipalId(): string|null {
return $this->principal?->getId();
} }
public function setPermissionLevel(int $permissionLevel) { public function setPermissionLevel(int $permissionLevel) {
@ -72,8 +88,9 @@ class OrganizationFolderMember extends Entity implements JsonSerializable, Table
'Id' => $this->id, 'Id' => $this->id,
'Organization Folder Id' => $this->organizationFolderId, 'Organization Folder Id' => $this->organizationFolderId,
'Permission Level' => OrganizationFolderMemberPermissionLevel::from($this->permissionLevel)->name, 'Permission Level' => OrganizationFolderMemberPermissionLevel::from($this->permissionLevel)->name,
'Principal Type' => PrincipalType::from($this->principalType)->name, 'Principal Type' => $this->principal?->getType()->name,
'Principal Id' => $this->principalId, 'Principal Id' => $this->principal?->getId(),
'Principal Friendly Name' => $this->principal?->getFriendlyName(),
'Created' => $this->createdTimestamp, 'Created' => $this->createdTimestamp,
'LastUpdated' => $this->lastUpdatedTimestamp, 'LastUpdated' => $this->lastUpdatedTimestamp,
]; ];

View file

@ -8,13 +8,44 @@ use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\Model\PrincipalFactory;
class OrganizationFolderMemberMapper extends QBMapper { class OrganizationFolderMemberMapper extends QBMapper {
public const ORGANIZATIONFOLDER_MEMBERS_TABLE = "organizationfolders_members"; 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); 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 * @param int $id
* @return Entity|OrganizationFolderMember * @return Entity|OrganizationFolderMember

View file

@ -14,8 +14,11 @@ use OCA\OrganizationFolders\Model\Principal;
class ResourceMember extends Entity implements JsonSerializable, TableSerializable { class ResourceMember extends Entity implements JsonSerializable, TableSerializable {
protected $resourceId; protected $resourceId;
protected $permissionLevel; protected $permissionLevel;
protected $principalType;
protected $principalId; /**
* @var Principal
*/
protected $principal;
protected $createdTimestamp; protected $createdTimestamp;
protected $lastUpdatedTimestamp; protected $lastUpdatedTimestamp;
@ -27,13 +30,28 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab
$this->addType('lastUpdatedTimestamp','integer'); $this->addType('lastUpdatedTimestamp','integer');
} }
public function getPrincipal(): Principal { public function setPrincipal(Principal $principal) {
return new Principal(PrincipalType::from($this->principalType), $this->principalId); 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 setPrincipal(Principal $principal) { public function getPrincipalType(): int {
$this->setPrincipalType($principal->getType()->value); return $this->principal?->getType()->value;
$this->setPrincipalId($principal->getId()); }
public function getPrincipalId(): string|null {
return $this->principal?->getId();
} }
public function setPermissionLevel(int $permissionLevel) { public function setPermissionLevel(int $permissionLevel) {
@ -55,7 +73,7 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab
'id' => $this->id, 'id' => $this->id,
'resourceId' => $this->resourceId, 'resourceId' => $this->resourceId,
'permissionLevel' => $this->permissionLevel, 'permissionLevel' => $this->permissionLevel,
'principal' => $this->getPrincipal(), 'principal' => $this->principal,
'createdTimestamp' => $this->createdTimestamp, 'createdTimestamp' => $this->createdTimestamp,
'lastUpdatedTimestamp' => $this->lastUpdatedTimestamp, 'lastUpdatedTimestamp' => $this->lastUpdatedTimestamp,
]; ];
@ -66,8 +84,9 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab
'Id' => $this->id, 'Id' => $this->id,
'Resource Id' => $this->resourceId, 'Resource Id' => $this->resourceId,
'Permission Level' => ResourceMemberPermissionLevel::from($this->permissionLevel)->name, 'Permission Level' => ResourceMemberPermissionLevel::from($this->permissionLevel)->name,
'Principal Type' => PrincipalType::from($this->principalType)->name, 'Principal Type' => $this->principal?->getType()->name,
'Principal Id' => $this->principalId, 'Principal Id' => $this->principal?->getId(),
'Principal Friendly Name' => $this->principal?->getFriendlyName(),
'Created' => $this->createdTimestamp, 'Created' => $this->createdTimestamp,
'LastUpdated' => $this->lastUpdatedTimestamp, 'LastUpdated' => $this->lastUpdatedTimestamp,
]; ];

View file

@ -8,13 +8,44 @@ use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\Model\PrincipalFactory;
class ResourceMemberMapper extends QBMapper { class ResourceMemberMapper extends QBMapper {
public const RESOURCE_MEMBERS_TABLE = "organizationfolders_resource_members"; 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); 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 * @param int $id
* @return Entity|ResourceMember * @return Entity|ResourceMember

View file

@ -0,0 +1,36 @@
<?php
namespace OCA\OrganizationFolders\Model;
use OCP\IGroupManager;
use \OCP\IGroup;
use OCA\OrganizationFolders\Enum\PrincipalType;
class GroupPrincipal extends Principal {
private ?IGroup $group;
public function __construct(
private IGroupManager $groupManager,
private string $id,
) {
try {
$this->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();
}
}

View file

@ -9,6 +9,7 @@ class Organization implements \JsonSerializable, TableSerializable {
private int $id, private int $id,
private string $friendlyName, private string $friendlyName,
private string $membersGroup, private string $membersGroup,
private ?int $parentOrganizationId = null,
) { ) {
} }
@ -24,6 +25,10 @@ class Organization implements \JsonSerializable, TableSerializable {
return $this->membersGroup; return $this->membersGroup;
} }
public function getParentOrganizationId(): ?int {
return $this->parentOrganizationId;
}
public function jsonSerialize(): array { public function jsonSerialize(): array {
return [ return [
'id' => $this->id, 'id' => $this->id,

View file

@ -4,26 +4,30 @@ namespace OCA\OrganizationFolders\Model;
use OCA\OrganizationFolders\Enum\PrincipalType; use OCA\OrganizationFolders\Enum\PrincipalType;
class Principal implements \JsonSerializable { abstract class Principal implements \JsonSerializable {
public function __construct( protected bool $valid;
private PrincipalType $type,
private string $id,
) {
// check if id fits format
}
public function getType(): PrincipalType { abstract public function getType(): PrincipalType;
return $this->type;
}
public function getId(): string { abstract public function getId(): string;
return $this->id;
abstract public function getFriendlyName(): string;
/**
* @return array
* @psalm-return string[]
*/
public function getFullHierarchyNames(): array {
return [$this->getFriendlyName()];
} }
public function jsonSerialize(): array { public function jsonSerialize(): array {
return [ return [
'type' => $this->type->value, 'type' => $this->getType(),
'id' => $this->id, 'id' => $this->getId(),
'valid' => $this->valid,
'friendlyName' => $this->getFriendlyName(),
'fullHierarchyNames' => $this->getFullHierarchyNames(),
]; ];
} }
} }

View file

@ -0,0 +1,34 @@
<?php
namespace OCA\OrganizationFolders\Model;
use OCP\IUserManager;
use OCP\IGroupManager;
use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager;
class PrincipalFactory {
public function __construct(
protected IUserManager $userManager,
protected IGroupManager $groupManager,
protected OrganizationProviderManager $organizationProviderManager,
) {
}
public function buildPrincipal(PrincipalType $type, string $id): Principal {
if($type === PrincipalType::USER) {
return new UserPrincipal($this->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");
}
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace OCA\OrganizationFolders\Model;
use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager;
class RolePrincipal extends Principal {
private ?OrganizationRole $role;
public function __construct(
private OrganizationProviderManager $organizationProviderManager,
private string $providerId,
private string $roleId,
) {
try {
$this->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);
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace OCA\OrganizationFolders\Model;
use OCP\IUserManager;
use \OCP\IUser;
use OCA\OrganizationFolders\Enum\PrincipalType;
class UserPrincipal extends Principal {
private ?IUser $user;
public function __construct(
private IUserManager $userManager,
private string $id,
) {
try {
$this->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();
}
}

View file

@ -17,6 +17,7 @@ use OCA\OrganizationFolders\Db\FolderResource;
use OCA\OrganizationFolders\Db\ResourceMapper; use OCA\OrganizationFolders\Db\ResourceMapper;
use OCA\OrganizationFolders\Model\OrganizationFolder; use OCA\OrganizationFolders\Model\OrganizationFolder;
use \OCA\OrganizationFolders\Model\Principal; use \OCA\OrganizationFolders\Model\Principal;
use OCA\OrganizationFolders\Model\PrincipalFactory;
use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel; use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel;
use OCA\OrganizationFolders\Enum\PrincipalType; use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\Errors\InvalidResourceType; use OCA\OrganizationFolders\Errors\InvalidResourceType;
@ -35,6 +36,7 @@ class ResourceService {
protected OrganizationProviderManager $organizationProviderManager, protected OrganizationProviderManager $organizationProviderManager,
protected OrganizationFolderService $organizationFolderService, protected OrganizationFolderService $organizationFolderService,
protected ContainerInterface $container, protected ContainerInterface $container,
protected PrincipalFactory $principalFactory,
) { ) {
} }
@ -215,7 +217,7 @@ class ResourceService {
$inheritingPrincipals = []; $inheritingPrincipals = [];
foreach($inheritingGroups as $inheritingGroup) { foreach($inheritingGroups as $inheritingGroup) {
$inheritingPrincipals[] = new Principal(PrincipalType::GROUP, $inheritingGroup); $inheritingPrincipals[] = $this->principalFactory->buildPrincipal(PrincipalType::GROUP, $inheritingGroup);
} }
return $this->recursivelySetFolderResourceALCs($topLevelFolderResources, "", $inheritingPrincipals); return $this->recursivelySetFolderResourceALCs($topLevelFolderResources, "", $inheritingPrincipals);