0
0
Fork 0
mirror of https://github.com/verdigado/organization_folders.git synced 2024-11-23 21:20:28 +01:00

many small resource member related improvements

This commit is contained in:
Jonathan Treffler 2024-11-16 03:06:21 +01:00
parent 8bfa9dfa29
commit 9ebae48238
9 changed files with 70 additions and 45 deletions

View file

@ -8,8 +8,9 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use OCA\OrganizationFolders\Command\BaseCommand; use OCA\OrganizationFolders\Command\BaseCommand;
use OCA\OrganizationFolders\Enum\MemberType; use OCA\OrganizationFolders\Model\Principal;
use OCA\OrganizationFolders\Enum\MemberPermissionLevel; use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel;
class CreateResourceMember extends BaseCommand { class CreateResourceMember extends BaseCommand {
protected function configure(): void { protected function configure(): void {
@ -18,27 +19,26 @@ class CreateResourceMember extends BaseCommand {
->setDescription('Create a new member of resource') ->setDescription('Create a new member of resource')
->addOption('resource-id', null, InputOption::VALUE_REQUIRED, 'Id of resource to create member of') ->addOption('resource-id', null, InputOption::VALUE_REQUIRED, 'Id of resource to create member of')
->addOption('permission-level', null, InputOption::VALUE_REQUIRED, 'Permissions level of member (valid values: MEMBER, MANAGER)') ->addOption('permission-level', null, InputOption::VALUE_REQUIRED, 'Permissions level of member (valid values: MEMBER, MANAGER)')
->addOption('type', null, InputOption::VALUE_REQUIRED, 'Type of principal (valid values: USER, GROUP, ROLE)') ->addOption('principal-type', null, InputOption::VALUE_REQUIRED, 'Type of principal (valid values: USER, GROUP, ROLE)')
->addOption('principal', null, InputOption::VALUE_OPTIONAL, 'For type user: "[user_id]", for group: "[group_name]", for role: "[organization_provider_id]:[role_id]"'); ->addOption('principal-id', null, InputOption::VALUE_OPTIONAL, 'For type user: "[user_id]", for group: "[group_name]", for role: "[organization_provider_id]:[role_id]"');
parent::configure(); parent::configure();
} }
protected function execute(InputInterface $input, OutputInterface $output): int { protected function execute(InputInterface $input, OutputInterface $output): int {
$resourceId = $input->getOption('resource-id'); $resourceId = $input->getOption('resource-id');
$permissionLevel = MemberPermissionLevel::fromNameOrValue($input->getOption('permission-level')); $permissionLevel = ResourceMemberPermissionLevel::fromNameOrValue($input->getOption('permission-level'));
$type = MemberType::fromNameOrValue($input->getOption('type')); $principalType = PrincipalType::fromNameOrValue($input->getOption('principal-type'));
$principal = $input->getOption('principal'); $principalId = $input->getOption('principal-id');
try { try {
$resource = $this->resourceMemberService->create( $member = $this->resourceMemberService->create(
resourceId: $resourceId, resourceId: $resourceId,
permissionLevel: $permissionLevel, permissionLevel: $permissionLevel,
type: $type, principal: new Principal($principalType, $principalId),
principal: $principal,
); );
$this->writeTableInOutputFormat($input, $output, [$this->formatTableSerializable($resource)]); $this->writeTableInOutputFormat($input, $output, [$this->formatTableSerializable($member)]);
return 0; return 0;
} catch (Exception $e) { } catch (Exception $e) {
$output->writeln("<error>Exception \"{$e->getMessage()}\" at {$e->getFile()} line {$e->getLine()}</error>"); $output->writeln("<error>Exception \"{$e->getMessage()}\" at {$e->getFile()} line {$e->getLine()}</error>");

View file

@ -60,7 +60,7 @@ class PropFindPlugin extends ServerPlugin {
$propFind->handle(self::ORGANIZATION_FOLDER_RESOURCE_MANAGER_PERMISSIONS_PROPERTYNAME, function () use ($node) { $propFind->handle(self::ORGANIZATION_FOLDER_RESOURCE_MANAGER_PERMISSIONS_PROPERTYNAME, function () use ($node) {
try { try {
$resource = $this->resourceService->findByFileId($node->getId()); $resource = $this->resourceService->findByFileId($node->getId());
return $this->authorizationService->isGranted(["READ"], $resource) ? 'true' : 'false'; return $this->authorizationService->isGranted(["UPDATE"], $resource) ? 'true' : 'false';
} catch (\Exception $e) { } catch (\Exception $e) {
return null; return null;
} }

View file

@ -7,7 +7,7 @@ use OCA\OrganizationFolders\Interface\TableSerializable;
use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\Entity;
use OCA\OrganizationFolders\Enum\MemberPermissionLevel; use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel;
use OCA\OrganizationFolders\Enum\PrincipalType; use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\Model\Principal; use OCA\OrganizationFolders\Model\Principal;
@ -26,6 +26,7 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab
$this->addType('createdTimestamp','integer'); $this->addType('createdTimestamp','integer');
$this->addType('lastUpdatedTimestamp','integer'); $this->addType('lastUpdatedTimestamp','integer');
} }
public function getPrincipal(): Principal { public function getPrincipal(): Principal {
return new Principal(PrincipalType::from($this->principalType), $this->principalId); return new Principal(PrincipalType::from($this->principalType), $this->principalId);
} }
@ -35,6 +36,20 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab
$this->setPrincipalId($principal->getId()); $this->setPrincipalId($principal->getId());
} }
public function setPermissionLevel(int $permissionLevel) {
if($permissionLevel >= 1 && $permissionLevel <= 2) {
if ($permissionLevel === $this->permissionLevel) {
// no change
return;
}
$this->markFieldUpdated("permissionLevel");
$this->permissionLevel = $permissionLevel;
} else {
throw new \Exception("invalid resource member permission level");
}
}
public function jsonSerialize(): array { public function jsonSerialize(): array {
return [ return [
'id' => $this->id, 'id' => $this->id,
@ -50,7 +65,7 @@ class ResourceMember extends Entity implements JsonSerializable, TableSerializab
return [ return [
'Id' => $this->id, 'Id' => $this->id,
'Resource Id' => $this->resourceId, 'Resource Id' => $this->resourceId,
'Permission Level' => MemberPermissionLevel::from($this->permissionLevel)->name, 'Permission Level' => ResourceMemberPermissionLevel::from($this->permissionLevel)->name,
'Principal Type' => PrincipalType::from($this->principalType)->name, 'Principal Type' => PrincipalType::from($this->principalType)->name,
'Principal Id' => $this->principalId, 'Principal Id' => $this->principalId,
'Created' => $this->createdTimestamp, 'Created' => $this->createdTimestamp,

View file

@ -49,15 +49,15 @@ class ResourceMemberMapper extends QBMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function exists(int $resourceId, int $type, string $principal): bool { public function exists(int $resourceId, int $principalType, string $principalId): bool {
/* @var $qb IQueryBuilder */ /* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select($qb->createFunction('COUNT(1)')) $qb->select($qb->createFunction('COUNT(1)'))
->from(self::RESOURCE_MEMBERS_TABLE) ->from(self::RESOURCE_MEMBERS_TABLE)
->where($qb->expr()->eq('resource_id', $qb->createNamedParameter($resourceId, IQueryBuilder::PARAM_INT))) ->where($qb->expr()->eq('resource_id', $qb->createNamedParameter($resourceId, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('principal_type', $qb->createNamedParameter($principalType, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('principal', $qb->createNamedParameter($principal))); ->andWhere($qb->expr()->eq('principal_id', $qb->createNamedParameter($principalId)));
return $qb->executeQuery()->fetch()["COUNT(1)"] === 1; return $qb->executeQuery()->fetch()["COUNT(1)"] === 1;
} }

View file

@ -2,7 +2,7 @@
namespace OCA\OrganizationFolders\Enum; namespace OCA\OrganizationFolders\Enum;
enum MemberPermissionLevel: int { enum ResourceMemberPermissionLevel: int {
use FromNameEnum; use FromNameEnum;
case MEMBER = 1; case MEMBER = 1;

View file

@ -34,20 +34,20 @@ class Version000000Date20241025120000 extends SimpleMigrationStep {
$table->addColumn('resource_id', Types::INTEGER, [ $table->addColumn('resource_id', Types::INTEGER, [
'notnull' => true, 'notnull' => true,
]); ]);
// 0: member // 1: MEMBER
// 1: manager // 2: MANAGER
$table->addColumn('permission_level', Types::INTEGER, [ $table->addColumn('permission_level', Types::INTEGER, [
'notnull' => true, 'notnull' => true,
]); ]);
// 0: user // 1: USER
// 1: group // 2: GROUP
// 2: role // 3: ROLE
$table->addColumn('principal_type', Types::INTEGER, [ $table->addColumn('principal_type', Types::INTEGER, [
'notnull' => true, 'notnull' => true,
]); ]);
// for principal type user: "[user_id]" // for principal type USER: "[user_id]"
// for principal type group: "[group_name]" // for principal type GROUP: "[group_name]"
// for principal type role: "[organization_provider_id]:[role_id]" // for principal type ROLE: "[organization_provider_id]:[role_id]"
$table->addColumn('principal_id', Types::STRING, [ $table->addColumn('principal_id', Types::STRING, [
'length' => 128, 'length' => 128,
'notnull' => true, 'notnull' => true,
@ -67,7 +67,7 @@ class Version000000Date20241025120000 extends SimpleMigrationStep {
['onDelete' => 'CASCADE'], ['onDelete' => 'CASCADE'],
'organizationfolders_resource_members_resource_id_fk'); 'organizationfolders_resource_members_resource_id_fk');
$table->addIndex(['resource_id'], 'organizationfolders_resource_members_resource_id_index'); $table->addIndex(['resource_id'], 'organizationfolders_resource_members_resource_id_index');
$table->addUniqueConstraint(['resource_id', 'type', 'principal'], "organizationfolders_resource_members_unique"); $table->addUniqueConstraint(['resource_id', 'principal_type', 'principal_id'], "organizationfolders_resource_members_unique");
} }
return $schema; return $schema;

View file

@ -8,7 +8,7 @@ use OCP\IGroupManager;
use OCA\OrganizationFolders\Db\Resource; use OCA\OrganizationFolders\Db\Resource;
use OCA\OrganizationFolders\Service\ResourceService; use OCA\OrganizationFolders\Service\ResourceService;
use OCA\OrganizationFolders\Service\ResourceMemberService; use OCA\OrganizationFolders\Service\ResourceMemberService;
use OCA\OrganizationFolders\Enum\MemberPermissionLevel; use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel;
use OCA\OrganizationFolders\Enum\PrincipalType; use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager; use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager;
@ -39,6 +39,7 @@ class ResourceVoter extends Voter {
'UPDATE' => $this->isGranted($user, $resource), 'UPDATE' => $this->isGranted($user, $resource),
'DELETE' => $this->isGranted($user, $resource), 'DELETE' => $this->isGranted($user, $resource),
'UPDATE_MEMBERS' => $this->isGranted($user, $resource), 'UPDATE_MEMBERS' => $this->isGranted($user, $resource),
'CREATE_SUBRESOURCE' => $this->isGranted($user, $resource),
default => throw new \LogicException('This code should not be reached!') default => throw new \LogicException('This code should not be reached!')
}; };
} }
@ -59,7 +60,7 @@ class ResourceVoter extends Voter {
$resourceMembers = $this->resourceMemberService->findAll($resource->getId()); $resourceMembers = $this->resourceMemberService->findAll($resource->getId());
foreach($resourceMembers as $resourceMember) { foreach($resourceMembers as $resourceMember) {
if($resourceMember->getPermissionLevel() === MemberPermissionLevel::MANAGER->value) { if($resourceMember->getPermissionLevel() === ResourceMemberPermissionLevel::MANAGER->value) {
$principal = $resourceMember->getPrincipal(); $principal = $resourceMember->getPrincipal();
if($principal->getType() === PrincipalType::USER) { if($principal->getType() === PrincipalType::USER) {
@ -67,16 +68,13 @@ class ResourceVoter extends Voter {
return true; return true;
} }
} else if($principal->getType() === PrincipalType::GROUP) { } else if($principal->getType() === PrincipalType::GROUP) {
if($this->groupManager->isInGroup($user->getUID(), $principal->getId())) { if($this->userIsInGroup($user, $principal->getId())) {
return true; return true;
} }
} else if($principal->getType() === PrincipalType::ROLE) { } else if($principal->getType() === PrincipalType::ROLE) {
[$organizationProviderId, $roleId] = explode(":", $principal->getId(), 2); [$organizationProviderId, $roleId] = explode(":", $principal->getId(), 2);
$organizationProvider = $this->organizationProviderManager->getOrganizationProvider($organizationProviderId); if($this->userHasRole($user, $organizationProviderId, $roleId)) {
$role = $organizationProvider->getRole($roleId);
if($this->groupManager->isInGroup($user->getUID(), $role->getMembersGroup())) {
return true; return true;
} }
} }
@ -97,4 +95,15 @@ class ResourceVoter extends Voter {
protected function isGranted(IUser $user, Resource $resource): bool { protected function isGranted(IUser $user, Resource $resource): bool {
return $this->isResourceOrganizationFolderAdmin($user, $resource) || $this->isResourceManager($user, $resource); return $this->isResourceOrganizationFolderAdmin($user, $resource) || $this->isResourceManager($user, $resource);
} }
private function userIsInGroup(IUser $user, string $groupId): bool {
return $this->groupManager->isInGroup($user->getUID(), $groupId);
}
private function userHasRole(IUser $user, string $organizationProviderId, string $roleId): bool {
$organizationProvider = $this->organizationProviderManager->getOrganizationProvider($organizationProviderId);
$role = $organizationProvider->getRole($roleId);
return $this->userIsInGroup($user, $role->getMembersGroup());
}
} }

View file

@ -11,8 +11,7 @@ use OCA\OrganizationFolders\Errors\ResourceMemberNotFound;
use OCA\OrganizationFolders\Db\ResourceMember; use OCA\OrganizationFolders\Db\ResourceMember;
use OCA\OrganizationFolders\Db\ResourceMemberMapper; use OCA\OrganizationFolders\Db\ResourceMemberMapper;
use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel;
use OCA\OrganizationFolders\Enum\MemberPermissionLevel;
use OCA\OrganizationFolders\Model\Principal; use OCA\OrganizationFolders\Model\Principal;
class ResourceMemberService { class ResourceMemberService {
@ -52,7 +51,7 @@ class ResourceMemberService {
public function create( public function create(
int $resourceId, int $resourceId,
MemberPermissionLevel $permissionLevel, ResourceMemberPermissionLevel $permissionLevel,
Principal $principal, Principal $principal,
): ResourceMember { ): ResourceMember {
$resource = $this->resourceService->find($resourceId); $resource = $this->resourceService->find($resourceId);
@ -62,8 +61,10 @@ class ResourceMemberService {
$member->setResourceId($resource->getId()); $member->setResourceId($resource->getId());
$member->setPermissionLevel($permissionLevel->value); $member->setPermissionLevel($permissionLevel->value);
$member->setPrincipal($principal); $member->setPrincipal($principal);
$member->setCreatedTimestamp(time());
$member->setLastUpdatedTimestamp(time()); $creationTime = time();
$member->setCreatedTimestamp($creationTime);
$member->setLastUpdatedTimestamp($creationTime);
$member = $this->mapper->insert($member); $member = $this->mapper->insert($member);
@ -72,7 +73,7 @@ class ResourceMemberService {
return $member; return $member;
} }
public function update(int $id, ?MemberPermissionLevel $permissionLevel = null, ?Principal $principal = null): ResourceMember { public function update(int $id, ?ResourceMemberPermissionLevel $permissionLevel = null, ?Principal $principal = null): ResourceMember {
try { try {
$member = $this->mapper->find($id); $member = $this->mapper->find($id);
@ -86,9 +87,9 @@ class ResourceMemberService {
if(count($member->getUpdatedFields()) > 0) { if(count($member->getUpdatedFields()) > 0) {
$member->setLastUpdatedTimestamp(time()); $member->setLastUpdatedTimestamp(time());
}
$member = $this->mapper->update($member); $member = $this->mapper->update($member);
}
$resource = $this->resourceService->find($member->getResourceId()); $resource = $this->resourceService->find($member->getResourceId());
$this->organizationFolderService->applyPermissions($resource->getOrganizationFolderId()); $this->organizationFolderService->applyPermissions($resource->getOrganizationFolderId());

View file

@ -17,7 +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\Enum\MemberPermissionLevel; 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;
use OCA\OrganizationFolders\Errors\ResourceNotFound; use OCA\OrganizationFolders\Errors\ResourceNotFound;
@ -264,9 +264,9 @@ class ResourceService {
$resourceMembers = $resourceMemberService->findAll($folderResource->getId()); $resourceMembers = $resourceMemberService->findAll($folderResource->getId());
foreach($resourceMembers as $resourceMember) { foreach($resourceMembers as $resourceMember) {
if($resourceMember->getPermissionLevel() === MemberPermissionLevel::MANAGER->value) { if($resourceMember->getPermissionLevel() === ResourceMemberPermissionLevel::MANAGER->value) {
$resourceMemberPermissions = $folderResource->getManagersAclPermission(); $resourceMemberPermissions = $folderResource->getManagersAclPermission();
} else if($resourceMember->getPermissionLevel() === MemberPermissionLevel::MEMBER->value) { } else if($resourceMember->getPermissionLevel() === ResourceMemberPermissionLevel::MEMBER->value) {
$resourceMemberPermissions = $folderResource->getMembersAclPermission(); $resourceMemberPermissions = $folderResource->getMembersAclPermission();
} else { } else {
throw new Exception("invalid resource member permission level"); throw new Exception("invalid resource member permission level");