mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Pass access modes to PermissionReaders
This allows PermissionReaders to potentially only check the necessary access modes for potential performance optimization.
This commit is contained in:
@@ -23,12 +23,12 @@ describe('An AllStaticReader', (): void => {
|
||||
|
||||
it('always returns permissions matching the given allow parameter.', async(): Promise<void> => {
|
||||
let authorizer = new AllStaticReader(true);
|
||||
await expect(authorizer.handle({ credentials, identifier })).resolves.toEqual({
|
||||
await expect(authorizer.handle({ credentials, identifier, modes: new Set() })).resolves.toEqual({
|
||||
[CredentialGroup.agent]: getPermissions(true),
|
||||
});
|
||||
|
||||
authorizer = new AllStaticReader(false);
|
||||
await expect(authorizer.handle({ credentials, identifier })).resolves.toEqual({
|
||||
await expect(authorizer.handle({ credentials, identifier, modes: new Set() })).resolves.toEqual({
|
||||
[CredentialGroup.agent]: getPermissions(false),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { AuxiliaryReader } from '../../../src/authorization/AuxiliaryReader';
|
||||
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
||||
import type { PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
||||
import type { AccessMode, PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
||||
import type { AuxiliaryStrategy } from '../../../src/http/auxiliary/AuxiliaryStrategy';
|
||||
import type { ResourceIdentifier } from '../../../src/http/representation/ResourceIdentifier';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
@@ -9,6 +9,7 @@ import { NotImplementedHttpError } from '../../../src/util/errors/NotImplemented
|
||||
describe('An AuxiliaryReader', (): void => {
|
||||
const suffix = '.dummy';
|
||||
const credentials = {};
|
||||
const modes = new Set<AccessMode>();
|
||||
const subjectIdentifier = { path: 'http://test.com/foo' };
|
||||
const auxiliaryIdentifier = { path: 'http://test.com/foo.dummy' };
|
||||
const permissionSet: PermissionSet = { [CredentialGroup.agent]: { read: true }};
|
||||
@@ -33,50 +34,50 @@ describe('An AuxiliaryReader', (): void => {
|
||||
});
|
||||
|
||||
it('can handle auxiliary resources if the source supports the subject resource.', async(): Promise<void> => {
|
||||
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials }))
|
||||
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials, modes }))
|
||||
.resolves.toBeUndefined();
|
||||
expect(source.canHandle).toHaveBeenLastCalledWith(
|
||||
{ identifier: subjectIdentifier, credentials },
|
||||
{ identifier: subjectIdentifier, credentials, modes },
|
||||
);
|
||||
await expect(reader.canHandle({ identifier: subjectIdentifier, credentials }))
|
||||
await expect(reader.canHandle({ identifier: subjectIdentifier, credentials, modes }))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
|
||||
strategy.usesOwnAuthorization.mockReturnValueOnce(true);
|
||||
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials }))
|
||||
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials, modes }))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
|
||||
source.canHandle.mockRejectedValue(new Error('no source support'));
|
||||
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials }))
|
||||
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials, modes }))
|
||||
.rejects.toThrow('no source support');
|
||||
});
|
||||
|
||||
it('handles resources by sending the updated parameters to the source.', async(): Promise<void> => {
|
||||
await expect(reader.handle({ identifier: auxiliaryIdentifier, credentials }))
|
||||
await expect(reader.handle({ identifier: auxiliaryIdentifier, credentials, modes }))
|
||||
.resolves.toBe(permissionSet);
|
||||
expect(source.handle).toHaveBeenLastCalledWith(
|
||||
{ identifier: subjectIdentifier, credentials },
|
||||
{ identifier: subjectIdentifier, credentials, modes },
|
||||
);
|
||||
// Safety checks are not present when calling `handle`
|
||||
await expect(reader.handle({ identifier: subjectIdentifier, credentials }))
|
||||
await expect(reader.handle({ identifier: subjectIdentifier, credentials, modes }))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
});
|
||||
|
||||
it('combines both checking and handling when calling handleSafe.', async(): Promise<void> => {
|
||||
await expect(reader.handleSafe({ identifier: auxiliaryIdentifier, credentials }))
|
||||
await expect(reader.handleSafe({ identifier: auxiliaryIdentifier, credentials, modes }))
|
||||
.resolves.toBe(permissionSet);
|
||||
expect(source.handleSafe).toHaveBeenLastCalledWith(
|
||||
{ identifier: subjectIdentifier, credentials },
|
||||
{ identifier: subjectIdentifier, credentials, modes },
|
||||
);
|
||||
|
||||
await expect(reader.handleSafe({ identifier: subjectIdentifier, credentials }))
|
||||
await expect(reader.handleSafe({ identifier: subjectIdentifier, credentials, modes }))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
|
||||
strategy.usesOwnAuthorization.mockReturnValueOnce(true);
|
||||
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials }))
|
||||
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials, modes }))
|
||||
.rejects.toThrow(NotImplementedHttpError);
|
||||
|
||||
source.handleSafe.mockRejectedValue(new Error('no source support'));
|
||||
await expect(reader.handleSafe({ identifier: auxiliaryIdentifier, credentials }))
|
||||
await expect(reader.handleSafe({ identifier: auxiliaryIdentifier, credentials, modes }))
|
||||
.rejects.toThrow('no source support');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { OwnerPermissionReader } from '../../../src/authorization/OwnerPermissionReader';
|
||||
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
|
||||
import type { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||
import type { AuxiliaryIdentifierStrategy } from '../../../src/http/auxiliary/AuxiliaryIdentifierStrategy';
|
||||
import type { ResourceIdentifier } from '../../../src/http/representation/ResourceIdentifier';
|
||||
import type {
|
||||
@@ -13,6 +15,7 @@ describe('An OwnerPermissionReader', (): void => {
|
||||
const podBaseUrl = 'http://test.com/alice/';
|
||||
let credentials: CredentialSet;
|
||||
let identifier: ResourceIdentifier;
|
||||
let modes: Set<AccessMode>;
|
||||
let settings: AccountSettings;
|
||||
let accountStore: jest.Mocked<AccountStore>;
|
||||
let aclStrategy: jest.Mocked<AuxiliaryIdentifierStrategy>;
|
||||
@@ -23,6 +26,8 @@ describe('An OwnerPermissionReader', (): void => {
|
||||
|
||||
identifier = { path: `${podBaseUrl}.acl` };
|
||||
|
||||
modes = new Set<AccessMode | AclMode>([ AclMode.control ]) as Set<AccessMode>;
|
||||
|
||||
settings = {
|
||||
useIdp: true,
|
||||
podBaseUrl,
|
||||
@@ -46,31 +51,31 @@ describe('An OwnerPermissionReader', (): void => {
|
||||
|
||||
it('returns empty permissions for non-ACL resources.', async(): Promise<void> => {
|
||||
identifier.path = podBaseUrl;
|
||||
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
|
||||
await expect(reader.handle({ credentials, identifier, modes })).resolves.toEqual({});
|
||||
});
|
||||
|
||||
it('returns empty permissions if there is no agent WebID.', async(): Promise<void> => {
|
||||
credentials = {};
|
||||
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
|
||||
await expect(reader.handle({ credentials, identifier, modes })).resolves.toEqual({});
|
||||
});
|
||||
|
||||
it('returns empty permissions if the agent has no account.', async(): Promise<void> => {
|
||||
credentials.agent!.webId = 'http://test.com/someone/else';
|
||||
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
|
||||
await expect(reader.handle({ credentials, identifier, modes })).resolves.toEqual({});
|
||||
});
|
||||
|
||||
it('returns empty permissions if the account has no pod.', async(): Promise<void> => {
|
||||
delete settings.podBaseUrl;
|
||||
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
|
||||
await expect(reader.handle({ credentials, identifier, modes })).resolves.toEqual({});
|
||||
});
|
||||
|
||||
it('returns empty permissions if the target identifier is not in the pod.', async(): Promise<void> => {
|
||||
identifier.path = 'http://somewhere.else/.acl';
|
||||
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
|
||||
await expect(reader.handle({ credentials, identifier, modes })).resolves.toEqual({});
|
||||
});
|
||||
|
||||
it('returns full permissions if the owner is accessing an ACL resource in their pod.', async(): Promise<void> => {
|
||||
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({
|
||||
await expect(reader.handle({ credentials, identifier, modes })).resolves.toEqual({
|
||||
[CredentialGroup.agent]: {
|
||||
read: true,
|
||||
write: true,
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('A PathBasedReader', (): void => {
|
||||
input = {
|
||||
identifier: { path: `${baseUrl}first` },
|
||||
credentials: {},
|
||||
modes: new Set(),
|
||||
};
|
||||
|
||||
readers = [
|
||||
|
||||
@@ -3,7 +3,8 @@ import type { PermissionReader, PermissionReaderInput } from '../../../src/autho
|
||||
import { UnionPermissionReader } from '../../../src/authorization/UnionPermissionReader';
|
||||
|
||||
describe('A UnionPermissionReader', (): void => {
|
||||
const input: PermissionReaderInput = { credentials: {}, identifier: { path: 'http://test.com/foo' }};
|
||||
const input: PermissionReaderInput =
|
||||
{ credentials: {}, identifier: { path: 'http://test.com/foo' }, modes: new Set() };
|
||||
let readers: jest.Mocked<PermissionReader>[];
|
||||
let unionReader: UnionPermissionReader;
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { DataFactory } from 'n3';
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { AccessChecker } from '../../../src/authorization/access/AccessChecker';
|
||||
import type { PermissionReaderInput } from '../../../src/authorization/PermissionReader';
|
||||
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
|
||||
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||
import { WebAclReader } from '../../../src/authorization/WebAclReader';
|
||||
import type { AuxiliaryIdentifierStrategy } from '../../../src/http/auxiliary/AuxiliaryIdentifierStrategy';
|
||||
import { BasicRepresentation } from '../../../src/http/representation/BasicRepresentation';
|
||||
@@ -31,12 +34,20 @@ describe('A WebAclReader', (): void => {
|
||||
const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/');
|
||||
let credentials: CredentialSet;
|
||||
let identifier: ResourceIdentifier;
|
||||
let modes: Set<AccessMode>;
|
||||
let input: PermissionReaderInput;
|
||||
let accessChecker: jest.Mocked<AccessChecker>;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
credentials = { [CredentialGroup.public]: {}, [CredentialGroup.agent]: {}};
|
||||
identifier = { path: 'http://test.com/foo' };
|
||||
|
||||
modes = new Set<AccessMode | AclMode>([
|
||||
AccessMode.read, AccessMode.write, AccessMode.append, AccessMode.create, AccessMode.delete, AclMode.control,
|
||||
]) as Set<AccessMode>;
|
||||
|
||||
input = { credentials, identifier, modes };
|
||||
|
||||
store = {
|
||||
getRepresentation: jest.fn().mockResolvedValue(new BasicRepresentation([
|
||||
quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
||||
@@ -55,8 +66,8 @@ describe('A WebAclReader', (): void => {
|
||||
});
|
||||
|
||||
it('returns undefined permissions for undefined credentials.', async(): Promise<void> => {
|
||||
credentials = {};
|
||||
await expect(reader.handle({ identifier, credentials })).resolves.toEqual({
|
||||
input.credentials = {};
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: {},
|
||||
[CredentialGroup.agent]: {},
|
||||
});
|
||||
@@ -69,7 +80,7 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
await expect(reader.handle({ identifier, credentials })).resolves.toEqual({
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: { read: true },
|
||||
[CredentialGroup.agent]: { read: true },
|
||||
});
|
||||
@@ -82,7 +93,7 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn('somewhereElse')),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
await expect(reader.handle({ identifier, credentials })).resolves.toEqual({
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: {},
|
||||
[CredentialGroup.agent]: {},
|
||||
});
|
||||
@@ -96,7 +107,7 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}fakeMode1`)),
|
||||
]) } as Representation);
|
||||
await expect(reader.handle({ identifier, credentials })).resolves.toEqual({
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: { read: true },
|
||||
[CredentialGroup.agent]: { read: true },
|
||||
});
|
||||
@@ -114,7 +125,7 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
], INTERNAL_QUADS);
|
||||
});
|
||||
await expect(reader.handle({ identifier, credentials })).resolves.toEqual({
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: { read: true },
|
||||
[CredentialGroup.agent]: { read: true },
|
||||
});
|
||||
@@ -122,14 +133,14 @@ describe('A WebAclReader', (): void => {
|
||||
|
||||
it('re-throws ResourceStore errors as internal errors.', async(): Promise<void> => {
|
||||
store.getRepresentation.mockRejectedValue(new Error('TEST!'));
|
||||
const promise = reader.handle({ identifier, credentials });
|
||||
const promise = reader.handle(input);
|
||||
await expect(promise).rejects.toThrow(`Error reading ACL for ${identifier.path}: TEST!`);
|
||||
await expect(promise).rejects.toThrow(InternalServerError);
|
||||
});
|
||||
|
||||
it('errors if the root container has no corresponding acl document.', async(): Promise<void> => {
|
||||
store.getRepresentation.mockRejectedValue(new NotFoundHttpError());
|
||||
const promise = reader.handle({ identifier, credentials });
|
||||
const promise = reader.handle(input);
|
||||
await expect(promise).rejects.toThrow('No ACL document found for root container');
|
||||
await expect(promise).rejects.toThrow(ForbiddenHttpError);
|
||||
});
|
||||
@@ -140,7 +151,7 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
|
||||
]) } as Representation);
|
||||
await expect(reader.handle({ identifier, credentials })).resolves.toEqual({
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: { write: true, append: true, create: true, delete: true },
|
||||
[CredentialGroup.agent]: { write: true, append: true, create: true, delete: true },
|
||||
});
|
||||
@@ -152,7 +163,8 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Control`)),
|
||||
]) } as Representation);
|
||||
await expect(reader.handle({ identifier: { path: `${identifier.path}.acl` }, credentials })).resolves.toEqual({
|
||||
input.identifier = { path: `${identifier.path}.acl` };
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: { read: true, write: true, append: true, create: true, delete: true, control: true },
|
||||
[CredentialGroup.agent]: { read: true, write: true, append: true, create: true, delete: true, control: true },
|
||||
});
|
||||
@@ -164,7 +176,8 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
]) } as Representation);
|
||||
await expect(reader.handle({ identifier: { path: `${identifier.path}.acl` }, credentials })).resolves.toEqual({
|
||||
input.identifier = { path: `${identifier.path}.acl` };
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: {},
|
||||
[CredentialGroup.agent]: {},
|
||||
});
|
||||
@@ -185,7 +198,7 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Control`)),
|
||||
]) } as Representation);
|
||||
|
||||
await expect(reader.handle({ identifier, credentials })).resolves.toEqual({
|
||||
await expect(reader.handle(input)).resolves.toEqual({
|
||||
[CredentialGroup.public]: { read: true },
|
||||
[CredentialGroup.agent]: { control: true },
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ describe('An AuthorizingHttpHandler', (): void => {
|
||||
expect(modesExtractor.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(modesExtractor.handleSafe).toHaveBeenLastCalledWith(operation);
|
||||
expect(permissionReader.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(permissionReader.handleSafe).toHaveBeenLastCalledWith({ credentials, identifier: operation.target });
|
||||
expect(permissionReader.handleSafe).toHaveBeenLastCalledWith({ credentials, identifier: operation.target, modes });
|
||||
expect(authorizer.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(authorizer.handleSafe)
|
||||
.toHaveBeenLastCalledWith({ credentials, identifier: operation.target, modes, permissionSet });
|
||||
|
||||
Reference in New Issue
Block a user