feat: Move WAC-Allow metadata collecting to HTTP handler

This depends on all auth related handlers to cache their results.
This allows us to remove the permission field from Operation.
This commit is contained in:
Joachim Van Herwegen
2022-11-18 10:54:19 +01:00
parent 59e64a22ea
commit 6ad5c0c797
16 changed files with 218 additions and 176 deletions

View File

@@ -71,7 +71,6 @@ export class AuthorizingHttpHandler extends OperationHttpHandler {
try {
await this.authorizer.handleSafe({ credentials, requestedModes, availablePermissions });
operation.availablePermissions = availablePermissions;
} catch (error: unknown) {
this.logger.verbose(`Authorization failed: ${(error as any).message}`);
throw error;

View File

@@ -1,5 +1,4 @@
import type { RequestParser } from '../http/input/RequestParser';
import type { OperationMetadataCollector } from '../http/ldp/metadata/OperationMetadataCollector';
import type { ErrorHandler } from '../http/output/error/ErrorHandler';
import type { ResponseDescription } from '../http/output/response/ResponseDescription';
import type { ResponseWriter } from '../http/output/ResponseWriter';
@@ -17,10 +16,6 @@ export interface ParsingHttpHandlerArgs {
* Parses the incoming requests.
*/
requestParser: RequestParser;
/**
* Generates generic operation metadata that is required for a response.
*/
metadataCollector: OperationMetadataCollector;
/**
* Converts errors to a serializable format.
*/
@@ -46,7 +41,6 @@ export class ParsingHttpHandler extends HttpHandler {
private readonly requestParser: RequestParser;
private readonly errorHandler: ErrorHandler;
private readonly responseWriter: ResponseWriter;
private readonly metadataCollector: OperationMetadataCollector;
private readonly operationHandler: OperationHttpHandler;
public constructor(args: ParsingHttpHandlerArgs) {
@@ -54,7 +48,6 @@ export class ParsingHttpHandler extends HttpHandler {
this.requestParser = args.requestParser;
this.errorHandler = args.errorHandler;
this.responseWriter = args.responseWriter;
this.metadataCollector = args.metadataCollector;
this.operationHandler = args.operationHandler;
}
@@ -80,10 +73,6 @@ export class ParsingHttpHandler extends HttpHandler {
const operation = await this.requestParser.handleSafe(request);
const result = await this.operationHandler.handleSafe({ operation, request, response });
if (result?.metadata) {
await this.metadataCollector.handleSafe({ operation, metadata: result.metadata });
}
this.logger.verbose(`Parsed ${operation.method} operation on ${operation.target.path}`);
return result;
}

View File

@@ -0,0 +1,91 @@
import type { Credentials } from '../authentication/Credentials';
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
import type { PermissionReader } from '../authorization/PermissionReader';
import { AclMode } from '../authorization/permissions/AclPermission';
import type { AclPermission } from '../authorization/permissions/AclPermission';
import type { ModesExtractor } from '../authorization/permissions/ModesExtractor';
import type { PermissionSet } from '../authorization/permissions/Permissions';
import { AccessMode } from '../authorization/permissions/Permissions';
import type { ResponseDescription } from '../http/output/response/ResponseDescription';
import type { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
import { getLoggerFor } from '../logging/LogUtil';
import { ACL, AUTH } from '../util/Vocabularies';
import type { OperationHttpHandlerInput } from './OperationHttpHandler';
import { OperationHttpHandler } from './OperationHttpHandler';
const VALID_METHODS = new Set([ 'HEAD', 'GET' ]);
const VALID_ACL_MODES = new Set([ AccessMode.read, AccessMode.write, AccessMode.append, AclMode.control ]);
export interface WacAllowHttpHandlerArgs {
credentialsExtractor: CredentialsExtractor;
modesExtractor: ModesExtractor;
permissionReader: PermissionReader;
operationHandler: OperationHttpHandler;
}
/**
* Adds all the available permissions to the response metadata,
* which can be used to generate the correct WAC-Allow header.
*
* This class does many things similar to the {@link AuthorizingHttpHandler},
* so in general it is a good idea to make sure all these classes cache their results.
*/
export class WacAllowHttpHandler extends OperationHttpHandler {
private readonly logger = getLoggerFor(this);
private readonly credentialsExtractor: CredentialsExtractor;
private readonly modesExtractor: ModesExtractor;
private readonly permissionReader: PermissionReader;
private readonly operationHandler: OperationHttpHandler;
public constructor(args: WacAllowHttpHandlerArgs) {
super();
this.credentialsExtractor = args.credentialsExtractor;
this.modesExtractor = args.modesExtractor;
this.permissionReader = args.permissionReader;
this.operationHandler = args.operationHandler;
}
public async handle(input: OperationHttpHandlerInput): Promise<ResponseDescription> {
const { request, operation } = input;
const response = await this.operationHandler.handleSafe(input);
const { metadata } = response;
// WAC-Allow is only needed for HEAD/GET requests
if (!VALID_METHODS.has(operation.method) || !metadata) {
return response;
}
this.logger.debug('Determining available permissions.');
const credentials: Credentials = await this.credentialsExtractor.handleSafe(request);
const requestedModes = await this.modesExtractor.handleSafe(operation);
const availablePermissions = await this.permissionReader.handleSafe({ credentials, requestedModes });
const permissionSet = availablePermissions.get(operation.target);
if (permissionSet) {
this.logger.debug('Adding WAC-Allow metadata.');
this.addWacAllowMetadata(metadata, permissionSet);
}
return response;
}
private addWacAllowMetadata(metadata: RepresentationMetadata, permissionSet: PermissionSet): void {
const user: AclPermission = permissionSet.agent ?? {};
const everyone: AclPermission = permissionSet.public ?? {};
const modes = new Set<AccessMode>([ ...Object.keys(user), ...Object.keys(everyone) ] as AccessMode[]);
for (const mode of modes) {
if (VALID_ACL_MODES.has(mode)) {
const capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1) as 'Read' | 'Write' | 'Append' | 'Control';
if (everyone[mode]) {
metadata.add(AUTH.terms.publicMode, ACL.terms[capitalizedMode]);
}
if (user[mode]) {
metadata.add(AUTH.terms.userMode, ACL.terms[capitalizedMode]);
}
}
}
}
}