fix: Correctly handle acl behaviour for acl identifiers

This commit is contained in:
Joachim Van Herwegen
2020-11-18 16:48:12 +01:00
parent de16af2104
commit ee312910d7
7 changed files with 77 additions and 47 deletions

View File

@@ -1,26 +1,37 @@
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
/**
* Handles where acl files are stored.
* Handles where acl resources are stored.
*/
export interface AclManager {
/**
* Returns the identifier of the acl file corresponding to the given resource.
* This does not guarantee that this acl file exists.
* In the case the input is already an acl file that will also be the response.
* @param id - The ResourceIdentifier of which we need the corresponding acl file.
* Returns the identifier of the acl resource corresponding to the given resource.
* This does not guarantee that this acl resource exists.
* In the case the input is already an acl resource that will also be the response.
* @param id - The ResourceIdentifier of which we need the corresponding acl resource.
*
* @returns The ResourceIdentifier of the corresponding acl file.
* @returns The ResourceIdentifier of the corresponding acl resource.
*/
getAcl: (id: ResourceIdentifier) => Promise<ResourceIdentifier>;
getAclDocument: (id: ResourceIdentifier) => Promise<ResourceIdentifier>;
/**
* Checks if the input identifier corresponds to an acl file.
* This does not check if that acl file exists,
* only if the identifier indicates that there could be an acl file there.
* Checks if the input identifier corresponds to an acl resource.
* This does not check if that acl resource exists,
* only if the identifier indicates that there could be an acl resource there.
* @param id - Identifier to check.
*
* @returns true if the input identifier points to an acl file.
* @returns true if the input identifier points to an acl resource.
*/
isAcl: (id: ResourceIdentifier) => Promise<boolean>;
isAclDocument: (id: ResourceIdentifier) => Promise<boolean>;
/**
* Returns the identifier of the resource on which the acl constraints are placed.
* In general, this is the resource identifier when the input is a normal resource,
* or the non-acl version if the input is an acl resource.
* This does not guarantee that this resource exists.
* @param aclId - Identifier of the acl resource.
*
* @returns The ResourceIdentifier of the corresponding resource.
*/
getAclConstrainedResource: (id: ResourceIdentifier) => Promise<ResourceIdentifier>;
}

View File

@@ -4,23 +4,23 @@ import type { AclManager } from './AclManager';
/**
* Generates acl URIs by adding an .acl file extension.
*
* What actually should happen in getAcl:
* 1. Return id if it isAcl
* 2. Check store if id exists
* 3a. (true) Close/destroy data stream! To prevent potential locking issues.
* 4a. Check metadata if it is a container or a resource.
* 3b. (false) Use input metadata/heuristic to check if container or resource.
* 5. Generate the correct identifier (.acl right of / for containers, left for resources if there is a /)
*
* It is potentially possible that an agent wants to generate the acl file before generating the actual file.
* (Unless this is not allowed by the spec, need to verify).
* Needs to be updated according to issue #113.
*/
export class UrlBasedAclManager implements AclManager {
public async getAcl(id: ResourceIdentifier): Promise<ResourceIdentifier> {
return await this.isAcl(id) ? id : { path: `${id.path}.acl` };
public async getAclDocument(id: ResourceIdentifier): Promise<ResourceIdentifier> {
return await this.isAclDocument(id) ? id : { path: `${id.path}.acl` };
}
public async isAcl(id: ResourceIdentifier): Promise<boolean> {
public async isAclDocument(id: ResourceIdentifier): Promise<boolean> {
return /\.acl\/?/u.test(id.path);
}
public async getAclConstrainedResource(id: ResourceIdentifier): Promise<ResourceIdentifier> {
if (!await this.isAclDocument(id)) {
return id;
}
// Slice off `.acl`
return { path: id.path.slice(0, -4) };
}
}

View File

@@ -40,7 +40,7 @@ export class WebAclAuthorizer extends Authorizer {
*/
public async handle(input: AuthorizerArgs): Promise<void> {
const store = await this.getAclRecursive(input.identifier);
if (await this.aclManager.isAcl(input.identifier)) {
if (await this.aclManager.isAclDocument(input.identifier)) {
this.checkPermission(input.credentials, store, 'control');
} else {
(Object.keys(input.permissions) as (keyof PermissionSet)[]).forEach((key): void => {
@@ -117,11 +117,13 @@ export class WebAclAuthorizer extends Authorizer {
private async getAclRecursive(id: ResourceIdentifier, recurse?: boolean): Promise<Store> {
this.logger.debug(`Trying to read the direct ACL document of ${id.path}`);
try {
const acl = await this.aclManager.getAcl(id);
const acl = await this.aclManager.getAclDocument(id);
this.logger.debug(`Trying to read the ACL document ${acl.path}`);
const data = await this.resourceStore.getRepresentation(acl, { type: [{ value: INTERNAL_QUADS, weight: 1 }]});
this.logger.info(`Reading ACL statements from ${acl.path}`);
return this.filterData(data, recurse ? ACL.default : ACL.accessTo, id.path);
const resourceId = await this.aclManager.getAclConstrainedResource(id);
return this.filterData(data, recurse ? ACL.default : ACL.accessTo, resourceId.path);
} catch (error: unknown) {
if (error instanceof NotFoundHttpError) {
this.logger.debug(`No direct ACL document found for ${id.path}`);