feat: Generalize AclManager to AuxiliaryManager

This commit is contained in:
Joachim Van Herwegen 2021-01-25 17:13:48 +01:00
parent d6cdd7dbdf
commit 758f5ed083
22 changed files with 106 additions and 192 deletions

View File

@ -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"

View File

@ -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"

View File

@ -42,8 +42,8 @@
},
{
"@type": "AclLinkMetadataWriter",
"AclLinkMetadataWriter:_aclManager": {
"@id": "urn:solid-server:default:AclManager"
"AclLinkMetadataWriter:_aclStrategy": {
"@id": "urn:solid-server:default:AclIdentifierStrategy"
}
}
]

View File

@ -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"
}
}
]
}

View File

@ -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<ResourceIdentifier>;
/**
* 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<boolean>;
/**
* 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<ResourceIdentifier>;
}

View File

@ -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<ResourceIdentifier> {
return await this.isAclDocument(id) ? id : { path: `${id.path}.acl` };
}
public async isAclDocument(id: ResourceIdentifier): Promise<boolean> {
return /\.acl\/?/u.test(id.path);
}
public async getAclConstrainedResource(id: ResourceIdentifier): Promise<ResourceIdentifier> {
if (!await this.isAclDocument(id)) {
return id;
}
// Slice off `.acl`
return { path: id.path.slice(0, -4) };
}
}

View File

@ -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<Store> {
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)) {

View File

@ -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

View File

@ -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<void> {
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 {

View File

@ -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,

View File

@ -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<void> {

View File

@ -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<void> {

View File

@ -34,8 +34,4 @@ export class SuffixAuxiliaryIdentifierStrategy implements AuxiliaryIdentifierStr
}
return { path: identifier.path.slice(0, -this.suffix.length) };
}
public canDeleteRoot(): boolean {
return true;
}
}

View File

@ -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<void> {
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}"`);
}
}

View File

@ -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"
}
]
}

View File

@ -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<void> => {
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<void> => {
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<void> => {
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<void> => {
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<void> => {
await expect(manager.getAclConstrainedResource({ path: '/foo/bar' })).resolves.toEqual({ path: '/foo/bar' });
});
});
});

View File

@ -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<ResourceIdentifier> =>
id.path.endsWith('.acl') ? id : { path: `${id.path}.acl` },
isAclDocument: async(id: ResourceIdentifier): Promise<boolean> => id.path.endsWith('.acl'),
getAclConstrainedResource: async(id: ResourceIdentifier): Promise<ResourceIdentifier> =>
!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<void> => {
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);
});

View File

@ -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<AclManager> = {
getAclDocument: jest.fn().mockResolvedValue(aclIdentifier),
const aclStrategy: jest.Mocked<AuxiliaryIdentifierStrategy> = {
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<void> => {
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<void> => {
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<void> => {
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');
});
});

View File

@ -45,8 +45,8 @@ describe('A ComposedAuxiliaryStrategy', (): void => {
expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenLastCalledWith(identifier);
});
it('returns the injected value for canDeleteRoot.', async(): Promise<void> => {
expect(strategy.requiresRootAuxiliary()).toBe(true);
it('returns the injected value for isRootRequired.', async(): Promise<void> => {
expect(strategy.isRootRequired()).toBe(true);
});
it('adds metadata through the MetadataGenerator.', async(): Promise<void> => {
@ -63,9 +63,9 @@ describe('A ComposedAuxiliaryStrategy', (): void => {
expect(validator.handleSafe).toHaveBeenLastCalledWith(representation);
});
it('defaults canDeleteRoot to false.', async(): Promise<void> => {
it('defaults isRootRequired to false.', async(): Promise<void> => {
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<void> => {

View File

@ -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<void> => {
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<void> => {
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<void> => {

View File

@ -38,8 +38,4 @@ describe('A SuffixAuxiliaryManager', (): void => {
it('removes the suffix to create the associated identifier.', async(): Promise<void> => {
expect(strategy.getAssociatedIdentifier(auxiliaryId)).toEqual(associatedId);
});
it('returns true on canDeleteRoot.', async(): Promise<void> => {
expect(strategy.canDeleteRoot()).toEqual(true);
});
});

View File

@ -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<ResourceIdentifier> => ({ 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<void> => {
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<void> => {
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();