diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2c2b527e5..d00038945 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -68,6 +68,7 @@ These changes are relevant if you wrote custom modules for the server that depen - `OwnerPermissionReader` input parameter `aclStrategy` was renamed to `authStrategy`. - `TemplatedResourcesGenerator` has been renamed to `BaseResourcesGenerator` and has a different interface now. - `CredentialSet` was replaced by a single `Credentials` interface. + `PermissionSet` and `Permission` were merged into a single interface. This impacts all authentication and authorization related classes. - `HttpServerFactory.startServer` function was renamed to `createServer` and is no longer expected to start the server. diff --git a/src/authorization/AcpReader.ts b/src/authorization/AcpReader.ts index 0112613f0..a3cead920 100644 --- a/src/authorization/AcpReader.ts +++ b/src/authorization/AcpReader.ts @@ -19,12 +19,12 @@ import { ACL } from '../util/Vocabularies'; import { getAccessControlledResources } from './AcpUtil'; import type { PermissionReaderInput } from './PermissionReader'; import { PermissionReader } from './PermissionReader'; -import type { AclPermission } from './permissions/AclPermission'; -import { AclMode } from './permissions/AclPermission'; +import type { AclPermissionSet } from './permissions/AclPermissionSet'; +import { AclMode } from './permissions/AclPermissionSet'; import { AccessMode } from './permissions/Permissions'; import type { PermissionMap, PermissionSet } from './permissions/Permissions'; -const modesMap: Record> = { +const modesMap: Record> = { [ACL.Read]: [ AccessMode.read ], [ACL.Write]: [ AccessMode.append, AccessMode.write ], [ACL.Append]: [ AccessMode.append ], @@ -85,15 +85,11 @@ export class AcpReader extends PermissionReader { } const modes = allowAccessModes(policies, context); - // We don't do a separate ACP run for public and agent credentials - // as that is only relevant for the WAC-Allow header. - // All permissions are put in the `agent` field of the PermissionSet, - // as the actual field used does not matter for authorization. - const permissionSet: PermissionSet = { agent: {}}; - for (const mode of modes) { - if (mode in modesMap) { - for (const permission of modesMap[mode]) { - permissionSet.agent![permission as AccessMode] = true; + const permissionSet: PermissionSet = { }; + for (const aclMode of modes) { + if (aclMode in modesMap) { + for (const mode of modesMap[aclMode]) { + permissionSet[mode as AccessMode] = true; } } } diff --git a/src/authorization/AllStaticReader.ts b/src/authorization/AllStaticReader.ts index cb47b7098..111ced76b 100644 --- a/src/authorization/AllStaticReader.ts +++ b/src/authorization/AllStaticReader.ts @@ -1,19 +1,18 @@ import { IdentifierMap } from '../util/map/IdentifierMap'; import type { PermissionReaderInput } from './PermissionReader'; import { PermissionReader } from './PermissionReader'; -import { permissionSetKeys } from './permissions/Permissions'; -import type { Permission, PermissionMap, PermissionSet } from './permissions/Permissions'; +import type { PermissionSet, PermissionMap } from './permissions/Permissions'; /** * PermissionReader which sets all permissions to true or false * independently of the identifier and requested permissions. */ export class AllStaticReader extends PermissionReader { - private readonly permissions: Permission; + private readonly permissionSet: PermissionSet; public constructor(allow: boolean) { super(); - this.permissions = Object.freeze({ + this.permissionSet = Object.freeze({ read: allow, write: allow, append: allow, @@ -24,18 +23,9 @@ export class AllStaticReader extends PermissionReader { public async handle({ requestedModes }: PermissionReaderInput): Promise { const availablePermissions = new IdentifierMap(); - const permissions = this.createPermissions(); for (const [ identifier ] of requestedModes) { - availablePermissions.set(identifier, permissions); + availablePermissions.set(identifier, this.permissionSet); } return availablePermissions; } - - private createPermissions(): PermissionSet { - const result: PermissionSet = {}; - for (const group of permissionSetKeys) { - result[group] = this.permissions; - } - return result; - } } diff --git a/src/authorization/AuthAuxiliaryReader.ts b/src/authorization/AuthAuxiliaryReader.ts index b3263dde4..ead074477 100644 --- a/src/authorization/AuthAuxiliaryReader.ts +++ b/src/authorization/AuthAuxiliaryReader.ts @@ -6,9 +6,9 @@ import type { MapEntry } from '../util/map/MapUtil'; import { modify } from '../util/map/MapUtil'; import type { PermissionReaderInput } from './PermissionReader'; import { PermissionReader } from './PermissionReader'; -import { AclMode } from './permissions/AclPermission'; -import type { AclPermission } from './permissions/AclPermission'; -import type { AccessMap, AccessMode, PermissionMap, PermissionSet } from './permissions/Permissions'; +import { AclMode } from './permissions/AclPermissionSet'; +import type { AclPermissionSet } from './permissions/AclPermissionSet'; +import type { AccessMap, AccessMode, PermissionSet, PermissionMap } from './permissions/Permissions'; /** * Determines the permission for authorization resources (such as ACL or ACR). @@ -64,17 +64,13 @@ export class AuthAuxiliaryReader extends PermissionReader { * Updates the permissions for an authorization resource * by interpreting the Control access mode as allowing full access. */ - protected interpretControl(identifier: ResourceIdentifier, permissionSet: PermissionSet = {}): PermissionSet { - const authSet: PermissionSet = {}; - for (const [ group, permissions ] of Object.entries(permissionSet) as [ keyof PermissionSet, AclPermission ][]) { - const { control } = permissions; - authSet[group] = { - read: control, - append: control, - write: control, - control, - } as AclPermission; - } - return authSet; + protected interpretControl(identifier: ResourceIdentifier, permissionSet: AclPermissionSet = {}): PermissionSet { + const { control } = permissionSet; + return { + read: control, + append: control, + write: control, + control, + } as AclPermissionSet; } } diff --git a/src/authorization/OwnerPermissionReader.ts b/src/authorization/OwnerPermissionReader.ts index 9e72039a1..f4ef27aba 100644 --- a/src/authorization/OwnerPermissionReader.ts +++ b/src/authorization/OwnerPermissionReader.ts @@ -10,7 +10,7 @@ import { filter } from '../util/IterableUtil'; import { IdentifierMap } from '../util/map/IdentifierMap'; import type { PermissionReaderInput } from './PermissionReader'; import { PermissionReader } from './PermissionReader'; -import type { AclPermission } from './permissions/AclPermission'; +import type { AclPermissionSet } from './permissions/AclPermissionSet'; import type { PermissionMap } from './permissions/Permissions'; /** @@ -51,14 +51,14 @@ export class OwnerPermissionReader extends PermissionReader { for (const auth of auths) { if (this.identifierStrategy.contains(podBaseUrl, auth, true)) { this.logger.debug(`Granting Control permissions to owner on ${auth.path}`); - result.set(auth, { agent: { + result.set(auth, { read: true, write: true, append: true, create: true, delete: true, control: true, - } as AclPermission }); + } as AclPermissionSet); } } return result; diff --git a/src/authorization/ParentContainerReader.ts b/src/authorization/ParentContainerReader.ts index 55c88ceb5..5289a2e2d 100644 --- a/src/authorization/ParentContainerReader.ts +++ b/src/authorization/ParentContainerReader.ts @@ -6,7 +6,7 @@ import type { MapEntry } from '../util/map/MapUtil'; import { modify } from '../util/map/MapUtil'; import type { PermissionReaderInput } from './PermissionReader'; import { PermissionReader } from './PermissionReader'; -import type { PermissionMap, Permission, PermissionSet, AccessMap } from './permissions/Permissions'; +import type { PermissionMap, PermissionSet, AccessMap } from './permissions/Permissions'; import { AccessMode } from './permissions/Permissions'; /** @@ -80,20 +80,16 @@ export class ParentContainerReader extends PermissionReader { private addContainerPermissions(resourceSet?: PermissionSet, containerSet?: PermissionSet): PermissionSet { resourceSet = resourceSet ?? {}; containerSet = containerSet ?? {}; - // Already copying the `permissionSet` here since the loop only iterates over the container entries. - // It is possible `resourceSet` contains a key that `containerSet` does not contain. - const resultSet: PermissionSet = { ...resourceSet }; - for (const [ group, containerPerms ] of Object.entries(containerSet) as [ keyof PermissionSet, Permission ][]) { - resultSet[group] = this.interpretContainerPermission(resourceSet[group] ?? {}, containerPerms); - } - return resultSet; + + return this.interpretContainerPermission(resourceSet, containerSet); } /** * Determines the create and delete permissions for the given resource permissions * based on those of its parent container. */ - private interpretContainerPermission(resourcePermission: Permission, containerPermission: Permission): Permission { + private interpretContainerPermission(resourcePermission: PermissionSet, containerPermission: PermissionSet): + PermissionSet { const mergedPermission = { ...resourcePermission }; // https://solidproject.org/TR/2021/wac-20210711: diff --git a/src/authorization/PermissionBasedAuthorizer.ts b/src/authorization/PermissionBasedAuthorizer.ts index cab271274..2040932a1 100644 --- a/src/authorization/PermissionBasedAuthorizer.ts +++ b/src/authorization/PermissionBasedAuthorizer.ts @@ -38,12 +38,12 @@ export class PermissionBasedAuthorizer extends Authorizer { for (const [ identifier, modes ] of requestedModes.entrySets()) { const modeString = [ ...modes ].join(','); this.logger.debug(`Checking if ${credentials.agent?.webId} has ${modeString} permissions for ${identifier.path}`); - const permissions = availablePermissions.get(identifier) ?? {}; + const permissionSet = availablePermissions.get(identifier) ?? {}; for (const mode of modes) { try { - this.requireModePermission(credentials, permissions, mode); + this.requireModePermission(credentials, permissionSet, mode); } catch (error: unknown) { - await this.reportAccessError(identifier, modes, permissions, error); + await this.reportAccessError(identifier, modes, permissionSet, error); } } this.logger.debug(`${JSON.stringify(credentials)} has ${modeString} permissions for ${identifier.path}`); @@ -58,8 +58,8 @@ export class PermissionBasedAuthorizer extends Authorizer { * Otherwise, deny access based on existing grounds. */ private async reportAccessError(identifier: ResourceIdentifier, modes: ReadonlySet, - permissions: PermissionSet, cause: unknown): Promise { - const exposeExistence = this.hasModePermission(permissions, AccessMode.read); + permissionSet: PermissionSet, cause: unknown): Promise { + const exposeExistence = permissionSet[AccessMode.read]; if (exposeExistence && !modes.has(AccessMode.create) && !await this.resourceSet.hasResource(identifier)) { throw new NotFoundHttpError(); } @@ -76,7 +76,7 @@ export class PermissionBasedAuthorizer extends Authorizer { * @param mode - Which mode is requested. */ private requireModePermission(credentials: Credentials, permissionSet: PermissionSet, mode: AccessMode): void { - if (!this.hasModePermission(permissionSet, mode)) { + if (!permissionSet[mode]) { if (this.isAuthenticated(credentials)) { this.logger.warn(`Agent ${credentials.agent!.webId} has no ${mode} permissions`); throw new ForbiddenHttpError(); @@ -90,18 +90,6 @@ export class PermissionBasedAuthorizer extends Authorizer { } } - /** - * Checks if one of the Permissions in the PermissionSet grants permission to use the given mode. - */ - private hasModePermission(permissionSet: PermissionSet, mode: AccessMode): boolean { - for (const permissions of Object.values(permissionSet)) { - if (permissions[mode]) { - return true; - } - } - return false; - } - /** * Checks whether the agent is authenticated (logged in) or not (public/anonymous). * @param credentials - Credentials to check. diff --git a/src/authorization/UnionPermissionReader.ts b/src/authorization/UnionPermissionReader.ts index 7972d9ff3..583ff2164 100644 --- a/src/authorization/UnionPermissionReader.ts +++ b/src/authorization/UnionPermissionReader.ts @@ -2,7 +2,7 @@ import { UnionHandler } from '../util/handlers/UnionHandler'; import { IdentifierMap } from '../util/map/IdentifierMap'; import { getDefault } from '../util/map/MapUtil'; import type { PermissionReader } from './PermissionReader'; -import type { Permission, PermissionMap, PermissionSet } from './permissions/Permissions'; +import type { PermissionMap, PermissionSet } from './permissions/Permissions'; /** * Combines the results of multiple PermissionReaders. @@ -26,22 +26,16 @@ export class UnionPermissionReader extends UnionHandler { */ private mergePermissionMaps(permissionMap: PermissionMap, result: PermissionMap): void { for (const [ identifier, permissionSet ] of permissionMap) { - for (const [ credential, permission ] of Object.entries(permissionSet) as [keyof PermissionSet, Permission][]) { - const resultSet = getDefault(result, identifier, (): PermissionSet => ({})); - resultSet[credential] = this.mergePermissions(permission, resultSet[credential]); - } + const resultSet = getDefault(result, identifier, (): PermissionSet => ({})); + result.set(identifier, this.mergePermissions(permissionSet, resultSet)); } } /** * Adds the given permissions to the result object according to the combination rules of the class. */ - private mergePermissions(permissions?: Permission, result: Permission = {}): Permission { - if (!permissions) { - return result; - } - - for (const [ key, value ] of Object.entries(permissions) as [ keyof Permission, boolean | undefined ][]) { + private mergePermissions(permissions: PermissionSet, result: PermissionSet): PermissionSet { + for (const [ key, value ] of Object.entries(permissions) as [ keyof PermissionSet, boolean | undefined ][]) { if (typeof value !== 'undefined' && result[key] !== false) { result[key] = value; } diff --git a/src/authorization/WebAclReader.ts b/src/authorization/WebAclReader.ts index c3ad965f0..11e4dff34 100644 --- a/src/authorization/WebAclReader.ts +++ b/src/authorization/WebAclReader.ts @@ -16,13 +16,13 @@ import { ACL, RDF } from '../util/Vocabularies'; import type { AccessChecker } from './access/AccessChecker'; import type { PermissionReaderInput } from './PermissionReader'; import { PermissionReader } from './PermissionReader'; -import type { AclPermission } from './permissions/AclPermission'; -import { AclMode } from './permissions/AclPermission'; +import type { AclPermissionSet } from './permissions/AclPermissionSet'; +import { AclMode } from './permissions/AclPermissionSet'; import type { PermissionMap } from './permissions/Permissions'; import { AccessMode } from './permissions/Permissions'; // Maps WebACL-specific modes to generic access modes. -const modesMap: Record> = { +const modesMap: Record> = { [ACL.Read]: [ AccessMode.read ], [ACL.Write]: [ AccessMode.append, AccessMode.write ], [ACL.Append]: [ AccessMode.append ], @@ -81,14 +81,9 @@ export class WebAclReader extends PermissionReader { Promise { const result: PermissionMap = new IdentifierMap(); for (const [ store, aclIdentifiers ] of aclMap) { - // WebACL requires knowledge of both the public and agent-specific permissions for the WAC-Allow header. - const publicPermissions = await this.determinePermissions(store, {}); - const agentPermissions = credentials.agent ? await this.determinePermissions(store, credentials) : {}; + const permissionSet = await this.determinePermissions(store, credentials); for (const identifier of aclIdentifiers) { - result.set(identifier, { - public: publicPermissions, - agent: agentPermissions, - }); + result.set(identifier, permissionSet); } } @@ -100,8 +95,8 @@ export class WebAclReader extends PermissionReader { * @param acl - Store containing all relevant authorization triples. * @param credentials - Credentials to find the permissions for. */ - private async determinePermissions(acl: Store, credentials: Credentials): Promise { - const aclPermissions: AclPermission = {}; + private async determinePermissions(acl: Store, credentials: Credentials): Promise { + const aclPermissions: AclPermissionSet = {}; // Apply all ACL rules const aclRules = acl.getSubjects(RDF.type, ACL.Authorization, null); diff --git a/src/authorization/permissions/AclPermission.ts b/src/authorization/permissions/AclPermissionSet.ts similarity index 61% rename from src/authorization/permissions/AclPermission.ts rename to src/authorization/permissions/AclPermissionSet.ts index a2ee939f9..b579f36c4 100644 --- a/src/authorization/permissions/AclPermission.ts +++ b/src/authorization/permissions/AclPermissionSet.ts @@ -1,10 +1,10 @@ -import type { Permission } from './Permissions'; +import type { PermissionSet } from './Permissions'; export enum AclMode { control = 'control', } // Adds a control field to the permissions to specify this WAC-specific value -export type AclPermission = Permission & { +export type AclPermissionSet = PermissionSet & { [mode in AclMode]?: boolean; }; diff --git a/src/authorization/permissions/Permissions.ts b/src/authorization/permissions/Permissions.ts index 882b68330..91cd7a637 100644 --- a/src/authorization/permissions/Permissions.ts +++ b/src/authorization/permissions/Permissions.ts @@ -19,21 +19,7 @@ export type AccessMap = IdentifierSetMultiMap; /** * A data interface indicating which permissions are required (based on the context). */ -export type Permission = Partial>; - -/** - * The keys that can be used in a {@link PermissionSet}; - */ -export const permissionSetKeys = [ 'public', 'agent' ] as const; - -/** - * Contains the public permissions and those specific for the agent. - * There is no good reason to subdivide permissions per type of credentials - * since credentials are a combination of multiple factors. - * The only reason is the WAC-Allow header which requires this subdivision, - * which is why we make that same division here. - */ -export type PermissionSet = Partial>; +export type PermissionSet = Partial>; /** * PermissionSet per identifier. diff --git a/src/index.ts b/src/index.ts index 0c0fcd6a8..48f5cf3b2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ export * from './authorization/access/AgentClassAccessChecker'; export * from './authorization/access/AgentGroupAccessChecker'; // Authorization/Permissions -export * from './authorization/permissions/AclPermission'; +export * from './authorization/permissions/AclPermissionSet'; export * from './authorization/permissions/CreateModesExtractor'; export * from './authorization/permissions/DeleteParentExtractor'; export * from './authorization/permissions/IntermediateCreateExtractor'; diff --git a/src/server/WacAllowHttpHandler.ts b/src/server/WacAllowHttpHandler.ts index c226c0b09..8e63a58d9 100644 --- a/src/server/WacAllowHttpHandler.ts +++ b/src/server/WacAllowHttpHandler.ts @@ -1,10 +1,9 @@ import type { Credentials } from '../authentication/Credentials'; import type { CredentialsExtractor } from '../authentication/CredentialsExtractor'; import type { PermissionReader } from '../authorization/PermissionReader'; -import { AclMode } from '../authorization/permissions/AclPermission'; -import type { AclPermission } from '../authorization/permissions/AclPermission'; +import type { AclPermissionSet } from '../authorization/permissions/AclPermissionSet'; +import { AclMode } from '../authorization/permissions/AclPermissionSet'; import type { ModesExtractor } from '../authorization/permissions/ModesExtractor'; -import type { PermissionSet } from '../authorization/permissions/Permissions'; import { AccessMode } from '../authorization/permissions/Permissions'; import type { ResponseDescription } from '../http/output/response/ResponseDescription'; import type { RepresentationMetadata } from '../http/representation/RepresentationMetadata'; @@ -63,19 +62,32 @@ export class WacAllowHttpHandler extends OperationHttpHandler { const permissionSet = availablePermissions.get(operation.target); if (permissionSet) { - this.logger.debug('Adding WAC-Allow metadata.'); - this.addWacAllowMetadata(metadata, permissionSet); + const user: AclPermissionSet = permissionSet; + let everyone: AclPermissionSet; + if (!credentials.agent?.webId) { + // User is not authenticated so public permissions are the same as agent permissions + this.logger.debug('User is not authenticated so has public permissions'); + everyone = user; + } else { + // Need to determine public permissions + this.logger.debug('Determining public permissions'); + const permissionMap = await this.permissionReader.handleSafe({ credentials: {}, requestedModes }); + everyone = permissionMap.get(operation.target) ?? {}; + } + + this.logger.debug('Adding WAC-Allow metadata'); + this.addWacAllowMetadata(metadata, everyone, user); } return response; } - private addWacAllowMetadata(metadata: RepresentationMetadata, permissionSet: PermissionSet): void { - const user: AclPermission = permissionSet.agent ?? {}; - const everyone: AclPermission = permissionSet.public ?? {}; - + /** + * Converts the found permissions to triples and puts them in the metadata. + */ + private addWacAllowMetadata(metadata: RepresentationMetadata, everyone: AclPermissionSet, user: AclPermissionSet): + void { const modes = new Set([ ...Object.keys(user), ...Object.keys(everyone) ] as AccessMode[]); - for (const mode of modes) { if (VALID_ACL_MODES.has(mode)) { const capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1) as 'Read' | 'Write' | 'Append' | 'Control'; diff --git a/test/integration/N3Patch.test.ts b/test/integration/N3Patch.test.ts index 21281c91a..f35e1b27f 100644 --- a/test/integration/N3Patch.test.ts +++ b/test/integration/N3Patch.test.ts @@ -1,7 +1,7 @@ import 'jest-rdf'; import { fetch } from 'cross-fetch'; import { Parser } from 'n3'; -import type { AclPermission } from '../../src/authorization/permissions/AclPermission'; +import type { AclPermissionSet } from '../../src/authorization/permissions/AclPermissionSet'; import { BasicRepresentation } from '../../src/http/representation/BasicRepresentation'; import type { App } from '../../src/init/App'; import type { ResourceStore } from '../../src/storage/ResourceStore'; @@ -59,7 +59,7 @@ async function expectPatch( } // Creates/updates a resource with the given data and permissions -async function setResource(path: string, turtle: string, permissions: AclPermission): Promise { +async function setResource(path: string, turtle: string, permissions: AclPermissionSet): Promise { const url = joinUrl(baseUrl, path); await store.setRepresentation({ path: url }, new BasicRepresentation(turtle, 'text/turtle')); await aclHelper.setSimpleAcl(url, { permissions, agentClass: 'agent', accessTo: true }); diff --git a/test/integration/PermissionTable.test.ts b/test/integration/PermissionTable.test.ts index 30cd11eb7..2d57060f7 100644 --- a/test/integration/PermissionTable.test.ts +++ b/test/integration/PermissionTable.test.ts @@ -1,6 +1,6 @@ import fetch from 'cross-fetch'; import { v4 } from 'uuid'; -import type { AclPermission } from '../../src/authorization/permissions/AclPermission'; +import type { AclPermissionSet } from '../../src/authorization/permissions/AclPermissionSet'; import { AccessMode as AM } from '../../src/authorization/permissions/Permissions'; import { BasicRepresentation } from '../../src/http/representation/BasicRepresentation'; import type { App } from '../../src/init/App'; @@ -120,12 +120,12 @@ const table: [string, string, AM[], AM[] | undefined, string, string, number, nu ]; /* eslint-enable no-multi-spaces */ -function toPermission(modes: AM[]): AclPermission { +function toPermission(modes: AM[]): AclPermissionSet { return Object.fromEntries(modes.map((mode): [AM, boolean] => [ mode, true ])); } -async function setWebAclPermissions(store: ResourceStore, target: string, permissions: AclPermission, - childPermissions: AclPermission): Promise { +async function setWebAclPermissions(store: ResourceStore, target: string, permissions: AclPermissionSet, + childPermissions: AclPermissionSet): Promise { const aclHelper = new AclHelper(store); await aclHelper.setSimpleAcl(target, [ { permissions, agentClass: 'agent', accessTo: true }, @@ -133,8 +133,8 @@ async function setWebAclPermissions(store: ResourceStore, target: string, permis ]); } -async function setAcpPermissions(store: ResourceStore, target: string, permissions: AclPermission, - childPermissions: AclPermission): Promise { +async function setAcpPermissions(store: ResourceStore, target: string, permissions: AclPermissionSet, + childPermissions: AclPermissionSet): Promise { const acpHelper = new AcpHelper(store); const publicMatcher = acpHelper.createMatcher({ publicAgent: true }); const policies = [ acpHelper.createPolicy({ @@ -157,7 +157,7 @@ const port = getPort('PermissionTable'); const baseUrl = `http://localhost:${port}/`; type AuthFunctionType = (store: ResourceStore, target: string, - permissions: AclPermission, childPermissions: AclPermission) => Promise; + permissions: AclPermissionSet, childPermissions: AclPermissionSet) => Promise; const rootFilePath = getTestFolder('permissionTable'); const stores: [string, string, { configs: string[]; authFunction: AuthFunctionType; teardown: () => Promise }][] = diff --git a/test/unit/authorization/AcpReader.test.ts b/test/unit/authorization/AcpReader.test.ts index 8218c655c..6747bd3bc 100644 --- a/test/unit/authorization/AcpReader.test.ts +++ b/test/unit/authorization/AcpReader.test.ts @@ -74,8 +74,8 @@ describe('An AcpReader', (): void => { [{ path: baseUrl }, AccessMode.read ], [ target, AccessMode.read ]]); const expectedPermissions = new IdentifierMap([ - [{ path: baseUrl }, { agent: { read: true }}], - [ target, { agent: {}}]]); + [{ path: baseUrl }, { read: true }], + [ target, {}]]); compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions); }); @@ -100,8 +100,8 @@ describe('An AcpReader', (): void => { [{ path: baseUrl }, AccessMode.read ], [ target, AccessMode.read ]]); const expectedPermissions = new IdentifierMap([ - [{ path: baseUrl }, { agent: {}}], - [ target, { agent: { read: true }}]]); + [{ path: baseUrl }, {}], + [ target, { read: true }]]); compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions); }); @@ -133,8 +133,8 @@ describe('An AcpReader', (): void => { [{ path: baseUrl }, AccessMode.read ], [ target, AccessMode.read ]]); const expectedPermissions = new IdentifierMap([ - [{ path: baseUrl }, { agent: { control: true }}], - [ target, { agent: { read: true, append: true }}]]); + [{ path: baseUrl }, { control: true }], + [ target, { read: true, append: true }]]); compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions); }); @@ -155,9 +155,9 @@ describe('An AcpReader', (): void => { [ target1, AccessMode.read ], [ target2, AccessMode.read ]]); const expectedPermissions = new IdentifierMap([ - [{ path: baseUrl }, { agent: {}}], - [ target1, { agent: { read: true }}], - [ target2, { agent: { read: true }}]]); + [{ path: baseUrl }, {}], + [ target1, { read: true }], + [ target2, { read: true }]]); compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions); expect(acrStore.getRepresentation).toHaveBeenCalledTimes(3); expect(acrStore.getRepresentation) @@ -182,7 +182,7 @@ describe('An AcpReader', (): void => { acp:issuer . `, baseUrl); const requestedModes = new IdentifierSetMultiMap([[{ path: baseUrl }, AccessMode.read ]]); - let expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { agent: {}}]]); + let expectedPermissions = new IdentifierMap([[{ path: baseUrl }, {}]]); compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions); credentials = { @@ -190,7 +190,7 @@ describe('An AcpReader', (): void => { client: { clientId: 'http://client.example.com/#me' }, issuer: { url: 'http://example.com/idp' }, }; - expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { agent: { read: true }}]]); + expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { read: true }]]); compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions); }); }); diff --git a/test/unit/authorization/AllStaticReader.test.ts b/test/unit/authorization/AllStaticReader.test.ts index 5f5bf32bd..ea0061adb 100644 --- a/test/unit/authorization/AllStaticReader.test.ts +++ b/test/unit/authorization/AllStaticReader.test.ts @@ -1,10 +1,10 @@ import { AllStaticReader } from '../../../src/authorization/AllStaticReader'; -import type { Permission } from '../../../src/authorization/permissions/Permissions'; +import type { PermissionSet } from '../../../src/authorization/permissions/Permissions'; import { AccessMode } from '../../../src/authorization/permissions/Permissions'; import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap'; import { compareMaps } from '../../util/Util'; -function getPermissions(allow: boolean): Permission { +function getPermissions(allow: boolean): PermissionSet { return { read: allow, write: allow, @@ -27,13 +27,11 @@ describe('An AllStaticReader', (): void => { let authorizer = new AllStaticReader(true); const requestedModes = new IdentifierSetMultiMap([[ identifier, AccessMode.read ]]); let result = await authorizer.handle({ credentials, requestedModes }); - compareMaps(result, new IdentifierMap([[ identifier, { public: getPermissions(true), - agent: getPermissions(true) }]])); + compareMaps(result, new IdentifierMap([[ identifier, getPermissions(true) ]])); authorizer = new AllStaticReader(false); result = await authorizer.handle({ credentials, requestedModes }); - compareMaps(result, new IdentifierMap([[ identifier, { public: getPermissions(false), - agent: getPermissions(false) }]])); + compareMaps(result, new IdentifierMap([[ identifier, getPermissions(false) ]])); }); }); diff --git a/test/unit/authorization/AuthAuxiliaryReader.test.ts b/test/unit/authorization/AuthAuxiliaryReader.test.ts index 2cbeab584..8ed9f8f3d 100644 --- a/test/unit/authorization/AuthAuxiliaryReader.test.ts +++ b/test/unit/authorization/AuthAuxiliaryReader.test.ts @@ -1,7 +1,7 @@ import type { Credentials } from '../../../src/authentication/Credentials'; import { AuthAuxiliaryReader } from '../../../src/authorization/AuthAuxiliaryReader'; import type { PermissionReader } from '../../../src/authorization/PermissionReader'; -import { AclMode } from '../../../src/authorization/permissions/AclPermission'; +import { AclMode } from '../../../src/authorization/permissions/AclPermissionSet'; import type { AccessMap, PermissionMap, PermissionSet } from '../../../src/authorization/permissions/Permissions'; import { AccessMode } from '../../../src/authorization/permissions/Permissions'; import type { AuxiliaryStrategy } from '../../../src/http/auxiliary/AuxiliaryStrategy'; @@ -40,10 +40,10 @@ describe('An AuthAuxiliaryReader', (): void => { it('requires control permissions on the subject resource to do everything.', async(): Promise => { requestedModes.set(acl1, AccessMode.read); requestedModes.set(acl2, AccessMode.read); - sourceResult.set(subject1, { public: { control: true }} as PermissionSet); + sourceResult.set(subject1, { control: true } as PermissionSet); const result = await reader.handle({ requestedModes, credentials }); - expect(result.get(acl1)).toEqual({ public: { read: true, append: true, write: true, control: true }}); + expect(result.get(acl1)).toEqual({ read: true, append: true, write: true, control: true }); expect(result.get(acl2)).toEqual({ }); const updatedMap = new IdentifierMap(); @@ -59,10 +59,10 @@ describe('An AuthAuxiliaryReader', (): void => { requestedModes.set(acl1, AccessMode.read); requestedModes.set(subject1, AccessMode.write); - const resultSet = { public: { read: true, write: true, control: true }} as PermissionSet; + const resultSet = { read: true, write: true, control: true } as PermissionSet; sourceResult.set(subject1, resultSet); const resultMap: PermissionMap = new IdentifierMap([ - [ acl1, { public: { read: true, write: true, control: true, append: true }} as PermissionSet ], + [ acl1, { read: true, write: true, control: true, append: true } as PermissionSet ], [ subject1, resultSet ], ]); compareMaps(await reader.handle({ credentials, requestedModes }), resultMap); diff --git a/test/unit/authorization/AuxiliaryReader.test.ts b/test/unit/authorization/AuxiliaryReader.test.ts index f6e2b8ed2..43b5c9252 100644 --- a/test/unit/authorization/AuxiliaryReader.test.ts +++ b/test/unit/authorization/AuxiliaryReader.test.ts @@ -15,7 +15,7 @@ describe('An AuxiliaryReader', (): void => { const subjectIdentifier = { path: 'http://test.com/foo' }; const auxiliaryIdentifier1 = { path: 'http://test.com/foo.dummy1' }; const auxiliaryIdentifier2 = { path: 'http://test.com/foo.dummy2' }; - const permissionSet: PermissionSet = { agent: { read: true }}; + const permissionSet: PermissionSet = { read: true }; let source: jest.Mocked; let strategy: jest.Mocked; let reader: AuxiliaryReader; @@ -76,7 +76,7 @@ describe('An AuxiliaryReader', (): void => { [ auxiliaryIdentifier2, AccessMode.read ], [ subjectIdentifier, AccessMode.delete ], ]); - const resultSet = { agent: { read: true, write: true, delete: true }}; + const resultSet = { read: true, write: true, delete: true }; source.handleSafe.mockResolvedValueOnce(new IdentifierMap([[ subjectIdentifier, resultSet ]])); const permissionMap: PermissionMap = new IdentifierMap([ [ subjectIdentifier, resultSet ], diff --git a/test/unit/authorization/OwnerPermissionReader.test.ts b/test/unit/authorization/OwnerPermissionReader.test.ts index 30a86b201..b7c79d2d7 100644 --- a/test/unit/authorization/OwnerPermissionReader.test.ts +++ b/test/unit/authorization/OwnerPermissionReader.test.ts @@ -1,6 +1,6 @@ import type { Credentials } from '../../../src/authentication/Credentials'; import { OwnerPermissionReader } from '../../../src/authorization/OwnerPermissionReader'; -import { AclMode } from '../../../src/authorization/permissions/AclPermission'; +import { AclMode } from '../../../src/authorization/permissions/AclPermissionSet'; import type { AccessMap } from '../../../src/authorization/permissions/Permissions'; import type { AuxiliaryIdentifierStrategy } from '../../../src/http/auxiliary/AuxiliaryIdentifierStrategy'; import type { ResourceIdentifier } from '../../../src/http/representation/ResourceIdentifier'; @@ -81,14 +81,14 @@ describe('An OwnerPermissionReader', (): void => { it('returns full permissions if the owner is accessing an ACL resource in their pod.', async(): Promise => { compareMaps(await reader.handle({ credentials, requestedModes }), new IdentifierMap([[ identifier, - { agent: { + { read: true, write: true, append: true, create: true, delete: true, control: true, - }}, + }, ]])); }); }); diff --git a/test/unit/authorization/ParentContainerReader.test.ts b/test/unit/authorization/ParentContainerReader.test.ts index e9e557ff9..9040e5b8e 100644 --- a/test/unit/authorization/ParentContainerReader.test.ts +++ b/test/unit/authorization/ParentContainerReader.test.ts @@ -26,7 +26,7 @@ describe('A ParentContainerReader', (): void => { beforeEach(async(): Promise => { requestedModes = new IdentifierSetMultiMap(); - sourceResult = new IdentifierMap([[{ path: joinUrl(baseUrl, 'test') }, { public: { read: true }}]]); + sourceResult = new IdentifierMap([[{ path: joinUrl(baseUrl, 'test') }, { read: true }]]); source = { handleSafe: jest.fn().mockResolvedValue(sourceResult) } as any; reader = new ParentContainerReader(source, identifierStrategy); @@ -35,10 +35,10 @@ describe('A ParentContainerReader', (): void => { it('requires parent append permissions to create resources.', async(): Promise => { requestedModes.set(target1, new Set([ AccessMode.create ])); requestedModes.set(target2, new Set([ AccessMode.create ])); - sourceResult.set(parent1, { public: { append: true }}); + sourceResult.set(parent1, { append: true }); const result = await reader.handle({ requestedModes, credentials }); - expect(result.get(target1)).toEqual({ public: { create: true }}); + expect(result.get(target1)).toEqual({ create: true }); expect(result.get(target2)).toEqual({ }); const updatedMap = new IdentifierSetMultiMap(requestedModes); @@ -54,15 +54,15 @@ describe('A ParentContainerReader', (): 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 }}); + sourceResult.set(parent1, { write: true }); + sourceResult.set(parent2, { write: true }); + sourceResult.set(target1, { write: true }); + sourceResult.set(target3, { 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 }}); + expect(result.get(target1)).toEqual({ delete: true, write: true }); + expect(result.get(target2)).toEqual({ }); + expect(result.get(target3)).toEqual({ write: true }); const updatedMap = new IdentifierSetMultiMap(requestedModes); updatedMap.set(parent1, AccessMode.write); @@ -76,14 +76,14 @@ describe('A ParentContainerReader', (): void => { it('does not allow create/delete if the source explicitly forbids it.', async(): Promise => { 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 }}); + sourceResult.set(parent1, { write: true, append: true }); + sourceResult.set(parent2, { write: true, append: true }); + sourceResult.set(target1, { write: true }); + sourceResult.set(target2, { 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 }}); + expect(result.get(target1)).toEqual({ write: true, create: true, delete: true }); + expect(result.get(target2)).toEqual({ write: true, create: false, delete: false }); const updatedMap = new IdentifierSetMultiMap(requestedModes); updatedMap.set(parent1, new Set([ AccessMode.write, AccessMode.append ])); @@ -96,12 +96,12 @@ describe('A ParentContainerReader', (): void => { it('combines the modes with the parent resource if it is also being requested.', async(): Promise => { requestedModes.set(target1, AccessMode.create); requestedModes.set(parent1, AccessMode.write); - sourceResult.set(parent1, { public: { write: true, append: true }}); - sourceResult.set(target1, { public: { write: true }}); + sourceResult.set(parent1, { write: true, append: true }); + sourceResult.set(target1, { 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 }}); + expect(result.get(target1)).toEqual({ write: true, create: true, delete: true }); + expect(result.get(parent1)).toEqual({ write: true, append: true }); const updatedMap = new IdentifierSetMultiMap(requestedModes); updatedMap.set(parent1, new Set([ AccessMode.write, AccessMode.append ])); diff --git a/test/unit/authorization/PathBasedReader.test.ts b/test/unit/authorization/PathBasedReader.test.ts index 3df6b1488..19524736a 100644 --- a/test/unit/authorization/PathBasedReader.test.ts +++ b/test/unit/authorization/PathBasedReader.test.ts @@ -10,7 +10,7 @@ import { compareMaps } from '../../util/Util'; describe('A PathBasedReader', (): void => { const baseUrl = 'http://test.com/foo/'; - const permissionSet: PermissionSet = { agent: { read: true }}; + const permissionSet: PermissionSet = { read: true }; let readers: jest.Mocked[]; let reader: PathBasedReader; diff --git a/test/unit/authorization/PermissionBasedAuthorizer.test.ts b/test/unit/authorization/PermissionBasedAuthorizer.test.ts index 2a51ade6b..944a47860 100644 --- a/test/unit/authorization/PermissionBasedAuthorizer.test.ts +++ b/test/unit/authorization/PermissionBasedAuthorizer.test.ts @@ -34,10 +34,7 @@ describe('A PermissionBasedAuthorizer', (): void => { input.requestedModes = new IdentifierSetMultiMap( [[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]], ); - input.availablePermissions = new IdentifierMap([[ identifier, { - public: { read: true, write: false }, - agent: { write: true }, - }]]); + input.availablePermissions = new IdentifierMap([[ identifier, { read: true, write: true }]]); await expect(authorizer.handle(input)).resolves.toBeUndefined(); }); @@ -45,9 +42,7 @@ describe('A PermissionBasedAuthorizer', (): void => { input.requestedModes = new IdentifierSetMultiMap( [[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]], ); - input.availablePermissions = new IdentifierMap([[ identifier, { - public: { read: true, write: false }, - }]]); + input.availablePermissions = new IdentifierMap([[ identifier, { read: true, write: false }]]); await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError); }); @@ -56,9 +51,7 @@ describe('A PermissionBasedAuthorizer', (): void => { input.requestedModes = new IdentifierSetMultiMap( [[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]], ); - input.availablePermissions = new IdentifierMap([[ identifier, { - public: { read: true, write: false }, - }]]); + input.availablePermissions = new IdentifierMap([[ identifier, { read: true, write: false }]]); await expect(authorizer.handle(input)).rejects.toThrow(ForbiddenHttpError); }); @@ -69,9 +62,7 @@ describe('A PermissionBasedAuthorizer', (): void => { it('throws a 404 in case the target resource does not exist and would not be written to.', async(): Promise => { resourceSet.hasResource.mockResolvedValueOnce(false); input.requestedModes = new IdentifierSetMultiMap([[ identifier, AccessMode.delete ]]); - input.availablePermissions = new IdentifierMap([[ identifier, { - public: { read: true }, - }]]); + input.availablePermissions = new IdentifierMap([[ identifier, { read: true }]]); await expect(authorizer.handle(input)).rejects.toThrow(NotFoundHttpError); }); @@ -84,14 +75,8 @@ describe('A PermissionBasedAuthorizer', (): void => { [ identifier2, AccessMode.write ], ]); input.availablePermissions = new IdentifierMap([ - [ identifier, { - public: { read: true, write: false }, - agent: { write: true }, - }], - [ identifier2, { - public: { read: false }, - agent: { write: true }, - }], + [ identifier, { read: true, write: true }], + [ identifier2, { read: false, write: true }], ]); await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError); }); @@ -105,10 +90,7 @@ describe('A PermissionBasedAuthorizer', (): void => { [ identifier2, AccessMode.write ], ]); input.availablePermissions = new IdentifierMap([ - [ identifier, { - public: { read: true, write: false }, - agent: { write: true }, - }], + [ identifier, { read: true, write: true }], ]); await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError); }); diff --git a/test/unit/authorization/UnionPermissionReader.test.ts b/test/unit/authorization/UnionPermissionReader.test.ts index 5074b7531..46820ec6b 100644 --- a/test/unit/authorization/UnionPermissionReader.test.ts +++ b/test/unit/authorization/UnionPermissionReader.test.ts @@ -30,45 +30,42 @@ describe('A UnionPermissionReader', (): void => { it('only uses the results of readers that can handle the input.', async(): Promise => { readers[0].canHandle.mockRejectedValue(new Error('bad request')); readers[0].handle.mockResolvedValue( - new IdentifierMap([[ identifier, { agent: { read: true }}]]), + new IdentifierMap([[ identifier, { read: true }]]), ); readers[1].handle.mockResolvedValue( - new IdentifierMap([[ identifier, { agent: { write: true }}]]), + new IdentifierMap([[ identifier, { write: true }]]), ); compareMaps(await unionReader.handle(input), - new IdentifierMap([[ identifier, { agent: { write: true }}]])); + new IdentifierMap([[ identifier, { write: true }]])); }); it('combines results.', async(): Promise => { const identifier2 = { path: 'http://example.com/foo2' }; const identifier3 = { path: 'http://example.com/foo3' }; readers[0].handle.mockResolvedValue(new IdentifierMap([ - [ identifier, { agent: { read: true }, public: undefined }], - [ identifier2, { agent: { write: true }}], - [ identifier3, { agent: { append: false }, public: { delete: true }}], + [ identifier, { read: true }], + [ identifier2, { write: true }], + [ identifier3, { append: false }], ])); readers[1].handle.mockResolvedValue(new IdentifierMap([ - [ identifier, { agent: { write: true }, public: { read: false }}], - [ identifier2, { public: { read: false }}], + [ identifier, { write: true }], ])); compareMaps(await unionReader.handle(input), new IdentifierMap([ - [ identifier, { agent: { read: true, write: true }, public: { read: false }}], - [ identifier2, { agent: { write: true }, public: { read: false }}], - [ identifier3, { agent: { append: false }, public: { delete: true }}], + [ identifier, { read: true, write: true }], + [ identifier2, { write: true }], + [ identifier3, { append: false }], ])); }); it('merges same fields using false > true > undefined.', async(): Promise => { readers[0].handle.mockResolvedValue(new IdentifierMap( - [[ identifier, - { agent: { read: true, write: false, append: undefined, create: true, delete: undefined }}]], + [[ identifier, { read: true, write: false, append: undefined, create: true, delete: undefined }]], )); readers[1].handle.mockResolvedValue(new IdentifierMap( - [[ identifier, { agent: - { read: false, write: true, append: true, create: true, delete: undefined }}]], + [[ identifier, { read: false, write: true, append: true, create: true, delete: undefined }]], + )); + compareMaps(await unionReader.handle(input), new IdentifierMap( + [[ identifier, { read: false, write: false, append: true, create: true }]], )); - compareMaps(await unionReader.handle(input), new IdentifierMap([[ identifier, { - agent: { read: false, write: false, append: true, create: true }, - }]])); }); }); diff --git a/test/unit/authorization/WebAclReader.test.ts b/test/unit/authorization/WebAclReader.test.ts index 55feaae5a..28c088c8f 100644 --- a/test/unit/authorization/WebAclReader.test.ts +++ b/test/unit/authorization/WebAclReader.test.ts @@ -2,7 +2,7 @@ import { DataFactory } from 'n3'; import type { Credentials } 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 { AclMode } from '../../../src/authorization/permissions/AclPermissionSet'; import type { AccessMap, PermissionSet } from '../../../src/authorization/permissions/Permissions'; import { AccessMode } from '../../../src/authorization/permissions/Permissions'; import { WebAclReader } from '../../../src/authorization/WebAclReader'; @@ -77,10 +77,7 @@ describe('A WebAclReader', (): void => { it('returns undefined permissions for undefined credentials.', async(): Promise => { input.credentials = {}; - compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - public: {}, - agent: {}, - }]])); + compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {}]])); }); it('reads the accessTo value of the acl resource.', async(): Promise => { @@ -90,10 +87,7 @@ describe('A WebAclReader', (): void => { quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), ], INTERNAL_QUADS)); - compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - public: { read: true }, - agent: { read: true }, - }]])); + compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { read: true }]])); }); it('ignores accessTo fields pointing to different resources.', async(): Promise => { @@ -103,10 +97,7 @@ describe('A WebAclReader', (): void => { quad(nn('auth'), nn(`${acl}accessTo`), nn('somewhereElse')), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), ], INTERNAL_QUADS)); - compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - public: {}, - agent: {}, - }]])); + compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {}]])); }); it('handles all valid modes and ignores other ones.', async(): Promise => { @@ -117,10 +108,7 @@ describe('A WebAclReader', (): void => { quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}fakeMode1`)), ], INTERNAL_QUADS)); - compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - public: { read: true }, - agent: { read: true }, - }]])); + compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { read: true }]])); }); it('reads the default value of a parent if there is no direct acl resource.', async(): Promise => { @@ -131,10 +119,7 @@ describe('A WebAclReader', (): void => { quad(nn('auth'), nn(`${acl}default`), nn(identifierStrategy.getParentContainer(identifier).path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), ], INTERNAL_QUADS)); - compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - public: { read: true }, - agent: { read: true }, - }]])); + compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { read: true }]])); }); it('does not use default authorizations for the resource itself.', async(): Promise => { @@ -148,10 +133,7 @@ describe('A WebAclReader', (): void => { quad(nn('auth2'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)), ], INTERNAL_QUADS)); - compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - public: { append: true }, - agent: { append: true }, - }]])); + compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { append: true }]])); }); it('re-throws ResourceStore errors as internal errors.', async(): Promise => { @@ -170,9 +152,7 @@ describe('A WebAclReader', (): void => { it('ignores rules where no access is granted.', async(): Promise => { credentials.agent = { webId: 'http://test.com/user' }; - // CredentialGroup.public gets true on auth1, CredentialGroup.agent on auth2 - accessChecker.handleSafe.mockImplementation(async({ rule, credentials: cred }): Promise => - (rule.value === 'auth1') === !cred.agent?.webId); + accessChecker.handleSafe.mockImplementation(async({ rule }): Promise => rule.value !== 'auth1'); store.getRepresentation.mockResolvedValue(new BasicRepresentation([ quad(nn('auth1'), nn(`${rdf}type`), nn(`${acl}Authorization`)), @@ -183,10 +163,7 @@ describe('A WebAclReader', (): void => { quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)), ], INTERNAL_QUADS)); - compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - public: { read: true }, - agent: { append: true }, - }]])); + compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { append: true }]])); }); it('combines ACL representation requests for resources when possible.', async(): Promise => { @@ -224,9 +201,9 @@ describe('A WebAclReader', (): void => { input.requestedModes.set(identifier3, new Set([ AccessMode.append ])); compareMaps(await reader.handle(input), new IdentifierMap([ - [ identifier, { public: { read: true }, agent: { read: true }}], - [ identifier2, { public: { read: true }, agent: { read: true }}], - [ identifier3, { public: { append: true }, agent: { append: true }}], + [ identifier, { read: true }], + [ identifier2, { read: true }], + [ identifier3, { append: true }], ])); // http://example.com/.acl and http://example.com/bar/.acl expect(store.getRepresentation).toHaveBeenCalledTimes(2); diff --git a/test/unit/server/AuthorizingHttpHandler.test.ts b/test/unit/server/AuthorizingHttpHandler.test.ts index 70daab2d8..2b3c91220 100644 --- a/test/unit/server/AuthorizingHttpHandler.test.ts +++ b/test/unit/server/AuthorizingHttpHandler.test.ts @@ -18,7 +18,7 @@ describe('An AuthorizingHttpHandler', (): void => { const target = { path: 'http://example.com/foo' }; const requestedModes: AccessMap = new IdentifierSetMultiMap([[ target, AccessMode.read ]]); const availablePermissions: PermissionMap = new IdentifierMap( - [[ target, { public: { read: true }}]], + [[ target, { read: true }]], ); const request: HttpRequest = {} as any; const response: HttpResponse = {} as any; diff --git a/test/unit/server/WacAllowHttpHandler.test.ts b/test/unit/server/WacAllowHttpHandler.test.ts index b6f07f7b6..ed80db100 100644 --- a/test/unit/server/WacAllowHttpHandler.test.ts +++ b/test/unit/server/WacAllowHttpHandler.test.ts @@ -1,7 +1,6 @@ import 'jest-rdf'; import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor'; import type { PermissionReader } from '../../../src/authorization/PermissionReader'; -import type { AclPermission } from '../../../src/authorization/permissions/AclPermission'; import type { ModesExtractor } from '../../../src/authorization/permissions/ModesExtractor'; import type { Operation } from '../../../src/http/Operation'; import { OkResponseDescription } from '../../../src/http/output/response/OkResponseDescription'; @@ -56,15 +55,14 @@ describe('A WacAllowHttpHandler', (): void => { }); it('adds permission metadata.', async(): Promise => { - permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap([[ target, { - agent: { read: true, write: true, control: false } as AclPermission, - public: { read: true, write: false }, - }]])); + permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap( + [[ target, { read: true, write: true, append: false }]], + )); await expect(handler.handle({ operation, request, response })).resolves.toEqual(output); - expect(output.metadata!.quads()).toHaveLength(3); + expect(output.metadata!.quads()).toHaveLength(4); expect(output.metadata!.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Write ]); - expect(output.metadata!.get(AUTH.terms.publicMode)).toEqualRdfTerm(ACL.terms.Read); + expect(output.metadata!.getAll(AUTH.terms.publicMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Write ]); expect(source.handleSafe).toHaveBeenCalledTimes(1); expect(source.handleSafe).toHaveBeenLastCalledWith({ operation, request, response }); @@ -79,21 +77,59 @@ describe('A WacAllowHttpHandler', (): void => { }); }); - it('adds no permissions for credential groups that are not defined.', async(): Promise => { - permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap([[ target, {}]])); + it('determines public permissions separately in case of an authenticated request.', async(): Promise => { + credentialsExtractor.handleSafe.mockResolvedValue({ agent: { webId: 'http://example.com/#me' }}); + permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap( + [[ target, { read: true, write: true, append: false }]], + )); + permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap( + [[ target, { read: true, write: false, append: true }]], + )); + + await expect(handler.handle({ operation, request, response })).resolves.toEqual(output); + expect(output.metadata!.quads()).toHaveLength(4); + expect(output.metadata!.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Write ]); + expect(output.metadata!.getAll(AUTH.terms.publicMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Append ]); + + expect(source.handleSafe).toHaveBeenCalledTimes(1); + expect(source.handleSafe).toHaveBeenLastCalledWith({ operation, request, response }); + expect(credentialsExtractor.handleSafe).toHaveBeenCalledTimes(1); + expect(credentialsExtractor.handleSafe).toHaveBeenLastCalledWith(request); + expect(modesExtractor.handleSafe).toHaveBeenCalledTimes(1); + expect(modesExtractor.handleSafe).toHaveBeenLastCalledWith(operation); + expect(permissionReader.handleSafe).toHaveBeenCalledTimes(2); + expect(permissionReader.handleSafe).toHaveBeenNthCalledWith(1, { + credentials: { agent: { webId: 'http://example.com/#me' }}, + requestedModes: await modesExtractor.handleSafe.mock.results[0].value, + }); + expect(permissionReader.handleSafe).toHaveBeenNthCalledWith(2, { + credentials: {}, + requestedModes: await modesExtractor.handleSafe.mock.results[0].value, + }); + }); + + it('adds no permissions if none of them are on the target.', async(): Promise => { + permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap( + [[{ path: 'http://example/other' }, { read: true, write: false }]], + )); await expect(handler.handle({ operation, request, response })).resolves.toEqual(output); expect(output.metadata!.quads()).toHaveLength(0); }); - it('adds no permissions if none of them are on the target.', async(): Promise => { - permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap([[{ path: 'http://example/other' }, { - agent: { read: true, write: true, control: false } as AclPermission, - public: { read: true, write: false }, - }]])); + it('adds no public permissions if the second call has no results for the target.', async(): Promise => { + credentialsExtractor.handleSafe.mockResolvedValue({ agent: { webId: 'http://example.com/#me' }}); + permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap( + [[ target, { read: true, write: false }]], + )); + permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap( + [[{ path: 'http://example/other' }, { read: false, write: true }]], + )); await expect(handler.handle({ operation, request, response })).resolves.toEqual(output); - expect(output.metadata!.quads()).toHaveLength(0); + expect(output.metadata!.quads()).toHaveLength(1); + expect(output.metadata!.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read ]); + expect(output.metadata!.getAll(AUTH.terms.publicMode)).toEqualRdfTermArray([]); }); it('immediately returns the source output if the operation method is not GET or HEAD.', async(): Promise => { diff --git a/test/util/AclHelper.ts b/test/util/AclHelper.ts index d1ad63a6c..4cdd961ab 100644 --- a/test/util/AclHelper.ts +++ b/test/util/AclHelper.ts @@ -1,9 +1,9 @@ import type { ResourceStore } from '../../src/'; import { BasicRepresentation } from '../../src/'; -import type { AclPermission } from '../../src/authorization/permissions/AclPermission'; +import type { AclPermissionSet } from '../../src/authorization/permissions/AclPermissionSet'; export type AclHelperInput = { - permissions: AclPermission; + permissions: AclPermissionSet; agentClass?: 'agent' | 'authenticated'; agent?: string; accessTo?: boolean; @@ -39,7 +39,7 @@ export class AclHelper { } for (const perm of [ 'Read', 'Append', 'Write', 'Control' ]) { - if (option.permissions[perm.toLowerCase() as keyof AclPermission]) { + if (option.permissions[perm.toLowerCase() as keyof AclPermissionSet]) { acl.push(`;\n acl:mode acl:${perm}`); } }