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

added subresources api endpoint

This commit is contained in:
Jonathan Treffler 2024-11-25 15:28:40 +01:00
parent ab378ea223
commit 789a05c4c1
6 changed files with 95 additions and 5 deletions

View file

@ -8,6 +8,7 @@ return [
['name' => 'resource#show', 'url' => '/resources/{resourceId}', 'verb' => 'GET'],
['name' => 'resource#create', 'url' => '/resources/{resourceId}', 'verb' => 'POST'],
['name' => 'resource#update', 'url' => '/resources/{resourceId}', 'verb' => 'PUT'],
['name' => 'resource#subResources', 'url' => '/resources/{resourceId}/subResources', 'verb' => 'GET'],
['name' => 'resource_member#index', 'url' => '/resources/{resourceId}/members', 'verb' => 'GET'],
['name' => 'resource_member#create', 'url' => '/resources/{resourceId}/members', 'verb' => 'POST'],
['name' => 'resource_member#update', 'url' => '/resources/members/{id}', 'verb' => 'PUT'],

View file

@ -9,7 +9,7 @@ use OCP\AppFramework\Controller;
use OCP\IRequest;
class BaseController extends Controller {
private AuthorizationService $authorizationService;
protected AuthorizationService $authorizationService;
public function __construct(
) {

View file

@ -8,6 +8,7 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCA\OrganizationFolders\Db\Resource;
use OCA\OrganizationFolders\Service\ResourceService;
use OCA\OrganizationFolders\Service\ResourceMemberService;
use OCA\OrganizationFolders\Service\OrganizationFolderService;
use OCA\OrganizationFolders\Traits\ApiObjectController;
class ResourceController extends BaseController {
@ -15,10 +16,12 @@ class ResourceController extends BaseController {
use ApiObjectController;
public const MEMBERS_INCLUDE = 'members';
public const SUBRESOURCES_INCLUDE = 'subresources';
public function __construct(
private ResourceService $service,
private ResourceMemberService $memberService,
private OrganizationFolderService $organizationFolderService,
private string $userId,
) {
parent::__construct();
@ -37,6 +40,10 @@ class ResourceController extends BaseController {
$result["members"] = $this->memberService->findAll($resource->getId());
}
if($this->shouldInclude(self::SUBRESOURCES_INCLUDE, $includes)) {
$result["subresources"] = $this->getSubResources($resource->getId());
}
return $result;
}
@ -68,16 +75,18 @@ class ResourceController extends BaseController {
?string $include,
): JSONResponse {
return $this->handleErrors(function () use ($organizationFolderId, $type, $name, $parentResourceId, $active, $inheritManagers, $membersAclPermission, $managersAclPermission, $inheritedAclPermission, $include) {
$organizationFolder = $this->organizationFolderService->find($organizationFolderId);
if(!is_null($parentResourceId)) {
$parentResource = $this->service->find($parentResourceId);
$this->denyAccessUnlessGranted(['CREATE_SUBRESOURCE'], $parentResource);
} else {
// TODO: ask future organization folder voter
$this->denyAccessUnlessGranted(['CREATE_RESOURCE'], $organizationFolder);
}
$resource = $this->service->create(
organizationFolderId: $organizationFolderId,
organizationFolderId: $organizationFolder->getId(),
type: $type,
name: $name,
parentResourceId: $parentResourceId,
@ -126,4 +135,44 @@ class ResourceController extends BaseController {
return $this->getApiObjectFromEntity($resource, $include);
});
}
#[NoAdminRequired]
public function subResources(int $resourceId): JSONResponse {
return $this->handleNotFound(function () use ($resourceId) {
return $this->getSubResources($resourceId);
});
}
protected function getSubResources(int $resourceId): array {
$resource = $this->service->find($resourceId);
$organizationFolder = $this->organizationFolderService->find($resource->getOrganizationFolderId());
$subresources = $this->service->getSubResources($resource);
$result = [];
if($this->authorizationService->isGranted(['MANAGE_ALL_RESOURCES'], $organizationFolder)) {
/* fastpath: access to all subresources */
$result = $subresources;
} else {
foreach($subresources as $subresource) {
// Future optimization potential 1: the following will potentially check the permissions of each of these subresources all the way up the resource tree.
// As sibling resources these subresources share the same resources above them in the tree.
// So if access to the parent resource is granted, all subresources with inheritManagers can be granted immediately.
// For all other subresources only a check if user has direct (non-inherited) manager rights is neccessary.
// Future optimization potential 2: READ permission check checks MANAGE_ALL_RESOURCES again, at this point we know this to be false, because of the fastpath.
// Could be replaced with something like a READ_DIRECT (name TBD) permission check, which does not check this again.
if($this->authorizationService->isGranted(['READ'], $resource)) {
$result[] = $subresource;
} else if($this->authorizationService->isGranted(['READ_LIMITED'], $resource)) {
$result[] = $subresource->limitedJsonSerialize();
}
}
}
return $result;
}
}

View file

@ -24,4 +24,14 @@ abstract class Resource extends Entity implements JsonSerializable, TableSeriali
}
abstract public function getType(): string;
public function limitedJsonSerialize(): array {
return [
'id' => $this->id,
'parentResource' => $this->parentResource,
'organizationFolderId' => $this->organizationFolderId,
'type' => $this->getType(),
'name' => $this->name,
];
}
}

View file

@ -40,7 +40,9 @@ class ResourceVoter extends Voter {
/** @var Resource */
$resource = $subject;
return match ($attribute) {
'READ' => $this->isGranted($user, $resource),
'READ' => $this->isGranted( $user, $resource),
// can read limited information about the resource (true: limited read is allowed, full read may be allowed, false: limited read is not allowed, full read may be allowed (!))
'READ_LIMITED' => $this->isGrantedLimitedRead($user, $resource),
'UPDATE' => $this->isGranted($user, $resource),
'DELETE' => $this->isGranted($user, $resource),
'UPDATE_MEMBERS' => $this->isGranted($user, $resource),
@ -108,6 +110,34 @@ class ResourceVoter extends Voter {
|| $this->isResourceManager($user, $resource, $resourceOrganizationFolder);
}
protected function isGrantedLimitedRead(IUser $user, Resource $resource): bool {
$subResources = $this->resourceService->getAllSubResources($resource);
foreach($subResources as $subResource) {
if($this->isManagerOfAnySubresource($user, $subResource)) {
return true;
}
}
return false;
}
protected function isManagerOfAnySubresource(IUser $user, Resource $resource) {
if($this->isGranted($user, $resource)) {
return true;
}
$subResources = $this->resourceService->getAllSubResources($resource);
foreach($subResources as $subResource) {
if($this->isManagerOfAnySubresource($user, $subResource)) {
return true;
}
}
return false;
}
private function userIsInGroup(IUser $user, string $groupId): bool {
return $this->groupManager->isInGroup($user->getUID(), $groupId);
}

View file

@ -40,7 +40,7 @@ const saveInheritManagers = async (inheritManagers) => {
watch(() => props.resourceId, async (newResourceId) => {
loading.value = true;
resource.value = await api.getResource(newResourceId, "model+members");
resource.value = await api.getResource(newResourceId, "model+members+subresources");
currentResourceName.value = resource.value.name;
loading.value = false;
}, { immediate: true });