mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Split HttpHandler behaviour over multiple classes
This allows easier reuse of certain reoccurring behaviours, such as authorization. The AuthenticatedLdpHandler is no longer required since it is a combination of parsing and authorization. This did require a small change to the OperationHandler interface.
This commit is contained in:
84
src/server/AuthorizingHttpHandler.ts
Normal file
84
src/server/AuthorizingHttpHandler.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { CredentialSet } from '../authentication/Credentials';
|
||||
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
||||
import type { Authorizer } from '../authorization/Authorizer';
|
||||
import type { PermissionReader } from '../authorization/PermissionReader';
|
||||
import type { ResponseDescription } from '../ldp/http/response/ResponseDescription';
|
||||
import type { ModesExtractor } from '../ldp/permissions/ModesExtractor';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { OperationHttpHandlerInput } from './OperationHttpHandler';
|
||||
import { OperationHttpHandler } from './OperationHttpHandler';
|
||||
|
||||
export interface AuthorizingHttpHandlerArgs {
|
||||
/**
|
||||
* Extracts the credentials from the incoming request.
|
||||
*/
|
||||
credentialsExtractor: CredentialsExtractor;
|
||||
/**
|
||||
* Extracts the required modes from the generated Operation.
|
||||
*/
|
||||
modesExtractor: ModesExtractor;
|
||||
/**
|
||||
* Reads the permissions available for the Operation.
|
||||
*/
|
||||
permissionReader: PermissionReader;
|
||||
/**
|
||||
* Verifies if the requested operation is allowed.
|
||||
*/
|
||||
authorizer: Authorizer;
|
||||
/**
|
||||
* Handler to call if the operation is authorized.
|
||||
*/
|
||||
operationHandler: OperationHttpHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles all the necessary steps for an authorization.
|
||||
* Errors if authorization fails, otherwise passes the parameter to the operationHandler handler.
|
||||
* The following steps are executed:
|
||||
* - Extracting credentials from the request.
|
||||
* - Extracting the required permissions.
|
||||
* - Reading the allowed permissions for the credentials.
|
||||
* - Validating if this operation is allowed.
|
||||
*/
|
||||
export class AuthorizingHttpHandler extends OperationHttpHandler {
|
||||
private readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly credentialsExtractor: CredentialsExtractor;
|
||||
private readonly modesExtractor: ModesExtractor;
|
||||
private readonly permissionReader: PermissionReader;
|
||||
private readonly authorizer: Authorizer;
|
||||
private readonly operationHandler: OperationHttpHandler;
|
||||
|
||||
public constructor(args: AuthorizingHttpHandlerArgs) {
|
||||
super();
|
||||
this.credentialsExtractor = args.credentialsExtractor;
|
||||
this.modesExtractor = args.modesExtractor;
|
||||
this.permissionReader = args.permissionReader;
|
||||
this.authorizer = args.authorizer;
|
||||
this.operationHandler = args.operationHandler;
|
||||
}
|
||||
|
||||
public async handle(input: OperationHttpHandlerInput): Promise<ResponseDescription | undefined> {
|
||||
const { request, operation } = input;
|
||||
const credentials: CredentialSet = await this.credentialsExtractor.handleSafe(request);
|
||||
this.logger.verbose(`Extracted credentials: ${JSON.stringify(credentials)}`);
|
||||
|
||||
const modes = await this.modesExtractor.handleSafe(operation);
|
||||
this.logger.verbose(`Required modes are read: ${[ ...modes ].join(',')}`);
|
||||
|
||||
const permissionSet = await this.permissionReader.handleSafe({ credentials, identifier: operation.target });
|
||||
this.logger.verbose(`Available permissions are ${JSON.stringify(permissionSet)}`);
|
||||
|
||||
try {
|
||||
await this.authorizer.handleSafe({ credentials, identifier: operation.target, modes, permissionSet });
|
||||
operation.permissionSet = permissionSet;
|
||||
} catch (error: unknown) {
|
||||
this.logger.verbose(`Authorization failed: ${(error as any).message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.logger.verbose(`Authorization succeeded, calling source handler`);
|
||||
|
||||
return this.operationHandler.handleSafe(input);
|
||||
}
|
||||
}
|
||||
16
src/server/OperationHttpHandler.ts
Normal file
16
src/server/OperationHttpHandler.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { ResponseDescription } from '../ldp/http/response/ResponseDescription';
|
||||
import type { Operation } from '../ldp/operations/Operation';
|
||||
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
||||
import type { HttpHandlerInput } from './HttpHandler';
|
||||
|
||||
export interface OperationHttpHandlerInput extends HttpHandlerInput {
|
||||
operation: Operation;
|
||||
}
|
||||
|
||||
/**
|
||||
* An HTTP handler that makes use of an already parsed Operation.
|
||||
* Can either return a ResponseDescription to be resolved by the calling class,
|
||||
* or undefined if this class handles the response itself.
|
||||
*/
|
||||
export abstract class OperationHttpHandler
|
||||
extends AsyncHandler<OperationHttpHandlerInput, ResponseDescription | undefined> {}
|
||||
@@ -2,20 +2,23 @@ import type { ErrorHandler } from '../ldp/http/ErrorHandler';
|
||||
import type { RequestParser } from '../ldp/http/RequestParser';
|
||||
import type { ResponseDescription } from '../ldp/http/response/ResponseDescription';
|
||||
import type { ResponseWriter } from '../ldp/http/ResponseWriter';
|
||||
import type { Operation } from '../ldp/operations/Operation';
|
||||
import type { OperationMetadataCollector } from '../ldp/operations/metadata/OperationMetadataCollector';
|
||||
import type { RepresentationPreferences } from '../ldp/representation/RepresentationPreferences';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import { assertError } from '../util/errors/ErrorUtil';
|
||||
import type { HttpHandlerInput } from './HttpHandler';
|
||||
import { HttpHandler } from './HttpHandler';
|
||||
import type { HttpRequest } from './HttpRequest';
|
||||
import type { HttpResponse } from './HttpResponse';
|
||||
import type { OperationHttpHandler } from './OperationHttpHandler';
|
||||
|
||||
export interface BaseHttpHandlerArgs {
|
||||
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.
|
||||
*/
|
||||
@@ -24,25 +27,33 @@ export interface BaseHttpHandlerArgs {
|
||||
* Writes out the response of the operation.
|
||||
*/
|
||||
responseWriter: ResponseWriter;
|
||||
/**
|
||||
* Handler to send the operation to.
|
||||
*/
|
||||
operationHandler: OperationHttpHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses requests and sends the resulting Operation to the abstract `handleOperation` function.
|
||||
* Parses requests and sends the resulting Operation to wrapped operationHandler.
|
||||
* Errors are caught and handled by the Errorhandler.
|
||||
* In case the `handleOperation` function returns a result it will be sent to the ResponseWriter.
|
||||
* In case the operationHandler returns a result it will be sent to the ResponseWriter.
|
||||
*/
|
||||
export abstract class BaseHttpHandler extends HttpHandler {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
export class ParsingHttpHandler extends HttpHandler {
|
||||
private readonly logger = getLoggerFor(this);
|
||||
|
||||
protected readonly requestParser: RequestParser;
|
||||
protected readonly errorHandler: ErrorHandler;
|
||||
protected readonly responseWriter: ResponseWriter;
|
||||
private readonly requestParser: RequestParser;
|
||||
private readonly errorHandler: ErrorHandler;
|
||||
private readonly responseWriter: ResponseWriter;
|
||||
private readonly metadataCollector: OperationMetadataCollector;
|
||||
private readonly operationHandler: OperationHttpHandler;
|
||||
|
||||
protected constructor(args: BaseHttpHandlerArgs) {
|
||||
public constructor(args: ParsingHttpHandlerArgs) {
|
||||
super();
|
||||
this.requestParser = args.requestParser;
|
||||
this.errorHandler = args.errorHandler;
|
||||
this.responseWriter = args.responseWriter;
|
||||
this.metadataCollector = args.metadataCollector;
|
||||
this.operationHandler = args.operationHandler;
|
||||
}
|
||||
|
||||
public async handle({ request, response }: HttpHandlerInput): Promise<void> {
|
||||
@@ -52,7 +63,12 @@ export abstract class BaseHttpHandler extends HttpHandler {
|
||||
try {
|
||||
const operation = await this.requestParser.handleSafe(request);
|
||||
({ preferences } = operation);
|
||||
result = await this.handleOperation(operation, request, response);
|
||||
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}`);
|
||||
} catch (error: unknown) {
|
||||
assertError(error);
|
||||
@@ -63,10 +79,4 @@ export abstract class BaseHttpHandler extends HttpHandler {
|
||||
await this.responseWriter.handleSafe({ response, result });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the operation. Should return a ResponseDescription if it does not handle the response itself.
|
||||
*/
|
||||
protected abstract handleOperation(operation: Operation, request: HttpRequest, response: HttpResponse):
|
||||
Promise<ResponseDescription | undefined>;
|
||||
}
|
||||
Reference in New Issue
Block a user