mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: add AuthenticatedLdpHandler
This commit is contained in:
parent
4229932a3a
commit
3e2cfaf11e
128
src/ldp/AuthenticatedLdpHandler.ts
Normal file
128
src/ldp/AuthenticatedLdpHandler.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import { Authorizer } from '../authorization/Authorizer';
|
||||
import { Credentials } from '../authentication/Credentials';
|
||||
import { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
||||
import { HttpHandler } from '../server/HttpHandler';
|
||||
import { HttpRequest } from '../server/HttpRequest';
|
||||
import { HttpResponse } from '../server/HttpResponse';
|
||||
import { Operation } from './operations/Operation';
|
||||
import { OperationHandler } from './operations/OperationHandler';
|
||||
import { PermissionSet } from './permissions/PermissionSet';
|
||||
import { PermissionsExtractor } from './permissions/PermissionsExtractor';
|
||||
import { RequestParser } from './http/RequestParser';
|
||||
import { ResponseWriter } from './http/ResponseWriter';
|
||||
|
||||
/**
|
||||
* Collection of handlers needed for {@link AuthenticatedLdpHandler} to function.
|
||||
*/
|
||||
export interface AuthenticatedLdpHandlerArgs {
|
||||
/**
|
||||
* Parses the incoming requests.
|
||||
*/
|
||||
requestParser: RequestParser;
|
||||
/**
|
||||
* Extracts the credentials from the incoming request.
|
||||
*/
|
||||
credentialsExtractor: CredentialsExtractor;
|
||||
/**
|
||||
* Extracts the required permissions from the generated Operation.
|
||||
*/
|
||||
permissionsExtractor: PermissionsExtractor;
|
||||
/**
|
||||
* Verifies if the requested operation is allowed.
|
||||
*/
|
||||
authorizer: Authorizer;
|
||||
/**
|
||||
* Executed the operation.
|
||||
*/
|
||||
operationHandler: OperationHandler;
|
||||
/**
|
||||
* Writes out the response of the operation.
|
||||
*/
|
||||
responseWriter: ResponseWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
* The central manager that connects all the necessary handlers to go from an incoming request to an executed operation.
|
||||
*/
|
||||
export class AuthenticatedLdpHandler extends HttpHandler {
|
||||
private readonly requestParser: RequestParser;
|
||||
|
||||
private readonly credentialsExtractor: CredentialsExtractor;
|
||||
|
||||
private readonly permissionsExtractor: PermissionsExtractor;
|
||||
|
||||
private readonly authorizer: Authorizer;
|
||||
|
||||
private readonly operationHandler: OperationHandler;
|
||||
|
||||
private readonly responseWriter: ResponseWriter;
|
||||
|
||||
/**
|
||||
* Creates the handler.
|
||||
* @param args - The handlers required. None of them are optional.
|
||||
*/
|
||||
public constructor (args: AuthenticatedLdpHandlerArgs) {
|
||||
super();
|
||||
Object.assign(this, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the incoming request can be handled. The check is very non-restrictive and will usually be true.
|
||||
* It is based on whether the incoming request can be parsed to an operation.
|
||||
* @param input - Incoming request and response. Only the request will be used.
|
||||
*
|
||||
* @returns A promise resolving if this request can be handled, otherwise rejecting with an Error.
|
||||
*/
|
||||
public async canHandle (input: { request: HttpRequest; response: HttpResponse }): Promise<void> {
|
||||
return this.requestParser.canHandle(input.request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the incoming request and writes out the response.
|
||||
* This includes the following steps:
|
||||
* - Parsing the request to an Operation.
|
||||
* - Extracting credentials from the request.
|
||||
* - Extracting the required permissions.
|
||||
* - Validating if this operation is allowed.
|
||||
* - Executing the operation.
|
||||
* - Writing out the response.
|
||||
* @param input - The incoming request and response object to write to.
|
||||
*
|
||||
* @returns A promise resolving when the handling is finished.
|
||||
*/
|
||||
public async handle (input: { request: HttpRequest; response: HttpResponse }): Promise<void> {
|
||||
let err: Error;
|
||||
let operation: Operation;
|
||||
|
||||
try {
|
||||
operation = await this.runHandlers(input.request);
|
||||
} catch (error) {
|
||||
err = error;
|
||||
}
|
||||
|
||||
const writeData = { response: input.response, operation, error: err };
|
||||
|
||||
return this.responseWriter.handleSafe(writeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs all handlers except writing the output to the response.
|
||||
* This because any errors thrown here have an impact on the response.
|
||||
* @param request - Incoming request.
|
||||
*
|
||||
* @returns A promise resolving to the generated Operation.
|
||||
*/
|
||||
private async runHandlers (request: HttpRequest): Promise<Operation> {
|
||||
const op: Operation = await this.requestParser.handleSafe(request);
|
||||
|
||||
const credentials: Credentials = await this.credentialsExtractor.handleSafe(request);
|
||||
|
||||
const permissions: PermissionSet = await this.permissionsExtractor.handleSafe(op);
|
||||
|
||||
await this.authorizer.handleSafe({ credentials, identifier: op.target, permissions });
|
||||
|
||||
await this.operationHandler.handleSafe(op);
|
||||
|
||||
return op;
|
||||
}
|
||||
}
|
9
src/ldp/http/ResponseWriter.ts
Normal file
9
src/ldp/http/ResponseWriter.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { AsyncHandler } from '../../util/AsyncHandler';
|
||||
import { HttpResponse } from '../../server/HttpResponse';
|
||||
import { Operation } from '../operations/Operation';
|
||||
|
||||
/**
|
||||
* Writes to the HttpResponse.
|
||||
* Response depends on the operation result and potentially which errors was thrown.
|
||||
*/
|
||||
export type ResponseWriter = AsyncHandler<{ response: HttpResponse; operation: Operation; error?: Error }>;
|
65
test/unit/ldp/AuthenticatedLdpHandler.test.ts
Normal file
65
test/unit/ldp/AuthenticatedLdpHandler.test.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Authorizer } from '../../../src/authorization/Authorizer';
|
||||
import { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor';
|
||||
import { OperationHandler } from '../../../src/ldp/operations/OperationHandler';
|
||||
import { PermissionsExtractor } from '../../../src/ldp/permissions/PermissionsExtractor';
|
||||
import { RequestParser } from '../../../src/ldp/http/RequestParser';
|
||||
import { ResponseWriter } from '../../../src/ldp/http/ResponseWriter';
|
||||
import { StaticAsyncHandler } from '../../util/StaticAsyncHandler';
|
||||
import { AuthenticatedLdpHandler, AuthenticatedLdpHandlerArgs } from '../../../src/ldp/AuthenticatedLdpHandler';
|
||||
|
||||
describe('An AuthenticatedLdpHandler', (): void => {
|
||||
let args: AuthenticatedLdpHandlerArgs;
|
||||
let responseFn: jest.Mock<Promise<void>, [any]>;
|
||||
|
||||
beforeEach(async (): Promise<void> => {
|
||||
const requestParser: RequestParser = new StaticAsyncHandler(true, 'parser' as any);
|
||||
const credentialsExtractor: CredentialsExtractor = new StaticAsyncHandler(true, 'credentials' as any);
|
||||
const permissionsExtractor: PermissionsExtractor = new StaticAsyncHandler(true, 'permissions' as any);
|
||||
const authorizer: Authorizer = new StaticAsyncHandler(true, 'authorizer' as any);
|
||||
const operationHandler: OperationHandler = new StaticAsyncHandler(true, 'operation' as any);
|
||||
const responseWriter: ResponseWriter = new StaticAsyncHandler(true, 'response' as any);
|
||||
|
||||
responseFn = jest.fn(async (input: any): Promise<void> => {
|
||||
if (!input) {
|
||||
throw new Error('error');
|
||||
}
|
||||
});
|
||||
responseWriter.canHandle = responseFn;
|
||||
|
||||
args = { requestParser, credentialsExtractor, permissionsExtractor, authorizer, operationHandler, responseWriter };
|
||||
});
|
||||
|
||||
it('can be created.', async (): Promise<void> => {
|
||||
expect(new AuthenticatedLdpHandler(args)).toBeInstanceOf(AuthenticatedLdpHandler);
|
||||
});
|
||||
|
||||
it('can check if it handles input.', async (): Promise<void> => {
|
||||
const handler = new AuthenticatedLdpHandler(args);
|
||||
|
||||
await expect(handler.canHandle({ request: null, response: null })).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('can handle input.', async (): Promise<void> => {
|
||||
const handler = new AuthenticatedLdpHandler(args);
|
||||
|
||||
await expect(handler.handle({ request: 'request' as any, response: 'response' as any })).resolves.toEqual('response');
|
||||
expect(responseFn).toHaveBeenCalledTimes(1);
|
||||
expect(responseFn).toHaveBeenLastCalledWith({ response: 'response', operation: 'parser' as any });
|
||||
});
|
||||
|
||||
it('sends an error to the output if a handler does not support the input.', async (): Promise<void> => {
|
||||
args.requestParser = new StaticAsyncHandler(false, null);
|
||||
const handler = new AuthenticatedLdpHandler(args);
|
||||
|
||||
await expect(handler.handle({ request: 'request' as any, response: null })).resolves.toEqual('response');
|
||||
expect(responseFn).toHaveBeenCalledTimes(1);
|
||||
expect(responseFn.mock.calls[0][0].error).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
it('errors if the response writer does not support the result.', async (): Promise< void> => {
|
||||
args.responseWriter = new StaticAsyncHandler(false, null);
|
||||
const handler = new AuthenticatedLdpHandler(args);
|
||||
|
||||
await expect(handler.handle({ request: 'request' as any, response: null })).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user