From 758f5ed083e310937422b84cdf1a9a1e48b26c7b Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Mon, 25 Jan 2021 17:13:48 +0100 Subject: [PATCH] feat: Generalize AclManager to AuxiliaryManager --- config/presets/acl.json | 28 +++++++++++-- config/presets/init.json | 4 +- config/presets/ldp/response-writer.json | 4 +- config/presets/representation-conversion.json | 12 +++++- src/authorization/AclManager.ts | 41 ------------------- src/authorization/UrlBasedAclManager.ts | 26 ------------ src/authorization/WebAclAuthorizer.ts | 16 ++++---- src/index.ts | 2 - src/init/AclInitializer.ts | 10 ++--- src/ldp/auxiliary/AuxiliaryStrategy.ts | 5 ++- .../auxiliary/ComposedAuxiliaryStrategy.ts | 10 ++--- src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts | 4 +- .../SuffixAuxiliaryIdentifierStrategy.ts | 4 -- .../http/metadata/AclLinkMetadataWriter.ts | 10 ++--- .../config/server-without-auth.json | 6 +-- .../authorization/UrlBasedAclManager.test.ts | 34 --------------- .../authorization/WebAclAuthorizer.test.ts | 22 +++++----- test/unit/init/AclInitializer.test.ts | 20 ++++----- .../ComposedAuxiliaryStrategy.test.ts | 8 ++-- .../RoutingAuxiliaryStrategy.test.ts | 16 ++++---- .../SuffixAuxiliaryIdentifierStrategy.test.ts | 4 -- .../metadata/AclLinkMetadataWriter.test.ts | 12 +++--- 22 files changed, 106 insertions(+), 192 deletions(-) delete mode 100644 src/authorization/AclManager.ts delete mode 100644 src/authorization/UrlBasedAclManager.ts delete mode 100644 test/unit/authorization/UrlBasedAclManager.test.ts diff --git a/config/presets/acl.json b/config/presets/acl.json index 80f03a32c..5776c40f3 100644 --- a/config/presets/acl.json +++ b/config/presets/acl.json @@ -2,14 +2,34 @@ "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@graph": [ { - "@id": "urn:solid-server:default:AclManager", - "@type": "UrlBasedAclManager" + "@id": "urn:solid-server:default:AclIdentifierStrategy", + "@type": "SuffixAuxiliaryIdentifierStrategy", + "SuffixAuxiliaryIdentifierStrategy:_suffix": ".acl" }, + { + "@id": "urn:solid-server:default:AclStrategy", + "@type": "ComposedAuxiliaryStrategy", + "ComposedAuxiliaryStrategy:_identifierStrategy": { + "@id": "urn:solid-server:default:AclIdentifierStrategy" + }, + "ComposedAuxiliaryStrategy:_metadataGenerator": { + "@type": "LinkMetadataGenerator", + "LinkMetadataGenerator:_link": "http://www.w3.org/ns/auth/acl#accessControl", + "LinkMetadataGenerator:_identifierStrategy": { + "@id": "urn:solid-server:default:AclIdentifierStrategy" + } + }, + "ComposedAuxiliaryStrategy:_validator": { + "@id": "urn:solid-server:default:RdfValidator" + }, + "ComposedAuxiliaryStrategy:_isRootRequired": true + }, + { "@id": "urn:solid-server:default:AclAuthorizer", "@type": "WebAclAuthorizer", - "WebAclAuthorizer:_aclManager": { - "@id": "urn:solid-server:default:AclManager" + "WebAclAuthorizer:_aclStrategy": { + "@id": "urn:solid-server:default:AclIdentifierStrategy" }, "WebAclAuthorizer:_resourceStore": { "@id": "urn:solid-server:default:ResourceStore" diff --git a/config/presets/init.json b/config/presets/init.json index afd1f0b11..e239ed3af 100644 --- a/config/presets/init.json +++ b/config/presets/init.json @@ -28,8 +28,8 @@ "AclInitializer:_settings_store": { "@id": "urn:solid-server:default:ResourceStore" }, - "AclInitializer:_settings_aclManager": { - "@id": "urn:solid-server:default:AclManager" + "AclInitializer:_settings_aclStrategy": { + "@id": "urn:solid-server:default:AclIdentifierStrategy" }, "AclInitializer:_settings_baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" diff --git a/config/presets/ldp/response-writer.json b/config/presets/ldp/response-writer.json index 17ba44c49..f4465d64d 100644 --- a/config/presets/ldp/response-writer.json +++ b/config/presets/ldp/response-writer.json @@ -42,8 +42,8 @@ }, { "@type": "AclLinkMetadataWriter", - "AclLinkMetadataWriter:_aclManager": { - "@id": "urn:solid-server:default:AclManager" + "AclLinkMetadataWriter:_aclStrategy": { + "@id": "urn:solid-server:default:AclIdentifierStrategy" } } ] diff --git a/config/presets/representation-conversion.json b/config/presets/representation-conversion.json index 4e765487a..5c7d97677 100644 --- a/config/presets/representation-conversion.json +++ b/config/presets/representation-conversion.json @@ -82,11 +82,11 @@ "@type": "WaterfallHandler", "WaterfallHandler:_handlers": [ { - "@id": "urn:solid-server:default:IndexConverter", + "@id": "urn:solid-server:default:IndexConverter" }, { "@type": "IfNeededConverter", - "comment": "Only continue converting if the requester cannot accept the available content type", + "comment": "Only continue converting if the requester cannot accept the available content type" }, { "@id": "urn:solid-server:default:ContentTypeReplacer" @@ -101,6 +101,14 @@ "@id": "urn:solid-server:default:RdfRepresentationConverter" } ] + }, + + { + "@id": "urn:solid-server:default:RdfValidator", + "@type": "RdfValidator", + "RdfValidator:_converter": { + "@id": "urn:solid-server:default:RepresentationConverter" + } } ] } diff --git a/src/authorization/AclManager.ts b/src/authorization/AclManager.ts deleted file mode 100644 index 7947a2aac..000000000 --- a/src/authorization/AclManager.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; - -/** - * Handles where acl resources are stored. - * - * Solid, §4.3.3: "A given Solid resource MUST NOT be directly associated with more than one ACL auxiliary resource. - * A given ACL auxiliary resource MUST NOT be directly associated with more than one Solid resource." - * https://solid.github.io/specification/protocol#auxiliary-resources-reserved - */ -export interface AclManager { - /** - * Returns the identifier of the acl resource corresponding to the given resource. - * This does not guarantee that this acl resource exists. - * In the case the input is already an acl resource that will also be the response. - * @param id - The ResourceIdentifier of which we need the corresponding acl resource. - * - * @returns The ResourceIdentifier of the corresponding acl resource. - */ - getAclDocument: (id: ResourceIdentifier) => Promise; - - /** - * Checks if the input identifier corresponds to an acl resource. - * This does not check if that acl resource exists, - * only if the identifier indicates that there could be an acl resource there. - * @param id - Identifier to check. - * - * @returns true if the input identifier points to an acl resource. - */ - isAclDocument: (id: ResourceIdentifier) => Promise; - - /** - * Returns the identifier of the resource on which the acl constraints are placed. - * In general, this is the resource identifier when the input is a normal resource, - * or the non-acl version if the input is an acl resource. - * This does not guarantee that this resource exists. - * @param aclId - Identifier of the acl resource. - * - * @returns The ResourceIdentifier of the corresponding resource. - */ - getAclConstrainedResource: (id: ResourceIdentifier) => Promise; -} diff --git a/src/authorization/UrlBasedAclManager.ts b/src/authorization/UrlBasedAclManager.ts deleted file mode 100644 index 17bb133f1..000000000 --- a/src/authorization/UrlBasedAclManager.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; -import type { AclManager } from './AclManager'; - -/** - * Generates acl URIs by adding an .acl file extension. - * - * Needs to be updated according to issue #113. - */ -export class UrlBasedAclManager implements AclManager { - public async getAclDocument(id: ResourceIdentifier): Promise { - return await this.isAclDocument(id) ? id : { path: `${id.path}.acl` }; - } - - public async isAclDocument(id: ResourceIdentifier): Promise { - return /\.acl\/?/u.test(id.path); - } - - public async getAclConstrainedResource(id: ResourceIdentifier): Promise { - if (!await this.isAclDocument(id)) { - return id; - } - - // Slice off `.acl` - return { path: id.path.slice(0, -4) }; - } -} diff --git a/src/authorization/WebAclAuthorizer.ts b/src/authorization/WebAclAuthorizer.ts index fca773647..fc232ac9e 100644 --- a/src/authorization/WebAclAuthorizer.ts +++ b/src/authorization/WebAclAuthorizer.ts @@ -1,6 +1,7 @@ import type { Quad, Term } from 'n3'; import { Store } from 'n3'; import type { Credentials } from '../authentication/Credentials'; +import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy'; import type { PermissionSet } from '../ldp/permissions/PermissionSet'; import type { Representation } from '../ldp/representation/Representation'; import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; @@ -12,7 +13,6 @@ import { NotFoundHttpError } from '../util/errors/NotFoundHttpError'; import { UnauthorizedHttpError } from '../util/errors/UnauthorizedHttpError'; import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy'; import { ACL, FOAF } from '../util/Vocabularies'; -import type { AclManager } from './AclManager'; import type { AuthorizerArgs } from './Authorizer'; import { Authorizer } from './Authorizer'; @@ -24,13 +24,14 @@ import { Authorizer } from './Authorizer'; export class WebAclAuthorizer extends Authorizer { protected readonly logger = getLoggerFor(this); - private readonly aclManager: AclManager; + private readonly aclStrategy: AuxiliaryIdentifierStrategy; private readonly resourceStore: ResourceStore; private readonly identifierStrategy: IdentifierStrategy; - public constructor(aclManager: AclManager, resourceStore: ResourceStore, identifierStrategy: IdentifierStrategy) { + public constructor(aclStrategy: AuxiliaryIdentifierStrategy, resourceStore: ResourceStore, + identifierStrategy: IdentifierStrategy) { super(); - this.aclManager = aclManager; + this.aclStrategy = aclStrategy; this.resourceStore = resourceStore; this.identifierStrategy = identifierStrategy; } @@ -44,7 +45,7 @@ export class WebAclAuthorizer extends Authorizer { // Solid, §4.3.3: "To discover, read, create, or modify an ACL auxiliary resource, an acl:agent MUST // have acl:Control privileges per the ACL inheritance algorithm on the resource directly associated with it." // https://solid.github.io/specification/protocol#auxiliary-resources-reserved - const modes = await this.aclManager.isAclDocument(identifier) ? + const modes = this.aclStrategy.isAuxiliaryIdentifier(identifier) ? [ 'control' ] : (Object.keys(permissions) as (keyof PermissionSet)[]).filter((key): boolean => permissions[key]); @@ -141,12 +142,13 @@ export class WebAclAuthorizer extends Authorizer { private async getAclRecursive(id: ResourceIdentifier, recurse?: boolean): Promise { this.logger.debug(`Trying to read the direct ACL document of ${id.path}`); try { - const acl = await this.aclManager.getAclDocument(id); + const isAcl = this.aclStrategy.isAuxiliaryIdentifier(id); + const acl = isAcl ? id : this.aclStrategy.getAuxiliaryIdentifier(id); this.logger.debug(`Trying to read the ACL document ${acl.path}`); const data = await this.resourceStore.getRepresentation(acl, { type: { [INTERNAL_QUADS]: 1 }}); this.logger.info(`Reading ACL statements from ${acl.path}`); - const resourceId = await this.aclManager.getAclConstrainedResource(id); + const resourceId = isAcl ? this.aclStrategy.getAssociatedIdentifier(id) : id; return this.filterData(data, recurse ? ACL.default : ACL.accessTo, resourceId.path); } catch (error: unknown) { if (NotFoundHttpError.isInstance(error)) { diff --git a/src/index.ts b/src/index.ts index c148b9887..6f4eb8dda 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,9 +9,7 @@ export * from './authentication/UnsecureWebIdExtractor'; // Authorization export * from './authorization/AllowEverythingAuthorizer'; -export * from './authorization/AclManager'; export * from './authorization/Authorizer'; -export * from './authorization/UrlBasedAclManager'; export * from './authorization/WebAclAuthorizer'; // Init diff --git a/src/init/AclInitializer.ts b/src/init/AclInitializer.ts index f7e55fdf1..81d836320 100644 --- a/src/init/AclInitializer.ts +++ b/src/init/AclInitializer.ts @@ -1,5 +1,5 @@ import { createReadStream } from 'fs'; -import type { AclManager } from '../authorization/AclManager'; +import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy'; import { BasicRepresentation } from '../ldp/representation/BasicRepresentation'; import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier'; import { getLoggerFor } from '../logging/LogUtil'; @@ -17,19 +17,19 @@ const DEFAULT_ACL_PATH = joinFilePath(__dirname, '../../templates/root/.acl'); export class AclInitializer extends Initializer { protected readonly logger = getLoggerFor(this); private readonly store: ResourceStore; - private readonly aclManager: AclManager; + private readonly aclStrategy: AuxiliaryIdentifierStrategy; private readonly root: ResourceIdentifier; private readonly aclPath: string; public constructor(settings: { store: ResourceStore; - aclManager: AclManager; + aclStrategy: AuxiliaryIdentifierStrategy; baseUrl: string; aclPath?: string; }) { super(); this.store = settings.store; - this.aclManager = settings.aclManager; + this.aclStrategy = settings.aclStrategy; this.root = { path: ensureTrailingSlash(settings.baseUrl) }; this.aclPath = settings.aclPath ?? DEFAULT_ACL_PATH; } @@ -38,7 +38,7 @@ export class AclInitializer extends Initializer { // The associated ACL document MUST include an authorization policy with acl:Control access privilege." // https://solid.github.io/specification/protocol#storage public async handle(): Promise { - const rootAcl = await this.aclManager.getAclDocument(this.root); + const rootAcl = this.aclStrategy.getAuxiliaryIdentifier(this.root); if (await containsResource(this.store, rootAcl)) { this.logger.debug(`Existing root ACL document found at ${rootAcl.path}`); } else { diff --git a/src/ldp/auxiliary/AuxiliaryStrategy.ts b/src/ldp/auxiliary/AuxiliaryStrategy.ts index 8b9a54460..1b2460703 100644 --- a/src/ldp/auxiliary/AuxiliaryStrategy.ts +++ b/src/ldp/auxiliary/AuxiliaryStrategy.ts @@ -10,10 +10,11 @@ import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy' */ export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy { /** - * Whether this auxiliary resource can be deleted when it's in a root storage container. + * Whether this auxiliary resource in a root storage container. + * If yes, this means they can't be deleted individually from such a container. * @param identifier - Identifier of the auxiliary resource. */ - requiresRootAuxiliary: (identifier: ResourceIdentifier) => boolean; + isRootRequired: (identifier: ResourceIdentifier) => boolean; /** * Adds metadata related to this auxiliary resource, diff --git a/src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts b/src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts index 4c2bbc563..4f1196b31 100644 --- a/src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts +++ b/src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts @@ -14,14 +14,14 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy { private readonly identifierStrategy: AuxiliaryIdentifierStrategy; private readonly metadataGenerator?: MetadataGenerator; private readonly validator?: Validator; - private readonly canDelete: boolean; + private readonly rootRequired: boolean; public constructor(identifierStrategy: AuxiliaryIdentifierStrategy, metadataGenerator?: MetadataGenerator, - validator?: Validator, canDeleteRoot = false) { + validator?: Validator, isRootRequired = false) { this.identifierStrategy = identifierStrategy; this.metadataGenerator = metadataGenerator; this.validator = validator; - this.canDelete = canDeleteRoot; + this.rootRequired = isRootRequired; } public getAuxiliaryIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { @@ -40,8 +40,8 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy { return this.identifierStrategy.getAssociatedIdentifier(identifier); } - public requiresRootAuxiliary(): boolean { - return this.canDelete; + public isRootRequired(): boolean { + return this.rootRequired; } public async addMetadata(metadata: RepresentationMetadata): Promise { diff --git a/src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts b/src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts index b358b683f..36047bcf0 100644 --- a/src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts +++ b/src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts @@ -18,9 +18,9 @@ export class RoutingAuxiliaryStrategy extends RoutingAuxiliaryIdentifierStrategy super(sources); } - public requiresRootAuxiliary(identifier: ResourceIdentifier): boolean { + public isRootRequired(identifier: ResourceIdentifier): boolean { const source = this.getMatchingSource(identifier); - return source.requiresRootAuxiliary(identifier); + return source.isRootRequired(identifier); } public async addMetadata(metadata: RepresentationMetadata): Promise { diff --git a/src/ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy.ts b/src/ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy.ts index de7812d33..8aeb7cb2f 100644 --- a/src/ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy.ts +++ b/src/ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy.ts @@ -34,8 +34,4 @@ export class SuffixAuxiliaryIdentifierStrategy implements AuxiliaryIdentifierStr } return { path: identifier.path.slice(0, -this.suffix.length) }; } - - public canDeleteRoot(): boolean { - return true; - } } diff --git a/src/ldp/http/metadata/AclLinkMetadataWriter.ts b/src/ldp/http/metadata/AclLinkMetadataWriter.ts index 2d9bfba43..80562411a 100644 --- a/src/ldp/http/metadata/AclLinkMetadataWriter.ts +++ b/src/ldp/http/metadata/AclLinkMetadataWriter.ts @@ -1,6 +1,6 @@ -import type { AclManager } from '../../../authorization/AclManager'; import type { HttpResponse } from '../../../server/HttpResponse'; import { addHeader } from '../../../util/HeaderUtil'; +import type { AuxiliaryIdentifierStrategy } from '../../auxiliary/AuxiliaryIdentifierStrategy'; import type { RepresentationMetadata } from '../../representation/RepresentationMetadata'; import { MetadataWriter } from './MetadataWriter'; @@ -9,17 +9,17 @@ import { MetadataWriter } from './MetadataWriter'; * The `rel` parameter can be used if a different `rel` value is needed (such as http://www.w3.org/ns/solid/terms#acl). */ export class AclLinkMetadataWriter extends MetadataWriter { - private readonly aclManager: AclManager; + private readonly aclStrategy: AuxiliaryIdentifierStrategy; private readonly rel: string; - public constructor(aclManager: AclManager, rel = 'acl') { + public constructor(aclStrategy: AuxiliaryIdentifierStrategy, rel = 'acl') { super(); - this.aclManager = aclManager; + this.aclStrategy = aclStrategy; this.rel = rel; } public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise { - const identifier = await this.aclManager.getAclDocument({ path: input.metadata.identifier.value }); + const identifier = this.aclStrategy.getAuxiliaryIdentifier({ path: input.metadata.identifier.value }); addHeader(input.response, 'Link', `<${identifier.path}>; rel="${this.rel}"`); } } diff --git a/test/integration/config/server-without-auth.json b/test/integration/config/server-without-auth.json index 6e167c41b..1f09e5e41 100644 --- a/test/integration/config/server-without-auth.json +++ b/test/integration/config/server-without-auth.json @@ -1,6 +1,7 @@ { "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "import": [ + "files-scs:config/presets/acl.json", "files-scs:config/presets/http.json", "files-scs:config/presets/ldp/credentials-extractor.json", "files-scs:config/presets/ldp/metadata-handler.json", @@ -46,11 +47,6 @@ "PassthroughStore:_source": { "@id": "urn:solid-server:default:MemoryResourceStore" } - }, - { - "@id": "urn:solid-server:default:AclManager", - "@type": "UrlBasedAclManager", - "comment": "Needed for AclLinkMetadataWriter" } ] } diff --git a/test/unit/authorization/UrlBasedAclManager.test.ts b/test/unit/authorization/UrlBasedAclManager.test.ts deleted file mode 100644 index 522579afb..000000000 --- a/test/unit/authorization/UrlBasedAclManager.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { UrlBasedAclManager } from '../../../src/authorization/UrlBasedAclManager'; - -describe('An UrlBasedAclManager', (): void => { - const manager = new UrlBasedAclManager(); - - describe('#getAcl', (): void => { - it('generates acl URLs by adding an .acl extension.', async(): Promise => { - await expect(manager.getAclDocument({ path: '/foo/bar' })).resolves.toEqual({ path: '/foo/bar.acl' }); - }); - - it('returns the identifier if the input is already an acl resource.', async(): Promise => { - await expect(manager.getAclDocument({ path: '/foo/bar.acl' })).resolves.toEqual({ path: '/foo/bar.acl' }); - }); - }); - - describe('#isAcl', (): void => { - it('checks if a resource is an acl resource by looking at the extension.', async(): Promise => { - await expect(manager.isAclDocument({ path: '/foo/bar' })).resolves.toBeFalsy(); - await expect(manager.isAclDocument({ path: '/foo/bar/' })).resolves.toBeFalsy(); - await expect(manager.isAclDocument({ path: '/foo/bar.acl' })).resolves.toBeTruthy(); - await expect(manager.isAclDocument({ path: '/foo/bar.acl/' })).resolves.toBeTruthy(); - }); - }); - - describe('#getResource', (): void => { - it('generates non-acl resource URLs by removing the .acl extension.', async(): Promise => { - await expect(manager.getAclConstrainedResource({ path: '/foo/bar.acl' })).resolves.toEqual({ path: '/foo/bar' }); - }); - - it('returns the identifier if the input is already a non-acl resource.', async(): Promise => { - await expect(manager.getAclConstrainedResource({ path: '/foo/bar' })).resolves.toEqual({ path: '/foo/bar' }); - }); - }); -}); diff --git a/test/unit/authorization/WebAclAuthorizer.test.ts b/test/unit/authorization/WebAclAuthorizer.test.ts index 912c4be25..f574ba2c4 100644 --- a/test/unit/authorization/WebAclAuthorizer.test.ts +++ b/test/unit/authorization/WebAclAuthorizer.test.ts @@ -1,8 +1,8 @@ import { namedNode, quad } from '@rdfjs/data-model'; import streamifyArray from 'streamify-array'; import type { Credentials } from '../../../src/authentication/Credentials'; -import type { AclManager } from '../../../src/authorization/AclManager'; import { WebAclAuthorizer } from '../../../src/authorization/WebAclAuthorizer'; +import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy'; import type { PermissionSet } from '../../../src/ldp/permissions/PermissionSet'; import type { Representation } from '../../../src/ldp/representation/Representation'; import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier'; @@ -18,13 +18,11 @@ const acl = 'http://www.w3.org/ns/auth/acl#'; describe('A WebAclAuthorizer', (): void => { let authorizer: WebAclAuthorizer; - const aclManager: AclManager = { - getAclDocument: async(id: ResourceIdentifier): Promise => - id.path.endsWith('.acl') ? id : { path: `${id.path}.acl` }, - isAclDocument: async(id: ResourceIdentifier): Promise => id.path.endsWith('.acl'), - getAclConstrainedResource: async(id: ResourceIdentifier): Promise => - !id.path.endsWith('.acl') ? id : { path: id.path.slice(0, -4) }, - }; + const aclStrategy: AuxiliaryIdentifierStrategy = { + getAuxiliaryIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: `${id.path}.acl` }), + isAuxiliaryIdentifier: (id: ResourceIdentifier): boolean => id.path.endsWith('.acl'), + getAssociatedIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: id.path.slice(0, -4) }), + } as any; let store: ResourceStore; const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/'); let permissions: PermissionSet; @@ -44,11 +42,11 @@ describe('A WebAclAuthorizer', (): void => { store = { getRepresentation: jest.fn(), } as any; - authorizer = new WebAclAuthorizer(aclManager, store, identifierStrategy); + authorizer = new WebAclAuthorizer(aclStrategy, store, identifierStrategy); }); it('handles all inputs.', async(): Promise => { - authorizer = new WebAclAuthorizer(aclManager, null as any, identifierStrategy); + authorizer = new WebAclAuthorizer(aclStrategy, null as any, identifierStrategy); await expect(authorizer.canHandle({} as any)).resolves.toBeUndefined(); }); @@ -130,7 +128,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Control`)), ]) } as Representation); - const aclIdentifier = await aclManager.getAclDocument(identifier); + const aclIdentifier = aclStrategy.getAuxiliaryIdentifier(identifier); await expect(authorizer.handle({ identifier: aclIdentifier, permissions, credentials })).resolves.toBeUndefined(); }); @@ -143,7 +141,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]) } as Representation); - identifier = await aclManager.getAclDocument(identifier); + identifier = aclStrategy.getAuxiliaryIdentifier(identifier); await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError); }); diff --git a/test/unit/init/AclInitializer.test.ts b/test/unit/init/AclInitializer.test.ts index 6554201b3..15f1d5707 100644 --- a/test/unit/init/AclInitializer.test.ts +++ b/test/unit/init/AclInitializer.test.ts @@ -1,6 +1,6 @@ import fs from 'fs'; -import type { AclManager } from '../../../src/authorization/AclManager'; import { AclInitializer } from '../../../src/init/AclInitializer'; +import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy'; import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation'; import type { ResourceStore } from '../../../src/storage/ResourceStore'; import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError'; @@ -19,8 +19,8 @@ describe('AclInitializer', (): void => { setRepresentation: jest.fn(), } as any; const aclIdentifier = { path: 'http://test.com/.acl' }; - const aclManager: jest.Mocked = { - getAclDocument: jest.fn().mockResolvedValue(aclIdentifier), + const aclStrategy: jest.Mocked = { + getAuxiliaryIdentifier: jest.fn().mockReturnValue(aclIdentifier), } as any; const baseUrl = 'http://localhost:3000/'; @@ -29,10 +29,10 @@ describe('AclInitializer', (): void => { }); it('sets the default ACL when none exists already.', async(): Promise => { - const initializer = new AclInitializer({ baseUrl, store, aclManager }); + const initializer = new AclInitializer({ baseUrl, store, aclStrategy }); await initializer.handle(); - expect(aclManager.getAclDocument).toHaveBeenCalledWith({ path: baseUrl }); + expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl }); expect(store.getRepresentation).toHaveBeenCalledTimes(1); expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {}); expect(store.setRepresentation).toHaveBeenCalledTimes(1); @@ -45,10 +45,10 @@ describe('AclInitializer', (): void => { }); it('sets the specific ACL when one was specified.', async(): Promise => { - const initializer = new AclInitializer({ baseUrl, store, aclManager, aclPath: '/path/doc.acl' }); + const initializer = new AclInitializer({ baseUrl, store, aclStrategy, aclPath: '/path/doc.acl' }); await initializer.handle(); - expect(aclManager.getAclDocument).toHaveBeenCalledWith({ path: baseUrl }); + expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl }); expect(store.getRepresentation).toHaveBeenCalledTimes(1); expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {}); expect(store.setRepresentation).toHaveBeenCalledTimes(1); @@ -65,10 +65,10 @@ describe('AclInitializer', (): void => { data: { destroy: jest.fn() }, } as any)); - const initializer = new AclInitializer({ baseUrl, store, aclManager }); + const initializer = new AclInitializer({ baseUrl, store, aclStrategy }); await initializer.handle(); - expect(aclManager.getAclDocument).toHaveBeenCalledWith({ path: baseUrl }); + expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl }); expect(store.getRepresentation).toHaveBeenCalledTimes(1); expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {}); expect(store.setRepresentation).toHaveBeenCalledTimes(0); @@ -77,7 +77,7 @@ describe('AclInitializer', (): void => { it('errors when the root ACL check errors.', async(): Promise => { store.getRepresentation.mockRejectedValueOnce(new Error('Fatal')); - const initializer = new AclInitializer({ baseUrl, store, aclManager }); + const initializer = new AclInitializer({ baseUrl, store, aclStrategy }); await expect(initializer.handle()).rejects.toThrow('Fatal'); }); }); diff --git a/test/unit/ldp/auxiliary/ComposedAuxiliaryStrategy.test.ts b/test/unit/ldp/auxiliary/ComposedAuxiliaryStrategy.test.ts index 61fed80eb..8bd24a5a0 100644 --- a/test/unit/ldp/auxiliary/ComposedAuxiliaryStrategy.test.ts +++ b/test/unit/ldp/auxiliary/ComposedAuxiliaryStrategy.test.ts @@ -45,8 +45,8 @@ describe('A ComposedAuxiliaryStrategy', (): void => { expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenLastCalledWith(identifier); }); - it('returns the injected value for canDeleteRoot.', async(): Promise => { - expect(strategy.requiresRootAuxiliary()).toBe(true); + it('returns the injected value for isRootRequired.', async(): Promise => { + expect(strategy.isRootRequired()).toBe(true); }); it('adds metadata through the MetadataGenerator.', async(): Promise => { @@ -63,9 +63,9 @@ describe('A ComposedAuxiliaryStrategy', (): void => { expect(validator.handleSafe).toHaveBeenLastCalledWith(representation); }); - it('defaults canDeleteRoot to false.', async(): Promise => { + it('defaults isRootRequired to false.', async(): Promise => { strategy = new ComposedAuxiliaryStrategy(identifierStrategy, metadataGenerator, validator); - expect(strategy.requiresRootAuxiliary()).toBe(false); + expect(strategy.isRootRequired()).toBe(false); }); it('does not add metadata or validate if the corresponding classes are not injected.', async(): Promise => { diff --git a/test/unit/ldp/auxiliary/RoutingAuxiliaryStrategy.test.ts b/test/unit/ldp/auxiliary/RoutingAuxiliaryStrategy.test.ts index 80351b056..3165ce352 100644 --- a/test/unit/ldp/auxiliary/RoutingAuxiliaryStrategy.test.ts +++ b/test/unit/ldp/auxiliary/RoutingAuxiliaryStrategy.test.ts @@ -27,7 +27,7 @@ class SimpleSuffixStrategy implements AuxiliaryStrategy { return { path: identifier.path.slice(0, -this.suffix.length) }; } - public requiresRootAuxiliary(): boolean { + public isRootRequired(): boolean { return true; } @@ -77,13 +77,13 @@ describe('A RoutingAuxiliaryStrategy', (): void => { expect(sources[1].addMetadata).toHaveBeenLastCalledWith(metadata); }); - it('#canDeleteRoot returns the result of the correct source.', async(): Promise => { - sources[0].requiresRootAuxiliary = jest.fn(); - sources[1].requiresRootAuxiliary = jest.fn(); - strategy.requiresRootAuxiliary(dummy2Id); - expect(sources[0].requiresRootAuxiliary).toHaveBeenCalledTimes(0); - expect(sources[1].requiresRootAuxiliary).toHaveBeenCalledTimes(1); - expect(sources[1].requiresRootAuxiliary).toHaveBeenLastCalledWith(dummy2Id); + it('#isRootRequired returns the result of the correct source.', async(): Promise => { + sources[0].isRootRequired = jest.fn(); + sources[1].isRootRequired = jest.fn(); + strategy.isRootRequired(dummy2Id); + expect(sources[0].isRootRequired).toHaveBeenCalledTimes(0); + expect(sources[1].isRootRequired).toHaveBeenCalledTimes(1); + expect(sources[1].isRootRequired).toHaveBeenLastCalledWith(dummy2Id); }); it('#validates using the correct validator.', async(): Promise => { diff --git a/test/unit/ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy.test.ts b/test/unit/ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy.test.ts index 024fb05b3..cb305a792 100644 --- a/test/unit/ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy.test.ts +++ b/test/unit/ldp/auxiliary/SuffixAuxiliaryIdentifierStrategy.test.ts @@ -38,8 +38,4 @@ describe('A SuffixAuxiliaryManager', (): void => { it('removes the suffix to create the associated identifier.', async(): Promise => { expect(strategy.getAssociatedIdentifier(auxiliaryId)).toEqual(associatedId); }); - - it('returns true on canDeleteRoot.', async(): Promise => { - expect(strategy.canDeleteRoot()).toEqual(true); - }); }); diff --git a/test/unit/ldp/http/metadata/AclLinkMetadataWriter.test.ts b/test/unit/ldp/http/metadata/AclLinkMetadataWriter.test.ts index 1a022fa0b..64b49ec9a 100644 --- a/test/unit/ldp/http/metadata/AclLinkMetadataWriter.test.ts +++ b/test/unit/ldp/http/metadata/AclLinkMetadataWriter.test.ts @@ -1,17 +1,17 @@ import { createResponse } from 'node-mocks-http'; -import type { AclManager } from '../../../../../src/authorization/AclManager'; +import type { AuxiliaryIdentifierStrategy } from '../../../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy'; import { AclLinkMetadataWriter } from '../../../../../src/ldp/http/metadata/AclLinkMetadataWriter'; import { RepresentationMetadata } from '../../../../../src/ldp/representation/RepresentationMetadata'; import type { ResourceIdentifier } from '../../../../../src/ldp/representation/ResourceIdentifier'; describe('An AclLinkMetadataWriter', (): void => { - const manager = { - getAclDocument: async(id: ResourceIdentifier): Promise => ({ path: `${id.path}.acl` }), - } as AclManager; + const strategy = { + getAuxiliaryIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: `${id.path}.acl` }), + } as AuxiliaryIdentifierStrategy; const identifier = { path: 'http://test.com/foo' }; it('adds the acl link header.', async(): Promise => { - const writer = new AclLinkMetadataWriter(manager); + const writer = new AclLinkMetadataWriter(strategy); const response = createResponse(); const metadata = new RepresentationMetadata(identifier); await expect(writer.handle({ response, metadata })).resolves.toBeUndefined(); @@ -19,7 +19,7 @@ describe('An AclLinkMetadataWriter', (): void => { }); it('can use a custom rel attribute.', async(): Promise => { - const writer = new AclLinkMetadataWriter(manager, 'http://www.w3.org/ns/solid/terms#acl'); + const writer = new AclLinkMetadataWriter(strategy, 'http://www.w3.org/ns/solid/terms#acl'); const response = createResponse(); const metadata = new RepresentationMetadata(identifier); await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();