mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Create separate authorizer for auxiliary functions
This commit is contained in:
76
test/unit/authorization/AuxiliaryAuthorizer.test.ts
Normal file
76
test/unit/authorization/AuxiliaryAuthorizer.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import type { Authorizer } from '../../../src/authorization/Authorizer';
|
||||
import { AuxiliaryAuthorizer } from '../../../src/authorization/AuxiliaryAuthorizer';
|
||||
import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||
import type { PermissionSet } from '../../../src/ldp/permissions/PermissionSet';
|
||||
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
|
||||
describe('An AuxiliaryAuthorizer', (): void => {
|
||||
const suffix = '.dummy';
|
||||
const credentials = {};
|
||||
const associatedIdentifier = { path: 'http://test.com/foo' };
|
||||
const auxiliaryIdentifier = { path: 'http://test.com/foo.dummy' };
|
||||
let permissions: PermissionSet;
|
||||
let source: Authorizer;
|
||||
let strategy: AuxiliaryIdentifierStrategy;
|
||||
let authorizer: AuxiliaryAuthorizer;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
permissions = {
|
||||
read: true,
|
||||
write: true,
|
||||
append: true,
|
||||
control: false,
|
||||
};
|
||||
|
||||
source = {
|
||||
canHandle: jest.fn(),
|
||||
handle: jest.fn(),
|
||||
handleSafe: jest.fn(),
|
||||
};
|
||||
|
||||
strategy = {
|
||||
isAuxiliaryIdentifier: jest.fn((identifier: ResourceIdentifier): boolean => identifier.path.endsWith(suffix)),
|
||||
getAssociatedIdentifier: jest.fn((identifier: ResourceIdentifier): ResourceIdentifier =>
|
||||
({ path: identifier.path.slice(0, -suffix.length) })),
|
||||
} as any;
|
||||
authorizer = new AuxiliaryAuthorizer(source, strategy);
|
||||
});
|
||||
|
||||
it('can handle auxiliary resources if the source supports the associated resource.', async(): Promise<void> => {
|
||||
await expect(authorizer.canHandle({ identifier: auxiliaryIdentifier, credentials, permissions }))
|
||||
.resolves.toBeUndefined();
|
||||
expect(source.canHandle).toHaveBeenLastCalledWith(
|
||||
{ identifier: associatedIdentifier, credentials, permissions },
|
||||
);
|
||||
await expect(authorizer.canHandle({ identifier: associatedIdentifier, credentials, permissions }))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
source.canHandle = jest.fn().mockRejectedValue(new Error('no source support'));
|
||||
await expect(authorizer.canHandle({ identifier: auxiliaryIdentifier, credentials, permissions }))
|
||||
.rejects.toThrow('no source support');
|
||||
});
|
||||
|
||||
it('handles resources by sending the updated parameters to the source.', async(): Promise<void> => {
|
||||
await expect(authorizer.handle({ identifier: auxiliaryIdentifier, credentials, permissions }))
|
||||
.resolves.toBeUndefined();
|
||||
expect(source.handle).toHaveBeenLastCalledWith(
|
||||
{ identifier: associatedIdentifier, credentials, permissions },
|
||||
);
|
||||
// Safety checks are not present when calling `handle`
|
||||
await expect(authorizer.handle({ identifier: associatedIdentifier, credentials, permissions }))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
});
|
||||
|
||||
it('combines both checking and handling when calling handleSafe.', async(): Promise<void> => {
|
||||
await expect(authorizer.handleSafe({ identifier: auxiliaryIdentifier, credentials, permissions }))
|
||||
.resolves.toBeUndefined();
|
||||
expect(source.handleSafe).toHaveBeenLastCalledWith(
|
||||
{ identifier: associatedIdentifier, credentials, permissions },
|
||||
);
|
||||
await expect(authorizer.handleSafe({ identifier: associatedIdentifier, credentials, permissions }))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
source.handleSafe = jest.fn().mockRejectedValue(new Error('no source support'));
|
||||
await expect(authorizer.handleSafe({ identifier: auxiliaryIdentifier, credentials, permissions }))
|
||||
.rejects.toThrow('no source support');
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { namedNode, quad } from '@rdfjs/data-model';
|
||||
import streamifyArray from 'streamify-array';
|
||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||
import { WebAclAuthorizer } from '../../../src/authorization/WebAclAuthorizer';
|
||||
import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||
@@ -9,8 +8,10 @@ import type { ResourceIdentifier } from '../../../src/ldp/representation/Resourc
|
||||
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
||||
import { ForbiddenHttpError } from '../../../src/util/errors/ForbiddenHttpError';
|
||||
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
import { UnauthorizedHttpError } from '../../../src/util/errors/UnauthorizedHttpError';
|
||||
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
|
||||
|
||||
const nn = namedNode;
|
||||
|
||||
@@ -45,13 +46,15 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
authorizer = new WebAclAuthorizer(aclStrategy, store, identifierStrategy);
|
||||
});
|
||||
|
||||
it('handles all inputs.', async(): Promise<void> => {
|
||||
it('handles all non-acl inputs.', async(): Promise<void> => {
|
||||
authorizer = new WebAclAuthorizer(aclStrategy, null as any, identifierStrategy);
|
||||
await expect(authorizer.canHandle({} as any)).resolves.toBeUndefined();
|
||||
await expect(authorizer.canHandle({ identifier } as any)).resolves.toBeUndefined();
|
||||
await expect(authorizer.canHandle({ identifier: aclStrategy.getAuxiliaryIdentifier(identifier) } as any))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
});
|
||||
|
||||
it('allows access if the acl file allows all agents.', async(): Promise<void> => {
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: guardedStreamFrom([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
@@ -66,7 +69,7 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
throw new NotFoundHttpError();
|
||||
}
|
||||
return {
|
||||
data: streamifyArray([
|
||||
data: guardedStreamFrom([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')),
|
||||
quad(nn('auth'), nn(`${acl}default`), nn(identifierStrategy.getParentContainer(identifier).path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
@@ -78,7 +81,7 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
});
|
||||
|
||||
it('allows access to authorized agents if the acl files allows all authorized users.', async(): Promise<void> => {
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: guardedStreamFrom([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/AuthenticatedAgent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
@@ -89,7 +92,7 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
});
|
||||
|
||||
it('errors if authorization is required but the agent is not authorized.', async(): Promise<void> => {
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: guardedStreamFrom([
|
||||
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/AuthenticatedAgent')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
@@ -100,7 +103,7 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
|
||||
it('allows access to specific agents if the acl files identifies them.', async(): Promise<void> => {
|
||||
credentials.webId = 'http://test.com/user';
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: guardedStreamFrom([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
@@ -111,7 +114,7 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
|
||||
it('errors if a specific agents wants to access files not assigned to them.', async(): Promise<void> => {
|
||||
credentials.webId = 'http://test.com/user';
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: guardedStreamFrom([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn('http://test.com/differentUser')),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
@@ -120,19 +123,6 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError);
|
||||
});
|
||||
|
||||
it('errors if an agent tries to edit the acl file without control permissions.', async(): Promise<void> => {
|
||||
credentials.webId = 'http://test.com/user';
|
||||
identifier.path = 'http://test.com/foo';
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
|
||||
]) } as Representation);
|
||||
identifier = aclStrategy.getAuxiliaryIdentifier(identifier);
|
||||
await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError);
|
||||
});
|
||||
|
||||
it('passes errors of the ResourceStore along.', async(): Promise<void> => {
|
||||
store.getRepresentation = async(): Promise<Representation> => {
|
||||
throw new Error('TEST!');
|
||||
@@ -158,7 +148,7 @@ describe('A WebAclAuthorizer', (): void => {
|
||||
append: true,
|
||||
control: false,
|
||||
};
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: streamifyArray([
|
||||
store.getRepresentation = async(): Promise<Representation> => ({ data: guardedStreamFrom([
|
||||
quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)),
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
|
||||
|
||||
Reference in New Issue
Block a user