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

added OrganizationFolder security voter; improved Resource security voter

This commit is contained in:
Jonathan Treffler 2024-11-18 18:04:40 +01:00
parent f062b2dd41
commit b64ae41cd0
6 changed files with 177 additions and 39 deletions

View file

@ -0,0 +1,134 @@
<?php
namespace OCA\OrganizationFolders\Security;
use OCP\IUser;
use OCP\IGroupManager;
use OCA\OrganizationFolders\Model\Principal;
use OCA\OrganizationFolders\Model\OrganizationFolder;
use OCA\OrganizationFolders\Service\OrganizationFolderMemberService;
use OCA\OrganizationFolders\Enum\OrganizationFolderMemberPermissionLevel;
use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager;
class OrganizationFolderVoter extends Voter {
public function __construct(
private OrganizationFolderMemberService $organizationFolderMemberService,
private IGroupManager $groupManager,
private OrganizationProviderManager $organizationProviderManager,
) {
}
protected function supports(string $attribute, mixed $subject): bool {
return $subject instanceof OrganizationFolder || $subject === OrganizationFolder::class;
}
protected function voteOnAttribute(string $attribute, mixed $subject, ?IUser $user): bool {
if (!$user) {
return false;
}
/** @var OrganizationFolder */
$organizationFolder = $subject;
return match ($attribute) {
// Admin permissions required
'READ' => $this->isOrganizationFolderAdmin($user, $organizationFolder),
'UPDATE' => $this->isOrganizationFolderAdmin($user, $organizationFolder),
'DELETE' => $this->isOrganizationFolderAdmin($user, $organizationFolder),
'UPDATE_MEMBERS' => $this->isOrganizationFolderAdmin($user, $organizationFolder),
'MANAGE_ALL_RESOURCES' => $this->isOrganizationFolderAdmin($user, $organizationFolder),
// At least Manager permissions required
'READ_LIMITED' => $this->isOrganizationFolderAdminOrManager($user, $organizationFolder),
'CREATE_RESOURCE' => $this->isOrganizationFolderAdminOrManager($user, $organizationFolder),
'MANAGE_TOP_LEVEL_RESOURCES' => $this->isOrganizationFolderAdminOrManager($user, $organizationFolder),
default => throw new \LogicException('This code should not be reached!')
};
}
/**
* @param IUser $user
* @param OrganizationFolder $organizationFolder
* @return bool
*/
private function isOrganizationFolderMember(IUser $user, OrganizationFolder $organizationFolder): bool {
$organizationFolderMembers = $this->organizationFolderMemberService->findAll($organizationFolder->getId());
foreach($organizationFolderMembers as $organizationFolderMember) {
if($this->userIsPrincipal($user, $organizationFolderMember->getPrincipal())) {
return true;
}
}
return false;
}
/**
* @param IUser $user
* @param OrganizationFolder $organizationFolder
* @return bool
*/
private function isOrganizationFolderAdmin(IUser $user, OrganizationFolder $organizationFolder): bool {
$organizationFolderMembers = $this->organizationFolderMemberService->findAll($organizationFolder->getId(), [
"permissionLevel" => OrganizationFolderMemberPermissionLevel::ADMIN,
]);
foreach($organizationFolderMembers as $organizationFolderMember) {
// should be true for all returned members because of the filter, double check because of the big security implications
if($organizationFolderMember->getPermissionLevel() === OrganizationFolderMemberPermissionLevel::ADMIN->value) {
if($this->userIsPrincipal($user, $organizationFolderMember->getPrincipal())) {
return true;
}
}
}
return false;
}
/**
* @param IUser $user
* @param OrganizationFolder $organizationFolder
* @return bool
*/
private function isOrganizationFolderAdminOrManager(IUser $user, OrganizationFolder $organizationFolder): bool {
$organizationFolderMembers = $this->organizationFolderMemberService->findAll($organizationFolder->getId());
foreach($organizationFolderMembers as $organizationFolderMember) {
$permissionLevel = $organizationFolderMember->getPermissionLevel();
if($permissionLevel === OrganizationFolderMemberPermissionLevel::ADMIN->value || $permissionLevel === OrganizationFolderMemberPermissionLevel::MANAGER->value) {
if($this->userIsPrincipal($user, $organizationFolderMember->getPrincipal())) {
return true;
}
}
}
return false;
}
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());
}
private function userIsPrincipal(IUser $user, Principal $principal): bool {
if($principal->getType() === PrincipalType::GROUP) {
return $this->userIsInGroup($user, $principal->getId());
} else if($principal->getType() === PrincipalType::ROLE) {
[$organizationProviderId, $roleId] = explode(":", $principal->getId(), 2);
return $this->userHasRole($user, $organizationProviderId, $roleId);
} else {
// user principals are not supported by Organization Folder Members and
// a principal object with that type should have never been put into this function
return false;
}
}
}

View file

@ -6,21 +6,25 @@ use OCP\IUser;
use OCP\IGroupManager;
use OCA\OrganizationFolders\Db\Resource;
use OCA\OrganizationFolders\Model\OrganizationFolder;
use OCA\OrganizationFolders\Service\OrganizationFolderService;
use OCA\OrganizationFolders\Service\OrganizationFolderMemberService;
use OCA\OrganizationFolders\Service\ResourceService;
use OCA\OrganizationFolders\Service\ResourceMemberService;
use OCA\OrganizationFolders\Enum\OrganizationFolderMemberPermissionLevel;
use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel;
use OCA\OrganizationFolders\Enum\PrincipalType;
use OCA\OrganizationFolders\OrganizationProvider\OrganizationProviderManager;
use OCA\OrganizationFolders\Security\OrganizationFolderVoter;
class ResourceVoter extends Voter {
public function __construct(
private OrganizationFolderService $organizationFolderService,
private OrganizationFolderMemberService $organizationFolderMemberService,
private ResourceService $resourceService,
private ResourceMemberService $resourceMemberService,
private IGroupManager $groupManager,
private OrganizationProviderManager $organizationProviderManager,
private OrganizationFolderVoter $organizationFolderVoter,
) {
}
protected function supports(string $attribute, mixed $subject): bool {
@ -29,8 +33,6 @@ class ResourceVoter extends Voter {
protected function voteOnAttribute(string $attribute, mixed $subject, ?IUser $user): bool {
// _dlog($attribute, $subject);
if (!$user) {
return false;
}
@ -47,31 +49,8 @@ class ResourceVoter extends Voter {
};
}
private function isResourceOrganizationFolderAdmin(IUser $user, Resource $resource): bool {
$organizationFolderMembers = $this->organizationFolderMemberService->findAll($resource->getOrganizationFolderId(), [
"permissionLevel" => OrganizationFolderMemberPermissionLevel::ADMIN,
]);
foreach($organizationFolderMembers as $organizationFolderMember) {
// should be true for all returned members because of the filter, double check because of the big security implications
if($organizationFolderMember->getPermissionLevel() === OrganizationFolderMemberPermissionLevel::ADMIN->value) {
$principal = $organizationFolderMember->getPrincipal();
if($principal->getType() === PrincipalType::GROUP) {
if($this->userIsInGroup($user, $principal->getId())) {
return true;
}
} else if($principal->getType() === PrincipalType::ROLE) {
[$organizationProviderId, $roleId] = explode(":", $principal->getId(), 2);
if($this->userHasRole($user, $organizationProviderId, $roleId)) {
return true;
}
}
}
}
return false;
private function allowedToManageAllResourcesInOrganizationFolder(IUser $user, OrganizationFolder $resourceOrganizationFolder): bool {
return $this->organizationFolderVoter->vote($user, $resourceOrganizationFolder, ["MANAGE_ALL_RESOURCES"]) === self::ACCESS_GRANTED;
}
/**
@ -79,9 +58,7 @@ class ResourceVoter extends Voter {
* @param Resource $resource
* @return bool
*/
private function isResourceManager(IUser $user, Resource $resource): bool {
// TODO: check if is top-level resource and user is organizationFolder manager
private function isResourceManager(IUser $user, Resource $resource, OrganizationFolder $resourceOrganizationFolder): bool {
$resourceMembers = $this->resourceMemberService->findAll($resource->getId());
foreach($resourceMembers as $resourceMember) {
@ -106,11 +83,18 @@ class ResourceVoter extends Voter {
}
}
// inherit manager permissions from level above
if($resource->getInheritManagers()) {
$parentResource = $this->resourceService->getParentResource($resource);
if(!is_null($resource->getParentResource())) {
// not top-level resource -> allowed to manage resource if allowed to manage parent resource
$parentResource = $this->resourceService->getParentResource($resource);
if(!is_null($parentResource)) {
return $this->isResourceManager($user, $parentResource);
if(!is_null($parentResource)) {
return $this->isResourceManager($user, $parentResource, $resourceOrganizationFolder);
}
} else {
// top-level resource -> allowed to manage resource if manager of organization folder
return $this->organizationFolderVoter->vote($user, $resourceOrganizationFolder, ["MANAGE_TOP_LEVEL_RESOURCES"]) === self::ACCESS_GRANTED;
}
}
@ -118,7 +102,10 @@ class ResourceVoter extends Voter {
}
protected function isGranted(IUser $user, Resource $resource): bool {
return $this->isResourceOrganizationFolderAdmin($user, $resource) || $this->isResourceManager($user, $resource);
$resourceOrganizationFolder = $this->organizationFolderService->find($resource->getOrganizationFolderId());
return $this->allowedToManageAllResourcesInOrganizationFolder($user, $resourceOrganizationFolder)
|| $this->isResourceManager($user, $resource, $resourceOrganizationFolder);
}
private function userIsInGroup(IUser $user, string $groupId): bool {