feat: Replace acl specific permissions with generic permissions

This required AuxiliaryStrategy to have a new function
indicating if the auxiliary resource just used its associated resource authorization
or its own.
This commit is contained in:
Joachim Van Herwegen
2021-09-21 11:56:05 +02:00
parent 5104cd56e8
commit 7f8b923399
46 changed files with 221 additions and 152 deletions

View File

@@ -16,7 +16,8 @@ export class AllStaticReader extends PermissionReader {
read: allow,
write: allow,
append: allow,
control: allow,
create: allow,
delete: allow,
});
}

View File

@@ -1,4 +1,4 @@
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
import type { AuxiliaryStrategy } from '../ldp/auxiliary/AuxiliaryStrategy';
import type { PermissionSet } from '../ldp/permissions/Permissions';
import { getLoggerFor } from '../logging/LogUtil';
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
@@ -15,9 +15,9 @@ export class AuxiliaryReader extends PermissionReader {
protected readonly logger = getLoggerFor(this);
private readonly resourceReader: PermissionReader;
private readonly auxiliaryStrategy: AuxiliaryIdentifierStrategy;
private readonly auxiliaryStrategy: AuxiliaryStrategy;
public constructor(resourceReader: PermissionReader, auxiliaryStrategy: AuxiliaryIdentifierStrategy) {
public constructor(resourceReader: PermissionReader, auxiliaryStrategy: AuxiliaryStrategy) {
super();
this.resourceReader = resourceReader;
this.auxiliaryStrategy = auxiliaryStrategy;
@@ -44,6 +44,11 @@ export class AuxiliaryReader extends PermissionReader {
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(auxiliaryAuth.identifier)) {
throw new NotImplementedHttpError('AuxiliaryAuthorizer only supports auxiliary resources.');
}
if (this.auxiliaryStrategy.usesOwnAuthorization(auxiliaryAuth.identifier)) {
throw new NotImplementedHttpError('Auxiliary resource uses its own permissions.');
}
return {
...auxiliaryAuth,
identifier: this.auxiliaryStrategy.getAssociatedIdentifier(auxiliaryAuth.identifier),

View File

@@ -3,7 +3,9 @@ import { Store } from 'n3';
import { CredentialGroup } from '../authentication/Credentials';
import type { Credential, CredentialSet } from '../authentication/Credentials';
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
import type { Permission, PermissionSet } from '../ldp/permissions/Permissions';
import { AclMode } from '../ldp/permissions/AclPermission';
import type { AclPermission } from '../ldp/permissions/AclPermission';
import type { PermissionSet } from '../ldp/permissions/Permissions';
import { AccessMode } from '../ldp/permissions/Permissions';
import type { Representation } from '../ldp/representation/Representation';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
@@ -14,7 +16,6 @@ import { createErrorMessage } from '../util/errors/ErrorUtil';
import { ForbiddenHttpError } from '../util/errors/ForbiddenHttpError';
import { InternalServerError } from '../util/errors/InternalServerError';
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
import { readableToQuads } from '../util/StreamUtil';
import { ACL, RDF } from '../util/Vocabularies';
@@ -22,11 +23,11 @@ import type { AccessChecker } from './access-checkers/AccessChecker';
import type { PermissionReaderInput } from './PermissionReader';
import { PermissionReader } from './PermissionReader';
const modesMap: Record<string, AccessMode> = {
const modesMap: Record<string, keyof AclPermission> = {
[ACL.Read]: AccessMode.read,
[ACL.Write]: AccessMode.write,
[ACL.Append]: AccessMode.append,
[ACL.Control]: AccessMode.control,
[ACL.Control]: AclMode.control,
} as const;
/**
@@ -50,12 +51,6 @@ export class WebAclReader extends PermissionReader {
this.accessChecker = accessChecker;
}
public async canHandle({ identifier }: PermissionReaderInput): Promise<void> {
if (this.aclStrategy.isAuxiliaryIdentifier(identifier)) {
throw new NotImplementedHttpError('WebAclAuthorizer does not support permissions on auxiliary resources.');
}
}
/**
* Checks if an agent is allowed to execute the requested actions.
* Will throw an error if this is not the case.
@@ -66,24 +61,28 @@ export class WebAclReader extends PermissionReader {
// Determine the required access modes
this.logger.debug(`Retrieving permissions of ${credentials.agent?.webId} for ${identifier.path}`);
const isAcl = this.aclStrategy.isAuxiliaryIdentifier(identifier);
const mainIdentifier = isAcl ? this.aclStrategy.getAssociatedIdentifier(identifier) : identifier;
// Determine the full authorization for the agent granted by the applicable ACL
const acl = await this.getAclRecursive(identifier);
return this.createPermissions(credentials, acl);
const acl = await this.getAclRecursive(mainIdentifier);
return this.createPermissions(credentials, acl, isAcl);
}
/**
* Creates an Authorization object based on the quads found in the ACL.
* @param credentials - Credentials to check permissions for.
* @param acl - Store containing all relevant authorization triples.
* @param isAcl - If the target resource is an acl document.
*/
private async createPermissions(credentials: CredentialSet, acl: Store):
private async createPermissions(credentials: CredentialSet, acl: Store, isAcl: boolean):
Promise<PermissionSet> {
const publicPermissions = await this.determinePermissions(acl, credentials.public);
const agentPermissions = await this.determinePermissions(acl, credentials.agent);
return {
[CredentialGroup.agent]: agentPermissions,
[CredentialGroup.public]: publicPermissions,
[CredentialGroup.agent]: this.updateAclPermissions(agentPermissions, isAcl),
[CredentialGroup.public]: this.updateAclPermissions(publicPermissions, isAcl),
};
}
@@ -93,10 +92,10 @@ export class WebAclReader extends PermissionReader {
* @param acl - Store containing all relevant authorization triples.
* @param credentials - Credentials to find the permissions for.
*/
private async determinePermissions(acl: Store, credentials?: Credential): Promise<Permission> {
const permissions: Permission = {};
private async determinePermissions(acl: Store, credentials?: Credential): Promise<AclPermission> {
const aclPermissions: AclPermission = {};
if (!credentials) {
return permissions;
return aclPermissions;
}
// Apply all ACL rules
@@ -108,18 +107,43 @@ export class WebAclReader extends PermissionReader {
const modes = acl.getObjects(rule, ACL.mode, null);
for (const { value: mode } of modes) {
if (mode in modesMap) {
permissions[modesMap[mode]] = true;
aclPermissions[modesMap[mode]] = true;
}
}
}
}
if (permissions.write) {
if (aclPermissions.write) {
// Write permission implies Append permission
permissions.append = true;
aclPermissions.append = true;
}
return permissions;
return aclPermissions;
}
/**
* Sets the correct values for non-acl permissions such as create and delete.
* Also adds the correct values to indicate that having control permission
* implies having read/write/etc. on the acl resource.
*
* The main reason for keeping the control value is so we can correctly set the WAC-Allow header later.
*/
private updateAclPermissions(aclPermissions: AclPermission, isAcl: boolean): AclPermission {
if (isAcl) {
return {
read: aclPermissions.control,
append: aclPermissions.control,
write: aclPermissions.control,
create: aclPermissions.control,
delete: aclPermissions.control,
control: aclPermissions.control,
};
}
return {
...aclPermissions,
create: aclPermissions.write,
delete: aclPermissions.write,
};
}
/**