mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
205 lines
8.1 KiB
TypeScript
205 lines
8.1 KiB
TypeScript
import { Parser } from 'n3';
|
|
import type { Quad } from '@rdfjs/types';
|
|
import type { Credentials } from '../../../src/authentication/Credentials';
|
|
import { AcpReader } from '../../../src/authorization/AcpReader';
|
|
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
|
import type { AuxiliaryStrategy } from '../../../src/http/auxiliary/AuxiliaryStrategy';
|
|
import { BasicRepresentation } from '../../../src/http/representation/BasicRepresentation';
|
|
import type { Representation } from '../../../src/http/representation/Representation';
|
|
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
|
import { INTERNAL_QUADS } from '../../../src/util/ContentTypes';
|
|
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
|
|
import type { IdentifierStrategy } from '../../../src/util/identifiers/IdentifierStrategy';
|
|
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
|
import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap';
|
|
import { joinUrl } from '../../../src/util/PathUtil';
|
|
import { SimpleSuffixStrategy } from '../../util/SimpleSuffixStrategy';
|
|
import { compareMaps } from '../../util/Util';
|
|
|
|
const acrSuffix = '.acr';
|
|
|
|
function toQuads(turtle: string, baseIRI: string): Quad[] {
|
|
baseIRI = `${baseIRI}${acrSuffix}`;
|
|
turtle = `
|
|
@prefix acp: <http://www.w3.org/ns/solid/acp#>.
|
|
@prefix acl: <http://www.w3.org/ns/auth/acl#>.
|
|
${turtle}
|
|
`;
|
|
return new Parser({ format: 'Turtle', baseIRI }).parse(turtle);
|
|
}
|
|
|
|
describe('An AcpReader', (): void => {
|
|
const baseUrl = 'http://example.com/';
|
|
let credentials: Credentials;
|
|
// Subject identifiers are used as keys, values are the output of their corresponding ACR resource
|
|
let dataMap: Record<string, Quad[]>;
|
|
let acrStrategy: AuxiliaryStrategy;
|
|
let acrStore: jest.Mocked<ResourceStore>;
|
|
let identifierStrategy: IdentifierStrategy;
|
|
let acpReader: AcpReader;
|
|
|
|
beforeEach(async(): Promise<void> => {
|
|
credentials = {};
|
|
dataMap = {};
|
|
|
|
acrStrategy = new SimpleSuffixStrategy(acrSuffix);
|
|
|
|
acrStore = {
|
|
getRepresentation: jest.fn((identifier): Representation => {
|
|
const subjectIdentifier = acrStrategy.getSubjectIdentifier(identifier);
|
|
if (!dataMap[subjectIdentifier.path]) {
|
|
throw new NotFoundHttpError();
|
|
}
|
|
return new BasicRepresentation(dataMap[subjectIdentifier.path], subjectIdentifier, INTERNAL_QUADS, false);
|
|
}),
|
|
} as any;
|
|
|
|
identifierStrategy = new SingleRootIdentifierStrategy(baseUrl);
|
|
|
|
acpReader = new AcpReader(acrStrategy, acrStore, identifierStrategy);
|
|
});
|
|
|
|
it('can check permissions on the root container.', async(): Promise<void> => {
|
|
const target = { path: joinUrl(baseUrl, 'foo') };
|
|
dataMap[baseUrl] = toQuads(`
|
|
[]
|
|
acp:resource <./>;
|
|
acp:accessControl [ acp:apply _:policy ].
|
|
_:policy
|
|
acp:allow acl:Read;
|
|
acp:allOf _:matcher.
|
|
_:matcher acp:agent acp:PublicAgent.
|
|
`, baseUrl);
|
|
const requestedModes = new IdentifierSetMultiMap([
|
|
[{ path: baseUrl }, AccessMode.read ],
|
|
[ target, AccessMode.read ],
|
|
]);
|
|
const expectedPermissions = new IdentifierMap([
|
|
[{ path: baseUrl }, { read: true }],
|
|
[ target, {}],
|
|
]);
|
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
|
});
|
|
|
|
it('throws an error if something goes wrong reading data.', async(): Promise<void> => {
|
|
acrStore.getRepresentation.mockRejectedValueOnce(new Error('bad request'));
|
|
const requestedModes = new IdentifierSetMultiMap([[{ path: baseUrl }, AccessMode.read ]]);
|
|
await expect(acpReader.handle({ credentials, requestedModes })).rejects.toThrow('bad request');
|
|
});
|
|
|
|
it('allows for permission inheritance.', async(): Promise<void> => {
|
|
const target = { path: joinUrl(baseUrl, 'foo') };
|
|
dataMap[baseUrl] = toQuads(`
|
|
[]
|
|
acp:resource <./>;
|
|
acp:memberAccessControl [ acp:apply _:policy ].
|
|
_:policy
|
|
acp:allow acl:Read;
|
|
acp:allOf _:matcher.
|
|
_:matcher acp:agent acp:PublicAgent.
|
|
`, baseUrl);
|
|
const requestedModes = new IdentifierSetMultiMap([
|
|
[{ path: baseUrl }, AccessMode.read ],
|
|
[ target, AccessMode.read ],
|
|
]);
|
|
const expectedPermissions = new IdentifierMap([
|
|
[{ path: baseUrl }, {}],
|
|
[ target, { read: true }],
|
|
]);
|
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
|
});
|
|
|
|
it('combines all relevant ACRs.', async(): Promise<void> => {
|
|
const target = { path: joinUrl(baseUrl, 'foo') };
|
|
dataMap[baseUrl] = toQuads(`
|
|
[]
|
|
acp:resource <./>;
|
|
acp:accessControl [ acp:apply _:controlPolicy ];
|
|
acp:memberAccessControl [ acp:apply _:readPolicy ].
|
|
_:readPolicy
|
|
acp:allow acl:Read;
|
|
acp:allOf _:matcher.
|
|
_:controlPolicy
|
|
acp:allow acl:Control;
|
|
acp:allOf _:matcher.
|
|
_:matcher acp:agent acp:PublicAgent.
|
|
`, baseUrl);
|
|
dataMap[target.path] = toQuads(`
|
|
[]
|
|
acp:resource <./foo>;
|
|
acp:accessControl [ acp:apply _:appendPolicy ].
|
|
_:appendPolicy
|
|
acp:allow acl:Append;
|
|
acp:allOf _:matcher.
|
|
_:matcher acp:agent acp:PublicAgent.
|
|
`, target.path);
|
|
const requestedModes = new IdentifierSetMultiMap([
|
|
[{ path: baseUrl }, AccessMode.read ],
|
|
[ target, AccessMode.read ],
|
|
]);
|
|
const expectedPermissions = new IdentifierMap([
|
|
[{ path: baseUrl }, { control: true }],
|
|
[ target, { read: true, append: true }],
|
|
]);
|
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
|
});
|
|
|
|
it('caches data to prevent duplicate ResourceStore calls.', async(): Promise<void> => {
|
|
const target1 = { path: joinUrl(baseUrl, 'foo/') };
|
|
const target2 = { path: joinUrl(baseUrl, 'foo/bar') };
|
|
dataMap[baseUrl] = toQuads(`
|
|
[]
|
|
acp:resource <./>;
|
|
acp:memberAccessControl [ acp:apply _:policy ].
|
|
_:policy
|
|
acp:allow acl:Read;
|
|
acp:allOf _:matcher.
|
|
_:matcher acp:agent acp:PublicAgent.
|
|
`, baseUrl);
|
|
const requestedModes = new IdentifierSetMultiMap([
|
|
[{ path: baseUrl }, AccessMode.read ],
|
|
[ target1, AccessMode.read ],
|
|
[ target2, AccessMode.read ],
|
|
]);
|
|
const expectedPermissions = new IdentifierMap([
|
|
[{ path: baseUrl }, {}],
|
|
[ target1, { read: true }],
|
|
[ target2, { read: true }],
|
|
]);
|
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
|
expect(acrStore.getRepresentation).toHaveBeenCalledTimes(3);
|
|
expect(acrStore.getRepresentation)
|
|
.toHaveBeenCalledWith(acrStrategy.getAuxiliaryIdentifier(target1), { type: { [INTERNAL_QUADS]: 1 }});
|
|
expect(acrStore.getRepresentation)
|
|
.toHaveBeenCalledWith(acrStrategy.getAuxiliaryIdentifier(target2), { type: { [INTERNAL_QUADS]: 1 }});
|
|
expect(acrStore.getRepresentation)
|
|
.toHaveBeenCalledWith(acrStrategy.getAuxiliaryIdentifier({ path: baseUrl }), { type: { [INTERNAL_QUADS]: 1 }});
|
|
});
|
|
|
|
it('correctly puts the credentials in the context.', async(): Promise<void> => {
|
|
dataMap[baseUrl] = toQuads(`
|
|
[]
|
|
acp:resource <./> ;
|
|
acp:accessControl [ acp:apply _:policy ].
|
|
_:policy
|
|
acp:allow acl:Read;
|
|
acp:allOf _:matcher.
|
|
_:matcher
|
|
acp:agent <http://example.com/#me>;
|
|
acp:client <http://client.example.com/#me>;
|
|
acp:issuer <http://example.com/idp>.
|
|
`, baseUrl);
|
|
const requestedModes = new IdentifierSetMultiMap([[{ path: baseUrl }, AccessMode.read ]]);
|
|
let expectedPermissions = new IdentifierMap([[{ path: baseUrl }, {}]]);
|
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
|
|
|
credentials = {
|
|
agent: { webId: 'http://example.com/#me' },
|
|
client: { clientId: 'http://client.example.com/#me' },
|
|
issuer: { url: 'http://example.com/idp' },
|
|
};
|
|
expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { read: true }]]);
|
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
|
});
|
|
});
|