From f3e7a208002cc687505b06c6737785a02b71f733 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Fri, 19 Aug 2022 14:11:20 +0200 Subject: [PATCH] feat: Update Credentials typings to support client/issuer --- src/authentication/BearerWebIdExtractor.ts | 16 ++++--- src/authentication/Credentials.ts | 21 +++------ src/authentication/CredentialsExtractor.ts | 4 +- src/authentication/DPoPWebIdExtractor.ts | 16 ++++--- .../PublicCredentialsExtractor.ts | 10 ++--- .../UnionCredentialsExtractor.ts | 22 +++++---- .../UnsecureConstantCredentialsExtractor.ts | 13 +++--- src/authentication/UnsecureWebIdExtractor.ts | 7 ++- src/authorization/AcpReader.ts | 8 ++-- src/authorization/AllStaticReader.ts | 10 ++--- src/authorization/AuthAuxiliaryReader.ts | 3 +- src/authorization/Authorizer.ts | 4 +- src/authorization/OwnerPermissionReader.ts | 7 ++- src/authorization/ParentContainerReader.ts | 5 +-- .../PermissionBasedAuthorizer.ts | 6 +-- src/authorization/PermissionReader.ts | 4 +- src/authorization/UnionPermissionReader.ts | 5 +-- src/authorization/WebAclReader.ts | 25 +++++------ src/authorization/access/AccessChecker.ts | 6 +-- .../access/AgentAccessChecker.ts | 6 +-- .../access/AgentClassAccessChecker.ts | 4 +- .../access/AgentGroupAccessChecker.ts | 6 +-- src/authorization/permissions/Permissions.ts | 14 ++++-- src/server/AuthorizingHttpHandler.ts | 8 ++-- test/__mocks__/.eslintrc.js | 5 --- .../__mocks__/@solid/access-token-verifier.ts | 4 -- test/integration/Identity.test.ts | 3 -- .../BearerWebIdExtractor.test.ts | 31 ++++++++++--- .../authentication/DPoPWebIdExtractor.test.ts | 25 ++++++++--- .../PublicCredentialsExtractor.test.ts | 3 +- .../UnionCredentialsExtractor.test.ts | 23 +++++----- ...secureConstantCredentialsExtractor.test.ts | 7 ++- .../UnsecureWebIdExtractor.test.ts | 5 +-- test/unit/authorization/AcpReader.test.ts | 32 +++++++++++-- .../authorization/AllStaticReader.test.ts | 9 ++-- .../authorization/AuthAuxiliaryReader.test.ts | 4 +- .../authorization/AuxiliaryReader.test.ts | 5 +-- .../OwnerPermissionReader.test.ts | 9 ++-- .../ParentContainerReader.test.ts | 4 +- .../authorization/PathBasedReader.test.ts | 3 +- .../PermissionBasedAuthorizer.test.ts | 23 +++++----- .../UnionPermissionReader.test.ts | 29 ++++++------ test/unit/authorization/WebAclReader.test.ts | 45 +++++++++---------- .../access/AgentAccessChecker.test.ts | 6 +-- .../access/AgentClassAccessChecker.test.ts | 8 ++-- .../access/AgentGroupAccessChecker.test.ts | 6 +-- .../metadata/WebAclMetadataCollector.test.ts | 11 +++-- .../server/AuthorizingHttpHandler.test.ts | 5 +-- 48 files changed, 286 insertions(+), 249 deletions(-) delete mode 100644 test/__mocks__/.eslintrc.js delete mode 100644 test/__mocks__/@solid/access-token-verifier.ts diff --git a/src/authentication/BearerWebIdExtractor.ts b/src/authentication/BearerWebIdExtractor.ts index 0b25a306d..72022a29e 100644 --- a/src/authentication/BearerWebIdExtractor.ts +++ b/src/authentication/BearerWebIdExtractor.ts @@ -5,8 +5,7 @@ import type { HttpRequest } from '../server/HttpRequest'; import { BadRequestHttpError } from '../util/errors/BadRequestHttpError'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; import { matchesAuthorizationScheme } from '../util/HeaderUtil'; -import { CredentialGroup } from './Credentials'; -import type { CredentialSet } from './Credentials'; +import type { Credentials } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; export class BearerWebIdExtractor extends CredentialsExtractor { @@ -25,13 +24,18 @@ export class BearerWebIdExtractor extends CredentialsExtractor { } } - public async handle(request: HttpRequest): Promise { + public async handle(request: HttpRequest): Promise { const { headers: { authorization }} = request; try { - const { webid: webId } = await this.verify(authorization!); - this.logger.info(`Verified WebID via Bearer access token: ${webId}`); - return { [CredentialGroup.agent]: { webId }}; + const { webid: webId, client_id: clientId, iss: issuer } = await this.verify(authorization!); + this.logger.info(`Verified credentials via Bearer access token. WebID: ${webId + }, client ID: ${clientId}, issuer: ${issuer}`); + const credentials: Credentials = { agent: { webId }, issuer: { url: issuer }}; + if (clientId) { + credentials.client = { clientId }; + } + return credentials; } catch (error: unknown) { const message = `Error verifying WebID via Bearer access token: ${(error as Error).message}`; this.logger.warn(message); diff --git a/src/authentication/Credentials.ts b/src/authentication/Credentials.ts index b91c0ffb9..284067871 100644 --- a/src/authentication/Credentials.ts +++ b/src/authentication/Credentials.ts @@ -1,19 +1,8 @@ /** * Credentials identifying an entity accessing or owning data. */ -export interface Credential { - webId?: string; -} - -/** - * Specific groups that can have credentials. - */ -export enum CredentialGroup { - public = 'public', - agent = 'agent', -} - -/** - * A combination of multiple credentials, where their group is specified by the key. - */ -export type CredentialSet = Partial>; +export type Credentials = { + agent?: { webId: string }; + client?: { clientId: string }; + issuer?: { url: string }; +}; diff --git a/src/authentication/CredentialsExtractor.ts b/src/authentication/CredentialsExtractor.ts index d4469fa74..1d7294b2a 100644 --- a/src/authentication/CredentialsExtractor.ts +++ b/src/authentication/CredentialsExtractor.ts @@ -1,8 +1,8 @@ import type { HttpRequest } from '../server/HttpRequest'; import { AsyncHandler } from '../util/handlers/AsyncHandler'; -import type { CredentialSet } from './Credentials'; +import type { Credentials } from './Credentials'; /** * Responsible for extracting credentials from an incoming request. */ -export abstract class CredentialsExtractor extends AsyncHandler {} +export abstract class CredentialsExtractor extends AsyncHandler {} diff --git a/src/authentication/DPoPWebIdExtractor.ts b/src/authentication/DPoPWebIdExtractor.ts index 19c987f6c..d539d93cb 100644 --- a/src/authentication/DPoPWebIdExtractor.ts +++ b/src/authentication/DPoPWebIdExtractor.ts @@ -6,8 +6,7 @@ import type { HttpRequest } from '../server/HttpRequest'; import { BadRequestHttpError } from '../util/errors/BadRequestHttpError'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; import { matchesAuthorizationScheme } from '../util/HeaderUtil'; -import { CredentialGroup } from './Credentials'; -import type { CredentialSet } from './Credentials'; +import type { Credentials } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; /** @@ -33,7 +32,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor { } } - public async handle(request: HttpRequest): Promise { + public async handle(request: HttpRequest): Promise { const { headers: { authorization, dpop }, method } = request; if (!dpop) { throw new BadRequestHttpError('No DPoP header specified.'); @@ -46,7 +45,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor { // Validate the Authorization and DPoP header headers // and extract the WebID provided by the client try { - const { webid: webId } = await this.verify( + const { webid: webId, client_id: clientId, iss: issuer } = await this.verify( authorization!, { header: dpop as string, @@ -54,8 +53,13 @@ export class DPoPWebIdExtractor extends CredentialsExtractor { url: originalUrl.path, }, ); - this.logger.info(`Verified WebID via DPoP-bound access token: ${webId}`); - return { [CredentialGroup.agent]: { webId }}; + this.logger.info(`Verified WebID via DPoP-bound access token. WebID: ${webId + }, client ID: ${clientId}, issuer: ${issuer}`); + const credentials: Credentials = { agent: { webId }, issuer: { url: issuer }}; + if (clientId) { + credentials.client = { clientId }; + } + return credentials; } catch (error: unknown) { const message = `Error verifying WebID via DPoP-bound access token: ${(error as Error).message}`; this.logger.warn(message); diff --git a/src/authentication/PublicCredentialsExtractor.ts b/src/authentication/PublicCredentialsExtractor.ts index f8df265b9..6a652be4a 100644 --- a/src/authentication/PublicCredentialsExtractor.ts +++ b/src/authentication/PublicCredentialsExtractor.ts @@ -1,12 +1,12 @@ -import { CredentialGroup } from './Credentials'; -import type { CredentialSet } from './Credentials'; +import type { Credentials } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; /** - * Extracts the public credentials, to be used for data everyone has access to. + * Extracts the "public credentials", to be used for data everyone has access to. + * This class mainly exists so a {@link Credentials} is still generated in case the token parsing fails. */ export class PublicCredentialsExtractor extends CredentialsExtractor { - public async handle(): Promise { - return { [CredentialGroup.public]: {}}; + public async handle(): Promise { + return {}; } } diff --git a/src/authentication/UnionCredentialsExtractor.ts b/src/authentication/UnionCredentialsExtractor.ts index 02fc14121..e1ed4c925 100644 --- a/src/authentication/UnionCredentialsExtractor.ts +++ b/src/authentication/UnionCredentialsExtractor.ts @@ -1,6 +1,5 @@ import { UnionHandler } from '../util/handlers/UnionHandler'; -import type { CredentialGroup, Credential, CredentialSet } from './Credentials'; - +import type { Credentials } from './Credentials'; import type { CredentialsExtractor } from './CredentialsExtractor'; /** @@ -13,15 +12,22 @@ export class UnionCredentialsExtractor extends UnionHandler { + public async combine(results: Credentials[]): Promise { // Combine all the results into a single object - return results.reduce((result, credential): CredentialSet => { - for (const [ key, value ] of Object.entries(credential) as [ CredentialGroup, Credential ][]) { - if (value) { - result[key] = value; - } + return results.reduce((result, credentials): Credentials => { + for (const key of Object.keys(credentials) as (keyof Credentials)[]) { + this.setValue(result, key, credentials[key]); } return result; }, {}); } + + /** + * Helper function that makes sure the typings are correct. + */ + private setValue(credentials: Credentials, key: T, value?: Credentials[T]): void { + if (value) { + credentials[key] = value; + } + } } diff --git a/src/authentication/UnsecureConstantCredentialsExtractor.ts b/src/authentication/UnsecureConstantCredentialsExtractor.ts index 720bd22a8..99cc195d7 100644 --- a/src/authentication/UnsecureConstantCredentialsExtractor.ts +++ b/src/authentication/UnsecureConstantCredentialsExtractor.ts @@ -1,6 +1,5 @@ import { getLoggerFor } from '../logging/LogUtil'; -import { CredentialGroup } from './Credentials'; -import type { Credential, CredentialSet } from './Credentials'; +import type { Credentials } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; /** @@ -8,17 +7,17 @@ import { CredentialsExtractor } from './CredentialsExtractor'; * (useful for development or debugging purposes). */ export class UnsecureConstantCredentialsExtractor extends CredentialsExtractor { - private readonly credentials: CredentialSet; + private readonly credentials: Credentials; private readonly logger = getLoggerFor(this); public constructor(agent: string); - public constructor(agent: Credential); - public constructor(agent: string | Credential) { + public constructor(agent: Credentials['agent']); + public constructor(agent: string | Credentials['agent']) { super(); - this.credentials = { [CredentialGroup.agent]: typeof agent === 'string' ? { webId: agent } : agent }; + this.credentials = { agent: typeof agent === 'string' ? { webId: agent } : agent }; } - public async handle(): Promise { + public async handle(): Promise { this.logger.info(`Agent unsecurely claims to be ${this.credentials.agent!.webId}`); return this.credentials; } diff --git a/src/authentication/UnsecureWebIdExtractor.ts b/src/authentication/UnsecureWebIdExtractor.ts index d05ec8294..dd6eaba0c 100644 --- a/src/authentication/UnsecureWebIdExtractor.ts +++ b/src/authentication/UnsecureWebIdExtractor.ts @@ -2,8 +2,7 @@ import { getLoggerFor } from '../logging/LogUtil'; import type { HttpRequest } from '../server/HttpRequest'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; import { matchesAuthorizationScheme } from '../util/HeaderUtil'; -import { CredentialGroup } from './Credentials'; -import type { CredentialSet } from './Credentials'; +import type { Credentials } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; /** @@ -19,9 +18,9 @@ export class UnsecureWebIdExtractor extends CredentialsExtractor { } } - public async handle({ headers }: HttpRequest): Promise { + public async handle({ headers }: HttpRequest): Promise { const webId = /^WebID\s+(.*)/ui.exec(headers.authorization!)![1]; this.logger.info(`Agent unsecurely claims to be ${webId}`); - return { [CredentialGroup.agent]: { webId }}; + return { agent: { webId }}; } } diff --git a/src/authorization/AcpReader.ts b/src/authorization/AcpReader.ts index d34311fd8..5a3365888 100644 --- a/src/authorization/AcpReader.ts +++ b/src/authorization/AcpReader.ts @@ -4,7 +4,7 @@ import type { IAccessControlledResource } from '@solid/access-control-policy/dis import type { IContext } from '@solid/access-control-policy/dist/type/i_context'; import type { IPolicy } from '@solid/access-control-policy/dist/type/i_policy'; import type { Store } from 'n3'; -import type { CredentialSet } from '../authentication/Credentials'; +import type { Credentials } from '../authentication/Credentials'; import type { AuxiliaryStrategy } from '../http/auxiliary/AuxiliaryStrategy'; import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier'; import { getLoggerFor } from '../logging/LogUtil'; @@ -70,7 +70,7 @@ export class AcpReader extends PermissionReader { * @param credentials - Credentials that are trying to access the resource. * @param resourceCache - Cache used to store ACR data. */ - private async extractPermissions(target: ResourceIdentifier, credentials: CredentialSet, + private async extractPermissions(target: ResourceIdentifier, credentials: Credentials, resourceCache: IdentifierMap): Promise { const context = this.createContext(target, credentials); const policies: IPolicy[] = []; @@ -108,10 +108,12 @@ export class AcpReader extends PermissionReader { /** * Creates an ACP context targeting the given identifier with the provided credentials. */ - private createContext(target: ResourceIdentifier, credentials: CredentialSet): IContext { + private createContext(target: ResourceIdentifier, credentials: Credentials): IContext { return { target: target.path, agent: credentials.agent?.webId, + client: credentials.client?.clientId, + issuer: credentials.issuer?.url, }; } diff --git a/src/authorization/AllStaticReader.ts b/src/authorization/AllStaticReader.ts index 4b88d43ce..cb47b7098 100644 --- a/src/authorization/AllStaticReader.ts +++ b/src/authorization/AllStaticReader.ts @@ -1,7 +1,7 @@ -import type { CredentialGroup, CredentialSet } from '../authentication/Credentials'; 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'; /** @@ -22,18 +22,18 @@ export class AllStaticReader extends PermissionReader { }); } - public async handle({ credentials, requestedModes }: PermissionReaderInput): Promise { + public async handle({ requestedModes }: PermissionReaderInput): Promise { const availablePermissions = new IdentifierMap(); - const permissions = this.createPermissions(credentials); + const permissions = this.createPermissions(); for (const [ identifier ] of requestedModes) { availablePermissions.set(identifier, permissions); } return availablePermissions; } - private createPermissions(credentials: CredentialSet): PermissionSet { + private createPermissions(): PermissionSet { const result: PermissionSet = {}; - for (const group of Object.keys(credentials) as CredentialGroup[]) { + for (const group of permissionSetKeys) { result[group] = this.permissions; } return result; diff --git a/src/authorization/AuthAuxiliaryReader.ts b/src/authorization/AuthAuxiliaryReader.ts index 08c263a58..b3263dde4 100644 --- a/src/authorization/AuthAuxiliaryReader.ts +++ b/src/authorization/AuthAuxiliaryReader.ts @@ -1,4 +1,3 @@ -import type { CredentialGroup } from '../authentication/Credentials'; import type { AuxiliaryStrategy } from '../http/auxiliary/AuxiliaryStrategy'; import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier'; import { getLoggerFor } from '../logging/LogUtil'; @@ -67,7 +66,7 @@ export class AuthAuxiliaryReader extends PermissionReader { */ protected interpretControl(identifier: ResourceIdentifier, permissionSet: PermissionSet = {}): PermissionSet { const authSet: PermissionSet = {}; - for (const [ group, permissions ] of Object.entries(permissionSet) as [ CredentialGroup, AclPermission ][]) { + for (const [ group, permissions ] of Object.entries(permissionSet) as [ keyof PermissionSet, AclPermission ][]) { const { control } = permissions; authSet[group] = { read: control, diff --git a/src/authorization/Authorizer.ts b/src/authorization/Authorizer.ts index 42bb4631c..161f249f4 100644 --- a/src/authorization/Authorizer.ts +++ b/src/authorization/Authorizer.ts @@ -1,4 +1,4 @@ -import type { CredentialSet } from '../authentication/Credentials'; +import type { Credentials } from '../authentication/Credentials'; import { AsyncHandler } from '../util/handlers/AsyncHandler'; import type { AccessMap, PermissionMap } from './permissions/Permissions'; @@ -6,7 +6,7 @@ export interface AuthorizerInput { /** * Credentials of the entity that wants to use the resource. */ - credentials: CredentialSet; + credentials: Credentials; /** * Requested access modes per resource. */ diff --git a/src/authorization/OwnerPermissionReader.ts b/src/authorization/OwnerPermissionReader.ts index a2ef68d92..9e72039a1 100644 --- a/src/authorization/OwnerPermissionReader.ts +++ b/src/authorization/OwnerPermissionReader.ts @@ -1,5 +1,4 @@ -import type { CredentialSet } from '../authentication/Credentials'; -import { CredentialGroup } from '../authentication/Credentials'; +import type { Credentials } from '../authentication/Credentials'; import type { AuxiliaryIdentifierStrategy } from '../http/auxiliary/AuxiliaryIdentifierStrategy'; import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier'; import type { AccountSettings, AccountStore } from '../identity/interaction/email-password/storage/AccountStore'; @@ -52,7 +51,7 @@ 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, { [CredentialGroup.agent]: { + result.set(auth, { agent: { read: true, write: true, append: true, @@ -69,7 +68,7 @@ export class OwnerPermissionReader extends PermissionReader { * Find the base URL of the pod the given credentials own. * Will throw an error if none can be found. */ - private async findPodBaseUrl(credentials: CredentialSet): Promise { + private async findPodBaseUrl(credentials: Credentials): Promise { if (!credentials.agent?.webId) { throw new NotImplementedHttpError('Only authenticated agents could be owners'); } diff --git a/src/authorization/ParentContainerReader.ts b/src/authorization/ParentContainerReader.ts index 6a015512d..55c88ceb5 100644 --- a/src/authorization/ParentContainerReader.ts +++ b/src/authorization/ParentContainerReader.ts @@ -1,4 +1,3 @@ -import type { CredentialGroup } from '../authentication/Credentials'; import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier'; import { getLoggerFor } from '../logging/LogUtil'; import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy'; @@ -84,8 +83,8 @@ export class ParentContainerReader extends PermissionReader { // 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, containerPermission ] of Object.entries(containerSet) as [ CredentialGroup, Permission ][]) { - resultSet[group] = this.interpretContainerPermission(resourceSet[group] ?? {}, containerPermission); + for (const [ group, containerPerms ] of Object.entries(containerSet) as [ keyof PermissionSet, Permission ][]) { + resultSet[group] = this.interpretContainerPermission(resourceSet[group] ?? {}, containerPerms); } return resultSet; } diff --git a/src/authorization/PermissionBasedAuthorizer.ts b/src/authorization/PermissionBasedAuthorizer.ts index 0171f8a8f..cab271274 100644 --- a/src/authorization/PermissionBasedAuthorizer.ts +++ b/src/authorization/PermissionBasedAuthorizer.ts @@ -1,4 +1,4 @@ -import type { CredentialSet } from '../authentication/Credentials'; +import type { Credentials } from '../authentication/Credentials'; import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier'; import { getLoggerFor } from '../logging/LogUtil'; import type { ResourceSet } from '../storage/ResourceSet'; @@ -75,7 +75,7 @@ export class PermissionBasedAuthorizer extends Authorizer { * @param permissionSet - PermissionSet describing the available permissions of the credentials. * @param mode - Which mode is requested. */ - private requireModePermission(credentials: CredentialSet, permissionSet: PermissionSet, mode: AccessMode): void { + private requireModePermission(credentials: Credentials, permissionSet: PermissionSet, mode: AccessMode): void { if (!this.hasModePermission(permissionSet, mode)) { if (this.isAuthenticated(credentials)) { this.logger.warn(`Agent ${credentials.agent!.webId} has no ${mode} permissions`); @@ -106,7 +106,7 @@ export class PermissionBasedAuthorizer extends Authorizer { * Checks whether the agent is authenticated (logged in) or not (public/anonymous). * @param credentials - Credentials to check. */ - private isAuthenticated(credentials: CredentialSet): boolean { + private isAuthenticated(credentials: Credentials): boolean { return typeof credentials.agent?.webId === 'string'; } } diff --git a/src/authorization/PermissionReader.ts b/src/authorization/PermissionReader.ts index ab1c3857e..cccb3ace3 100644 --- a/src/authorization/PermissionReader.ts +++ b/src/authorization/PermissionReader.ts @@ -1,4 +1,4 @@ -import type { CredentialSet } from '../authentication/Credentials'; +import type { Credentials } from '../authentication/Credentials'; import { AsyncHandler } from '../util/handlers/AsyncHandler'; import type { AccessMap, PermissionMap } from './permissions/Permissions'; @@ -6,7 +6,7 @@ export interface PermissionReaderInput { /** * Credentials of the entity requesting access to resources. */ - credentials: CredentialSet; + credentials: Credentials; /** * For each credential, the reader will check which of the given per-resource access modes are available. * However, non-exhaustive information about other access modes and resources can still be returned. diff --git a/src/authorization/UnionPermissionReader.ts b/src/authorization/UnionPermissionReader.ts index e74dc551f..ad35b7977 100644 --- a/src/authorization/UnionPermissionReader.ts +++ b/src/authorization/UnionPermissionReader.ts @@ -1,9 +1,8 @@ -import type { CredentialGroup } from '../authentication/Credentials'; 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 } from './permissions/Permissions'; +import type { Permission, PermissionMap, PermissionSet } from './permissions/Permissions'; /** * Combines the results of multiple PermissionReaders. @@ -27,7 +26,7 @@ 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 [CredentialGroup, Permission][]) { + for (const [ credential, permission ] of Object.entries(permissionSet) as [keyof PermissionSet, Permission][]) { const resultSet = getDefault(result, identifier, {}); resultSet[credential] = this.mergePermissions(permission, resultSet[credential]); } diff --git a/src/authorization/WebAclReader.ts b/src/authorization/WebAclReader.ts index e1960386c..c3ad965f0 100644 --- a/src/authorization/WebAclReader.ts +++ b/src/authorization/WebAclReader.ts @@ -1,6 +1,5 @@ import { Store } from 'n3'; -import type { Credential, CredentialSet } from '../authentication/Credentials'; -import { CredentialGroup } from '../authentication/Credentials'; +import type { Credentials } from '../authentication/Credentials'; import type { AuxiliaryIdentifierStrategy } from '../http/auxiliary/AuxiliaryIdentifierStrategy'; import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier'; import { getLoggerFor } from '../logging/LogUtil'; @@ -78,17 +77,17 @@ export class WebAclReader extends PermissionReader { * @param aclMap - A map containing stores of ACL data linked to their relevant identifiers. * @param credentials - Credentials to check permissions for. */ - private async findPermissions(aclMap: Map, credentials: CredentialSet): + private async findPermissions(aclMap: Map, credentials: Credentials): Promise { const result: PermissionMap = new IdentifierMap(); for (const [ store, aclIdentifiers ] of aclMap) { - // WebACL only supports public and agent permissions - const publicPermissions = await this.determinePermissions(store, credentials.public); - const agentPermissions = await this.determinePermissions(store, credentials.agent); + // 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) : {}; for (const identifier of aclIdentifiers) { result.set(identifier, { - [CredentialGroup.public]: publicPermissions, - [CredentialGroup.agent]: agentPermissions, + public: publicPermissions, + agent: agentPermissions, }); } } @@ -98,20 +97,16 @@ export class WebAclReader extends PermissionReader { /** * Determines the available permissions for the given credentials. - * Will deny all permissions if credentials are not defined * @param acl - Store containing all relevant authorization triples. - * @param credential - Credentials to find the permissions for. + * @param credentials - Credentials to find the permissions for. */ - private async determinePermissions(acl: Store, credential?: Credential): Promise { + private async determinePermissions(acl: Store, credentials: Credentials): Promise { const aclPermissions: AclPermission = {}; - if (!credential) { - return aclPermissions; - } // Apply all ACL rules const aclRules = acl.getSubjects(RDF.type, ACL.Authorization, null); for (const rule of aclRules) { - const hasAccess = await this.accessChecker.handleSafe({ acl, rule, credential }); + const hasAccess = await this.accessChecker.handleSafe({ acl, rule, credentials }); if (hasAccess) { // Set all allowed modes to true const modes = acl.getObjects(rule, ACL.mode, null); diff --git a/src/authorization/access/AccessChecker.ts b/src/authorization/access/AccessChecker.ts index dfbc7b66c..30186b785 100644 --- a/src/authorization/access/AccessChecker.ts +++ b/src/authorization/access/AccessChecker.ts @@ -1,5 +1,5 @@ import type { Store, Term } from 'n3'; -import type { Credential } from '../../authentication/Credentials'; +import type { Credentials } from '../../authentication/Credentials'; import { AsyncHandler } from '../../util/handlers/AsyncHandler'; /** @@ -19,7 +19,7 @@ export interface AccessCheckerArgs { rule: Term; /** - * Credential of the entity that wants to use the resource. + * Credentials of the entity that wants to use the resource. */ - credential: Credential; + credentials: Credentials; } diff --git a/src/authorization/access/AgentAccessChecker.ts b/src/authorization/access/AgentAccessChecker.ts index fa58db53c..e5c589406 100644 --- a/src/authorization/access/AgentAccessChecker.ts +++ b/src/authorization/access/AgentAccessChecker.ts @@ -6,9 +6,9 @@ import { AccessChecker } from './AccessChecker'; * Checks if the given WebID has been given access. */ export class AgentAccessChecker extends AccessChecker { - public async handle({ acl, rule, credential }: AccessCheckerArgs): Promise { - if (typeof credential.webId === 'string') { - return acl.countQuads(rule, ACL.terms.agent, credential.webId, null) !== 0; + public async handle({ acl, rule, credentials }: AccessCheckerArgs): Promise { + if (typeof credentials.agent?.webId === 'string') { + return acl.countQuads(rule, ACL.terms.agent, credentials.agent.webId, null) !== 0; } return false; } diff --git a/src/authorization/access/AgentClassAccessChecker.ts b/src/authorization/access/AgentClassAccessChecker.ts index 2d2a7483a..3fd7cc6ec 100644 --- a/src/authorization/access/AgentClassAccessChecker.ts +++ b/src/authorization/access/AgentClassAccessChecker.ts @@ -6,13 +6,13 @@ import { AccessChecker } from './AccessChecker'; * Checks access based on the agent class. */ export class AgentClassAccessChecker extends AccessChecker { - public async handle({ acl, rule, credential }: AccessCheckerArgs): Promise { + public async handle({ acl, rule, credentials }: AccessCheckerArgs): Promise { // Check if unauthenticated agents have access if (acl.countQuads(rule, ACL.terms.agentClass, FOAF.terms.Agent, null) !== 0) { return true; } // Check if the agent is authenticated and if authenticated agents have access - if (typeof credential.webId === 'string') { + if (typeof credentials.agent?.webId === 'string') { return acl.countQuads(rule, ACL.terms.agentClass, ACL.terms.AuthenticatedAgent, null) !== 0; } return false; diff --git a/src/authorization/access/AgentGroupAccessChecker.ts b/src/authorization/access/AgentGroupAccessChecker.ts index 1dfc3e18d..957956bde 100644 --- a/src/authorization/access/AgentGroupAccessChecker.ts +++ b/src/authorization/access/AgentGroupAccessChecker.ts @@ -16,9 +16,9 @@ export class AgentGroupAccessChecker extends AccessChecker { super(); } - public async handle({ acl, rule, credential }: AccessCheckerArgs): Promise { - if (typeof credential.webId === 'string') { - const { webId } = credential; + public async handle({ acl, rule, credentials }: AccessCheckerArgs): Promise { + if (typeof credentials.agent?.webId === 'string') { + const { webId } = credentials.agent; const groups = acl.getObjects(rule, ACL.terms.agentGroup, null); return await promiseSome(groups.map(async(group: Term): Promise => diff --git a/src/authorization/permissions/Permissions.ts b/src/authorization/permissions/Permissions.ts index e296142f7..882b68330 100644 --- a/src/authorization/permissions/Permissions.ts +++ b/src/authorization/permissions/Permissions.ts @@ -1,4 +1,3 @@ -import type { CredentialGroup } from '../../authentication/Credentials'; import type { IdentifierMap, IdentifierSetMultiMap } from '../../util/map/IdentifierMap'; /** @@ -23,9 +22,18 @@ export type AccessMap = IdentifierSetMultiMap; export type Permission = Partial>; /** - * Permission per CredentialGroup. + * The keys that can be used in a {@link PermissionSet}; */ -export type PermissionSet = Partial>; +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>; /** * PermissionSet per identifier. diff --git a/src/server/AuthorizingHttpHandler.ts b/src/server/AuthorizingHttpHandler.ts index 6794ee7ca..f910f4977 100644 --- a/src/server/AuthorizingHttpHandler.ts +++ b/src/server/AuthorizingHttpHandler.ts @@ -1,4 +1,4 @@ -import type { CredentialSet } from '../authentication/Credentials'; +import type { Credentials } from '../authentication/Credentials'; import type { CredentialsExtractor } from '../authentication/CredentialsExtractor'; import type { Authorizer } from '../authorization/Authorizer'; import type { PermissionReader } from '../authorization/PermissionReader'; @@ -60,14 +60,14 @@ export class AuthorizingHttpHandler extends OperationHttpHandler { public async handle(input: OperationHttpHandlerInput): Promise { const { request, operation } = input; - const credentials: CredentialSet = await this.credentialsExtractor.handleSafe(request); + const credentials: Credentials = await this.credentialsExtractor.handleSafe(request); this.logger.verbose(`Extracted credentials: ${JSON.stringify(credentials)}`); const requestedModes = await this.modesExtractor.handleSafe(operation); - this.logger.verbose(`Retrieved required modes: ${[ ...requestedModes ].join(',')}`); + this.logger.verbose(`Retrieved required modes: ${[ ...requestedModes.entrySets() ]}`); const availablePermissions = await this.permissionReader.handleSafe({ credentials, requestedModes }); - this.logger.verbose(`Available permissions are ${JSON.stringify(availablePermissions)}`); + this.logger.verbose(`Available permissions are ${[ ...availablePermissions.entries() ]}`); try { await this.authorizer.handleSafe({ credentials, requestedModes, availablePermissions }); diff --git a/test/__mocks__/.eslintrc.js b/test/__mocks__/.eslintrc.js deleted file mode 100644 index cf7a649eb..000000000 --- a/test/__mocks__/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - rules: { - 'unicorn/filename-case': 'off', - }, -}; diff --git a/test/__mocks__/@solid/access-token-verifier.ts b/test/__mocks__/@solid/access-token-verifier.ts deleted file mode 100644 index 7d628a972..000000000 --- a/test/__mocks__/@solid/access-token-verifier.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { SolidTokenVerifierFunction } from '@solid/access-token-verifier'; - -const solidTokenVerifier = jest.fn().mockResolvedValue({ aud: 'solid', exp: 1234, iat: 1234, iss: 'example.com/idp', webid: 'http://alice.example/card#me' }); -export const createSolidTokenVerifier = jest.fn((): SolidTokenVerifierFunction => solidTokenVerifier); diff --git a/test/integration/Identity.test.ts b/test/integration/Identity.test.ts index d1c3ee3c8..8b1f62458 100644 --- a/test/integration/Identity.test.ts +++ b/test/integration/Identity.test.ts @@ -19,9 +19,6 @@ import { IdentityTestState } from './IdentityTestState'; const port = getPort('Identity'); const baseUrl = `http://localhost:${port}/`; -// Undo the global access token verifier mock -jest.unmock('@solid/access-token-verifier'); - // Don't send actual e-mails jest.mock('nodemailer'); diff --git a/test/unit/authentication/BearerWebIdExtractor.test.ts b/test/unit/authentication/BearerWebIdExtractor.test.ts index 20721cde9..09e6e581b 100644 --- a/test/unit/authentication/BearerWebIdExtractor.test.ts +++ b/test/unit/authentication/BearerWebIdExtractor.test.ts @@ -1,15 +1,24 @@ -import { createSolidTokenVerifier } from '@solid/access-token-verifier'; +import type { SolidTokenVerifierFunction } from '@solid/access-token-verifier'; +import type { SolidAccessTokenPayload } from '@solid/access-token-verifier/dist/type/SolidAccessTokenPayload'; import { BearerWebIdExtractor } from '../../../src/authentication/BearerWebIdExtractor'; -import { CredentialGroup } from '../../../src/authentication/Credentials'; import type { HttpRequest } from '../../../src/server/HttpRequest'; import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError'; import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; -const solidTokenVerifier = createSolidTokenVerifier() as jest.MockedFunction; +let clientId: string | undefined; +const solidTokenVerifier = jest.fn(async(): Promise => + // eslint-disable-next-line @typescript-eslint/naming-convention + ({ aud: 'solid', exp: 1234, iat: 1234, iss: 'example.com/idp', webid: 'http://alice.example/card#me', client_id: clientId })); +jest.mock('@solid/access-token-verifier', (): any => + ({ createSolidTokenVerifier: (): SolidTokenVerifierFunction => solidTokenVerifier })); describe('A BearerWebIdExtractor', (): void => { const webIdExtractor = new BearerWebIdExtractor(); + beforeEach((): void => { + clientId = undefined; + }); + afterEach((): void => { jest.clearAllMocks(); }); @@ -56,9 +65,19 @@ describe('A BearerWebIdExtractor', (): void => { expect(solidTokenVerifier).toHaveBeenCalledWith('Bearer token-1234'); }); - it('returns the extracted WebID.', async(): Promise => { + it('returns the extracted credentials.', async(): Promise => { const result = webIdExtractor.handleSafe(request); - await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }}); + await expect(result).resolves.toEqual( + { agent: { webId: 'http://alice.example/card#me' }, issuer: { url: 'example.com/idp' }}, + ); + }); + + it('also returns the clientID if defined.', async(): Promise => { + clientId = 'http://client.example.com/#me'; + const result = webIdExtractor.handleSafe(request); + await expect(result).resolves.toEqual( + { agent: { webId: 'http://alice.example/card#me' }, issuer: { url: 'example.com/idp' }, client: { clientId }}, + ); }); }); @@ -86,7 +105,7 @@ describe('A BearerWebIdExtractor', (): void => { } as any as HttpRequest; beforeEach((): void => { - solidTokenVerifier.mockImplementationOnce((): void => { + solidTokenVerifier.mockImplementationOnce((): never => { throw new Error('invalid'); }); }); diff --git a/test/unit/authentication/DPoPWebIdExtractor.test.ts b/test/unit/authentication/DPoPWebIdExtractor.test.ts index fdbdd2122..3aae0272e 100644 --- a/test/unit/authentication/DPoPWebIdExtractor.test.ts +++ b/test/unit/authentication/DPoPWebIdExtractor.test.ts @@ -1,12 +1,17 @@ -import { createSolidTokenVerifier } from '@solid/access-token-verifier'; -import { CredentialGroup } from '../../../src/authentication/Credentials'; +import type { SolidTokenVerifierFunction } from '@solid/access-token-verifier'; +import type { SolidAccessTokenPayload } from '@solid/access-token-verifier/dist/type/SolidAccessTokenPayload'; import { DPoPWebIdExtractor } from '../../../src/authentication/DPoPWebIdExtractor'; import type { HttpRequest } from '../../../src/server/HttpRequest'; import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError'; import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; import { StaticAsyncHandler } from '../../util/StaticAsyncHandler'; -const solidTokenVerifier = createSolidTokenVerifier() as jest.MockedFunction; +let clientId: string | undefined; +const solidTokenVerifier = jest.fn(async(): Promise => + // eslint-disable-next-line @typescript-eslint/naming-convention + ({ aud: 'solid', exp: 1234, iat: 1234, iss: 'example.com/idp', webid: 'http://alice.example/card#me', client_id: clientId })); +jest.mock('@solid/access-token-verifier', (): any => + ({ createSolidTokenVerifier: (): SolidTokenVerifierFunction => solidTokenVerifier })); describe('A DPoPWebIdExtractor', (): void => { const targetExtractor = new StaticAsyncHandler(true, { path: 'http://example.org/foo/bar' }); @@ -84,9 +89,17 @@ describe('A DPoPWebIdExtractor', (): void => { expect(solidTokenVerifier).toHaveBeenCalledWith('DPoP token-1234', { header: 'token-5678', method: 'GET', url: 'http://example.org/foo/bar' }); }); - it('returns the extracted WebID.', async(): Promise => { + it('returns the extracted credentials.', async(): Promise => { const result = webIdExtractor.handleSafe(request); - await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }}); + await expect(result).resolves.toEqual({ agent: { webId: 'http://alice.example/card#me' }, issuer: { url: 'example.com/idp' }}); + }); + + it('also returns the clientID if defined.', async(): Promise => { + clientId = 'http://client.example.com/#me'; + const result = webIdExtractor.handleSafe(request); + await expect(result).resolves.toEqual( + { agent: { webId: 'http://alice.example/card#me' }, issuer: { url: 'example.com/idp' }, client: { clientId }}, + ); }); }); @@ -116,7 +129,7 @@ describe('A DPoPWebIdExtractor', (): void => { } as any as HttpRequest; beforeEach((): void => { - solidTokenVerifier.mockImplementationOnce((): void => { + solidTokenVerifier.mockImplementationOnce((): never => { throw new Error('invalid'); }); }); diff --git a/test/unit/authentication/PublicCredentialsExtractor.test.ts b/test/unit/authentication/PublicCredentialsExtractor.test.ts index d8e9f2958..94fc8204d 100644 --- a/test/unit/authentication/PublicCredentialsExtractor.test.ts +++ b/test/unit/authentication/PublicCredentialsExtractor.test.ts @@ -1,4 +1,3 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import { PublicCredentialsExtractor } from '../../../src/authentication/PublicCredentialsExtractor'; import type { HttpRequest } from '../../../src/server/HttpRequest'; @@ -8,6 +7,6 @@ describe('A PublicCredentialsExtractor', (): void => { it('returns the empty credentials.', async(): Promise => { const headers = {}; const result = extractor.handleSafe({ headers } as HttpRequest); - await expect(result).resolves.toEqual({ [CredentialGroup.public]: {}}); + await expect(result).resolves.toEqual({}); }); }); diff --git a/test/unit/authentication/UnionCredentialsExtractor.test.ts b/test/unit/authentication/UnionCredentialsExtractor.test.ts index 427f6ba40..4f5df6873 100644 --- a/test/unit/authentication/UnionCredentialsExtractor.test.ts +++ b/test/unit/authentication/UnionCredentialsExtractor.test.ts @@ -1,12 +1,11 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; -import type { CredentialSet } from '../../../src/authentication/Credentials'; +import type { Credentials } from '../../../src/authentication/Credentials'; import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor'; import { UnionCredentialsExtractor } from '../../../src/authentication/UnionCredentialsExtractor'; import type { HttpRequest } from '../../../src/server/HttpRequest'; describe('A UnionCredentialsExtractor', (): void => { - const agent: CredentialSet = { [CredentialGroup.agent]: { webId: 'http://test.com/#me' }}; - const everyone: CredentialSet = { [CredentialGroup.public]: {}}; + const agent: Credentials = { agent: { webId: 'http://user.example.com/#me' }}; + const client: Credentials = { client: { clientId: 'http://client.example.com/#me' }}; const request: HttpRequest = {} as any; let extractors: jest.Mocked[]; let extractor: UnionCredentialsExtractor; @@ -19,7 +18,7 @@ describe('A UnionCredentialsExtractor', (): void => { } as any, { canHandle: jest.fn(), - handle: jest.fn().mockResolvedValue(everyone), + handle: jest.fn().mockResolvedValue(client), } as any, ]; @@ -28,26 +27,26 @@ describe('A UnionCredentialsExtractor', (): void => { it('combines the results of the extractors.', async(): Promise => { await expect(extractor.handle(request)).resolves.toEqual({ - [CredentialGroup.agent]: agent.agent, - [CredentialGroup.public]: {}, + agent: agent.agent, + client: client.client, }); }); it('ignores undefined values.', async(): Promise => { extractors[1].handle.mockResolvedValueOnce({ - [CredentialGroup.public]: {}, - [CredentialGroup.agent]: undefined, + client: client.client, + agent: undefined, }); await expect(extractor.handle(request)).resolves.toEqual({ - [CredentialGroup.agent]: agent.agent, - [CredentialGroup.public]: {}, + agent: agent.agent, + client: client.client, }); }); it('skips erroring handlers.', async(): Promise => { extractors[0].handle.mockRejectedValueOnce(new Error('error')); await expect(extractor.handle(request)).resolves.toEqual({ - [CredentialGroup.public]: {}, + client: client.client, }); }); }); diff --git a/test/unit/authentication/UnsecureConstantCredentialsExtractor.test.ts b/test/unit/authentication/UnsecureConstantCredentialsExtractor.test.ts index 89d5c1ba8..97c2c2348 100644 --- a/test/unit/authentication/UnsecureConstantCredentialsExtractor.test.ts +++ b/test/unit/authentication/UnsecureConstantCredentialsExtractor.test.ts @@ -1,16 +1,15 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import { UnsecureConstantCredentialsExtractor } from '../../../src/authentication/UnsecureConstantCredentialsExtractor'; describe('An UnsecureConstantCredentialsExtractor', (): void => { it('extracts a constant WebID.', async(): Promise => { const agent = 'http://alice.example/card#me'; const extractor = new UnsecureConstantCredentialsExtractor(agent); - await expect(extractor.handle()).resolves.toEqual({ [CredentialGroup.agent]: { webId: agent }}); + await expect(extractor.handle()).resolves.toEqual({ agent: { webId: agent }}); }); it('extracts constant credentials.', async(): Promise => { - const agent = {}; + const agent = { webId: 'http://example.com/#me' }; const extractor = new UnsecureConstantCredentialsExtractor(agent); - await expect(extractor.handle()).resolves.toEqual({ [CredentialGroup.agent]: agent }); + await expect(extractor.handle()).resolves.toEqual({ agent }); }); }); diff --git a/test/unit/authentication/UnsecureWebIdExtractor.test.ts b/test/unit/authentication/UnsecureWebIdExtractor.test.ts index a5501a49b..9068d4f77 100644 --- a/test/unit/authentication/UnsecureWebIdExtractor.test.ts +++ b/test/unit/authentication/UnsecureWebIdExtractor.test.ts @@ -1,4 +1,3 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import { UnsecureWebIdExtractor } from '../../../src/authentication/UnsecureWebIdExtractor'; import type { HttpRequest } from '../../../src/server/HttpRequest'; import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; @@ -23,12 +22,12 @@ describe('An UnsecureWebIdExtractor', (): void => { it('returns the authorization header as WebID if specified.', async(): Promise => { const headers = { authorization: 'WebID http://alice.example/card#me' }; const result = extractor.handleSafe({ headers } as HttpRequest); - await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }}); + await expect(result).resolves.toEqual({ agent: { webId: 'http://alice.example/card#me' }}); }); it('returns the authorization header as WebID if specified with a lowercase token.', async(): Promise => { const headers = { authorization: 'webid http://alice.example/card#me' }; const result = extractor.handleSafe({ headers } as HttpRequest); - await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }}); + await expect(result).resolves.toEqual({ agent: { webId: 'http://alice.example/card#me' }}); }); }); diff --git a/test/unit/authorization/AcpReader.test.ts b/test/unit/authorization/AcpReader.test.ts index e86a171b2..8218c655c 100644 --- a/test/unit/authorization/AcpReader.test.ts +++ b/test/unit/authorization/AcpReader.test.ts @@ -1,6 +1,6 @@ import { Parser } from 'n3'; import type { Quad } from 'rdf-js'; -import type { CredentialSet } from '../../../src/authentication/Credentials'; +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'; @@ -30,7 +30,7 @@ function toQuads(turtle: string, baseIRI: string): Quad[] { describe('An AcpReader', (): void => { const baseUrl = 'http://example.com/'; - let credentials: CredentialSet; + let credentials: Credentials; // Subject identifiers are used as keys, values are the output of their corresponding ACR resource let dataMap: Record; let acrStrategy: AuxiliaryStrategy; @@ -39,7 +39,7 @@ describe('An AcpReader', (): void => { let acpReader: AcpReader; beforeEach(async(): Promise => { - credentials = { public: {}}; + credentials = {}; dataMap = {}; acrStrategy = new SimpleSuffixStrategy(acrSuffix); @@ -167,4 +167,30 @@ describe('An AcpReader', (): void => { expect(acrStore.getRepresentation) .toHaveBeenCalledWith(acrStrategy.getAuxiliaryIdentifier({ path: baseUrl }), { type: { [INTERNAL_QUADS]: 1 }}); }); + + it('correctly puts the credentials in the context.', async(): Promise => { + dataMap[baseUrl] = toQuads(` + [] + acp:resource <./> ; + acp:accessControl [ acp:apply _:policy ]. + _:policy + acp:allow acl:Read; + acp:allOf _:matcher. + _:matcher + acp:agent ; + acp:client ; + acp:issuer . + `, baseUrl); + const requestedModes = new IdentifierSetMultiMap([[{ path: baseUrl }, AccessMode.read ]]); + let expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { agent: {}}]]); + 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 }, { agent: { 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 b921e540e..5f5bf32bd 100644 --- a/test/unit/authorization/AllStaticReader.test.ts +++ b/test/unit/authorization/AllStaticReader.test.ts @@ -1,4 +1,3 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import { AllStaticReader } from '../../../src/authorization/AllStaticReader'; import type { Permission } from '../../../src/authorization/permissions/Permissions'; import { AccessMode } from '../../../src/authorization/permissions/Permissions'; @@ -16,7 +15,7 @@ function getPermissions(allow: boolean): Permission { } describe('An AllStaticReader', (): void => { - const credentials = { [CredentialGroup.agent]: {}}; + const credentials = {}; const identifier = { path: 'http://test.com/resource' }; it('can handle everything.', async(): Promise => { @@ -28,11 +27,13 @@ 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, { [CredentialGroup.agent]: getPermissions(true) }]])); + compareMaps(result, new IdentifierMap([[ identifier, { public: getPermissions(true), + agent: getPermissions(true) }]])); authorizer = new AllStaticReader(false); result = await authorizer.handle({ credentials, requestedModes }); - compareMaps(result, new IdentifierMap([[ identifier, { [CredentialGroup.agent]: getPermissions(false) }]])); + compareMaps(result, new IdentifierMap([[ identifier, { public: getPermissions(false), + agent: getPermissions(false) }]])); }); }); diff --git a/test/unit/authorization/AuthAuxiliaryReader.test.ts b/test/unit/authorization/AuthAuxiliaryReader.test.ts index eea4baa6c..2cbeab584 100644 --- a/test/unit/authorization/AuthAuxiliaryReader.test.ts +++ b/test/unit/authorization/AuthAuxiliaryReader.test.ts @@ -1,4 +1,4 @@ -import type { CredentialSet } from '../../../src/authentication/Credentials'; +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'; @@ -16,7 +16,7 @@ describe('An AuthAuxiliaryReader', (): void => { const acl1 = { path: joinUrl(subject1.path, '.acl') }; const subject2 = { path: joinUrl(baseUrl, 'bar/') }; const acl2 = { path: joinUrl(subject2.path, '.acl') }; - const credentials: CredentialSet = { public: {}}; + const credentials: Credentials = {}; let requestedModes: AccessMap; let sourceResult: PermissionMap; let aclStrategy: jest.Mocked; diff --git a/test/unit/authorization/AuxiliaryReader.test.ts b/test/unit/authorization/AuxiliaryReader.test.ts index e70baea5a..f6e2b8ed2 100644 --- a/test/unit/authorization/AuxiliaryReader.test.ts +++ b/test/unit/authorization/AuxiliaryReader.test.ts @@ -1,4 +1,3 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import { AuxiliaryReader } from '../../../src/authorization/AuxiliaryReader'; import type { PermissionReaderInput, PermissionReader } from '../../../src/authorization/PermissionReader'; import type { AccessMap, PermissionMap, PermissionSet } from '../../../src/authorization/permissions/Permissions'; @@ -16,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 = { [CredentialGroup.agent]: { read: true }}; + const permissionSet: PermissionSet = { agent: { read: true }}; let source: jest.Mocked; let strategy: jest.Mocked; let reader: AuxiliaryReader; @@ -77,7 +76,7 @@ describe('An AuxiliaryReader', (): void => { [ auxiliaryIdentifier2, AccessMode.read ], [ subjectIdentifier, AccessMode.delete ], ]); - const resultSet = { [CredentialGroup.agent]: { read: true, write: true, delete: true }}; + const resultSet = { agent: { 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 701bc3fd8..30a86b201 100644 --- a/test/unit/authorization/OwnerPermissionReader.test.ts +++ b/test/unit/authorization/OwnerPermissionReader.test.ts @@ -1,5 +1,4 @@ -import type { CredentialSet } from '../../../src/authentication/Credentials'; -import { CredentialGroup } from '../../../src/authentication/Credentials'; +import type { Credentials } from '../../../src/authentication/Credentials'; import { OwnerPermissionReader } from '../../../src/authorization/OwnerPermissionReader'; import { AclMode } from '../../../src/authorization/permissions/AclPermission'; import type { AccessMap } from '../../../src/authorization/permissions/Permissions'; @@ -16,7 +15,7 @@ import { compareMaps } from '../../util/Util'; describe('An OwnerPermissionReader', (): void => { const owner = 'http://example.com/alice/profile/card#me'; const podBaseUrl = 'http://example.com/alice/'; - let credentials: CredentialSet; + let credentials: Credentials; let identifier: ResourceIdentifier; let requestedModes: AccessMap; let settings: AccountSettings; @@ -26,7 +25,7 @@ describe('An OwnerPermissionReader', (): void => { let reader: OwnerPermissionReader; beforeEach(async(): Promise => { - credentials = { [CredentialGroup.agent]: { webId: owner }}; + credentials = { agent: { webId: owner }}; identifier = { path: `${podBaseUrl}.acl` }; @@ -82,7 +81,7 @@ 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, - { [CredentialGroup.agent]: { + { agent: { read: true, write: true, append: true, diff --git a/test/unit/authorization/ParentContainerReader.test.ts b/test/unit/authorization/ParentContainerReader.test.ts index 6f14a5816..e9e557ff9 100644 --- a/test/unit/authorization/ParentContainerReader.test.ts +++ b/test/unit/authorization/ParentContainerReader.test.ts @@ -1,4 +1,4 @@ -import type { CredentialSet } from '../../../src/authentication/Credentials'; +import type { Credentials } 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'; @@ -16,7 +16,7 @@ describe('A ParentContainerReader', (): void => { const target2 = { path: joinUrl(parent2.path, 'bar') }; const parent3 = { path: joinUrl(baseUrl, 'baz/') }; const target3 = { path: joinUrl(parent3.path, 'baz') }; - const credentials: CredentialSet = { public: {}}; + const credentials: Credentials = {}; let requestedModes: AccessMap; let sourceResult: PermissionMap; const identifierStrategy = new SingleRootIdentifierStrategy(baseUrl); diff --git a/test/unit/authorization/PathBasedReader.test.ts b/test/unit/authorization/PathBasedReader.test.ts index 29abccd79..3df6b1488 100644 --- a/test/unit/authorization/PathBasedReader.test.ts +++ b/test/unit/authorization/PathBasedReader.test.ts @@ -1,4 +1,3 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import { PathBasedReader } from '../../../src/authorization/PathBasedReader'; import type { PermissionReader, PermissionReaderInput } from '../../../src/authorization/PermissionReader'; import type { PermissionMap, PermissionSet } from '../../../src/authorization/permissions/Permissions'; @@ -11,7 +10,7 @@ import { compareMaps } from '../../util/Util'; describe('A PathBasedReader', (): void => { const baseUrl = 'http://test.com/foo/'; - const permissionSet: PermissionSet = { [CredentialGroup.agent]: { read: true }}; + const permissionSet: PermissionSet = { agent: { 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 e10b8af83..2a51ade6b 100644 --- a/test/unit/authorization/PermissionBasedAuthorizer.test.ts +++ b/test/unit/authorization/PermissionBasedAuthorizer.test.ts @@ -1,4 +1,3 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import type { AuthorizerInput } from '../../../src/authorization/Authorizer'; import { PermissionBasedAuthorizer } from '../../../src/authorization/PermissionBasedAuthorizer'; import { AccessMode } from '../../../src/authorization/permissions/Permissions'; @@ -36,8 +35,8 @@ describe('A PermissionBasedAuthorizer', (): void => { [[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]], ); input.availablePermissions = new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { read: true, write: false }, - [CredentialGroup.agent]: { write: true }, + public: { read: true, write: false }, + agent: { write: true }, }]]); await expect(authorizer.handle(input)).resolves.toBeUndefined(); }); @@ -47,7 +46,7 @@ describe('A PermissionBasedAuthorizer', (): void => { [[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]], ); input.availablePermissions = new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { read: true, write: false }, + public: { read: true, write: false }, }]]); await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError); }); @@ -58,7 +57,7 @@ describe('A PermissionBasedAuthorizer', (): void => { [[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]], ); input.availablePermissions = new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { read: true, write: false }, + public: { read: true, write: false }, }]]); await expect(authorizer.handle(input)).rejects.toThrow(ForbiddenHttpError); }); @@ -71,7 +70,7 @@ describe('A PermissionBasedAuthorizer', (): void => { resourceSet.hasResource.mockResolvedValueOnce(false); input.requestedModes = new IdentifierSetMultiMap([[ identifier, AccessMode.delete ]]); input.availablePermissions = new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { read: true }, + public: { read: true }, }]]); await expect(authorizer.handle(input)).rejects.toThrow(NotFoundHttpError); }); @@ -86,12 +85,12 @@ describe('A PermissionBasedAuthorizer', (): void => { ]); input.availablePermissions = new IdentifierMap([ [ identifier, { - [CredentialGroup.public]: { read: true, write: false }, - [CredentialGroup.agent]: { write: true }, + public: { read: true, write: false }, + agent: { write: true }, }], [ identifier2, { - [CredentialGroup.public]: { read: false }, - [CredentialGroup.agent]: { write: true }, + public: { read: false }, + agent: { write: true }, }], ]); await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError); @@ -107,8 +106,8 @@ describe('A PermissionBasedAuthorizer', (): void => { ]); input.availablePermissions = new IdentifierMap([ [ identifier, { - [CredentialGroup.public]: { read: true, write: false }, - [CredentialGroup.agent]: { write: true }, + public: { read: true, write: false }, + agent: { 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 570859217..5074b7531 100644 --- a/test/unit/authorization/UnionPermissionReader.test.ts +++ b/test/unit/authorization/UnionPermissionReader.test.ts @@ -1,4 +1,3 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import type { PermissionReader, PermissionReaderInput } from '../../../src/authorization/PermissionReader'; import type { PermissionSet } from '../../../src/authorization/permissions/Permissions'; import { AccessMode } from '../../../src/authorization/permissions/Permissions'; @@ -31,45 +30,45 @@ 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, { [CredentialGroup.agent]: { read: true }}]]), + new IdentifierMap([[ identifier, { agent: { read: true }}]]), ); readers[1].handle.mockResolvedValue( - new IdentifierMap([[ identifier, { [CredentialGroup.agent]: { write: true }}]]), + new IdentifierMap([[ identifier, { agent: { write: true }}]]), ); compareMaps(await unionReader.handle(input), - new IdentifierMap([[ identifier, { [CredentialGroup.agent]: { write: true }}]])); + new IdentifierMap([[ identifier, { agent: { 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, { [CredentialGroup.agent]: { read: true }, [CredentialGroup.public]: undefined }], - [ identifier2, { [CredentialGroup.agent]: { write: true }}], - [ identifier3, { [CredentialGroup.agent]: { append: false }, [CredentialGroup.public]: { delete: true }}], + [ identifier, { agent: { read: true }, public: undefined }], + [ identifier2, { agent: { write: true }}], + [ identifier3, { agent: { append: false }, public: { delete: true }}], ])); readers[1].handle.mockResolvedValue(new IdentifierMap([ - [ identifier, { [CredentialGroup.agent]: { write: true }, [CredentialGroup.public]: { read: false }}], - [ identifier2, { [CredentialGroup.public]: { read: false }}], + [ identifier, { agent: { write: true }, public: { read: false }}], + [ identifier2, { public: { read: false }}], ])); compareMaps(await unionReader.handle(input), new IdentifierMap([ - [ identifier, { [CredentialGroup.agent]: { read: true, write: true }, [CredentialGroup.public]: { read: false }}], - [ identifier2, { [CredentialGroup.agent]: { write: true }, [CredentialGroup.public]: { read: false }}], - [ identifier3, { [CredentialGroup.agent]: { append: false }, [CredentialGroup.public]: { delete: true }}], + [ identifier, { agent: { read: true, write: true }, public: { read: false }}], + [ identifier2, { agent: { write: true }, public: { read: false }}], + [ identifier3, { agent: { append: false }, public: { delete: true }}], ])); }); it('merges same fields using false > true > undefined.', async(): Promise => { readers[0].handle.mockResolvedValue(new IdentifierMap( [[ identifier, - { [CredentialGroup.agent]: { read: true, write: false, append: undefined, create: true, delete: undefined }}]], + { agent: { read: true, write: false, append: undefined, create: true, delete: undefined }}]], )); readers[1].handle.mockResolvedValue(new IdentifierMap( - [[ identifier, { [CredentialGroup.agent]: + [[ identifier, { agent: { read: false, write: true, append: true, create: true, delete: undefined }}]], )); compareMaps(await unionReader.handle(input), new IdentifierMap([[ identifier, { - [CredentialGroup.agent]: { read: false, write: false, append: true, create: true }, + 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 b5fa83d25..55feaae5a 100644 --- a/test/unit/authorization/WebAclReader.test.ts +++ b/test/unit/authorization/WebAclReader.test.ts @@ -1,6 +1,5 @@ import { DataFactory } from 'n3'; -import type { CredentialSet } from '../../../src/authentication/Credentials'; -import { CredentialGroup } from '../../../src/authentication/Credentials'; +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'; @@ -36,14 +35,14 @@ describe('A WebAclReader', (): void => { let resourceSet: jest.Mocked; let store: jest.Mocked; const identifierStrategy = new SingleRootIdentifierStrategy('http://example.com/'); - let credentials: CredentialSet; + let credentials: Credentials; let identifier: ResourceIdentifier; let accessMap: AccessMap; let input: PermissionReaderInput; let accessChecker: jest.Mocked; beforeEach(async(): Promise => { - credentials = { [CredentialGroup.public]: {}, [CredentialGroup.agent]: {}}; + credentials = { agent: { webId: 'http://example.com/#me' }}; identifier = { path: 'http://example.com/foo' }; accessMap = new IdentifierSetMultiMap([ @@ -79,8 +78,8 @@ describe('A WebAclReader', (): void => { it('returns undefined permissions for undefined credentials.', async(): Promise => { input.credentials = {}; compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - [CredentialGroup.public]: {}, - [CredentialGroup.agent]: {}, + public: {}, + agent: {}, }]])); }); @@ -92,8 +91,8 @@ describe('A WebAclReader', (): void => { quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), ], INTERNAL_QUADS)); compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { read: true }, - [CredentialGroup.agent]: { read: true }, + public: { read: true }, + agent: { read: true }, }]])); }); @@ -105,8 +104,8 @@ describe('A WebAclReader', (): void => { quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), ], INTERNAL_QUADS)); compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - [CredentialGroup.public]: {}, - [CredentialGroup.agent]: {}, + public: {}, + agent: {}, }]])); }); @@ -119,8 +118,8 @@ describe('A WebAclReader', (): void => { quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}fakeMode1`)), ], INTERNAL_QUADS)); compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { read: true }, - [CredentialGroup.agent]: { read: true }, + public: { read: true }, + agent: { read: true }, }]])); }); @@ -133,8 +132,8 @@ describe('A WebAclReader', (): void => { quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), ], INTERNAL_QUADS)); compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { read: true }, - [CredentialGroup.agent]: { read: true }, + public: { read: true }, + agent: { read: true }, }]])); }); @@ -150,8 +149,8 @@ describe('A WebAclReader', (): void => { quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)), ], INTERNAL_QUADS)); compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { append: true }, - [CredentialGroup.agent]: { append: true }, + public: { append: true }, + agent: { append: true }, }]])); }); @@ -172,8 +171,8 @@ 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, credential: cred }): Promise => - (rule.value === 'auth1') === !cred.webId); + accessChecker.handleSafe.mockImplementation(async({ rule, credentials: cred }): Promise => + (rule.value === 'auth1') === !cred.agent?.webId); store.getRepresentation.mockResolvedValue(new BasicRepresentation([ quad(nn('auth1'), nn(`${rdf}type`), nn(`${acl}Authorization`)), @@ -185,8 +184,8 @@ describe('A WebAclReader', (): void => { ], INTERNAL_QUADS)); compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { - [CredentialGroup.public]: { read: true }, - [CredentialGroup.agent]: { append: true }, + public: { read: true }, + agent: { append: true }, }]])); }); @@ -225,9 +224,9 @@ describe('A WebAclReader', (): void => { input.requestedModes.set(identifier3, new Set([ AccessMode.append ])); compareMaps(await reader.handle(input), new IdentifierMap([ - [ identifier, { [CredentialGroup.public]: { read: true }, [CredentialGroup.agent]: { read: true }}], - [ identifier2, { [CredentialGroup.public]: { read: true }, [CredentialGroup.agent]: { read: true }}], - [ identifier3, { [CredentialGroup.public]: { append: true }, [CredentialGroup.agent]: { append: true }}], + [ identifier, { public: { read: true }, agent: { read: true }}], + [ identifier2, { public: { read: true }, agent: { read: true }}], + [ identifier3, { public: { append: true }, agent: { append: true }}], ])); // http://example.com/.acl and http://example.com/bar/.acl expect(store.getRepresentation).toHaveBeenCalledTimes(2); diff --git a/test/unit/authorization/access/AgentAccessChecker.test.ts b/test/unit/authorization/access/AgentAccessChecker.test.ts index a64bd2e1f..227ad1823 100644 --- a/test/unit/authorization/access/AgentAccessChecker.test.ts +++ b/test/unit/authorization/access/AgentAccessChecker.test.ts @@ -16,17 +16,17 @@ describe('A AgentAccessChecker', (): void => { }); it('returns true if a match is found for the given WebID.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('match'), credential: { webId }}; + const input: AccessCheckerArgs = { acl, rule: namedNode('match'), credentials: { agent: { webId }}}; await expect(checker.handle(input)).resolves.toBe(true); }); it('returns false if no match is found.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credential: { webId }}; + const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credentials: { agent: { webId }}}; await expect(checker.handle(input)).resolves.toBe(false); }); it('returns false if the credentials contain no WebID.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('match'), credential: {}}; + const input: AccessCheckerArgs = { acl, rule: namedNode('match'), credentials: {}}; await expect(checker.handle(input)).resolves.toBe(false); }); }); diff --git a/test/unit/authorization/access/AgentClassAccessChecker.test.ts b/test/unit/authorization/access/AgentClassAccessChecker.test.ts index 9e505c896..4d0564877 100644 --- a/test/unit/authorization/access/AgentClassAccessChecker.test.ts +++ b/test/unit/authorization/access/AgentClassAccessChecker.test.ts @@ -16,22 +16,22 @@ describe('An AgentClassAccessChecker', (): void => { }); it('returns true if the rule contains foaf:agent as supported class.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('agentMatch'), credential: {}}; + const input: AccessCheckerArgs = { acl, rule: namedNode('agentMatch'), credentials: {}}; await expect(checker.handle(input)).resolves.toBe(true); }); it('returns true for authenticated users with an acl:AuthenticatedAgent rule.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('authenticatedMatch'), credential: { webId }}; + const input: AccessCheckerArgs = { acl, rule: namedNode('authenticatedMatch'), credentials: { agent: { webId }}}; await expect(checker.handle(input)).resolves.toBe(true); }); it('returns false for unauthenticated users with an acl:AuthenticatedAgent rule.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('authenticatedMatch'), credential: {}}; + const input: AccessCheckerArgs = { acl, rule: namedNode('authenticatedMatch'), credentials: {}}; await expect(checker.handle(input)).resolves.toBe(false); }); it('returns false if no class rule is found.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credential: {}}; + const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credentials: {}}; await expect(checker.handle(input)).resolves.toBe(false); }); }); diff --git a/test/unit/authorization/access/AgentGroupAccessChecker.test.ts b/test/unit/authorization/access/AgentGroupAccessChecker.test.ts index d563379dd..54567e2c9 100644 --- a/test/unit/authorization/access/AgentGroupAccessChecker.test.ts +++ b/test/unit/authorization/access/AgentGroupAccessChecker.test.ts @@ -33,17 +33,17 @@ describe('An AgentGroupAccessChecker', (): void => { }); it('returns true if the WebID is a valid group member.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credential: { webId }}; + const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credentials: { agent: { webId }}}; await expect(checker.handle(input)).resolves.toBe(true); }); it('returns false if the WebID is not a valid group member.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credential: { webId }}; + const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credentials: { agent: { webId }}}; await expect(checker.handle(input)).resolves.toBe(false); }); it('returns false if there are no WebID credentials.', async(): Promise => { - const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credential: {}}; + const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credentials: {}}; await expect(checker.handle(input)).resolves.toBe(false); }); }); diff --git a/test/unit/http/ldp/metadata/WebAclMetadataCollector.test.ts b/test/unit/http/ldp/metadata/WebAclMetadataCollector.test.ts index fdc05e87e..c57ccdd2f 100644 --- a/test/unit/http/ldp/metadata/WebAclMetadataCollector.test.ts +++ b/test/unit/http/ldp/metadata/WebAclMetadataCollector.test.ts @@ -1,5 +1,4 @@ import 'jest-rdf'; -import { CredentialGroup } from '../../../../../src/authentication/Credentials'; import type { AclPermission } from '../../../../../src/authorization/permissions/AclPermission'; import { WebAclMetadataCollector } from '../../../../../src/http/ldp/metadata/WebAclMetadataCollector'; import type { Operation } from '../../../../../src/http/Operation'; @@ -45,7 +44,7 @@ describe('A WebAclMetadataCollector', (): void => { it('adds no metadata if the method is wrong.', async(): Promise => { operation.availablePermissions = new IdentifierMap( - [[ target, { [CredentialGroup.public]: { read: true, write: false }}]], + [[ target, { public: { read: true, write: false }}]], ); operation.method = 'DELETE'; await expect(writer.handle({ metadata, operation })).resolves.toBeUndefined(); @@ -54,8 +53,8 @@ describe('A WebAclMetadataCollector', (): void => { it('adds corresponding metadata for all permissions present.', async(): Promise => { operation.availablePermissions = new IdentifierMap([[ target, { - [CredentialGroup.agent]: { read: true, write: true, control: false } as AclPermission, - [CredentialGroup.public]: { read: true, write: false }, + agent: { read: true, write: true, control: false } as AclPermission, + public: { read: true, write: false }, }]]); await expect(writer.handle({ metadata, operation })).resolves.toBeUndefined(); expect(metadata.quads()).toHaveLength(3); @@ -65,8 +64,8 @@ describe('A WebAclMetadataCollector', (): void => { it('ignores unknown modes.', async(): Promise => { operation.availablePermissions = new IdentifierMap([[ target, { - [CredentialGroup.agent]: { read: true, create: true }, - [CredentialGroup.public]: { read: true }, + agent: { read: true, create: true }, + public: { read: true }, }]]); await expect(writer.handle({ metadata, operation })).resolves.toBeUndefined(); expect(metadata.quads()).toHaveLength(2); diff --git a/test/unit/server/AuthorizingHttpHandler.test.ts b/test/unit/server/AuthorizingHttpHandler.test.ts index 01b3403e0..d36ca7d93 100644 --- a/test/unit/server/AuthorizingHttpHandler.test.ts +++ b/test/unit/server/AuthorizingHttpHandler.test.ts @@ -1,4 +1,3 @@ -import { CredentialGroup } from '../../../src/authentication/Credentials'; import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor'; import type { Authorizer } from '../../../src/authorization/Authorizer'; import type { PermissionReader } from '../../../src/authorization/PermissionReader'; @@ -15,11 +14,11 @@ import { ForbiddenHttpError } from '../../../src/util/errors/ForbiddenHttpError' import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap'; describe('An AuthorizingHttpHandler', (): void => { - const credentials = { [CredentialGroup.public]: {}}; + const credentials = { }; const target = { path: 'http://test.com/foo' }; const requestedModes: AccessMap = new IdentifierSetMultiMap([[ target, AccessMode.read ]]); const availablePermissions: PermissionMap = new IdentifierMap( - [[ target, { [CredentialGroup.public]: { read: true }}]], + [[ target, { public: { read: true }}]], ); const request: HttpRequest = {} as any; const response: HttpResponse = {} as any;