feat: Check parent ACL permissions for create/delete requests

This commit is contained in:
Joachim Van Herwegen
2022-02-28 16:08:33 +01:00
parent e86e0cf36b
commit d908374364
2 changed files with 241 additions and 49 deletions

View File

@@ -16,6 +16,7 @@ import { ForbiddenHttpError } from '../../../src/util/errors/ForbiddenHttpError'
import { InternalServerError } from '../../../src/util/errors/InternalServerError';
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
import { ensureTrailingSlash } from '../../../src/util/PathUtil';
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
const { namedNode: nn, quad } = DataFactory;
@@ -43,7 +44,7 @@ describe('A WebAclReader', (): void => {
identifier = { path: 'http://test.com/foo' };
modes = new Set<AccessMode | AclMode>([
AccessMode.read, AccessMode.write, AccessMode.append, AccessMode.create, AccessMode.delete, AclMode.control,
AccessMode.read, AccessMode.write, AccessMode.append, AclMode.control,
]) as Set<AccessMode>;
input = { credentials, identifier, modes };
@@ -131,6 +132,25 @@ describe('A WebAclReader', (): void => {
});
});
it('does not use default authorizations for the resource itself.', async(): Promise<void> => {
input.identifier = { path: ensureTrailingSlash(input.identifier.path) };
store.getRepresentation.mockImplementation(async(): Promise<Representation> =>
new BasicRepresentation([
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')),
quad(nn('auth'), nn(`${acl}default`), nn(input.identifier.path)),
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
quad(nn('auth2'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
quad(nn('auth2'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')),
quad(nn('auth2'), nn(`${acl}accessTo`), nn(input.identifier.path)),
quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)),
], INTERNAL_QUADS));
await expect(reader.handle(input)).resolves.toEqual({
[CredentialGroup.public]: { append: true },
[CredentialGroup.agent]: { append: true },
});
});
it('re-throws ResourceStore errors as internal errors.', async(): Promise<void> => {
store.getRepresentation.mockRejectedValue(new Error('TEST!'));
const promise = reader.handle(input);
@@ -203,4 +223,103 @@ describe('A WebAclReader', (): void => {
[CredentialGroup.agent]: { control: true },
});
});
it('requires append permissions on the parent container to create resources.', async(): Promise<void> => {
store.getRepresentation.mockImplementation(async(id): Promise<Representation> => {
const subject = id.path.slice(0, -4);
if (subject === input.identifier.path) {
throw new NotFoundHttpError();
}
return new BasicRepresentation([
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
quad(nn('auth'), nn(`${acl}accessTo`), nn(subject)),
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Append`)),
], 'internal/quads');
});
input.modes.add(AccessMode.create);
await expect(reader.handle(input)).resolves.toEqual({
[CredentialGroup.public]: { create: true },
[CredentialGroup.agent]: { create: true },
});
});
it('requires write permissions on the parent container to delete resources.', async(): Promise<void> => {
store.getRepresentation.mockImplementation(async(id): Promise<Representation> => new BasicRepresentation([
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
quad(nn('auth'), nn(`${acl}accessTo`), nn(id.path.slice(0, -4))),
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
], 'internal/quads'));
input.modes.add(AccessMode.delete);
await expect(reader.handle(input)).resolves.toEqual({
[CredentialGroup.public]: { append: true, write: true, delete: true, create: true },
[CredentialGroup.agent]: { append: true, write: true, delete: true, create: true },
});
});
it('can use the same acl resource for both target and parent.', async(): Promise<void> => {
store.getRepresentation.mockImplementation(async(id): Promise<Representation> => {
const subject = id.path.slice(0, -4);
if (subject === input.identifier.path) {
throw new NotFoundHttpError();
}
return new BasicRepresentation([
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
quad(nn('auth'), nn(`${acl}accessTo`), nn(subject)),
quad(nn('auth'), nn(`${acl}default`), nn(subject)),
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
], 'internal/quads');
});
input.modes.add(AccessMode.create);
await expect(reader.handle(input)).resolves.toEqual({
[CredentialGroup.public]: { append: true, write: true, delete: true, create: true },
[CredentialGroup.agent]: { append: true, write: true, delete: true, create: true },
});
});
it('does not grant create permission if the parent does not have append rights.', async(): Promise<void> => {
store.getRepresentation.mockImplementation(async(id): Promise<Representation> => {
const subject = id.path.slice(0, -4);
if (subject === input.identifier.path) {
throw new NotFoundHttpError();
}
return new BasicRepresentation([
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
quad(nn('auth'), nn(`${acl}default`), nn(subject)),
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
quad(nn('auth2'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
quad(nn('auth2'), nn(`${acl}accessTo`), nn(subject)),
quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Read`)),
], 'internal/quads');
});
input.modes.add(AccessMode.create);
await expect(reader.handle(input)).resolves.toEqual({
[CredentialGroup.public]: { append: true, write: true },
[CredentialGroup.agent]: { append: true, write: true },
});
});
it('can use a grandparent acl resource for both target and parent.', async(): Promise<void> => {
input.identifier = { path: 'http://test.com/foo/bar/' };
store.getRepresentation.mockImplementation(async(id): Promise<Representation> => {
const subject = id.path.slice(0, -4);
if (subject !== 'http://test.com/') {
throw new NotFoundHttpError();
}
return new BasicRepresentation([
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
quad(nn('auth'), nn(`${acl}default`), nn(subject)),
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
], 'internal/quads');
});
input.modes.add(AccessMode.create);
await expect(reader.handle(input)).resolves.toEqual({
[CredentialGroup.public]: { append: true, write: true, delete: true, create: true },
[CredentialGroup.agent]: { append: true, write: true, delete: true, create: true },
});
});
});