mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: pass requestedModes metadata on 401
* feat: pass requestedModes metadata on 401 * fix: bundle modes per target bnode * fix: use custom instance check for HttpError
This commit is contained in:
parent
04711b112b
commit
58daeb684f
@ -1,13 +1,19 @@
|
|||||||
|
import { DataFactory } from 'n3';
|
||||||
import type { Credentials } from '../authentication/Credentials';
|
import type { Credentials } from '../authentication/Credentials';
|
||||||
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
||||||
import type { Authorizer } from '../authorization/Authorizer';
|
import type { Authorizer } from '../authorization/Authorizer';
|
||||||
import type { PermissionReader } from '../authorization/PermissionReader';
|
import type { PermissionReader } from '../authorization/PermissionReader';
|
||||||
import type { ModesExtractor } from '../authorization/permissions/ModesExtractor';
|
import type { ModesExtractor } from '../authorization/permissions/ModesExtractor';
|
||||||
|
import type { AccessMap } from '../authorization/permissions/Permissions';
|
||||||
import type { ResponseDescription } from '../http/output/response/ResponseDescription';
|
import type { ResponseDescription } from '../http/output/response/ResponseDescription';
|
||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
|
import { HttpError } from '../util/errors/HttpError';
|
||||||
|
import { SOLID_META } from '../util/Vocabularies';
|
||||||
import type { OperationHttpHandlerInput } from './OperationHttpHandler';
|
import type { OperationHttpHandlerInput } from './OperationHttpHandler';
|
||||||
import { OperationHttpHandler } from './OperationHttpHandler';
|
import { OperationHttpHandler } from './OperationHttpHandler';
|
||||||
|
|
||||||
|
const { blankNode, namedNode, literal } = DataFactory;
|
||||||
|
|
||||||
export interface AuthorizingHttpHandlerArgs {
|
export interface AuthorizingHttpHandlerArgs {
|
||||||
/**
|
/**
|
||||||
* Extracts the credentials from the incoming request.
|
* Extracts the credentials from the incoming request.
|
||||||
@ -77,6 +83,9 @@ export class AuthorizingHttpHandler extends OperationHttpHandler {
|
|||||||
await this.authorizer.handleSafe({ credentials, requestedModes, availablePermissions });
|
await this.authorizer.handleSafe({ credentials, requestedModes, availablePermissions });
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
this.logger.verbose(`Authorization failed: ${(error as any).message}`);
|
this.logger.verbose(`Authorization failed: ${(error as any).message}`);
|
||||||
|
if (HttpError.isInstance(error)) {
|
||||||
|
this.addAccessModesToError(error, requestedModes);
|
||||||
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,4 +93,15 @@ export class AuthorizingHttpHandler extends OperationHttpHandler {
|
|||||||
|
|
||||||
return this.operationHandler.handleSafe(input);
|
return this.operationHandler.handleSafe(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addAccessModesToError(error: HttpError, requestedModes: AccessMap): void {
|
||||||
|
for (const [ identifier, modes ] of requestedModes.entrySets()) {
|
||||||
|
const bnode = blankNode();
|
||||||
|
error.metadata.add(SOLID_META.terms.requestedAccess, bnode);
|
||||||
|
error.metadata.addQuad(bnode, SOLID_META.terms.accessTarget, namedNode(identifier.path));
|
||||||
|
for (const mode of modes.values()) {
|
||||||
|
error.metadata.addQuad(bnode, SOLID_META.terms.accessMode, literal(mode));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,6 +287,10 @@ export const SOLID_META = createVocabulary('urn:npm:solid:community-server:meta:
|
|||||||
'value',
|
'value',
|
||||||
// This is used to indicate whether metadata should be preserved or not during a PUT operation
|
// This is used to indicate whether metadata should be preserved or not during a PUT operation
|
||||||
'preserve',
|
'preserve',
|
||||||
|
// These predicates are used to describe the requested access in case of an unauthorized request
|
||||||
|
'requestedAccess',
|
||||||
|
'accessTarget',
|
||||||
|
'accessMode',
|
||||||
);
|
);
|
||||||
|
|
||||||
export const VANN = createVocabulary('http://purl.org/vocab/vann/',
|
export const VANN = createVocabulary('http://purl.org/vocab/vann/',
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { BlankNode } from 'n3';
|
||||||
import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor';
|
import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor';
|
||||||
import type { Authorizer } from '../../../src/authorization/Authorizer';
|
import type { Authorizer } from '../../../src/authorization/Authorizer';
|
||||||
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
||||||
@ -11,14 +12,18 @@ import type { HttpRequest } from '../../../src/server/HttpRequest';
|
|||||||
import type { HttpResponse } from '../../../src/server/HttpResponse';
|
import type { HttpResponse } from '../../../src/server/HttpResponse';
|
||||||
import type { OperationHttpHandler } from '../../../src/server/OperationHttpHandler';
|
import type { OperationHttpHandler } from '../../../src/server/OperationHttpHandler';
|
||||||
import { ForbiddenHttpError } from '../../../src/util/errors/ForbiddenHttpError';
|
import { ForbiddenHttpError } from '../../../src/util/errors/ForbiddenHttpError';
|
||||||
|
import { HttpError } from '../../../src/util/errors/HttpError';
|
||||||
import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap';
|
import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap';
|
||||||
|
import { SOLID_META } from '../../../src/util/Vocabularies';
|
||||||
|
|
||||||
describe('An AuthorizingHttpHandler', (): void => {
|
describe('An AuthorizingHttpHandler', (): void => {
|
||||||
const credentials = { };
|
const credentials = { };
|
||||||
const target = { path: 'http://example.com/foo' };
|
const target = { path: 'http://example.com/foo' };
|
||||||
const requestedModes: AccessMap = new IdentifierSetMultiMap<AccessMode>([[ target, AccessMode.read ]]);
|
const requestedModes: AccessMap = new IdentifierSetMultiMap<AccessMode>(
|
||||||
|
[[ target, new Set([ AccessMode.read, AccessMode.write ]) ]],
|
||||||
|
);
|
||||||
const availablePermissions: PermissionMap = new IdentifierMap(
|
const availablePermissions: PermissionMap = new IdentifierMap(
|
||||||
[[ target, { read: true }]],
|
[[ target, { read: true, write: true }]],
|
||||||
);
|
);
|
||||||
const request: HttpRequest = {} as any;
|
const request: HttpRequest = {} as any;
|
||||||
const response: HttpResponse = {} as any;
|
const response: HttpResponse = {} as any;
|
||||||
@ -73,10 +78,26 @@ describe('An AuthorizingHttpHandler', (): void => {
|
|||||||
expect(source.handleSafe).toHaveBeenLastCalledWith({ request, response, operation });
|
expect(source.handleSafe).toHaveBeenLastCalledWith({ request, response, operation });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('errors if authorization fails.', async(): Promise<void> => {
|
it('errors with added access modes if authorization fails.', async(): Promise<void> => {
|
||||||
const error = new ForbiddenHttpError();
|
const error = new ForbiddenHttpError();
|
||||||
authorizer.handleSafe.mockRejectedValueOnce(error);
|
authorizer.handleSafe.mockRejectedValueOnce(error);
|
||||||
await expect(handler.handle({ request, response, operation })).rejects.toThrow(error);
|
let handlerError: HttpError | undefined;
|
||||||
|
try {
|
||||||
|
await handler.handle({ request, response, operation });
|
||||||
|
} catch (receivedError: unknown) {
|
||||||
|
if (receivedError instanceof HttpError) {
|
||||||
|
handlerError = receivedError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(handlerError).toBe(error);
|
||||||
|
const [ bnode ] = handlerError?.metadata?.getAll(SOLID_META.terms.requestedAccess) ?? [];
|
||||||
|
expect(bnode?.termType).toBe('BlankNode');
|
||||||
|
const [ targetQuad ] = handlerError?.metadata?.quads(bnode as BlankNode, SOLID_META.terms.accessTarget) ?? [];
|
||||||
|
expect(targetQuad.object.value).toBe(target.path);
|
||||||
|
const modeQuads = handlerError?.metadata?.quads(bnode as BlankNode, SOLID_META.terms.accessMode) ?? [];
|
||||||
|
const modes = modeQuads.map((quad): string => quad.object.value);
|
||||||
|
expect(modes).toContain(AccessMode.read);
|
||||||
|
expect(modes).toContain(AccessMode.write);
|
||||||
expect(source.handleSafe).toHaveBeenCalledTimes(0);
|
expect(source.handleSafe).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user