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
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 98ba4f99f6..dc81f70ae0 100644
--- a/config/ldp/handler/components/operation-handler.json
+++ b/config/ldp/handler/components/operation-handler.json
@@ -39,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",
diff --git a/documentation/markdown/mandat/creator-post-handler.md b/documentation/markdown/mandat/creator-post-handler.md
new file mode 100644
index 0000000000..d5a1e8774d
--- /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
diff --git a/src/http/ldp/CreatorPostOperationHandler.ts b/src/http/ldp/CreatorPostOperationHandler.ts
new file mode 100644
index 0000000000..f4f6e948dc
--- /dev/null
+++ b/src/http/ldp/CreatorPostOperationHandler.ts
@@ -0,0 +1,191 @@
+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 { AuxiliaryIdentifierStrategy } from '../../http/auxiliary/AuxiliaryIdentifierStrategy';
+import { isContainerIdentifier, trimTrailingSlashes } from '../../util/PathUtil';
+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 { INTERNAL_QUADS, TEXT_TURTLE } from '../../util/ContentTypes';
+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.
+ * Calls the addResource function from a {@link ResourceStore}.
+ */
+export class CreatorPostOperationHandler extends OperationHandler {
+ protected readonly logger = getLoggerFor(this);
+
+ private readonly store: ResourceStore;
+ private readonly creatorContainerNamesAndModes: any[];
+ private readonly aclStrategy: AuxiliaryIdentifierStrategy;
+ private readonly credentialsExtractor: CredentialsExtractor;
+ private readonly resourceSet: ResourceSet;
+ private readonly identifierStrategy: IdentifierStrategy;
+ private readonly aclStore: ResourceStore;
+
+ public constructor(
+ store: ResourceStore,
+ creatorContainerNamesAndModes: any[],
+ aclStrategy: AuxiliaryIdentifierStrategy,
+ credentialsExtractor: CredentialsExtractor,
+ resourceSet: ResourceSet,
+ identifierStrategy: IdentifierStrategy,
+ aclStore: ResourceStore
+ ) {
+ super();
+ this.store = store;
+ this.creatorContainerNamesAndModes = creatorContainerNamesAndModes;
+ this.aclStrategy = aclStrategy;
+ this.credentialsExtractor = credentialsExtractor;
+ this.resourceSet = resourceSet;
+ this.identifierStrategy = identifierStrategy;
+ this.aclStore = aclStore;
+ }
+
+ 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');
+ }
+
+ // 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) {
+ throw new NotImplementedHttpError('This handler only handles creator containers specified in the config');
+ }
+ }
+
+ 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);
+ 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.');
+ }
+
+ // We need the WebId of the agent that executed the POST request
+ const agentCredentials = await this.credentialsExtractor.handle(input.request);
+
+ if(agentCredentials.agent) {
+ // 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 }});
+ 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 });
+ }
+
+ // 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);
+ 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));
+ }
+ }
+ }
+ }
+
+ // 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));
+ await this.store.setRepresentation(aclIdentifier, representation);
+ }
+ 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();
+ writer.addQuads(quads);
+ writer.end((err, result) => {
+ if(err) {
+ reject(err);
+ } else {
+ resolve(result);
+ }
+ })
+ });
+ }
+
+ /*
+ 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}`);
+
+ 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);
+ }
+}
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