From c3fa74de78efbcbd5008f42877cdeed92dfa9f9b Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Fri, 17 Sep 2021 11:17:43 +0200 Subject: [PATCH] feat: Let CredentialsExtractors specify what type of Credentials they generate --- src/authentication/BearerWebIdExtractor.ts | 10 ++--- src/authentication/Credentials.ts | 15 ++++++- src/authentication/CredentialsExtractor.ts | 4 +- src/authentication/DPoPWebIdExtractor.ts | 7 +-- .../EmptyCredentialsExtractor.ts | 7 +-- .../UnsecureConstantCredentialsExtractor.ts | 17 ++++---- src/authentication/UnsecureWebIdExtractor.ts | 7 +-- src/authorization/Authorizer.ts | 18 ++++---- src/authorization/AuxiliaryAuthorizer.ts | 10 ++--- src/authorization/PathBasedAuthorizer.ts | 6 +-- src/authorization/WebAclAuthorizer.ts | 43 ++++++++++++------- .../access-checkers/AccessChecker.ts | 4 +- src/ldp/AuthenticatedLdpHandler.ts | 6 +-- .../BearerWebIdExtractor.test.ts | 3 +- .../authentication/DPoPWebIdExtractor.test.ts | 3 +- .../EmptyCredentialsExtractor.test.ts | 3 +- ...secureConstantCredentialsExtractor.test.ts | 5 ++- .../UnsecureWebIdExtractor.test.ts | 3 +- .../authorization/PathBasedAuthorizer.test.ts | 6 +-- .../authorization/WebAclAuthorizer.test.ts | 15 ++++--- test/unit/ldp/AuthenticatedLdpHandler.test.ts | 4 +- 21 files changed, 115 insertions(+), 81 deletions(-) diff --git a/src/authentication/BearerWebIdExtractor.ts b/src/authentication/BearerWebIdExtractor.ts index 6b98f3095..09e455c1f 100644 --- a/src/authentication/BearerWebIdExtractor.ts +++ b/src/authentication/BearerWebIdExtractor.ts @@ -4,12 +4,10 @@ import { getLoggerFor } from '../logging/LogUtil'; import type { HttpRequest } from '../server/HttpRequest'; import { BadRequestHttpError } from '../util/errors/BadRequestHttpError'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; -import type { Credentials } from './Credentials'; +import { CredentialGroup } from './Credentials'; +import type { CredentialSet } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; -/** - * Credentials extractor that extracts a WebID from a Bearer access token. - */ export class BearerWebIdExtractor extends CredentialsExtractor { protected readonly logger = getLoggerFor(this); private readonly verify: SolidTokenVerifierFunction; @@ -26,13 +24,13 @@ 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 { webId }; + return { [CredentialGroup.agent]: { webId }}; } 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 af40670a7..b91c0ffb9 100644 --- a/src/authentication/Credentials.ts +++ b/src/authentication/Credentials.ts @@ -1,6 +1,19 @@ /** * Credentials identifying an entity accessing or owning data. */ -export interface Credentials { +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>; diff --git a/src/authentication/CredentialsExtractor.ts b/src/authentication/CredentialsExtractor.ts index 1d7294b2a..d4469fa74 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 { Credentials } from './Credentials'; +import type { CredentialSet } 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 68c7eb5a6..9af571ca0 100644 --- a/src/authentication/DPoPWebIdExtractor.ts +++ b/src/authentication/DPoPWebIdExtractor.ts @@ -5,7 +5,8 @@ import { getLoggerFor } from '../logging/LogUtil'; import type { HttpRequest } from '../server/HttpRequest'; import { BadRequestHttpError } from '../util/errors/BadRequestHttpError'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; -import type { Credentials } from './Credentials'; +import { CredentialGroup } from './Credentials'; +import type { CredentialSet } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; /** @@ -31,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.'); @@ -53,7 +54,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor { }, ); this.logger.info(`Verified WebID via DPoP-bound access token: ${webId}`); - return { webId }; + return { [CredentialGroup.agent]: { webId }}; } 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/EmptyCredentialsExtractor.ts b/src/authentication/EmptyCredentialsExtractor.ts index d78c85291..847aeabbc 100644 --- a/src/authentication/EmptyCredentialsExtractor.ts +++ b/src/authentication/EmptyCredentialsExtractor.ts @@ -1,6 +1,7 @@ import type { HttpRequest } from '../server/HttpRequest'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; -import type { Credentials } from './Credentials'; +import { CredentialGroup } from './Credentials'; +import type { CredentialSet } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; /** @@ -14,7 +15,7 @@ export class EmptyCredentialsExtractor extends CredentialsExtractor { } } - public async handle(): Promise { - return {}; + public async handle(): Promise { + return { [CredentialGroup.public]: {}}; } } diff --git a/src/authentication/UnsecureConstantCredentialsExtractor.ts b/src/authentication/UnsecureConstantCredentialsExtractor.ts index 615bea536..720bd22a8 100644 --- a/src/authentication/UnsecureConstantCredentialsExtractor.ts +++ b/src/authentication/UnsecureConstantCredentialsExtractor.ts @@ -1,5 +1,6 @@ import { getLoggerFor } from '../logging/LogUtil'; -import type { Credentials } from './Credentials'; +import { CredentialGroup } from './Credentials'; +import type { Credential, CredentialSet } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; /** @@ -7,18 +8,18 @@ import { CredentialsExtractor } from './CredentialsExtractor'; * (useful for development or debugging purposes). */ export class UnsecureConstantCredentialsExtractor extends CredentialsExtractor { - private readonly agent: Credentials; + private readonly credentials: CredentialSet; private readonly logger = getLoggerFor(this); public constructor(agent: string); - public constructor(agent: Credentials); - public constructor(agent: string | Credentials) { + public constructor(agent: Credential); + public constructor(agent: string | Credential) { super(); - this.agent = typeof agent === 'string' ? { webId: agent } : agent; + this.credentials = { [CredentialGroup.agent]: typeof agent === 'string' ? { webId: agent } : agent }; } - public async handle(): Promise { - this.logger.info(`Agent unsecurely claims to be ${this.agent.webId}`); - return this.agent; + 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 dd75dc362..28a35019d 100644 --- a/src/authentication/UnsecureWebIdExtractor.ts +++ b/src/authentication/UnsecureWebIdExtractor.ts @@ -1,7 +1,8 @@ import { getLoggerFor } from '../logging/LogUtil'; import type { HttpRequest } from '../server/HttpRequest'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; -import type { Credentials } from './Credentials'; +import { CredentialGroup } from './Credentials'; +import type { CredentialSet } from './Credentials'; import { CredentialsExtractor } from './CredentialsExtractor'; /** @@ -17,9 +18,9 @@ export class UnsecureWebIdExtractor extends CredentialsExtractor { } } - public async handle({ headers }: HttpRequest): Promise { + public async handle({ headers }: HttpRequest): Promise { const webId = /^WebID\s+(.*)/u.exec(headers.authorization!)![1]; this.logger.info(`Agent unsecurely claims to be ${webId}`); - return { webId }; + return { [CredentialGroup.agent]: { webId }}; } } diff --git a/src/authorization/Authorizer.ts b/src/authorization/Authorizer.ts index bb4f2f3a9..cba83bc76 100644 --- a/src/authorization/Authorizer.ts +++ b/src/authorization/Authorizer.ts @@ -1,20 +1,14 @@ -import type { Credentials } from '../authentication/Credentials'; +import type { CredentialSet } from '../authentication/Credentials'; import type { PermissionSet } from '../ldp/permissions/PermissionSet'; import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; import { AsyncHandler } from '../util/handlers/AsyncHandler'; import type { Authorization } from './Authorization'; -/** - * Verifies if the given credentials have access to the given permissions on the given resource. - * An {@link Error} with the necessary explanation will be thrown when permissions are not granted. - */ -export abstract class Authorizer extends AsyncHandler {} - -export interface AuthorizerArgs { +export interface AuthorizerInput { /** * Credentials of the entity that wants to use the resource. */ - credentials: Credentials; + credentials: CredentialSet; /** * Identifier of the resource that will be read/modified. */ @@ -24,3 +18,9 @@ export interface AuthorizerArgs { */ permissions: PermissionSet; } + +/** + * Verifies if the given credentials have access to the given permissions on the given resource. + * An {@link Error} with the necessary explanation will be thrown when permissions are not granted. + */ +export abstract class Authorizer extends AsyncHandler {} diff --git a/src/authorization/AuxiliaryAuthorizer.ts b/src/authorization/AuxiliaryAuthorizer.ts index ab00df9a2..3b7bab4a7 100644 --- a/src/authorization/AuxiliaryAuthorizer.ts +++ b/src/authorization/AuxiliaryAuthorizer.ts @@ -2,7 +2,7 @@ import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIden import { getLoggerFor } from '../logging/LogUtil'; import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; import type { Authorization } from './Authorization'; -import type { AuthorizerArgs } from './Authorizer'; +import type { AuthorizerInput } from './Authorizer'; import { Authorizer } from './Authorizer'; /** @@ -22,24 +22,24 @@ export class AuxiliaryAuthorizer extends Authorizer { this.auxiliaryStrategy = auxiliaryStrategy; } - public async canHandle(auxiliaryAuth: AuthorizerArgs): Promise { + public async canHandle(auxiliaryAuth: AuthorizerInput): Promise { const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth); return this.resourceAuthorizer.canHandle(resourceAuth); } - public async handle(auxiliaryAuth: AuthorizerArgs): Promise { + public async handle(auxiliaryAuth: AuthorizerInput): Promise { const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth); this.logger.debug(`Checking auth request for ${auxiliaryAuth.identifier.path} on ${resourceAuth.identifier.path}`); return this.resourceAuthorizer.handle(resourceAuth); } - public async handleSafe(auxiliaryAuth: AuthorizerArgs): Promise { + public async handleSafe(auxiliaryAuth: AuthorizerInput): Promise { const resourceAuth = this.getRequiredAuthorization(auxiliaryAuth); this.logger.debug(`Checking auth request for ${auxiliaryAuth.identifier.path} to ${resourceAuth.identifier.path}`); return this.resourceAuthorizer.handleSafe(resourceAuth); } - private getRequiredAuthorization(auxiliaryAuth: AuthorizerArgs): AuthorizerArgs { + private getRequiredAuthorization(auxiliaryAuth: AuthorizerInput): AuthorizerInput { if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(auxiliaryAuth.identifier)) { throw new NotImplementedHttpError('AuxiliaryAuthorizer only supports auxiliary resources.'); } diff --git a/src/authorization/PathBasedAuthorizer.ts b/src/authorization/PathBasedAuthorizer.ts index 39fc697a5..0e7f6d341 100644 --- a/src/authorization/PathBasedAuthorizer.ts +++ b/src/authorization/PathBasedAuthorizer.ts @@ -1,7 +1,7 @@ import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError'; import { ensureTrailingSlash, trimTrailingSlashes } from '../util/PathUtil'; import type { Authorization } from './Authorization'; -import type { AuthorizerArgs } from './Authorizer'; +import type { AuthorizerInput } from './Authorizer'; import { Authorizer } from './Authorizer'; /** @@ -23,12 +23,12 @@ export class PathBasedAuthorizer extends Authorizer { this.paths = new Map(entries); } - public async canHandle(input: AuthorizerArgs): Promise { + public async canHandle(input: AuthorizerInput): Promise { const authorizer = this.findAuthorizer(input.identifier.path); await authorizer.canHandle(input); } - public async handle(input: AuthorizerArgs): Promise { + public async handle(input: AuthorizerInput): Promise { const authorizer = this.findAuthorizer(input.identifier.path); return authorizer.handle(input); } diff --git a/src/authorization/WebAclAuthorizer.ts b/src/authorization/WebAclAuthorizer.ts index 747decd45..369c78e00 100644 --- a/src/authorization/WebAclAuthorizer.ts +++ b/src/authorization/WebAclAuthorizer.ts @@ -1,6 +1,6 @@ import type { Quad, Term } from 'n3'; import { Store } from 'n3'; -import type { Credentials } from '../authentication/Credentials'; +import type { Credential, CredentialSet } from '../authentication/Credentials'; import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy'; import type { PermissionSet } from '../ldp/permissions/PermissionSet'; import type { Representation } from '../ldp/representation/Representation'; @@ -18,7 +18,7 @@ import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy' import { readableToQuads } from '../util/StreamUtil'; import { ACL, RDF } from '../util/Vocabularies'; import type { AccessChecker } from './access-checkers/AccessChecker'; -import type { AuthorizerArgs } from './Authorizer'; +import type { AuthorizerInput } from './Authorizer'; import { Authorizer } from './Authorizer'; import { WebAclAuthorization } from './WebAclAuthorization'; @@ -50,7 +50,7 @@ export class WebAclAuthorizer extends Authorizer { this.accessChecker = accessChecker; } - public async canHandle({ identifier }: AuthorizerArgs): Promise { + public async canHandle({ identifier }: AuthorizerInput): Promise { if (this.aclStrategy.isAuxiliaryIdentifier(identifier)) { throw new NotImplementedHttpError('WebAclAuthorizer does not support permissions on auxiliary resources.'); } @@ -61,20 +61,22 @@ export class WebAclAuthorizer extends Authorizer { * Will throw an error if this is not the case. * @param input - Relevant data needed to check if access can be granted. */ - public async handle({ identifier, permissions, credentials }: AuthorizerArgs): Promise { + public async handle({ identifier, permissions, credentials }: AuthorizerInput): + Promise { // Determine the required access modes const modes = (Object.keys(permissions) as (keyof PermissionSet)[]).filter((key): boolean => permissions[key]); - this.logger.debug(`Checking if ${credentials.webId} has ${modes.join()} permissions for ${identifier.path}`); + this.logger.debug(`Checking if ${credentials.agent?.webId} has ${modes.join()} permissions for ${identifier.path}`); // Determine the full authorization for the agent granted by the applicable ACL const acl = await this.getAclRecursive(identifier); const authorization = await this.createAuthorization(credentials, acl); // Verify that the authorization allows all required modes + const agent = credentials.agent ?? credentials.public ?? {}; for (const mode of modes) { - this.requirePermission(credentials, authorization, mode); + this.requirePermission(agent, authorization, mode); } - this.logger.debug(`${credentials.webId} has ${modes.join()} permissions for ${identifier.path}`); + this.logger.debug(`${agent.webId} has ${modes.join()} permissions for ${identifier.path}`); return authorization; } @@ -82,34 +84,45 @@ export class WebAclAuthorizer extends Authorizer { * Checks whether the agent is authenticated (logged in) or not (public/anonymous). * @param agent - Agent whose credentials will be checked. */ - private isAuthenticated(agent: Credentials): agent is ({ webId: string }) { + private isAuthenticated(agent: Credential): agent is ({ webId: string }) { return typeof agent.webId === 'string'; } /** * Creates an Authorization object based on the quads found in the ACL. - * @param agent - Agent whose credentials will be used for the `user` field. + * @param credentials - Credentials to check permissions for. * @param acl - Store containing all relevant authorization triples. */ - private async createAuthorization(agent: Credentials, acl: Store): Promise { - const publicPermissions = await this.determinePermissions({}, acl); - const agentPermissions = await this.determinePermissions(agent, acl); + private async createAuthorization(credentials: CredentialSet, acl: Store): + Promise { + const publicPermissions = await this.determinePermissions(acl, credentials.public); + const agentPermissions = await this.determinePermissions(acl, credentials.agent); + + // Agent at least has the public permissions + // This can be relevant when no agent is provided + for (const [ key, value ] of Object.entries(agentPermissions) as [keyof PermissionSet, boolean][]) { + agentPermissions[key] = value || publicPermissions[key]; + } return new WebAclAuthorization(agentPermissions, publicPermissions); } /** * Determines the available permissions for the given credentials. - * @param credentials - Credentials to find the permissions for. + * Will deny all permissions if credentials are not defined * @param acl - Store containing all relevant authorization triples. + * @param credentials - Credentials to find the permissions for. */ - private async determinePermissions(credentials: Credentials, acl: Store): Promise { + private async determinePermissions(acl: Store, credentials?: Credential): Promise { const permissions = { read: false, write: false, append: false, control: false, }; + if (!credentials) { + return permissions; + } // Apply all ACL rules const aclRules = acl.getSubjects(RDF.type, ACL.Authorization, null); @@ -142,7 +155,7 @@ export class WebAclAuthorizer extends Authorizer { * @param authorization - An Authorization containing the permissions the agent has on the resource. * @param mode - Which mode is requested. */ - private requirePermission(agent: Credentials, authorization: WebAclAuthorization, mode: keyof PermissionSet): void { + private requirePermission(agent: Credential, authorization: WebAclAuthorization, mode: keyof PermissionSet): void { if (!authorization.user[mode]) { if (this.isAuthenticated(agent)) { this.logger.warn(`Agent ${agent.webId} has no ${mode} permissions`); diff --git a/src/authorization/access-checkers/AccessChecker.ts b/src/authorization/access-checkers/AccessChecker.ts index 30186b785..f9a5a314f 100644 --- a/src/authorization/access-checkers/AccessChecker.ts +++ b/src/authorization/access-checkers/AccessChecker.ts @@ -1,5 +1,5 @@ import type { Store, Term } from 'n3'; -import type { Credentials } from '../../authentication/Credentials'; +import type { Credential } from '../../authentication/Credentials'; import { AsyncHandler } from '../../util/handlers/AsyncHandler'; /** @@ -21,5 +21,5 @@ export interface AccessCheckerArgs { /** * Credentials of the entity that wants to use the resource. */ - credentials: Credentials; + credentials: Credential; } diff --git a/src/ldp/AuthenticatedLdpHandler.ts b/src/ldp/AuthenticatedLdpHandler.ts index 0c594b9e2..70f723882 100644 --- a/src/ldp/AuthenticatedLdpHandler.ts +++ b/src/ldp/AuthenticatedLdpHandler.ts @@ -1,4 +1,4 @@ -import type { Credentials } from '../authentication/Credentials'; +import type { CredentialSet } from '../authentication/Credentials'; import type { CredentialsExtractor } from '../authentication/CredentialsExtractor'; import type { Authorizer } from '../authorization/Authorizer'; import { BaseHttpHandler } from '../server/BaseHttpHandler'; @@ -78,8 +78,8 @@ export class AuthenticatedLdpHandler extends BaseHttpHandler { * - Executing the operation. */ protected async handleOperation(operation: Operation, request: HttpRequest): Promise { - const credentials: Credentials = await this.credentialsExtractor.handleSafe(request); - this.logger.verbose(`Extracted credentials: ${credentials.webId}`); + const credentials: CredentialSet = await this.credentialsExtractor.handleSafe(request); + this.logger.verbose(`Extracted credentials: ${JSON.stringify(credentials)}`); const permissions: PermissionSet = await this.permissionsExtractor.handleSafe(operation); const { read, write, append } = permissions; diff --git a/test/unit/authentication/BearerWebIdExtractor.test.ts b/test/unit/authentication/BearerWebIdExtractor.test.ts index 3a434261d..39dfcbe72 100644 --- a/test/unit/authentication/BearerWebIdExtractor.test.ts +++ b/test/unit/authentication/BearerWebIdExtractor.test.ts @@ -1,5 +1,6 @@ import { createSolidTokenVerifier } from '@solid/access-token-verifier'; 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'; @@ -57,7 +58,7 @@ describe('A BearerWebIdExtractor', (): void => { it('returns the extracted WebID.', async(): Promise => { const result = webIdExtractor.handleSafe(request); - await expect(result).resolves.toEqual({ webId: 'http://alice.example/card#me' }); + await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }}); }); }); diff --git a/test/unit/authentication/DPoPWebIdExtractor.test.ts b/test/unit/authentication/DPoPWebIdExtractor.test.ts index d9cfb097e..37688987b 100644 --- a/test/unit/authentication/DPoPWebIdExtractor.test.ts +++ b/test/unit/authentication/DPoPWebIdExtractor.test.ts @@ -1,4 +1,5 @@ import { createSolidTokenVerifier } from '@solid/access-token-verifier'; +import { CredentialGroup } from '../../../src/authentication/Credentials'; import { DPoPWebIdExtractor } from '../../../src/authentication/DPoPWebIdExtractor'; import type { HttpRequest } from '../../../src/server/HttpRequest'; import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError'; @@ -85,7 +86,7 @@ describe('A DPoPWebIdExtractor', (): void => { it('returns the extracted WebID.', async(): Promise => { const result = webIdExtractor.handleSafe(request); - await expect(result).resolves.toEqual({ webId: 'http://alice.example/card#me' }); + await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }}); }); }); diff --git a/test/unit/authentication/EmptyCredentialsExtractor.test.ts b/test/unit/authentication/EmptyCredentialsExtractor.test.ts index aa5fc1833..61f1bc731 100644 --- a/test/unit/authentication/EmptyCredentialsExtractor.test.ts +++ b/test/unit/authentication/EmptyCredentialsExtractor.test.ts @@ -1,3 +1,4 @@ +import { CredentialGroup } from '../../../src/authentication/Credentials'; import { EmptyCredentialsExtractor } from '../../../src/authentication/EmptyCredentialsExtractor'; import type { HttpRequest } from '../../../src/server/HttpRequest'; import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; @@ -15,6 +16,6 @@ describe('An EmptyCredentialsExtractor', (): void => { it('returns the empty credentials.', async(): Promise => { const headers = {}; const result = extractor.handleSafe({ headers } as HttpRequest); - await expect(result).resolves.toEqual({}); + await expect(result).resolves.toEqual({ [CredentialGroup.public]: {}}); }); }); diff --git a/test/unit/authentication/UnsecureConstantCredentialsExtractor.test.ts b/test/unit/authentication/UnsecureConstantCredentialsExtractor.test.ts index 67e20e08a..89d5c1ba8 100644 --- a/test/unit/authentication/UnsecureConstantCredentialsExtractor.test.ts +++ b/test/unit/authentication/UnsecureConstantCredentialsExtractor.test.ts @@ -1,15 +1,16 @@ +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({ webId: agent }); + await expect(extractor.handle()).resolves.toEqual({ [CredentialGroup.agent]: { webId: agent }}); }); it('extracts constant credentials.', async(): Promise => { const agent = {}; const extractor = new UnsecureConstantCredentialsExtractor(agent); - await expect(extractor.handle()).resolves.toBe(agent); + await expect(extractor.handle()).resolves.toEqual({ [CredentialGroup.agent]: agent }); }); }); diff --git a/test/unit/authentication/UnsecureWebIdExtractor.test.ts b/test/unit/authentication/UnsecureWebIdExtractor.test.ts index 8fd249b54..949bd53bf 100644 --- a/test/unit/authentication/UnsecureWebIdExtractor.test.ts +++ b/test/unit/authentication/UnsecureWebIdExtractor.test.ts @@ -1,3 +1,4 @@ +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'; @@ -22,6 +23,6 @@ describe('An UnsecureWebIdExtractor', (): void => { it('returns the authorization header as WebID if there is one.', async(): Promise => { const headers = { authorization: 'WebID http://alice.example/card#me' }; const result = extractor.handleSafe({ headers } as HttpRequest); - await expect(result).resolves.toEqual({ webId: 'http://alice.example/card#me' }); + await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }}); }); }); diff --git a/test/unit/authorization/PathBasedAuthorizer.test.ts b/test/unit/authorization/PathBasedAuthorizer.test.ts index ecc773f29..d9a7b3348 100644 --- a/test/unit/authorization/PathBasedAuthorizer.test.ts +++ b/test/unit/authorization/PathBasedAuthorizer.test.ts @@ -1,10 +1,10 @@ -import type { Authorizer, AuthorizerArgs } from '../../../src/authorization/Authorizer'; +import type { Authorizer, AuthorizerInput } from '../../../src/authorization/Authorizer'; import { PathBasedAuthorizer } from '../../../src/authorization/PathBasedAuthorizer'; import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError'; describe('A PathBasedAuthorizer', (): void => { const baseUrl = 'http://test.com/foo/'; - let input: AuthorizerArgs; + let input: AuthorizerInput; let authorizers: jest.Mocked[]; let authorizer: PathBasedAuthorizer; @@ -12,7 +12,7 @@ describe('A PathBasedAuthorizer', (): void => { input = { identifier: { path: `${baseUrl}first` }, permissions: { read: true, append: false, write: false, control: false }, - credentials: { webId: 'http://alice.test.com/card#me' }, + credentials: {}, }; authorizers = [ diff --git a/test/unit/authorization/WebAclAuthorizer.test.ts b/test/unit/authorization/WebAclAuthorizer.test.ts index c72468aca..177ba1f88 100644 --- a/test/unit/authorization/WebAclAuthorizer.test.ts +++ b/test/unit/authorization/WebAclAuthorizer.test.ts @@ -1,5 +1,6 @@ import { namedNode, quad } from '@rdfjs/data-model'; -import type { Credentials } from '../../../src/authentication/Credentials'; +import { CredentialGroup } from '../../../src/authentication/Credentials'; +import type { CredentialSet } from '../../../src/authentication/Credentials'; import type { AccessChecker } from '../../../src/authorization/access-checkers/AccessChecker'; import { WebAclAuthorization } from '../../../src/authorization/WebAclAuthorization'; import { WebAclAuthorizer } from '../../../src/authorization/WebAclAuthorizer'; @@ -31,7 +32,7 @@ describe('A WebAclAuthorizer', (): void => { let store: jest.Mocked; const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/'); let permissions: PermissionSet; - let credentials: Credentials; + let credentials: CredentialSet; let identifier: ResourceIdentifier; let authorization: WebAclAuthorization; let accessChecker: jest.Mocked; @@ -43,7 +44,7 @@ describe('A WebAclAuthorizer', (): void => { write: true, control: false, }; - credentials = {}; + credentials = { [CredentialGroup.public]: {}, [CredentialGroup.agent]: {}}; identifier = { path: 'http://test.com/foo' }; authorization = new WebAclAuthorization( { @@ -72,14 +73,13 @@ describe('A WebAclAuthorizer', (): void => { }); it('handles all non-acl inputs.', async(): Promise => { - authorizer = new WebAclAuthorizer(aclStrategy, null as any, identifierStrategy, accessChecker); - await expect(authorizer.canHandle({ identifier } as any)).resolves.toBeUndefined(); + await expect(authorizer.canHandle({ identifier, credentials, permissions })).resolves.toBeUndefined(); await expect(authorizer.canHandle({ identifier: aclStrategy.getAuxiliaryIdentifier(identifier) } as any)) .rejects.toThrow(NotImplementedHttpError); }); it('handles all valid modes and ignores other ones.', async(): Promise => { - credentials.webId = 'http://test.com/user'; + credentials.agent = { webId: 'http://test.com/user' }; store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([ quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), @@ -131,11 +131,12 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), ]) } as Representation); - credentials.webId = 'http://test.com/alice/profile/card#me'; + credentials.agent = { webId: 'http://test.com/alice/profile/card#me' }; await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError); }); it('throws an UnauthorizedHttpError if access is not granted there are no credentials.', async(): Promise => { + credentials = {}; accessChecker.handleSafe.mockResolvedValue(false); store.getRepresentation.mockResolvedValue({ data: guardedStreamFrom([ quad(nn('auth'), nn(`${rdf}type`), nn(`${acl}Authorization`)), diff --git a/test/unit/ldp/AuthenticatedLdpHandler.test.ts b/test/unit/ldp/AuthenticatedLdpHandler.test.ts index 05b63782e..6a4ab6db0 100644 --- a/test/unit/ldp/AuthenticatedLdpHandler.test.ts +++ b/test/unit/ldp/AuthenticatedLdpHandler.test.ts @@ -1,4 +1,4 @@ -import type { Credentials } from '../../../src/authentication/Credentials'; +import type { CredentialSet } from '../../../src/authentication/Credentials'; import type { Authorization } from '../../../src/authorization/Authorization'; import type { AuthenticatedLdpHandlerArgs } from '../../../src/ldp/AuthenticatedLdpHandler'; import { AuthenticatedLdpHandler } from '../../../src/ldp/AuthenticatedLdpHandler'; @@ -16,7 +16,7 @@ describe('An AuthenticatedLdpHandler', (): void => { const response: HttpResponse = {} as any; const preferences: RepresentationPreferences = { type: { 'text/turtle': 0.9 }}; let operation: Operation; - const credentials: Credentials = {}; + const credentials: CredentialSet = {}; const permissions: PermissionSet = { read: true, write: false, append: false, control: false }; const authorization: Authorization = { addMetadata: jest.fn() }; const result: ResponseDescription = new ResetResponseDescription();