From 3c64449c6c717fe4d9e1392476e9531d213ca454 Mon Sep 17 00:00:00 2001 From: Daniel Schraudner Date: Tue, 24 Oct 2023 14:16:13 +0200 Subject: [PATCH 1/7] ACL with read is created for creator for configurable containers --- .../handler/components/operation-handler.json | 7 ++ src/http/ldp/CreatorPostOperationHandler.ts | 106 ++++++++++++++++++ src/index.ts | 1 + 3 files changed, 114 insertions(+) create mode 100644 src/http/ldp/CreatorPostOperationHandler.ts diff --git a/config/ldp/handler/components/operation-handler.json b/config/ldp/handler/components/operation-handler.json index 98ba4f99f6..9c6098c54c 100644 --- a/config/ldp/handler/components/operation-handler.json +++ b/config/ldp/handler/components/operation-handler.json @@ -10,6 +10,13 @@ "store": { "@id": "urn:solid-server:default:ResourceStore" }, "eTagHandler": { "@id": "urn:solid-server:default:ETagHandler" } }, + { + "@type": "CreatorPostOperationHandler", + "store": { "@id": "urn:solid-server:default:ResourceStore" }, + "creatorContainerNames": [ "demands" ], + "aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" }, + "credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" } + }, { "@type": "PostOperationHandler", "store": { "@id": "urn:solid-server:default:ResourceStore" } diff --git a/src/http/ldp/CreatorPostOperationHandler.ts b/src/http/ldp/CreatorPostOperationHandler.ts new file mode 100644 index 0000000000..1b2b2748d1 --- /dev/null +++ b/src/http/ldp/CreatorPostOperationHandler.ts @@ -0,0 +1,106 @@ +import type { Term } from 'rdf-js'; +import { getLoggerFor } from '../../logging/LogUtil'; +import type { ResourceStore } from '../../storage/ResourceStore'; +import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError'; +import { InternalServerError } from '../../util/errors/InternalServerError'; +import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; +import { find } from '../../util/IterableUtil'; +import { ACL, AS, LDP, RDF, SOLID_AS } from '../../util/Vocabularies'; +import { CreatedResponseDescription } from '../output/response/CreatedResponseDescription'; +import type { ResponseDescription } from '../output/response/ResponseDescription'; +import type { OperationHandlerInput } from './OperationHandler'; +import { OperationHandler } from './OperationHandler'; +import { randomInt } from 'crypto'; +import { AuxiliaryIdentifierStrategy } from '../../http/auxiliary/AuxiliaryIdentifierStrategy'; +import { isContainerIdentifier, trimTrailingSlashes } from '../../util/PathUtil'; +import { DataFactory, Store, Writer } from 'n3'; +import { RdfDatasetRepresentation } from '../representation/RdfDatasetRepresentation'; +import { CredentialsExtractor } from '../../authentication/CredentialsExtractor'; +import { OperationHttpHandlerInput } from '../../server/OperationHttpHandler'; +import { RepresentationMetadata } from '../../http/representation/RepresentationMetadata'; +import { BasicRepresentation } from '../../http/representation/BasicRepresentation'; +import { TEXT_TURTLE } from '../../util/ContentTypes'; +import { ResourceIdentifier } from '../..'; + +/** + * Handles POST {@link Operation}s. + * Calls the addResource function from a {@link ResourceStore}. + */ +export class CreatorPostOperationHandler extends OperationHandler { + protected readonly logger = getLoggerFor(this); + + private readonly store: ResourceStore; + private readonly creatorContainerNames: string[]; + private readonly aclStrategy: AuxiliaryIdentifierStrategy; + private readonly credentialsExtractor: CredentialsExtractor; + + public constructor(store: ResourceStore, creatorContainerNames: string[], aclStrategy: AuxiliaryIdentifierStrategy, credentialsExtractor: CredentialsExtractor) { + super(); + this.store = store; + this.creatorContainerNames = creatorContainerNames; + this.aclStrategy = aclStrategy; + this.credentialsExtractor = credentialsExtractor; + } + + public async canHandle({ operation }: OperationHandlerInput): Promise { + if (operation.method !== 'POST') { + throw new NotImplementedHttpError('This handler only supports POST operations'); + } + if (!isContainerIdentifier(operation.target)) { + throw new NotImplementedHttpError('This handler only handles POST requests to containers'); + } + let targetPath = trimTrailingSlashes(operation.target.path); + let isCreatorContainer = this.creatorContainerNames.map(c => targetPath.endsWith(c)).reduce((a, b) => a || b); + if(!isCreatorContainer) { + throw new NotImplementedHttpError('This handler only handles creator containers specified in the config'); + } + } + + public async handle(input: OperationHttpHandlerInput): Promise { + const type = new Set(input.operation.body.metadata.getAll(RDF.terms.type).map((term: Term): string => term.value)); + const isContainerType = type.has(LDP.Container) || type.has(LDP.BasicContainer); + // Solid, §2.1: "A Solid server MUST reject PUT, POST and PATCH requests + // without the Content-Type header with a status code of 400." + // https://solid.github.io/specification/protocol#http-server + // An exception is made for LDP Containers as nothing is done with the body, so a Content-type is not required + if (!input.operation.body.metadata.contentType && !isContainerType) { + this.logger.warn('POST requests require the Content-Type header to be set'); + throw new BadRequestHttpError('POST requests require the Content-Type header to be set'); + } + const changes = await this.store.addResource(input.operation.target, input.operation.body, input.operation.conditions); + const createdIdentifier = find(changes.keys(), (identifier): boolean => + Boolean(changes.get(identifier)?.has(SOLID_AS.terms.activity, AS.terms.Create))); + if (!createdIdentifier) { + throw new InternalServerError('Operation was successful but no created identifier was returned.'); + } + const agentCredentials = await this.credentialsExtractor.handle(input.request); + if(agentCredentials.agent) { + const aclIdentifier = this.aclStrategy.getAuxiliaryIdentifier(createdIdentifier); + const serializedQuads = await this.writeQuads(createdIdentifier, agentCredentials.agent.webId); + const representation = new BasicRepresentation(serializedQuads, new RepresentationMetadata(aclIdentifier, TEXT_TURTLE)); + await this.store.setRepresentation(aclIdentifier, representation); + } + return new CreatedResponseDescription(createdIdentifier); + } + + private async writeQuads(createdIdentifier: ResourceIdentifier, agentWebId: string): Promise { + return new Promise((resolve, reject) => { + const authorization = DataFactory.blankNode(); + const quads = [ + DataFactory.quad(authorization, DataFactory.namedNode(RDF.type) , DataFactory.namedNode(ACL.Authorization)), + DataFactory.quad(authorization, DataFactory.namedNode(ACL.accessTo), DataFactory.namedNode(createdIdentifier.path)), + DataFactory.quad(authorization, DataFactory.namedNode(ACL.agent), DataFactory.namedNode(agentWebId)), + DataFactory.quad(authorization, DataFactory.namedNode(ACL.mode), DataFactory.namedNode(ACL.Read)), + ]; + const writer = new Writer(); + writer.addQuads(quads); + writer.end((err, result) => { + if(err) { + reject(err); + } else { + resolve(result); + } + }) + }); + } +} diff --git a/src/index.ts b/src/index.ts index a57c18c4cb..55357cf711 100644 --- a/src/index.ts +++ b/src/index.ts @@ -93,6 +93,7 @@ export * from './http/ldp/HeadOperationHandler'; export * from './http/ldp/OperationHandler'; export * from './http/ldp/PatchOperationHandler'; export * from './http/ldp/PostOperationHandler'; +export * from './http/ldp/CreatorPostOperationHandler'; export * from './http/ldp/PutOperationHandler'; // HTTP/Output/Error From 72405e8293688735f40d4906f26e98615b8f4bdf Mon Sep 17 00:00:00 2001 From: Daniel Schraudner Date: Tue, 24 Oct 2023 16:09:26 +0200 Subject: [PATCH 2/7] Copying effective ACL --- .../handler/components/operation-handler.json | 5 +- src/http/ldp/CreatorPostOperationHandler.ts | 111 +++++++++++++++--- 2 files changed, 100 insertions(+), 16 deletions(-) diff --git a/config/ldp/handler/components/operation-handler.json b/config/ldp/handler/components/operation-handler.json index 9c6098c54c..9e3084a189 100644 --- a/config/ldp/handler/components/operation-handler.json +++ b/config/ldp/handler/components/operation-handler.json @@ -15,7 +15,10 @@ "store": { "@id": "urn:solid-server:default:ResourceStore" }, "creatorContainerNames": [ "demands" ], "aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" }, - "credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" } + "credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" }, + "resourceSet": { "@id": "urn:solid-server:default:CachedResourceSet" }, + "identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }, + "aclStore": { "@id": "urn:solid-server:default:ResourceStore" } }, { "@type": "PostOperationHandler", diff --git a/src/http/ldp/CreatorPostOperationHandler.ts b/src/http/ldp/CreatorPostOperationHandler.ts index 1b2b2748d1..ed363aaa30 100644 --- a/src/http/ldp/CreatorPostOperationHandler.ts +++ b/src/http/ldp/CreatorPostOperationHandler.ts @@ -10,17 +10,20 @@ import { CreatedResponseDescription } from '../output/response/CreatedResponseDe import type { ResponseDescription } from '../output/response/ResponseDescription'; import type { OperationHandlerInput } from './OperationHandler'; import { OperationHandler } from './OperationHandler'; -import { randomInt } from 'crypto'; import { AuxiliaryIdentifierStrategy } from '../../http/auxiliary/AuxiliaryIdentifierStrategy'; import { isContainerIdentifier, trimTrailingSlashes } from '../../util/PathUtil'; -import { DataFactory, Store, Writer } from 'n3'; -import { RdfDatasetRepresentation } from '../representation/RdfDatasetRepresentation'; +import { DataFactory, Quad, Store, Writer } from 'n3'; import { CredentialsExtractor } from '../../authentication/CredentialsExtractor'; import { OperationHttpHandlerInput } from '../../server/OperationHttpHandler'; import { RepresentationMetadata } from '../../http/representation/RepresentationMetadata'; import { BasicRepresentation } from '../../http/representation/BasicRepresentation'; -import { TEXT_TURTLE } from '../../util/ContentTypes'; -import { ResourceIdentifier } from '../..'; +import { INTERNAL_QUADS, TEXT_TURTLE } from '../../util/ContentTypes'; +import { PermissionReader } from '../../authorization/PermissionReader'; +import { ResourceIdentifier } from '../representation/ResourceIdentifier'; +import { ResourceSet } from '../../storage/ResourceSet'; +import { IdentifierStrategy } from '../../util/identifiers/IdentifierStrategy'; +import { ForbiddenHttpError } from '../../util/errors/ForbiddenHttpError'; +import { createErrorMessage, readableToQuads } from '../..'; /** * Handles POST {@link Operation}s. @@ -33,13 +36,27 @@ export class CreatorPostOperationHandler extends OperationHandler { private readonly creatorContainerNames: string[]; private readonly aclStrategy: AuxiliaryIdentifierStrategy; private readonly credentialsExtractor: CredentialsExtractor; + private readonly resourceSet: ResourceSet; + private readonly identifierStrategy: IdentifierStrategy; + private readonly aclStore: ResourceStore; - public constructor(store: ResourceStore, creatorContainerNames: string[], aclStrategy: AuxiliaryIdentifierStrategy, credentialsExtractor: CredentialsExtractor) { + public constructor( + store: ResourceStore, + creatorContainerNames: string[], + aclStrategy: AuxiliaryIdentifierStrategy, + credentialsExtractor: CredentialsExtractor, + resourceSet: ResourceSet, + identifierStrategy: IdentifierStrategy, + aclStore: ResourceStore + ) { super(); this.store = store; this.creatorContainerNames = creatorContainerNames; this.aclStrategy = aclStrategy; this.credentialsExtractor = credentialsExtractor; + this.resourceSet = resourceSet; + this.identifierStrategy = identifierStrategy; + this.aclStore = aclStore; } public async canHandle({ operation }: OperationHandlerInput): Promise { @@ -75,23 +92,53 @@ export class CreatorPostOperationHandler extends OperationHandler { } const agentCredentials = await this.credentialsExtractor.handle(input.request); if(agentCredentials.agent) { + const effectiveAclIdentifier = await this.getAclRecursive(createdIdentifier); + + let contents: Store; + try { + const data = await this.aclStore.getRepresentation(effectiveAclIdentifier, { type: { [INTERNAL_QUADS]: 1 }}); + contents = await readableToQuads(data.data); + } catch (error: unknown) { + // Something is wrong with the server if we can't read the resource + const message = `Error reading ACL resource ${effectiveAclIdentifier.path}: ${createErrorMessage(error)}`; + this.logger.error(message); + throw new InternalServerError(message, { cause: error }); + } + + const authorization = DataFactory.blankNode(); + const quads = [ + DataFactory.quad(authorization, DataFactory.namedNode(RDF.type) , DataFactory.namedNode(ACL.Authorization)), + DataFactory.quad(authorization, DataFactory.namedNode(ACL.accessTo), DataFactory.namedNode(createdIdentifier.path)), + DataFactory.quad(authorization, DataFactory.namedNode(ACL.agent), DataFactory.namedNode(agentCredentials.agent.webId)), + DataFactory.quad(authorization, DataFactory.namedNode(ACL.mode), DataFactory.namedNode(ACL.Read)), + ]; + + const subject = this.aclStrategy.getSubjectIdentifier(effectiveAclIdentifier); + //is Subject + if(createdIdentifier.path !== subject.path) { + const authorizations = contents.getSubjects(DataFactory.namedNode(ACL.default), null, null); + for(let a of authorizations) { + let aBlank = DataFactory.blankNode(); + for(let q of contents.getQuads(a, null, null, null)) { + if(q.predicate.equals(DataFactory.namedNode(ACL.default))) { + quads.push(DataFactory.quad(aBlank, DataFactory.namedNode(ACL.accessTo), DataFactory.namedNode(createdIdentifier.path))); + } else if(!q.predicate.equals(DataFactory.namedNode(ACL.accessTo))) { + quads.push(DataFactory.quad(aBlank, q.predicate, q.object)); + } + } + } + } + const aclIdentifier = this.aclStrategy.getAuxiliaryIdentifier(createdIdentifier); - const serializedQuads = await this.writeQuads(createdIdentifier, agentCredentials.agent.webId); + const serializedQuads = await this.writeQuads(quads); const representation = new BasicRepresentation(serializedQuads, new RepresentationMetadata(aclIdentifier, TEXT_TURTLE)); await this.store.setRepresentation(aclIdentifier, representation); } return new CreatedResponseDescription(createdIdentifier); } - private async writeQuads(createdIdentifier: ResourceIdentifier, agentWebId: string): Promise { + private async writeQuads(quads: Quad[]): Promise { return new Promise((resolve, reject) => { - const authorization = DataFactory.blankNode(); - const quads = [ - DataFactory.quad(authorization, DataFactory.namedNode(RDF.type) , DataFactory.namedNode(ACL.Authorization)), - DataFactory.quad(authorization, DataFactory.namedNode(ACL.accessTo), DataFactory.namedNode(createdIdentifier.path)), - DataFactory.quad(authorization, DataFactory.namedNode(ACL.agent), DataFactory.namedNode(agentWebId)), - DataFactory.quad(authorization, DataFactory.namedNode(ACL.mode), DataFactory.namedNode(ACL.Read)), - ]; const writer = new Writer(); writer.addQuads(quads); writer.end((err, result) => { @@ -103,4 +150,38 @@ export class CreatorPostOperationHandler extends OperationHandler { }) }); } + + private async getAclRecursive(identifier: ResourceIdentifier): Promise { + // Obtain the direct ACL document for the resource, if it exists + this.logger.debug(`Trying to read the direct ACL document of ${identifier.path}`); + + const acl = this.aclStrategy.getAuxiliaryIdentifier(identifier); + this.logger.debug(`Determining existence of ${acl.path}`); + if (await this.resourceSet.hasResource(acl)) { + this.logger.info(`Found applicable ACL document ${acl.path}`); + return acl; + } + this.logger.debug(`No direct ACL document found for ${identifier.path}`); + + // Find the applicable ACL document of the parent container + this.logger.debug(`Traversing to the parent of ${identifier.path}`); + if (this.identifierStrategy.isRootContainer(identifier)) { + this.logger.error(`No ACL document found for root container ${identifier.path}`); + // https://solidproject.org/TR/2021/wac-20210711#acl-resource-representation + // The root container MUST have an ACL resource with a representation. + throw new ForbiddenHttpError('No ACL document found for root container'); + } + const parent = this.identifierStrategy.getParentContainer(identifier); + return this.getAclRecursive(parent); + } + + private async filterStore(store: Store, target: string, directAcl: boolean): Promise { + // Find subjects that occur with a given predicate/object, and collect all their triples + const subjectData = new Store(); + const subjects = store.getSubjects(directAcl ? ACL.terms.accessTo : ACL.terms.default, target, null); + for (const subject of subjects) { + subjectData.addQuads(store.getQuads(subject, null, null, null)); + } + return subjectData; + } } From 0e9968c0353876166c505f3cedf92a5f55c59e01 Mon Sep 17 00:00:00 2001 From: Daniel Schraudner Date: Tue, 24 Oct 2023 17:01:46 +0200 Subject: [PATCH 3/7] Multiple access modes can be configured --- .../handler/components/operation-handler.json | 7 ++++++- src/http/ldp/CreatorPostOperationHandler.ts | 16 +++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/config/ldp/handler/components/operation-handler.json b/config/ldp/handler/components/operation-handler.json index 9e3084a189..97f1f047a6 100644 --- a/config/ldp/handler/components/operation-handler.json +++ b/config/ldp/handler/components/operation-handler.json @@ -13,7 +13,12 @@ { "@type": "CreatorPostOperationHandler", "store": { "@id": "urn:solid-server:default:ResourceStore" }, - "creatorContainerNames": [ "demands" ], + "creatorContainerNamesAndModes": [ + ["demands", [ + "Read", + "Append" + ]] + ], "aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" }, "credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" }, "resourceSet": { "@id": "urn:solid-server:default:CachedResourceSet" }, diff --git a/src/http/ldp/CreatorPostOperationHandler.ts b/src/http/ldp/CreatorPostOperationHandler.ts index ed363aaa30..5350b7c7ab 100644 --- a/src/http/ldp/CreatorPostOperationHandler.ts +++ b/src/http/ldp/CreatorPostOperationHandler.ts @@ -33,7 +33,7 @@ export class CreatorPostOperationHandler extends OperationHandler { protected readonly logger = getLoggerFor(this); private readonly store: ResourceStore; - private readonly creatorContainerNames: string[]; + private readonly creatorContainerNamesAndModes: any[]; private readonly aclStrategy: AuxiliaryIdentifierStrategy; private readonly credentialsExtractor: CredentialsExtractor; private readonly resourceSet: ResourceSet; @@ -42,7 +42,7 @@ export class CreatorPostOperationHandler extends OperationHandler { public constructor( store: ResourceStore, - creatorContainerNames: string[], + creatorContainerNamesAndModes: any[], aclStrategy: AuxiliaryIdentifierStrategy, credentialsExtractor: CredentialsExtractor, resourceSet: ResourceSet, @@ -51,7 +51,7 @@ export class CreatorPostOperationHandler extends OperationHandler { ) { super(); this.store = store; - this.creatorContainerNames = creatorContainerNames; + this.creatorContainerNamesAndModes = creatorContainerNamesAndModes; this.aclStrategy = aclStrategy; this.credentialsExtractor = credentialsExtractor; this.resourceSet = resourceSet; @@ -67,8 +67,8 @@ export class CreatorPostOperationHandler extends OperationHandler { throw new NotImplementedHttpError('This handler only handles POST requests to containers'); } let targetPath = trimTrailingSlashes(operation.target.path); - let isCreatorContainer = this.creatorContainerNames.map(c => targetPath.endsWith(c)).reduce((a, b) => a || b); - if(!isCreatorContainer) { + let creatorContainer = this.creatorContainerNamesAndModes.find(([c, _]) => targetPath.endsWith(c)); + if(!creatorContainer) { throw new NotImplementedHttpError('This handler only handles creator containers specified in the config'); } } @@ -105,13 +105,15 @@ export class CreatorPostOperationHandler extends OperationHandler { throw new InternalServerError(message, { cause: error }); } + let targetPath = trimTrailingSlashes(input.operation.target.path); + let accessModes = this.creatorContainerNamesAndModes.find(([c, _]) => targetPath.endsWith(c))![1]; const authorization = DataFactory.blankNode(); + let accessModeQuads: Quad[] = accessModes.map((m: any) => DataFactory.quad(authorization, DataFactory.namedNode(ACL.mode), DataFactory.namedNode(ACL.namespace + (m as String)))); const quads = [ DataFactory.quad(authorization, DataFactory.namedNode(RDF.type) , DataFactory.namedNode(ACL.Authorization)), DataFactory.quad(authorization, DataFactory.namedNode(ACL.accessTo), DataFactory.namedNode(createdIdentifier.path)), DataFactory.quad(authorization, DataFactory.namedNode(ACL.agent), DataFactory.namedNode(agentCredentials.agent.webId)), - DataFactory.quad(authorization, DataFactory.namedNode(ACL.mode), DataFactory.namedNode(ACL.Read)), - ]; + ].concat(accessModeQuads); const subject = this.aclStrategy.getSubjectIdentifier(effectiveAclIdentifier); //is Subject From 0e4b3b85177e5e5537d0ba718fd4925067d789df Mon Sep 17 00:00:00 2001 From: Daniel Schraudner Date: Wed, 25 Oct 2023 10:21:38 +0200 Subject: [PATCH 4/7] Config stuff to extra file --- config/default.json | 2 +- config/dynamic.json | 2 +- config/example-https-file.json | 2 +- config/file-acp.json | 2 +- config/file-root-pod.json | 2 +- config/file-root.json | 2 +- config/file.json | 2 +- config/https-file-cli.json | 2 +- .../operation-handler-with-creator.json | 57 +++++++++++++++++++ .../handler/components/operation-handler.json | 17 +----- config/ldp/handler/with-creator.json | 36 ++++++++++++ config/memory-subdomains.json | 2 +- config/path-routing.json | 2 +- config/quota-file.json | 2 +- config/restrict-idp.json | 2 +- config/sparql-endpoint-root.json | 2 +- config/sparql-endpoint.json | 2 +- config/sparql-file-storage.json | 2 +- 18 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 config/ldp/handler/components/operation-handler-with-creator.json create mode 100644 config/ldp/handler/with-creator.json diff --git a/config/default.json b/config/default.json index 459dde98a8..ce577ef6a4 100644 --- a/config/default.json +++ b/config/default.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator.json", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/dynamic.json b/config/dynamic.json index ba29451643..6e087dcefb 100644 --- a/config/dynamic.json +++ b/config/dynamic.json @@ -17,7 +17,7 @@ "css:config/identity/pod/dynamic.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator.json", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/example-https-file.json b/config/example-https-file.json index 7bab273802..e8d2d884af 100644 --- a/config/example-https-file.json +++ b/config/example-https-file.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator.json", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/file-acp.json b/config/file-acp.json index 0e823c4c1d..feaed12539 100644 --- a/config/file-acp.json +++ b/config/file-acp.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/acp.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator.json", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/file-root-pod.json b/config/file-root-pod.json index cdadbf003c..17041b139e 100644 --- a/config/file-root-pod.json +++ b/config/file-root-pod.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/file-root.json b/config/file-root.json index 806af9e2d8..ce3685c891 100644 --- a/config/file-root.json +++ b/config/file-root.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/file.json b/config/file.json index e4f5a5b479..565b37d904 100644 --- a/config/file.json +++ b/config/file.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/https-file-cli.json b/config/https-file-cli.json index fe0da1f162..b3c27cb1c0 100644 --- a/config/https-file-cli.json +++ b/config/https-file-cli.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/ldp/handler/components/operation-handler-with-creator.json b/config/ldp/handler/components/operation-handler-with-creator.json new file mode 100644 index 0000000000..97f1f047a6 --- /dev/null +++ b/config/ldp/handler/components/operation-handler-with-creator.json @@ -0,0 +1,57 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld", + "@graph": [ + { + "@id": "urn:solid-server:default:OperationHandler", + "@type": "WaterfallHandler", + "handlers": [ + { + "@type": "GetOperationHandler", + "store": { "@id": "urn:solid-server:default:ResourceStore" }, + "eTagHandler": { "@id": "urn:solid-server:default:ETagHandler" } + }, + { + "@type": "CreatorPostOperationHandler", + "store": { "@id": "urn:solid-server:default:ResourceStore" }, + "creatorContainerNamesAndModes": [ + ["demands", [ + "Read", + "Append" + ]] + ], + "aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" }, + "credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" }, + "resourceSet": { "@id": "urn:solid-server:default:CachedResourceSet" }, + "identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }, + "aclStore": { "@id": "urn:solid-server:default:ResourceStore" } + }, + { + "@type": "PostOperationHandler", + "store": { "@id": "urn:solid-server:default:ResourceStore" } + }, + { + "@type": "PutOperationHandler", + "store": { "@id": "urn:solid-server:default:ResourceStore" }, + "metadataStrategy":{ "@id": "urn:solid-server:default:MetadataStrategy" } + }, + { + "@type": "DeleteOperationHandler", + "store": { "@id": "urn:solid-server:default:ResourceStore" } + }, + { + "@type": "HeadOperationHandler", + "store": { "@id": "urn:solid-server:default:ResourceStore" }, + "eTagHandler": { "@id": "urn:solid-server:default:ETagHandler" } + }, + { + "@type": "PatchOperationHandler", + "store": { "@id": "urn:solid-server:default:ResourceStore" } + }, + { + "@type": "StaticThrowHandler", + "error": { "@type": "MethodNotAllowedHttpError" } + } + ] + } + ] +} diff --git a/config/ldp/handler/components/operation-handler.json b/config/ldp/handler/components/operation-handler.json index 97f1f047a6..dc81f70ae0 100644 --- a/config/ldp/handler/components/operation-handler.json +++ b/config/ldp/handler/components/operation-handler.json @@ -10,21 +10,6 @@ "store": { "@id": "urn:solid-server:default:ResourceStore" }, "eTagHandler": { "@id": "urn:solid-server:default:ETagHandler" } }, - { - "@type": "CreatorPostOperationHandler", - "store": { "@id": "urn:solid-server:default:ResourceStore" }, - "creatorContainerNamesAndModes": [ - ["demands", [ - "Read", - "Append" - ]] - ], - "aclStrategy": { "@id": "urn:solid-server:default:AclStrategy" }, - "credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" }, - "resourceSet": { "@id": "urn:solid-server:default:CachedResourceSet" }, - "identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }, - "aclStore": { "@id": "urn:solid-server:default:ResourceStore" } - }, { "@type": "PostOperationHandler", "store": { "@id": "urn:solid-server:default:ResourceStore" } @@ -54,4 +39,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/config/ldp/handler/with-creator.json b/config/ldp/handler/with-creator.json new file mode 100644 index 0000000000..48128225b7 --- /dev/null +++ b/config/ldp/handler/with-creator.json @@ -0,0 +1,36 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld", + "import": [ + "css:config/ldp/handler/components/authorizer.json", + "css:config/ldp/handler/components/error-handler.json", + "css:config/ldp/handler/components/operation-handler-with-creator.json", + "css:config/ldp/handler/components/operation-metadata.json", + "css:config/ldp/handler/components/preferences.json", + "css:config/ldp/handler/components/request-parser.json", + "css:config/ldp/handler/components/response-writer.json" + ], + "@graph": [ + { + "comment": "The main entry point into the main Solid behaviour.", + "@id": "urn:solid-server:default:LdpHandler", + "@type": "ParsingHttpHandler", + "args_requestParser": { "@id": "urn:solid-server:default:RequestParser" }, + "args_errorHandler": { "@id": "urn:solid-server:default:ErrorHandler" }, + "args_responseWriter": { "@id": "urn:solid-server:default:ResponseWriter" }, + "args_operationHandler": { + "@type": "AuthorizingHttpHandler", + "args_credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" }, + "args_modesExtractor": { "@id": "urn:solid-server:default:ModesExtractor" }, + "args_permissionReader": { "@id": "urn:solid-server:default:PermissionReader" }, + "args_authorizer": { "@id": "urn:solid-server:default:Authorizer" }, + "args_operationHandler": { + "@type": "WacAllowHttpHandler", + "args_credentialsExtractor": { "@id": "urn:solid-server:default:CredentialsExtractor" }, + "args_modesExtractor": { "@id": "urn:solid-server:default:ModesExtractor" }, + "args_permissionReader": { "@id": "urn:solid-server:default:PermissionReader" }, + "args_operationHandler": { "@id": "urn:solid-server:default:OperationHandler" } + } + } + } + ] +} diff --git a/config/memory-subdomains.json b/config/memory-subdomains.json index b2e4ce30b1..9ef32f8d6e 100644 --- a/config/memory-subdomains.json +++ b/config/memory-subdomains.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/path-routing.json b/config/path-routing.json index 8fed00381b..4e7b8d5692 100644 --- a/config/path-routing.json +++ b/config/path-routing.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/quota-file.json b/config/quota-file.json index a374790c46..ec15d2bc55 100644 --- a/config/quota-file.json +++ b/config/quota-file.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/restrict-idp.json b/config/restrict-idp.json index 8752fc88f1..b2cf183034 100644 --- a/config/restrict-idp.json +++ b/config/restrict-idp.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/sparql-endpoint-root.json b/config/sparql-endpoint-root.json index c864db84f0..ea7a31125c 100644 --- a/config/sparql-endpoint-root.json +++ b/config/sparql-endpoint-root.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/sparql-endpoint.json b/config/sparql-endpoint.json index 314d03dc27..edb06e50e0 100644 --- a/config/sparql-endpoint.json +++ b/config/sparql-endpoint.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", diff --git a/config/sparql-file-storage.json b/config/sparql-file-storage.json index 8e994933a9..a46663b1ef 100644 --- a/config/sparql-file-storage.json +++ b/config/sparql-file-storage.json @@ -17,7 +17,7 @@ "css:config/identity/pod/static.json", "css:config/ldp/authentication/dpop-bearer.json", "css:config/ldp/authorization/webacl.json", - "css:config/ldp/handler/default.json", + "css:config/ldp/handler/with-creator", "css:config/ldp/metadata-parser/default.json", "css:config/ldp/metadata-writer/default.json", "css:config/ldp/modes/default.json", From 103d2c002df1897d74c0e362ecc21f1c7ee5b2d1 Mon Sep 17 00:00:00 2001 From: Daniel Schraudner Date: Wed, 25 Oct 2023 10:38:18 +0200 Subject: [PATCH 5/7] Some documentation --- README.md | 8 +++++++- .../markdown/mandat/creator-post-handler.md | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 documentation/markdown/mandat/creator-post-handler.md diff --git a/README.md b/README.md index 26b5708ef3..f72bdf3fb1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -# Community Solid Server +# Community Solid Server - MANDAT Fork + +## Features added for MANDAT +- A handler for POST requests (creating a resource) that allows the creator of the resource to automatically have access to the resource by creating the corresponding ACL ([further explanation](/documentation/markdown/mandat/creator-post-handler.md)) + + +## Community Solid Server [Solid logo] diff --git a/documentation/markdown/mandat/creator-post-handler.md b/documentation/markdown/mandat/creator-post-handler.md new file mode 100644 index 0000000000..99540b64c0 --- /dev/null +++ b/documentation/markdown/mandat/creator-post-handler.md @@ -0,0 +1,15 @@ +# Creator Post Handler +A handler for POST requests (creating a resource) that allows the creator of the resource to automatically have access to the resource by creating the corresponding ACL + +## Config +The handlers we are interested in are configured in [config/ldp/handlers/components/operation-handler.json](config/ldp/handler/components/operation-handler.json). We copied the file to [config/ldp/handlers/components/operation-handler-with-creator.json](config/ldp/handler/components/operation-handler-with-creator.json) and added a `CreatorPostOperationHandler` to the waterfall handler before the normal `PostOperationHandler`. + +The `CreatorPostOperationHandler` is configured as follows: Under `creatorContainerNamesAndModes` we can add an array of pairs where the first element of the pair is the name of the container to which the handler should apply and the second element of the pair is a list of ACL access modes that should be given to the creator of resources in this container. + +To use [config/ldp/handlers/components/operation-handler-with-creator.json](config/ldp/handler/components/operation-handler-with-creator.json) instead of [config/ldp/handlers/components/operation-handler.json](config/ldp/handler/components/operation-handler.json) we also created the config file [config/ldp/handler/with-creator.json](config/ldp/handler/with-creator.json) as alternative to [config/ldp/handler/default.json](config/ldp/handler/default.json) and added it to the provided global config files (e.g. [config/default.json](config/default.json), [config/file.json](config/file.json)). + +## Code +All implementation has been done in the class [src/http/ldp/CreatorPostOperationHandler.ts](src/http/ldp/CreatorPostOperationHandler.ts). + +## Contributors +[dschraudner](https://github.com/dschraudner), [se-schmid](https://github.com/se-schmid), [kaefer3000](https://github.com/kaefer3000) \ No newline at end of file From ae576085801ae680f23114e0a3fb5e22834b7e50 Mon Sep 17 00:00:00 2001 From: Daniel Schraudner Date: Wed, 25 Oct 2023 10:50:34 +0200 Subject: [PATCH 6/7] Comments to code --- src/http/ldp/CreatorPostOperationHandler.ts | 56 +++++++++++---------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/http/ldp/CreatorPostOperationHandler.ts b/src/http/ldp/CreatorPostOperationHandler.ts index 5350b7c7ab..f4f6e948dc 100644 --- a/src/http/ldp/CreatorPostOperationHandler.ts +++ b/src/http/ldp/CreatorPostOperationHandler.ts @@ -18,7 +18,6 @@ import { OperationHttpHandlerInput } from '../../server/OperationHttpHandler'; import { RepresentationMetadata } from '../../http/representation/RepresentationMetadata'; import { BasicRepresentation } from '../../http/representation/BasicRepresentation'; import { INTERNAL_QUADS, TEXT_TURTLE } from '../../util/ContentTypes'; -import { PermissionReader } from '../../authorization/PermissionReader'; import { ResourceIdentifier } from '../representation/ResourceIdentifier'; import { ResourceSet } from '../../storage/ResourceSet'; import { IdentifierStrategy } from '../../util/identifiers/IdentifierStrategy'; @@ -66,6 +65,8 @@ export class CreatorPostOperationHandler extends OperationHandler { if (!isContainerIdentifier(operation.target)) { throw new NotImplementedHttpError('This handler only handles POST requests to containers'); } + + // Check whether the container identifier ends with one of the configured strings to see if we are responsible let targetPath = trimTrailingSlashes(operation.target.path); let creatorContainer = this.creatorContainerNamesAndModes.find(([c, _]) => targetPath.endsWith(c)); if(!creatorContainer) { @@ -74,12 +75,9 @@ export class CreatorPostOperationHandler extends OperationHandler { } public async handle(input: OperationHttpHandlerInput): Promise { + // Solid stuff that the normal POST handler also does const type = new Set(input.operation.body.metadata.getAll(RDF.terms.type).map((term: Term): string => term.value)); const isContainerType = type.has(LDP.Container) || type.has(LDP.BasicContainer); - // Solid, §2.1: "A Solid server MUST reject PUT, POST and PATCH requests - // without the Content-Type header with a status code of 400." - // https://solid.github.io/specification/protocol#http-server - // An exception is made for LDP Containers as nothing is done with the body, so a Content-type is not required if (!input.operation.body.metadata.contentType && !isContainerType) { this.logger.warn('POST requests require the Content-Type header to be set'); throw new BadRequestHttpError('POST requests require the Content-Type header to be set'); @@ -90,10 +88,25 @@ export class CreatorPostOperationHandler extends OperationHandler { if (!createdIdentifier) { throw new InternalServerError('Operation was successful but no created identifier was returned.'); } + + // We need the WebId of the agent that executed the POST request const agentCredentials = await this.credentialsExtractor.handle(input.request); + if(agentCredentials.agent) { - const effectiveAclIdentifier = await this.getAclRecursive(createdIdentifier); + // Create quads for ACL with configured access rights for agent the executed the POST request + let targetPath = trimTrailingSlashes(input.operation.target.path); + let accessModes = this.creatorContainerNamesAndModes.find(([c, _]) => targetPath.endsWith(c))![1]; + const authorization = DataFactory.blankNode(); + let accessModeQuads: Quad[] = accessModes.map((m: any) => DataFactory.quad(authorization, DataFactory.namedNode(ACL.mode), DataFactory.namedNode(ACL.namespace + (m as String)))); + const quads = [ + DataFactory.quad(authorization, DataFactory.namedNode(RDF.type) , DataFactory.namedNode(ACL.Authorization)), + DataFactory.quad(authorization, DataFactory.namedNode(ACL.accessTo), DataFactory.namedNode(createdIdentifier.path)), + DataFactory.quad(authorization, DataFactory.namedNode(ACL.agent), DataFactory.namedNode(agentCredentials.agent.webId)), + ].concat(accessModeQuads); + // Look for the ACL that currently is the effective ACL for the newly created resource and parse the triples + // to be able to copy the currently existing access rights to the new ACL + const effectiveAclIdentifier = await this.getAclRecursive(createdIdentifier); let contents: Store; try { const data = await this.aclStore.getRepresentation(effectiveAclIdentifier, { type: { [INTERNAL_QUADS]: 1 }}); @@ -105,18 +118,10 @@ export class CreatorPostOperationHandler extends OperationHandler { throw new InternalServerError(message, { cause: error }); } - let targetPath = trimTrailingSlashes(input.operation.target.path); - let accessModes = this.creatorContainerNamesAndModes.find(([c, _]) => targetPath.endsWith(c))![1]; - const authorization = DataFactory.blankNode(); - let accessModeQuads: Quad[] = accessModes.map((m: any) => DataFactory.quad(authorization, DataFactory.namedNode(ACL.mode), DataFactory.namedNode(ACL.namespace + (m as String)))); - const quads = [ - DataFactory.quad(authorization, DataFactory.namedNode(RDF.type) , DataFactory.namedNode(ACL.Authorization)), - DataFactory.quad(authorization, DataFactory.namedNode(ACL.accessTo), DataFactory.namedNode(createdIdentifier.path)), - DataFactory.quad(authorization, DataFactory.namedNode(ACL.agent), DataFactory.namedNode(agentCredentials.agent.webId)), - ].concat(accessModeQuads); - + // From the old effective ACL throw away all triples with predicate acl:access as they were not meant for the + // new resource. acl:default on the other hand affect the new resource so they become _:authorization acl:accessTo + // . const subject = this.aclStrategy.getSubjectIdentifier(effectiveAclIdentifier); - //is Subject if(createdIdentifier.path !== subject.path) { const authorizations = contents.getSubjects(DataFactory.namedNode(ACL.default), null, null); for(let a of authorizations) { @@ -131,6 +136,7 @@ export class CreatorPostOperationHandler extends OperationHandler { } } + // Write quads to ACL for new resource const aclIdentifier = this.aclStrategy.getAuxiliaryIdentifier(createdIdentifier); const serializedQuads = await this.writeQuads(quads); const representation = new BasicRepresentation(serializedQuads, new RepresentationMetadata(aclIdentifier, TEXT_TURTLE)); @@ -139,6 +145,9 @@ export class CreatorPostOperationHandler extends OperationHandler { return new CreatedResponseDescription(createdIdentifier); } + /* + Auxiliary method for serializing quads without callback + */ private async writeQuads(quads: Quad[]): Promise { return new Promise((resolve, reject) => { const writer = new Writer(); @@ -153,6 +162,9 @@ export class CreatorPostOperationHandler extends OperationHandler { }); } + /* + Auxiliary method for finding the effective ACL of a resource. Copied from another class... + */ private async getAclRecursive(identifier: ResourceIdentifier): Promise { // Obtain the direct ACL document for the resource, if it exists this.logger.debug(`Trying to read the direct ACL document of ${identifier.path}`); @@ -176,14 +188,4 @@ export class CreatorPostOperationHandler extends OperationHandler { const parent = this.identifierStrategy.getParentContainer(identifier); return this.getAclRecursive(parent); } - - private async filterStore(store: Store, target: string, directAcl: boolean): Promise { - // Find subjects that occur with a given predicate/object, and collect all their triples - const subjectData = new Store(); - const subjects = store.getSubjects(directAcl ? ACL.terms.accessTo : ACL.terms.default, target, null); - for (const subject of subjects) { - subjectData.addQuads(store.getQuads(subject, null, null, null)); - } - return subjectData; - } } From cfe49f50b1cce03ceb8ebf62ad4de3e287555a65 Mon Sep 17 00:00:00 2001 From: Daniel Schraudner Date: Wed, 25 Oct 2023 10:59:56 +0200 Subject: [PATCH 7/7] Fixed realtive paths in documentation --- documentation/markdown/mandat/creator-post-handler.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/markdown/mandat/creator-post-handler.md b/documentation/markdown/mandat/creator-post-handler.md index 99540b64c0..d5a1e8774d 100644 --- a/documentation/markdown/mandat/creator-post-handler.md +++ b/documentation/markdown/mandat/creator-post-handler.md @@ -2,14 +2,14 @@ A handler for POST requests (creating a resource) that allows the creator of the resource to automatically have access to the resource by creating the corresponding ACL ## Config -The handlers we are interested in are configured in [config/ldp/handlers/components/operation-handler.json](config/ldp/handler/components/operation-handler.json). We copied the file to [config/ldp/handlers/components/operation-handler-with-creator.json](config/ldp/handler/components/operation-handler-with-creator.json) and added a `CreatorPostOperationHandler` to the waterfall handler before the normal `PostOperationHandler`. +The handlers we are interested in are configured in [/config/ldp/handlers/components/operation-handler.json](../../../config/ldp/handler/components/operation-handler.json). We copied the file to [/config/ldp/handlers/components/operation-handler-with-creator.json](../../../config/ldp/handler/components/operation-handler-with-creator.json) and added a `CreatorPostOperationHandler` to the waterfall handler before the normal `PostOperationHandler`. The `CreatorPostOperationHandler` is configured as follows: Under `creatorContainerNamesAndModes` we can add an array of pairs where the first element of the pair is the name of the container to which the handler should apply and the second element of the pair is a list of ACL access modes that should be given to the creator of resources in this container. -To use [config/ldp/handlers/components/operation-handler-with-creator.json](config/ldp/handler/components/operation-handler-with-creator.json) instead of [config/ldp/handlers/components/operation-handler.json](config/ldp/handler/components/operation-handler.json) we also created the config file [config/ldp/handler/with-creator.json](config/ldp/handler/with-creator.json) as alternative to [config/ldp/handler/default.json](config/ldp/handler/default.json) and added it to the provided global config files (e.g. [config/default.json](config/default.json), [config/file.json](config/file.json)). +To use [/config/ldp/handlers/components/operation-handler-with-creator.json](../../../config/ldp/handler/components/operation-handler-with-creator.json) instead of [/config/ldp/handlers/components/operation-handler.json](../../../config/ldp/handler/components/operation-handler.json) we also created the config file [/config/ldp/handler/with-creator.json](../../../config/ldp/handler/with-creator.json) as alternative to [/config/ldp/handler/default.json](../../../config/ldp/handler/default.json) and added it to the provided global config files (e.g. [/config/default.json](../../../config/default.json), [/config/file.json](../../../config/file.json)). ## Code -All implementation has been done in the class [src/http/ldp/CreatorPostOperationHandler.ts](src/http/ldp/CreatorPostOperationHandler.ts). +All implementation has been done in the class [/src/http/ldp/CreatorPostOperationHandler.ts](../../../src/http/ldp/CreatorPostOperationHandler.ts). ## Contributors [dschraudner](https://github.com/dschraudner), [se-schmid](https://github.com/se-schmid), [kaefer3000](https://github.com/kaefer3000) \ No newline at end of file