mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Check parent ACL permissions for create/delete requests
This commit is contained in:
@@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user