CommunitySolidServer/test/unit/authorization/ParentContainerReader.test.ts
2022-07-11 14:07:43 +02:00

115 lines
6.2 KiB
TypeScript

import type { CredentialSet } from '../../../src/authentication/Credentials';
import { ParentContainerReader } from '../../../src/authorization/ParentContainerReader';
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
import type { AccessMap, PermissionMap } from '../../../src/authorization/permissions/Permissions';
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap';
import { joinUrl } from '../../../src/util/PathUtil';
import { compareMaps } from '../../util/Util';
describe('A ParentContainerReader', (): void => {
const baseUrl = 'http://example.com/';
const parent1 = { path: joinUrl(baseUrl, 'foo/') };
const target1 = { path: joinUrl(parent1.path, 'foo') };
const parent2 = { path: joinUrl(baseUrl, 'bar/') };
const target2 = { path: joinUrl(parent2.path, 'bar') };
const parent3 = { path: joinUrl(baseUrl, 'baz/') };
const target3 = { path: joinUrl(parent3.path, 'baz') };
const credentials: CredentialSet = { public: {}};
let requestedModes: AccessMap;
let sourceResult: PermissionMap;
const identifierStrategy = new SingleRootIdentifierStrategy(baseUrl);
let source: jest.Mocked<PermissionReader>;
let reader: ParentContainerReader;
beforeEach(async(): Promise<void> => {
requestedModes = new IdentifierSetMultiMap();
sourceResult = new IdentifierMap([[{ path: joinUrl(baseUrl, 'test') }, { public: { read: true }}]]);
source = { handleSafe: jest.fn().mockResolvedValue(sourceResult) } as any;
reader = new ParentContainerReader(source, identifierStrategy);
});
it('requires parent append permissions to create resources.', async(): Promise<void> => {
requestedModes.set(target1, new Set([ AccessMode.create ]));
requestedModes.set(target2, new Set([ AccessMode.create ]));
sourceResult.set(parent1, { public: { append: true }});
const result = await reader.handle({ requestedModes, credentials });
expect(result.get(target1)).toEqual({ public: { create: true }});
expect(result.get(target2)).toEqual({ });
const updatedMap = new IdentifierSetMultiMap(requestedModes);
updatedMap.set(parent1, AccessMode.append);
updatedMap.set(parent2, AccessMode.append);
expect(source.handleSafe).toHaveBeenCalledTimes(1);
expect(source.handleSafe.mock.calls[0][0].credentials).toBe(credentials);
compareMaps(source.handleSafe.mock.calls[0][0].requestedModes, updatedMap);
expect(source.handleSafe.mock.calls[0][0].requestedModes).not.toEqual(requestedModes);
});
it('requires write and parent write permissions to delete resources.', async(): Promise<void> => {
requestedModes.set(target1, new Set([ AccessMode.delete ]));
requestedModes.set(target2, new Set([ AccessMode.delete ]));
requestedModes.set(target3, new Set([ AccessMode.delete ]));
sourceResult.set(parent1, { public: { write: true }});
sourceResult.set(parent2, { public: { write: true }});
sourceResult.set(target1, { public: { write: true }});
sourceResult.set(target3, { public: { write: true }});
const result = await reader.handle({ requestedModes, credentials });
expect(result.get(target1)).toEqual({ public: { delete: true, write: true }});
expect(result.get(target2)).toEqual({ public: {}});
expect(result.get(target3)).toEqual({ public: { write: true }});
const updatedMap = new IdentifierSetMultiMap(requestedModes);
updatedMap.set(parent1, AccessMode.write);
updatedMap.set(parent2, AccessMode.write);
updatedMap.set(parent3, AccessMode.write);
expect(source.handleSafe).toHaveBeenCalledTimes(1);
expect(source.handleSafe.mock.calls[0][0].credentials).toBe(credentials);
compareMaps(source.handleSafe.mock.calls[0][0].requestedModes, updatedMap);
});
it('does not allow create/delete if the source explicitly forbids it.', async(): Promise<void> => {
requestedModes.set(target1, new Set([ AccessMode.create, AccessMode.delete ]));
requestedModes.set(target2, new Set([ AccessMode.create, AccessMode.delete ]));
sourceResult.set(parent1, { public: { write: true, append: true }});
sourceResult.set(parent2, { public: { write: true, append: true }});
sourceResult.set(target1, { public: { write: true }});
sourceResult.set(target2, { public: { write: true, create: false, delete: false }});
const result = await reader.handle({ requestedModes, credentials });
expect(result.get(target1)).toEqual({ public: { write: true, create: true, delete: true }});
expect(result.get(target2)).toEqual({ public: { write: true, create: false, delete: false }});
const updatedMap = new IdentifierSetMultiMap(requestedModes);
updatedMap.set(parent1, new Set([ AccessMode.write, AccessMode.append ]));
updatedMap.set(parent2, new Set([ AccessMode.write, AccessMode.append ]));
expect(source.handleSafe).toHaveBeenCalledTimes(1);
expect(source.handleSafe.mock.calls[0][0].credentials).toBe(credentials);
compareMaps(source.handleSafe.mock.calls[0][0].requestedModes, updatedMap);
});
it('combines the modes with the parent resource if it is also being requested.', async(): Promise<void> => {
requestedModes.set(target1, AccessMode.create);
requestedModes.set(parent1, AccessMode.write);
sourceResult.set(parent1, { public: { write: true, append: true }});
sourceResult.set(target1, { public: { write: true }});
const result = await reader.handle({ requestedModes, credentials });
expect(result.get(target1)).toEqual({ public: { write: true, create: true, delete: true }});
expect(result.get(parent1)).toEqual({ public: { write: true, append: true }});
const updatedMap = new IdentifierSetMultiMap(requestedModes);
updatedMap.set(parent1, new Set([ AccessMode.write, AccessMode.append ]));
expect(source.handleSafe).toHaveBeenCalledTimes(1);
expect(source.handleSafe.mock.calls[0][0].credentials).toBe(credentials);
compareMaps(source.handleSafe.mock.calls[0][0].requestedModes, updatedMap);
expect(source.handleSafe.mock.calls[0][0].requestedModes.get(parent1))
.toEqual(new Set([ AccessMode.write, AccessMode.append ]));
});
});