mirror of
https://github.com/verdigado/organization_folders.git
synced 2024-11-24 05:30:27 +01:00
added OrganizationFolder security voter; improved Resource security voter
This commit is contained in:
parent
f062b2dd41
commit
b64ae41cd0
6 changed files with 177 additions and 39 deletions
|
@ -17,6 +17,7 @@ use OCA\OrganizationFolders\Listener\SabrePluginAddListener;
|
||||||
use OCA\OrganizationFolders\Listener\LoadAdditionalScripts;
|
use OCA\OrganizationFolders\Listener\LoadAdditionalScripts;
|
||||||
use OCA\OrganizationFolders\Security\AuthorizationService;
|
use OCA\OrganizationFolders\Security\AuthorizationService;
|
||||||
use OCA\OrganizationFolders\Security\ResourceVoter;
|
use OCA\OrganizationFolders\Security\ResourceVoter;
|
||||||
|
use OCA\OrganizationFolders\Security\OrganizationFolderVoter;
|
||||||
|
|
||||||
class Application extends App implements IBootstrap {
|
class Application extends App implements IBootstrap {
|
||||||
public const APP_ID = 'organization_folders';
|
public const APP_ID = 'organization_folders';
|
||||||
|
@ -31,6 +32,7 @@ class Application extends App implements IBootstrap {
|
||||||
|
|
||||||
$context->registerService(AuthorizationService::class, function (ContainerInterface $c) {
|
$context->registerService(AuthorizationService::class, function (ContainerInterface $c) {
|
||||||
$service = new AuthorizationService($c->get(IUserSession::class));
|
$service = new AuthorizationService($c->get(IUserSession::class));
|
||||||
|
$service->registerVoter($c->get(OrganizationFolderVoter::class));
|
||||||
$service->registerVoter($c->get(ResourceVoter::class));
|
$service->registerVoter($c->get(ResourceVoter::class));
|
||||||
return $service;
|
return $service;
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
namespace OCA\OrganizationFolders\Errors;
|
namespace OCA\OrganizationFolders\Errors;
|
||||||
|
|
||||||
class OrganizationNotFound extends NotFoundException {
|
class OrganizationNotFound extends NotFoundException {
|
||||||
public function __construct($provider, $id) {
|
public function __construct(string $provider, int $id) {
|
||||||
parent::__construct(\OCA\OrganizationFolders\Model\Organization::class, ["provider" => $provider, "id" => $id]);
|
parent::__construct(\OCA\OrganizationFolders\Model\Organization::class, ["provider" => $provider, "id" => $id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
9
lib/Errors/OrganizationProviderNotFound.php
Normal file
9
lib/Errors/OrganizationProviderNotFound.php
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace OCA\OrganizationFolders\Errors;
|
||||||
|
|
||||||
|
class OrganizationProviderNotFound extends NotFoundException {
|
||||||
|
public function __construct(string $id) {
|
||||||
|
parent::__construct(\OCA\OrganizationFolders\OrganizationProvider\OrganizationProvider::class, ["id" => $id]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,8 +5,8 @@ declare(strict_types=1);
|
||||||
namespace OCA\OrganizationFolders\OrganizationProvider;
|
namespace OCA\OrganizationFolders\OrganizationProvider;
|
||||||
|
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\Server;
|
|
||||||
|
|
||||||
|
use OCA\OrganizationFolders\Errors\OrganizationProviderNotFound;
|
||||||
use OCA\OrganizationFolders\Events\RegisterOrganizationProviderEvent;
|
use OCA\OrganizationFolders\Events\RegisterOrganizationProviderEvent;
|
||||||
|
|
||||||
class OrganizationProviderManager {
|
class OrganizationProviderManager {
|
||||||
|
@ -35,9 +35,15 @@ class OrganizationProviderManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return OrganizationProvider
|
* @return OrganizationProvider
|
||||||
|
* @throws OrganizationProviderNotFound
|
||||||
*/
|
*/
|
||||||
public function getOrganizationProvider($id): ?OrganizationProvider {
|
public function getOrganizationProvider($id): OrganizationProvider {
|
||||||
return $this->organizationProviders[$id];
|
$organizationProvider = $this->organizationProviders[$id];
|
||||||
|
if(isset($organizationProvider)) {
|
||||||
|
return $organizationProvider;
|
||||||
|
} else {
|
||||||
|
throw new OrganizationProviderNotFound($id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerOrganizationProvider(OrganizationProvider $organizationProvider): self {
|
public function registerOrganizationProvider(OrganizationProvider $organizationProvider): self {
|
||||||
|
|
134
lib/Security/OrganizationFolderVoter.php
Normal file
134
lib/Security/OrganizationFolderVoter.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,21 +6,25 @@ use OCP\IUser;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
|
|
||||||
use OCA\OrganizationFolders\Db\Resource;
|
use OCA\OrganizationFolders\Db\Resource;
|
||||||
|
use OCA\OrganizationFolders\Model\OrganizationFolder;
|
||||||
|
use OCA\OrganizationFolders\Service\OrganizationFolderService;
|
||||||
use OCA\OrganizationFolders\Service\OrganizationFolderMemberService;
|
use OCA\OrganizationFolders\Service\OrganizationFolderMemberService;
|
||||||
use OCA\OrganizationFolders\Service\ResourceService;
|
use OCA\OrganizationFolders\Service\ResourceService;
|
||||||
use OCA\OrganizationFolders\Service\ResourceMemberService;
|
use OCA\OrganizationFolders\Service\ResourceMemberService;
|
||||||
use OCA\OrganizationFolders\Enum\OrganizationFolderMemberPermissionLevel;
|
|
||||||
use OCA\OrganizationFolders\Enum\ResourceMemberPermissionLevel;
|
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;
|
||||||
|
use OCA\OrganizationFolders\Security\OrganizationFolderVoter;
|
||||||
|
|
||||||
class ResourceVoter extends Voter {
|
class ResourceVoter extends Voter {
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
private OrganizationFolderService $organizationFolderService,
|
||||||
private OrganizationFolderMemberService $organizationFolderMemberService,
|
private OrganizationFolderMemberService $organizationFolderMemberService,
|
||||||
private ResourceService $resourceService,
|
private ResourceService $resourceService,
|
||||||
private ResourceMemberService $resourceMemberService,
|
private ResourceMemberService $resourceMemberService,
|
||||||
private IGroupManager $groupManager,
|
private IGroupManager $groupManager,
|
||||||
private OrganizationProviderManager $organizationProviderManager,
|
private OrganizationProviderManager $organizationProviderManager,
|
||||||
|
private OrganizationFolderVoter $organizationFolderVoter,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
protected function supports(string $attribute, mixed $subject): bool {
|
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 {
|
protected function voteOnAttribute(string $attribute, mixed $subject, ?IUser $user): bool {
|
||||||
// _dlog($attribute, $subject);
|
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -47,31 +49,8 @@ class ResourceVoter extends Voter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isResourceOrganizationFolderAdmin(IUser $user, Resource $resource): bool {
|
private function allowedToManageAllResourcesInOrganizationFolder(IUser $user, OrganizationFolder $resourceOrganizationFolder): bool {
|
||||||
$organizationFolderMembers = $this->organizationFolderMemberService->findAll($resource->getOrganizationFolderId(), [
|
return $this->organizationFolderVoter->vote($user, $resourceOrganizationFolder, ["MANAGE_ALL_RESOURCES"]) === self::ACCESS_GRANTED;
|
||||||
"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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,9 +58,7 @@ class ResourceVoter extends Voter {
|
||||||
* @param Resource $resource
|
* @param Resource $resource
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
private function isResourceManager(IUser $user, Resource $resource): bool {
|
private function isResourceManager(IUser $user, Resource $resource, OrganizationFolder $resourceOrganizationFolder): bool {
|
||||||
// TODO: check if is top-level resource and user is organizationFolder manager
|
|
||||||
|
|
||||||
$resourceMembers = $this->resourceMemberService->findAll($resource->getId());
|
$resourceMembers = $this->resourceMemberService->findAll($resource->getId());
|
||||||
|
|
||||||
foreach($resourceMembers as $resourceMember) {
|
foreach($resourceMembers as $resourceMember) {
|
||||||
|
@ -106,11 +83,18 @@ class ResourceVoter extends Voter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inherit manager permissions from level above
|
||||||
if($resource->getInheritManagers()) {
|
if($resource->getInheritManagers()) {
|
||||||
|
if(!is_null($resource->getParentResource())) {
|
||||||
|
// not top-level resource -> allowed to manage resource if allowed to manage parent resource
|
||||||
$parentResource = $this->resourceService->getParentResource($resource);
|
$parentResource = $this->resourceService->getParentResource($resource);
|
||||||
|
|
||||||
if(!is_null($parentResource)) {
|
if(!is_null($parentResource)) {
|
||||||
return $this->isResourceManager($user, $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 {
|
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 {
|
private function userIsInGroup(IUser $user, string $groupId): bool {
|
||||||
|
|
Loading…
Reference in a new issue