mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Generalize AclManager to AuxiliaryManager
This commit is contained in:
parent
d6cdd7dbdf
commit
758f5ed083
@ -2,14 +2,34 @@
|
|||||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
|
||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:AclManager",
|
"@id": "urn:solid-server:default:AclIdentifierStrategy",
|
||||||
"@type": "UrlBasedAclManager"
|
"@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",
|
"@id": "urn:solid-server:default:AclAuthorizer",
|
||||||
"@type": "WebAclAuthorizer",
|
"@type": "WebAclAuthorizer",
|
||||||
"WebAclAuthorizer:_aclManager": {
|
"WebAclAuthorizer:_aclStrategy": {
|
||||||
"@id": "urn:solid-server:default:AclManager"
|
"@id": "urn:solid-server:default:AclIdentifierStrategy"
|
||||||
},
|
},
|
||||||
"WebAclAuthorizer:_resourceStore": {
|
"WebAclAuthorizer:_resourceStore": {
|
||||||
"@id": "urn:solid-server:default:ResourceStore"
|
"@id": "urn:solid-server:default:ResourceStore"
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
"AclInitializer:_settings_store": {
|
"AclInitializer:_settings_store": {
|
||||||
"@id": "urn:solid-server:default:ResourceStore"
|
"@id": "urn:solid-server:default:ResourceStore"
|
||||||
},
|
},
|
||||||
"AclInitializer:_settings_aclManager": {
|
"AclInitializer:_settings_aclStrategy": {
|
||||||
"@id": "urn:solid-server:default:AclManager"
|
"@id": "urn:solid-server:default:AclIdentifierStrategy"
|
||||||
},
|
},
|
||||||
"AclInitializer:_settings_baseUrl": {
|
"AclInitializer:_settings_baseUrl": {
|
||||||
"@id": "urn:solid-server:default:variable:baseUrl"
|
"@id": "urn:solid-server:default:variable:baseUrl"
|
||||||
|
@ -42,8 +42,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@type": "AclLinkMetadataWriter",
|
"@type": "AclLinkMetadataWriter",
|
||||||
"AclLinkMetadataWriter:_aclManager": {
|
"AclLinkMetadataWriter:_aclStrategy": {
|
||||||
"@id": "urn:solid-server:default:AclManager"
|
"@id": "urn:solid-server:default:AclIdentifierStrategy"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -82,11 +82,11 @@
|
|||||||
"@type": "WaterfallHandler",
|
"@type": "WaterfallHandler",
|
||||||
"WaterfallHandler:_handlers": [
|
"WaterfallHandler:_handlers": [
|
||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:IndexConverter",
|
"@id": "urn:solid-server:default:IndexConverter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@type": "IfNeededConverter",
|
"@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"
|
"@id": "urn:solid-server:default:ContentTypeReplacer"
|
||||||
@ -101,6 +101,14 @@
|
|||||||
"@id": "urn:solid-server:default:RdfRepresentationConverter"
|
"@id": "urn:solid-server:default:RdfRepresentationConverter"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"@id": "urn:solid-server:default:RdfValidator",
|
||||||
|
"@type": "RdfValidator",
|
||||||
|
"RdfValidator:_converter": {
|
||||||
|
"@id": "urn:solid-server:default:RepresentationConverter"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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>;
|
|
||||||
}
|
|
@ -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) };
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
import type { Quad, Term } from 'n3';
|
import type { Quad, Term } from 'n3';
|
||||||
import { Store } from 'n3';
|
import { Store } from 'n3';
|
||||||
import type { Credentials } from '../authentication/Credentials';
|
import type { Credentials } from '../authentication/Credentials';
|
||||||
|
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||||
import type { PermissionSet } from '../ldp/permissions/PermissionSet';
|
import type { PermissionSet } from '../ldp/permissions/PermissionSet';
|
||||||
import type { Representation } from '../ldp/representation/Representation';
|
import type { Representation } from '../ldp/representation/Representation';
|
||||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
@ -12,7 +13,6 @@ import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
|||||||
import { UnauthorizedHttpError } from '../util/errors/UnauthorizedHttpError';
|
import { UnauthorizedHttpError } from '../util/errors/UnauthorizedHttpError';
|
||||||
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
||||||
import { ACL, FOAF } from '../util/Vocabularies';
|
import { ACL, FOAF } from '../util/Vocabularies';
|
||||||
import type { AclManager } from './AclManager';
|
|
||||||
import type { AuthorizerArgs } from './Authorizer';
|
import type { AuthorizerArgs } from './Authorizer';
|
||||||
import { Authorizer } from './Authorizer';
|
import { Authorizer } from './Authorizer';
|
||||||
|
|
||||||
@ -24,13 +24,14 @@ import { Authorizer } from './Authorizer';
|
|||||||
export class WebAclAuthorizer extends Authorizer {
|
export class WebAclAuthorizer extends Authorizer {
|
||||||
protected readonly logger = getLoggerFor(this);
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
private readonly aclManager: AclManager;
|
private readonly aclStrategy: AuxiliaryIdentifierStrategy;
|
||||||
private readonly resourceStore: ResourceStore;
|
private readonly resourceStore: ResourceStore;
|
||||||
private readonly identifierStrategy: IdentifierStrategy;
|
private readonly identifierStrategy: IdentifierStrategy;
|
||||||
|
|
||||||
public constructor(aclManager: AclManager, resourceStore: ResourceStore, identifierStrategy: IdentifierStrategy) {
|
public constructor(aclStrategy: AuxiliaryIdentifierStrategy, resourceStore: ResourceStore,
|
||||||
|
identifierStrategy: IdentifierStrategy) {
|
||||||
super();
|
super();
|
||||||
this.aclManager = aclManager;
|
this.aclStrategy = aclStrategy;
|
||||||
this.resourceStore = resourceStore;
|
this.resourceStore = resourceStore;
|
||||||
this.identifierStrategy = identifierStrategy;
|
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
|
// 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."
|
// 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
|
// https://solid.github.io/specification/protocol#auxiliary-resources-reserved
|
||||||
const modes = await this.aclManager.isAclDocument(identifier) ?
|
const modes = this.aclStrategy.isAuxiliaryIdentifier(identifier) ?
|
||||||
[ 'control' ] :
|
[ 'control' ] :
|
||||||
(Object.keys(permissions) as (keyof PermissionSet)[]).filter((key): boolean => permissions[key]);
|
(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> {
|
private async getAclRecursive(id: ResourceIdentifier, recurse?: boolean): Promise<Store> {
|
||||||
this.logger.debug(`Trying to read the direct ACL document of ${id.path}`);
|
this.logger.debug(`Trying to read the direct ACL document of ${id.path}`);
|
||||||
try {
|
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}`);
|
this.logger.debug(`Trying to read the ACL document ${acl.path}`);
|
||||||
const data = await this.resourceStore.getRepresentation(acl, { type: { [INTERNAL_QUADS]: 1 }});
|
const data = await this.resourceStore.getRepresentation(acl, { type: { [INTERNAL_QUADS]: 1 }});
|
||||||
this.logger.info(`Reading ACL statements from ${acl.path}`);
|
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);
|
return this.filterData(data, recurse ? ACL.default : ACL.accessTo, resourceId.path);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (NotFoundHttpError.isInstance(error)) {
|
if (NotFoundHttpError.isInstance(error)) {
|
||||||
|
@ -9,9 +9,7 @@ export * from './authentication/UnsecureWebIdExtractor';
|
|||||||
|
|
||||||
// Authorization
|
// Authorization
|
||||||
export * from './authorization/AllowEverythingAuthorizer';
|
export * from './authorization/AllowEverythingAuthorizer';
|
||||||
export * from './authorization/AclManager';
|
|
||||||
export * from './authorization/Authorizer';
|
export * from './authorization/Authorizer';
|
||||||
export * from './authorization/UrlBasedAclManager';
|
|
||||||
export * from './authorization/WebAclAuthorizer';
|
export * from './authorization/WebAclAuthorizer';
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createReadStream } from 'fs';
|
import { createReadStream } from 'fs';
|
||||||
import type { AclManager } from '../authorization/AclManager';
|
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||||
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
|
||||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
@ -17,19 +17,19 @@ const DEFAULT_ACL_PATH = joinFilePath(__dirname, '../../templates/root/.acl');
|
|||||||
export class AclInitializer extends Initializer {
|
export class AclInitializer extends Initializer {
|
||||||
protected readonly logger = getLoggerFor(this);
|
protected readonly logger = getLoggerFor(this);
|
||||||
private readonly store: ResourceStore;
|
private readonly store: ResourceStore;
|
||||||
private readonly aclManager: AclManager;
|
private readonly aclStrategy: AuxiliaryIdentifierStrategy;
|
||||||
private readonly root: ResourceIdentifier;
|
private readonly root: ResourceIdentifier;
|
||||||
private readonly aclPath: string;
|
private readonly aclPath: string;
|
||||||
|
|
||||||
public constructor(settings: {
|
public constructor(settings: {
|
||||||
store: ResourceStore;
|
store: ResourceStore;
|
||||||
aclManager: AclManager;
|
aclStrategy: AuxiliaryIdentifierStrategy;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
aclPath?: string;
|
aclPath?: string;
|
||||||
}) {
|
}) {
|
||||||
super();
|
super();
|
||||||
this.store = settings.store;
|
this.store = settings.store;
|
||||||
this.aclManager = settings.aclManager;
|
this.aclStrategy = settings.aclStrategy;
|
||||||
this.root = { path: ensureTrailingSlash(settings.baseUrl) };
|
this.root = { path: ensureTrailingSlash(settings.baseUrl) };
|
||||||
this.aclPath = settings.aclPath ?? DEFAULT_ACL_PATH;
|
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."
|
// The associated ACL document MUST include an authorization policy with acl:Control access privilege."
|
||||||
// https://solid.github.io/specification/protocol#storage
|
// https://solid.github.io/specification/protocol#storage
|
||||||
public async handle(): Promise<void> {
|
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)) {
|
if (await containsResource(this.store, rootAcl)) {
|
||||||
this.logger.debug(`Existing root ACL document found at ${rootAcl.path}`);
|
this.logger.debug(`Existing root ACL document found at ${rootAcl.path}`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -10,10 +10,11 @@ import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy'
|
|||||||
*/
|
*/
|
||||||
export interface AuxiliaryStrategy extends 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.
|
* @param identifier - Identifier of the auxiliary resource.
|
||||||
*/
|
*/
|
||||||
requiresRootAuxiliary: (identifier: ResourceIdentifier) => boolean;
|
isRootRequired: (identifier: ResourceIdentifier) => boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds metadata related to this auxiliary resource,
|
* Adds metadata related to this auxiliary resource,
|
||||||
|
@ -14,14 +14,14 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy {
|
|||||||
private readonly identifierStrategy: AuxiliaryIdentifierStrategy;
|
private readonly identifierStrategy: AuxiliaryIdentifierStrategy;
|
||||||
private readonly metadataGenerator?: MetadataGenerator;
|
private readonly metadataGenerator?: MetadataGenerator;
|
||||||
private readonly validator?: Validator;
|
private readonly validator?: Validator;
|
||||||
private readonly canDelete: boolean;
|
private readonly rootRequired: boolean;
|
||||||
|
|
||||||
public constructor(identifierStrategy: AuxiliaryIdentifierStrategy, metadataGenerator?: MetadataGenerator,
|
public constructor(identifierStrategy: AuxiliaryIdentifierStrategy, metadataGenerator?: MetadataGenerator,
|
||||||
validator?: Validator, canDeleteRoot = false) {
|
validator?: Validator, isRootRequired = false) {
|
||||||
this.identifierStrategy = identifierStrategy;
|
this.identifierStrategy = identifierStrategy;
|
||||||
this.metadataGenerator = metadataGenerator;
|
this.metadataGenerator = metadataGenerator;
|
||||||
this.validator = validator;
|
this.validator = validator;
|
||||||
this.canDelete = canDeleteRoot;
|
this.rootRequired = isRootRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAuxiliaryIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
|
public getAuxiliaryIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
|
||||||
@ -40,8 +40,8 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy {
|
|||||||
return this.identifierStrategy.getAssociatedIdentifier(identifier);
|
return this.identifierStrategy.getAssociatedIdentifier(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public requiresRootAuxiliary(): boolean {
|
public isRootRequired(): boolean {
|
||||||
return this.canDelete;
|
return this.rootRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addMetadata(metadata: RepresentationMetadata): Promise<void> {
|
public async addMetadata(metadata: RepresentationMetadata): Promise<void> {
|
||||||
|
@ -18,9 +18,9 @@ export class RoutingAuxiliaryStrategy extends RoutingAuxiliaryIdentifierStrategy
|
|||||||
super(sources);
|
super(sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
public requiresRootAuxiliary(identifier: ResourceIdentifier): boolean {
|
public isRootRequired(identifier: ResourceIdentifier): boolean {
|
||||||
const source = this.getMatchingSource(identifier);
|
const source = this.getMatchingSource(identifier);
|
||||||
return source.requiresRootAuxiliary(identifier);
|
return source.isRootRequired(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addMetadata(metadata: RepresentationMetadata): Promise<void> {
|
public async addMetadata(metadata: RepresentationMetadata): Promise<void> {
|
||||||
|
@ -34,8 +34,4 @@ export class SuffixAuxiliaryIdentifierStrategy implements AuxiliaryIdentifierStr
|
|||||||
}
|
}
|
||||||
return { path: identifier.path.slice(0, -this.suffix.length) };
|
return { path: identifier.path.slice(0, -this.suffix.length) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public canDeleteRoot(): boolean {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { AclManager } from '../../../authorization/AclManager';
|
|
||||||
import type { HttpResponse } from '../../../server/HttpResponse';
|
import type { HttpResponse } from '../../../server/HttpResponse';
|
||||||
import { addHeader } from '../../../util/HeaderUtil';
|
import { addHeader } from '../../../util/HeaderUtil';
|
||||||
|
import type { AuxiliaryIdentifierStrategy } from '../../auxiliary/AuxiliaryIdentifierStrategy';
|
||||||
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||||
import { MetadataWriter } from './MetadataWriter';
|
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).
|
* 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 {
|
export class AclLinkMetadataWriter extends MetadataWriter {
|
||||||
private readonly aclManager: AclManager;
|
private readonly aclStrategy: AuxiliaryIdentifierStrategy;
|
||||||
private readonly rel: string;
|
private readonly rel: string;
|
||||||
|
|
||||||
public constructor(aclManager: AclManager, rel = 'acl') {
|
public constructor(aclStrategy: AuxiliaryIdentifierStrategy, rel = 'acl') {
|
||||||
super();
|
super();
|
||||||
this.aclManager = aclManager;
|
this.aclStrategy = aclStrategy;
|
||||||
this.rel = rel;
|
this.rel = rel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {
|
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}"`);
|
addHeader(input.response, 'Link', `<${identifier.path}>; rel="${this.rel}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
|
||||||
"import": [
|
"import": [
|
||||||
|
"files-scs:config/presets/acl.json",
|
||||||
"files-scs:config/presets/http.json",
|
"files-scs:config/presets/http.json",
|
||||||
"files-scs:config/presets/ldp/credentials-extractor.json",
|
"files-scs:config/presets/ldp/credentials-extractor.json",
|
||||||
"files-scs:config/presets/ldp/metadata-handler.json",
|
"files-scs:config/presets/ldp/metadata-handler.json",
|
||||||
@ -46,11 +47,6 @@
|
|||||||
"PassthroughStore:_source": {
|
"PassthroughStore:_source": {
|
||||||
"@id": "urn:solid-server:default:MemoryResourceStore"
|
"@id": "urn:solid-server:default:MemoryResourceStore"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"@id": "urn:solid-server:default:AclManager",
|
|
||||||
"@type": "UrlBasedAclManager",
|
|
||||||
"comment": "Needed for AclLinkMetadataWriter"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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' });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,8 +1,8 @@
|
|||||||
import { namedNode, quad } from '@rdfjs/data-model';
|
import { namedNode, quad } from '@rdfjs/data-model';
|
||||||
import streamifyArray from 'streamify-array';
|
import streamifyArray from 'streamify-array';
|
||||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||||
import type { AclManager } from '../../../src/authorization/AclManager';
|
|
||||||
import { WebAclAuthorizer } from '../../../src/authorization/WebAclAuthorizer';
|
import { WebAclAuthorizer } from '../../../src/authorization/WebAclAuthorizer';
|
||||||
|
import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||||
import type { PermissionSet } from '../../../src/ldp/permissions/PermissionSet';
|
import type { PermissionSet } from '../../../src/ldp/permissions/PermissionSet';
|
||||||
import type { Representation } from '../../../src/ldp/representation/Representation';
|
import type { Representation } from '../../../src/ldp/representation/Representation';
|
||||||
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
|
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 => {
|
describe('A WebAclAuthorizer', (): void => {
|
||||||
let authorizer: WebAclAuthorizer;
|
let authorizer: WebAclAuthorizer;
|
||||||
const aclManager: AclManager = {
|
const aclStrategy: AuxiliaryIdentifierStrategy = {
|
||||||
getAclDocument: async(id: ResourceIdentifier): Promise<ResourceIdentifier> =>
|
getAuxiliaryIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: `${id.path}.acl` }),
|
||||||
id.path.endsWith('.acl') ? id : { path: `${id.path}.acl` },
|
isAuxiliaryIdentifier: (id: ResourceIdentifier): boolean => id.path.endsWith('.acl'),
|
||||||
isAclDocument: async(id: ResourceIdentifier): Promise<boolean> => id.path.endsWith('.acl'),
|
getAssociatedIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: id.path.slice(0, -4) }),
|
||||||
getAclConstrainedResource: async(id: ResourceIdentifier): Promise<ResourceIdentifier> =>
|
} as any;
|
||||||
!id.path.endsWith('.acl') ? id : { path: id.path.slice(0, -4) },
|
|
||||||
};
|
|
||||||
let store: ResourceStore;
|
let store: ResourceStore;
|
||||||
const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/');
|
const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/');
|
||||||
let permissions: PermissionSet;
|
let permissions: PermissionSet;
|
||||||
@ -44,11 +42,11 @@ describe('A WebAclAuthorizer', (): void => {
|
|||||||
store = {
|
store = {
|
||||||
getRepresentation: jest.fn(),
|
getRepresentation: jest.fn(),
|
||||||
} as any;
|
} as any;
|
||||||
authorizer = new WebAclAuthorizer(aclManager, store, identifierStrategy);
|
authorizer = new WebAclAuthorizer(aclStrategy, store, identifierStrategy);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles all inputs.', async(): Promise<void> => {
|
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();
|
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}accessTo`), nn(identifier.path)),
|
||||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Control`)),
|
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Control`)),
|
||||||
]) } as Representation);
|
]) } as Representation);
|
||||||
const aclIdentifier = await aclManager.getAclDocument(identifier);
|
const aclIdentifier = aclStrategy.getAuxiliaryIdentifier(identifier);
|
||||||
await expect(authorizer.handle({ identifier: aclIdentifier, permissions, credentials })).resolves.toBeUndefined();
|
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}Read`)),
|
||||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
|
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)),
|
||||||
]) } as Representation);
|
]) } as Representation);
|
||||||
identifier = await aclManager.getAclDocument(identifier);
|
identifier = aclStrategy.getAuxiliaryIdentifier(identifier);
|
||||||
await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError);
|
await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import type { AclManager } from '../../../src/authorization/AclManager';
|
|
||||||
import { AclInitializer } from '../../../src/init/AclInitializer';
|
import { AclInitializer } from '../../../src/init/AclInitializer';
|
||||||
|
import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy';
|
||||||
import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation';
|
||||||
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
||||||
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
|
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
|
||||||
@ -19,8 +19,8 @@ describe('AclInitializer', (): void => {
|
|||||||
setRepresentation: jest.fn(),
|
setRepresentation: jest.fn(),
|
||||||
} as any;
|
} as any;
|
||||||
const aclIdentifier = { path: 'http://test.com/.acl' };
|
const aclIdentifier = { path: 'http://test.com/.acl' };
|
||||||
const aclManager: jest.Mocked<AclManager> = {
|
const aclStrategy: jest.Mocked<AuxiliaryIdentifierStrategy> = {
|
||||||
getAclDocument: jest.fn().mockResolvedValue(aclIdentifier),
|
getAuxiliaryIdentifier: jest.fn().mockReturnValue(aclIdentifier),
|
||||||
} as any;
|
} as any;
|
||||||
const baseUrl = 'http://localhost:3000/';
|
const baseUrl = 'http://localhost:3000/';
|
||||||
|
|
||||||
@ -29,10 +29,10 @@ describe('AclInitializer', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('sets the default ACL when none exists already.', async(): Promise<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();
|
await initializer.handle();
|
||||||
|
|
||||||
expect(aclManager.getAclDocument).toHaveBeenCalledWith({ path: baseUrl });
|
expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl });
|
||||||
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
|
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
|
||||||
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
|
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
|
||||||
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
|
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
|
||||||
@ -45,10 +45,10 @@ describe('AclInitializer', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('sets the specific ACL when one was specified.', async(): Promise<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();
|
await initializer.handle();
|
||||||
|
|
||||||
expect(aclManager.getAclDocument).toHaveBeenCalledWith({ path: baseUrl });
|
expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl });
|
||||||
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
|
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
|
||||||
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
|
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
|
||||||
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
|
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
|
||||||
@ -65,10 +65,10 @@ describe('AclInitializer', (): void => {
|
|||||||
data: { destroy: jest.fn() },
|
data: { destroy: jest.fn() },
|
||||||
} as any));
|
} as any));
|
||||||
|
|
||||||
const initializer = new AclInitializer({ baseUrl, store, aclManager });
|
const initializer = new AclInitializer({ baseUrl, store, aclStrategy });
|
||||||
await initializer.handle();
|
await initializer.handle();
|
||||||
|
|
||||||
expect(aclManager.getAclDocument).toHaveBeenCalledWith({ path: baseUrl });
|
expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl });
|
||||||
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
|
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
|
||||||
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
|
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
|
||||||
expect(store.setRepresentation).toHaveBeenCalledTimes(0);
|
expect(store.setRepresentation).toHaveBeenCalledTimes(0);
|
||||||
@ -77,7 +77,7 @@ describe('AclInitializer', (): void => {
|
|||||||
it('errors when the root ACL check errors.', async(): Promise<void> => {
|
it('errors when the root ACL check errors.', async(): Promise<void> => {
|
||||||
store.getRepresentation.mockRejectedValueOnce(new Error('Fatal'));
|
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');
|
await expect(initializer.handle()).rejects.toThrow('Fatal');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -45,8 +45,8 @@ describe('A ComposedAuxiliaryStrategy', (): void => {
|
|||||||
expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenLastCalledWith(identifier);
|
expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenLastCalledWith(identifier);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the injected value for canDeleteRoot.', async(): Promise<void> => {
|
it('returns the injected value for isRootRequired.', async(): Promise<void> => {
|
||||||
expect(strategy.requiresRootAuxiliary()).toBe(true);
|
expect(strategy.isRootRequired()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds metadata through the MetadataGenerator.', async(): Promise<void> => {
|
it('adds metadata through the MetadataGenerator.', async(): Promise<void> => {
|
||||||
@ -63,9 +63,9 @@ describe('A ComposedAuxiliaryStrategy', (): void => {
|
|||||||
expect(validator.handleSafe).toHaveBeenLastCalledWith(representation);
|
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);
|
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> => {
|
it('does not add metadata or validate if the corresponding classes are not injected.', async(): Promise<void> => {
|
||||||
|
@ -27,7 +27,7 @@ class SimpleSuffixStrategy implements AuxiliaryStrategy {
|
|||||||
return { path: identifier.path.slice(0, -this.suffix.length) };
|
return { path: identifier.path.slice(0, -this.suffix.length) };
|
||||||
}
|
}
|
||||||
|
|
||||||
public requiresRootAuxiliary(): boolean {
|
public isRootRequired(): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +77,13 @@ describe('A RoutingAuxiliaryStrategy', (): void => {
|
|||||||
expect(sources[1].addMetadata).toHaveBeenLastCalledWith(metadata);
|
expect(sources[1].addMetadata).toHaveBeenLastCalledWith(metadata);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#canDeleteRoot returns the result of the correct source.', async(): Promise<void> => {
|
it('#isRootRequired returns the result of the correct source.', async(): Promise<void> => {
|
||||||
sources[0].requiresRootAuxiliary = jest.fn();
|
sources[0].isRootRequired = jest.fn();
|
||||||
sources[1].requiresRootAuxiliary = jest.fn();
|
sources[1].isRootRequired = jest.fn();
|
||||||
strategy.requiresRootAuxiliary(dummy2Id);
|
strategy.isRootRequired(dummy2Id);
|
||||||
expect(sources[0].requiresRootAuxiliary).toHaveBeenCalledTimes(0);
|
expect(sources[0].isRootRequired).toHaveBeenCalledTimes(0);
|
||||||
expect(sources[1].requiresRootAuxiliary).toHaveBeenCalledTimes(1);
|
expect(sources[1].isRootRequired).toHaveBeenCalledTimes(1);
|
||||||
expect(sources[1].requiresRootAuxiliary).toHaveBeenLastCalledWith(dummy2Id);
|
expect(sources[1].isRootRequired).toHaveBeenLastCalledWith(dummy2Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#validates using the correct validator.', async(): Promise<void> => {
|
it('#validates using the correct validator.', async(): Promise<void> => {
|
||||||
|
@ -38,8 +38,4 @@ describe('A SuffixAuxiliaryManager', (): void => {
|
|||||||
it('removes the suffix to create the associated identifier.', async(): Promise<void> => {
|
it('removes the suffix to create the associated identifier.', async(): Promise<void> => {
|
||||||
expect(strategy.getAssociatedIdentifier(auxiliaryId)).toEqual(associatedId);
|
expect(strategy.getAssociatedIdentifier(auxiliaryId)).toEqual(associatedId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns true on canDeleteRoot.', async(): Promise<void> => {
|
|
||||||
expect(strategy.canDeleteRoot()).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { createResponse } from 'node-mocks-http';
|
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 { AclLinkMetadataWriter } from '../../../../../src/ldp/http/metadata/AclLinkMetadataWriter';
|
||||||
import { RepresentationMetadata } from '../../../../../src/ldp/representation/RepresentationMetadata';
|
import { RepresentationMetadata } from '../../../../../src/ldp/representation/RepresentationMetadata';
|
||||||
import type { ResourceIdentifier } from '../../../../../src/ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../../../../src/ldp/representation/ResourceIdentifier';
|
||||||
|
|
||||||
describe('An AclLinkMetadataWriter', (): void => {
|
describe('An AclLinkMetadataWriter', (): void => {
|
||||||
const manager = {
|
const strategy = {
|
||||||
getAclDocument: async(id: ResourceIdentifier): Promise<ResourceIdentifier> => ({ path: `${id.path}.acl` }),
|
getAuxiliaryIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: `${id.path}.acl` }),
|
||||||
} as AclManager;
|
} as AuxiliaryIdentifierStrategy;
|
||||||
const identifier = { path: 'http://test.com/foo' };
|
const identifier = { path: 'http://test.com/foo' };
|
||||||
|
|
||||||
it('adds the acl link header.', async(): Promise<void> => {
|
it('adds the acl link header.', async(): Promise<void> => {
|
||||||
const writer = new AclLinkMetadataWriter(manager);
|
const writer = new AclLinkMetadataWriter(strategy);
|
||||||
const response = createResponse();
|
const response = createResponse();
|
||||||
const metadata = new RepresentationMetadata(identifier);
|
const metadata = new RepresentationMetadata(identifier);
|
||||||
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
|
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> => {
|
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 response = createResponse();
|
||||||
const metadata = new RepresentationMetadata(identifier);
|
const metadata = new RepresentationMetadata(identifier);
|
||||||
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
|
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user