diff --git a/bin/admin/ontologies/add-construct.sh b/bin/admin/ontologies/add-constructor.sh similarity index 100% rename from bin/admin/ontologies/add-construct.sh rename to bin/admin/ontologies/add-constructor.sh diff --git a/http-tests/admin/packages/install-uninstall-package-ontology.sh b/http-tests/admin/packages/install-uninstall-package-ontology.sh index b96db04fd..9f5fa2330 100755 --- a/http-tests/admin/packages/install-uninstall-package-ontology.sh +++ b/http-tests/admin/packages/install-uninstall-package-ontology.sh @@ -58,4 +58,4 @@ fi curl -k -w "%{http_code}\n" -o /dev/null -s \ -E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \ "${ADMIN_BASE_URL}ontologies/${package_ontology_hash}/" \ -| grep -q "$STATUS_FORBIDDEN" +| grep -q "$STATUS_NOT_FOUND" diff --git a/http-tests/document-hierarchy/DELETE-no-parent-403.sh b/http-tests/document-hierarchy/DELETE-404.sh similarity index 66% rename from http-tests/document-hierarchy/DELETE-no-parent-403.sh rename to http-tests/document-hierarchy/DELETE-404.sh index cf10ffd5b..e5b99b11a 100755 --- a/http-tests/document-hierarchy/DELETE-no-parent-403.sh +++ b/http-tests/document-hierarchy/DELETE-404.sh @@ -15,11 +15,20 @@ add-agent-to-group.sh \ --agent "$AGENT_URI" \ "${ADMIN_BASE_URL}acl/groups/writers/" -# check that graph without parent is forbidden +# check that non-existing document is not found + +curl -k -w "%{http_code}\n" -o /dev/null -s -G \ + -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ + -X DELETE \ + -H "Accept: application/n-triples" \ + "${END_USER_BASE_URL}non-existing/" \ +| grep -q "$STATUS_NOT_FOUND" + +# check that document without parent is not found curl -k -w "%{http_code}\n" -o /dev/null -s -G \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ -X DELETE \ -H "Accept: application/n-triples" \ "${END_USER_BASE_URL}parent/non-existing/" \ -| grep -q "$STATUS_FORBIDDEN" \ No newline at end of file +| grep -q "$STATUS_NOT_FOUND" diff --git a/http-tests/document-hierarchy/DELETE.sh b/http-tests/document-hierarchy/DELETE.sh index 5f784f27c..8ffd924f4 100755 --- a/http-tests/document-hierarchy/DELETE.sh +++ b/http-tests/document-hierarchy/DELETE.sh @@ -45,4 +45,4 @@ curl -k -w "%{http_code}\n" -o /dev/null -s -G \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ -H "Accept: application/n-triples" \ "$container" \ -| grep -q "$STATUS_FORBIDDEN" \ No newline at end of file +| grep -q "$STATUS_NOT_FOUND" diff --git a/http-tests/document-hierarchy/DELETE-non-existing-403.sh b/http-tests/document-hierarchy/GET-404.sh similarity index 87% rename from http-tests/document-hierarchy/DELETE-non-existing-403.sh rename to http-tests/document-hierarchy/GET-404.sh index 25d67b5a9..de39593de 100755 --- a/http-tests/document-hierarchy/DELETE-non-existing-403.sh +++ b/http-tests/document-hierarchy/GET-404.sh @@ -15,11 +15,10 @@ add-agent-to-group.sh \ --agent "$AGENT_URI" \ "${ADMIN_BASE_URL}acl/groups/writers/" -# check that access to non-existing graph is forbidden +# check that non-existing document is not found curl -k -w "%{http_code}\n" -o /dev/null -s -G \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ - -X DELETE \ -H "Accept: application/n-triples" \ "${END_USER_BASE_URL}non-existing/" \ -| grep -q "$STATUS_FORBIDDEN" \ No newline at end of file +| grep -q "$STATUS_NOT_FOUND" \ No newline at end of file diff --git a/http-tests/document-hierarchy/GET-non-existing-403.sh b/http-tests/document-hierarchy/GET-non-existing-403.sh deleted file mode 100755 index 07e1b3d61..000000000 --- a/http-tests/document-hierarchy/GET-non-existing-403.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -initialize_dataset "$END_USER_BASE_URL" "$TMP_END_USER_DATASET" "$END_USER_ENDPOINT_URL" -initialize_dataset "$ADMIN_BASE_URL" "$TMP_ADMIN_DATASET" "$ADMIN_ENDPOINT_URL" -purge_cache "$END_USER_VARNISH_SERVICE" -purge_cache "$ADMIN_VARNISH_SERVICE" -purge_cache "$FRONTEND_VARNISH_SERVICE" - -# add agent to the writers - -add-agent-to-group.sh \ - -f "$OWNER_CERT_FILE" \ - -p "$OWNER_CERT_PWD" \ - --agent "$AGENT_URI" \ - "${ADMIN_BASE_URL}acl/groups/writers/" - -# check that access to graph with parent is allowed, but the graph is not found - -curl -k -w "%{http_code}\n" -o /dev/null -s -G \ - -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ - -H "Accept: application/n-triples" \ - "${END_USER_BASE_URL}non-existing/" \ -| grep -q "$STATUS_FORBIDDEN" \ No newline at end of file diff --git a/http-tests/document-hierarchy/PATCH-non-existing-403.sh b/http-tests/document-hierarchy/PATCH-404.sh similarity index 97% rename from http-tests/document-hierarchy/PATCH-non-existing-403.sh rename to http-tests/document-hierarchy/PATCH-404.sh index 6bc4ecfc5..c8055110b 100755 --- a/http-tests/document-hierarchy/PATCH-non-existing-403.sh +++ b/http-tests/document-hierarchy/PATCH-404.sh @@ -37,4 +37,4 @@ curl -k -w "%{http_code}\n" -o /dev/null -s \ "${END_USER_BASE_URL}non-existing/" \ --data-binary "$update" ) \ -| grep -q "$STATUS_FORBIDDEN" \ No newline at end of file +| grep -q "$STATUS_NOT_FOUND" \ No newline at end of file diff --git a/http-tests/document-hierarchy/PATCH-empty-container.sh b/http-tests/document-hierarchy/PATCH-empty-container.sh index 5b95eb841..f61f40dad 100755 --- a/http-tests/document-hierarchy/PATCH-empty-container.sh +++ b/http-tests/document-hierarchy/PATCH-empty-container.sh @@ -32,11 +32,11 @@ container=$(create-container.sh \ update=$(cat < ?p ?o + ?s ?p ?o } WHERE { - <${container}> ?p ?o + ?s ?p ?o } EOF ) @@ -55,4 +55,4 @@ curl -k -w "%{http_code}\n" -o /dev/null -s \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ -H "Accept: application/n-triples" \ "$container" \ -| grep -q "$STATUS_FORBIDDEN" +| grep -q "$STATUS_NOT_FOUND" diff --git a/http-tests/document-hierarchy/PATCH-empty-item.sh b/http-tests/document-hierarchy/PATCH-empty-item.sh index 26737efee..6f4d02978 100755 --- a/http-tests/document-hierarchy/PATCH-empty-item.sh +++ b/http-tests/document-hierarchy/PATCH-empty-item.sh @@ -32,11 +32,11 @@ item=$(create-item.sh \ update=$(cat < ?p ?o + ?s ?p ?o } WHERE { - <${item}> ?p ?o + ?s ?p ?o } EOF ) @@ -55,4 +55,4 @@ curl -k -w "%{http_code}\n" -o /dev/null -s \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ -H "Accept: application/n-triples" \ "$item" \ -| grep -q "$STATUS_FORBIDDEN" +| grep -q "$STATUS_NOT_FOUND" diff --git a/http-tests/document-hierarchy/POST-non-existing-403.sh b/http-tests/document-hierarchy/POST-404.sh similarity index 89% rename from http-tests/document-hierarchy/POST-non-existing-403.sh rename to http-tests/document-hierarchy/POST-404.sh index c5d17178d..2cf0fccf9 100755 --- a/http-tests/document-hierarchy/POST-non-existing-403.sh +++ b/http-tests/document-hierarchy/POST-404.sh @@ -15,7 +15,7 @@ add-agent-to-group.sh \ --agent "$AGENT_URI" \ "${ADMIN_BASE_URL}acl/groups/writers/" -# check that access to non-existing graph is forbidden +# check that non-existing document is not found ( curl -k -w "%{http_code}\n" -o /dev/null -s \ @@ -27,4 +27,4 @@ curl -k -w "%{http_code}\n" -o /dev/null -s \ . EOF ) \ -| grep -q "$STATUS_FORBIDDEN" \ No newline at end of file +| grep -q "$STATUS_NOT_FOUND" diff --git a/http-tests/document-hierarchy/PUT-double-slash-uri-400.sh b/http-tests/document-hierarchy/PUT-double-slash-uri-400.sh index 09585116f..23ffd6883 100755 --- a/http-tests/document-hierarchy/PUT-double-slash-uri-400.sh +++ b/http-tests/document-hierarchy/PUT-double-slash-uri-400.sh @@ -15,9 +15,20 @@ add-agent-to-group.sh \ --agent "$AGENT_URI" \ "${ADMIN_BASE_URL}acl/groups/writers/" +# create a container - IRIx resolves ".." on "new-item//" to "new-item/" (one segment per slash), +# so the parent container must exist for authorization to pass and reach the // validation in put() + +container=$(create-container.sh \ + -f "$AGENT_CERT_FILE" \ + -p "$AGENT_CERT_PWD" \ + -b "$END_USER_BASE_URL" \ + --title "Test Container" \ + --slug "new-item" \ + --parent "$END_USER_BASE_URL") + # creating new document fails because URIs with double slashes are not allowed -item="${END_USER_BASE_URL}new-item//" +item="${container}/" ( curl -k -w "%{http_code}\n" -o /dev/null -s \ diff --git a/http-tests/document-hierarchy/PUT-no-slash-308.sh b/http-tests/document-hierarchy/PUT-no-slash-308.sh index f4507ede9..e0b6ae1ce 100755 --- a/http-tests/document-hierarchy/PUT-no-slash-308.sh +++ b/http-tests/document-hierarchy/PUT-no-slash-308.sh @@ -7,6 +7,14 @@ purge_cache "$END_USER_VARNISH_SERVICE" purge_cache "$ADMIN_VARNISH_SERVICE" purge_cache "$FRONTEND_VARNISH_SERVICE" +# add agent to the writers group + +add-agent-to-group.sh \ + -f "$OWNER_CERT_FILE" \ + -p "$OWNER_CERT_PWD" \ + --agent "$AGENT_URI" \ + "${ADMIN_BASE_URL}acl/groups/writers/" + # add an explicit read/write authorization for the parent since the child document will inherit it create-authorization.sh \ @@ -19,10 +27,10 @@ create-authorization.sh \ --read \ --write -invalid_item="${END_USER_BASE_URL}no-slash" - # check URI without trailing slash gets redirected +invalid_item="${END_USER_BASE_URL}no-slash" + ( curl -k -w "%{http_code}\n" -o /dev/null -s \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ diff --git a/http-tests/proxy/GET-proxied-403.sh b/http-tests/proxy/GET-proxied-404.sh similarity index 61% rename from http-tests/proxy/GET-proxied-403.sh rename to http-tests/proxy/GET-proxied-404.sh index 04eadb652..ef695402f 100755 --- a/http-tests/proxy/GET-proxied-403.sh +++ b/http-tests/proxy/GET-proxied-404.sh @@ -17,20 +17,14 @@ add-agent-to-group.sh \ # Test that status codes are correctly proxied through # Generate a random UUID for a non-existing resource -random_uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen) -non_existing_uri="${END_USER_BASE_URL}${random_uuid}/" +uuid=$(cat /proc/sys/kernel/random/uuid 2>/dev/null || uuidgen) +non_existing_uri="${END_USER_BASE_URL}${uuid}/" # Attempt to proxy a non-existing document on the END_USER_BASE_URL -# This should return 403 Forbidden (not found resources return 403 in LinkedDataHub) -http_status=$(curl -k -s -o /dev/null -w "%{http_code}" \ +curl -k -s -o /dev/null -w "%{http_code}" \ -G \ -E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \ -H 'Accept: application/n-triples' \ --data-urlencode "uri=${non_existing_uri}" \ - "$END_USER_BASE_URL" || true) - -# Verify that the proxied status code matches the backend status code (403) -if [ "$http_status" != "403" ]; then - echo "Expected HTTP 403 Forbidden for non-existing proxied document, got: $http_status" - exit 1 -fi + "$END_USER_BASE_URL" \ +| grep -q "$STATUS_NOT_FOUND" diff --git a/platform/datasets/admin.trig b/platform/datasets/admin.trig index 07eac47c0..9f3448235 100644 --- a/platform/datasets/admin.trig +++ b/platform/datasets/admin.trig @@ -1,11 +1,6 @@ @prefix def: . @prefix ldh: . -@prefix ac: . @prefix rdf: . -@prefix xsd: . -@prefix dh: . -@prefix sd: . -@prefix sp: . @prefix sioc: . @prefix foaf: . @prefix dct: . @@ -23,54 +18,21 @@ } -# ENDPOINTS - - -{ - - a foaf:Document ; - dct:title "SPARQL endpoint" . - -} - - -{ - - a foaf:Document ; - dct:title "Namespace endpoint" . - -} - - -{ - - a foaf:Document ; - dct:title "Add data endpoint" . - -} - - -{ - - a foaf:Document ; - dct:title "Generate data endpoint" . - -} - ### ADMIN-SPECIFIC @prefix lacl: . @prefix adm: . +@prefix dh: . @prefix rdfs: . @prefix owl: . @prefix acl: . @prefix cert: . -@prefix spin: . +@prefix sp: . { - a adm:SignUp ; + a foaf:Document ; dct:title "Sign up" ; rdf:_1 . @@ -288,44 +250,6 @@ WHERE } -# access endpoint - - -{ - - a dh:Item ; - sioc:has_container ; - dct:title "Access description access" ; - foaf:primaryTopic . - - a acl:Authorization ; - rdfs:label "Access description access" ; - rdfs:comment "Allows non-authenticated access" ; - acl:accessToClass ldh:Access ; - acl:mode acl:Read ; - acl:agentClass foaf:Agent, acl:AuthenticatedAgent . - -} - -# access request endpoint - - -{ - - a dh:Item ; - sioc:has_container ; - dct:title "Access request access" ; - foaf:primaryTopic . - - a acl:Authorization ; - rdfs:label "Access request access" ; - rdfs:comment "Allows non-authenticated access" ; - acl:accessToClass ldh:AccessRequest ; - acl:mode acl:Append ; - acl:agentClass foaf:Agent, acl:AuthenticatedAgent . - -} - # sign up @@ -339,8 +263,7 @@ WHERE a acl:Authorization ; rdfs:label "Signup access" ; rdfs:comment "Required to enable public signup" ; - acl:accessTo ; # TO-DO: only allow access by the secretary agent? - acl:accessToClass adm:SignUp ; + acl:accessTo , ; # TO-DO: only allow access by the secretary agent? acl:mode acl:Read, acl:Append ; acl:agentClass foaf:Agent . @@ -359,7 +282,7 @@ WHERE a acl:Authorization ; rdfs:label "OAuth2 login access" ; rdfs:comment "Required to enable public OAuth2 login" ; - acl:accessToClass ldh:OAuthLogin ; + acl:accessToClass , ; acl:mode acl:Read ; acl:agentClass foaf:Agent . @@ -378,7 +301,7 @@ WHERE a acl:Authorization ; rdfs:label "OAuth2 authorization" ; rdfs:comment "Required to enable public OAuth2 login" ; - acl:accessToClass ldh:OAuthAuthorize ; + acl:accessTo , ; acl:mode acl:Read ; acl:agentClass foaf:Agent . diff --git a/platform/datasets/end-user.trig b/platform/datasets/end-user.trig index 65c624610..351174081 100644 --- a/platform/datasets/end-user.trig +++ b/platform/datasets/end-user.trig @@ -1,11 +1,6 @@ @prefix def: . @prefix ldh: . -@prefix ac: . @prefix rdf: . -@prefix xsd: . -@prefix dh: . -@prefix sd: . -@prefix sp: . @prefix sioc: . @prefix foaf: . @prefix dct: . @@ -23,97 +18,10 @@ } -# ENDPOINTS - - -{ - - a foaf:Document ; - dct:title "SPARQL endpoint" . - -} - - -{ - - a foaf:Document ; - dct:title "Namespace endpoint" . - -} - - -{ - - a foaf:Document ; - dct:title "Add data endpoint" . - -} - - -{ - - a foaf:Document ; - dct:title "Generate data endpoint" . - -} - ### END-USER-SPECIFIC - -{ - - a ldh:Access ; - dct:title "Access endpoint" . - -} - - -{ - - a ldh:AccessRequest ; - dct:title "Access request endpoint" . - -} - - -{ - - a ldh:OAuthLogin ; - dct:title "OAuth 2.0 login" . - -} - - -{ - - a ldh:OAuthAuthorize ; - dct:title "Google OAuth2.0 authorization" . - -} - - -{ - - a ldh:OAuthLogin ; - dct:title "ORCID OAuth2.0 login" . - -} - - -{ - - a ldh:OAuthAuthorize ; - dct:title "ORCID OAuth2.0 authorization" . - -} - - -{ - - a foaf:Document ; - dct:title "Settings endpoint" . - -} +@prefix dh: . +@prefix sd: . { diff --git a/platform/entrypoint.sh b/platform/entrypoint.sh index 1fbe571cf..b91be4b64 100755 --- a/platform/entrypoint.sh +++ b/platform/entrypoint.sh @@ -698,7 +698,7 @@ for app in "${apps[@]}"; do namespace_ontology_dataset_path="/var/linkeddatahub/datasets/${app_folder}/namespace-ontology.trig" mkdir -p "$(dirname "$namespace_ontology_dataset_path")" - export end_user_origin admin_origin + export end_user_origin envsubst < namespace-ontology.trig.template > "$namespace_ontology_dataset_path" trig --base="${admin_origin}/" --output=nq "$namespace_ontology_dataset_path" > "/var/linkeddatahub/based-datasets/${app_folder}/namespace-ontology.nq" diff --git a/platform/namespace-ontology.trig.template b/platform/namespace-ontology.trig.template index a3531ccb8..b9042d5a6 100644 --- a/platform/namespace-ontology.trig.template +++ b/platform/namespace-ontology.trig.template @@ -20,10 +20,10 @@ # namespace ontology -<${admin_origin}/ontologies/namespace/> + { - <${admin_origin}/ontologies/namespace/> a dh:Item ; - sioc:has_container <${admin_origin}/ontologies/> ; + a dh:Item ; + sioc:has_container ; dct:title "Namespace" ; foaf:primaryTopic <${end_user_origin}/ns#> . @@ -37,15 +37,15 @@ # public namespace authorization -<${admin_origin}/acl/authorizations/public-namespace/> + { - <${admin_origin}/acl/authorizations/public-namespace/> a dh:Item ; - sioc:has_container <${admin_origin}/acl/authorizations/> ; + a dh:Item ; + sioc:has_container ; dct:title "Public namespace access" ; - foaf:primaryTopic <${admin_origin}/acl/authorizations/public-namespace/#this> . + foaf:primaryTopic . - <${admin_origin}/acl/authorizations/public-namespace/#this> a acl:Authorization ; + a acl:Authorization ; rdfs:label "Public namespace access" ; rdfs:comment "Allows non-authenticated access" ; acl:accessTo <${end_user_origin}/ns> ; # end-user ontologies are public @@ -56,15 +56,15 @@ # SPARQL endpoint authorization -<${admin_origin}/acl/authorizations/sparql-endpoint/> + { - <${admin_origin}/acl/authorizations/sparql-endpoint/> a dh:Item ; - sioc:has_container <${admin_origin}/acl/authorizations/> ; + a dh:Item ; + sioc:has_container ; dct:title "SPARQL endpoint access" ; - foaf:primaryTopic <${admin_origin}/acl/authorizations/sparql-endpoint/#this> . + foaf:primaryTopic . - <${admin_origin}/acl/authorizations/sparql-endpoint/#this> a acl:Authorization ; + a acl:Authorization ; rdfs:label "SPARQL endpoint access" ; rdfs:comment "Allows only authenticated access" ; acl:accessTo <${end_user_origin}/sparql> ; @@ -75,60 +75,98 @@ # write/append authorization -<${admin_origin}/acl/authorizations/write-append/> + { - <${admin_origin}/acl/authorizations/write-append/> a dh:Item ; - sioc:has_container <${admin_origin}/acl/authorizations/> ; + a dh:Item ; + sioc:has_container ; dct:title "Write/append access" ; - foaf:primaryTopic <${admin_origin}/acl/authorizations/write-append/#this> . + foaf:primaryTopic . - <${admin_origin}/acl/authorizations/write-append/#this> a acl:Authorization ; + a acl:Authorization ; rdfs:label "Write/append access" ; rdfs:comment "Allows write access to all documents and containers" ; acl:accessToClass dh:Item, dh:Container, def:Root ; acl:accessTo <${end_user_origin}/sparql>, <${end_user_origin}/importer>, <${end_user_origin}/add>, <${end_user_origin}/generate>, <${end_user_origin}/ns> ; acl:mode acl:Write, acl:Append ; - acl:agentGroup <${admin_origin}/acl/groups/owners/#this>, <${admin_origin}/acl/groups/writers/#this> . + acl:agentGroup , . } # full access authorization -<${admin_origin}/acl/authorizations/full-control/> + { - <${admin_origin}/acl/authorizations/full-control/> a dh:Item ; - sioc:has_container <${admin_origin}/acl/authorizations/> ; + a dh:Item ; + sioc:has_container ; dct:title "Full control" ; - foaf:primaryTopic <${admin_origin}/acl/authorizations/full-control/#this> . + foaf:primaryTopic . - <${admin_origin}/acl/authorizations/full-control/#this> a acl:Authorization ; + a acl:Authorization ; rdfs:label "Full control" ; rdfs:comment "Allows full read/write access to all application resources" ; acl:accessToClass dh:Item, dh:Container, def:Root ; acl:accessTo <${end_user_origin}/sparql>, <${end_user_origin}/importer>, <${end_user_origin}/add>, <${end_user_origin}/generate>, <${end_user_origin}/ns>, <${end_user_origin}/settings> ; acl:mode acl:Read, acl:Append, acl:Write, acl:Control ; - acl:agentGroup <${admin_origin}/acl/groups/owners/#this> . + acl:agentGroup . } # read access -<${admin_origin}/acl/authorizations/read/> + { - <${admin_origin}/acl/authorizations/read/> a dh:Item ; - sioc:has_container <${admin_origin}/acl/authorizations/> ; + a dh:Item ; + sioc:has_container ; dct:title "Read access" ; - foaf:primaryTopic <${admin_origin}/acl/authorizations/read/#this> . + foaf:primaryTopic . - <${admin_origin}/acl/authorizations/read/#this> a acl:Authorization ; + a acl:Authorization ; rdfs:label "Read access" ; rdfs:comment "Allows read access to all resources" ; acl:accessToClass dh:Item, dh:Container, def:Root, ; acl:accessTo <${end_user_origin}/sparql> ; acl:mode acl:Read ; - acl:agentGroup <${admin_origin}/acl/groups/owners/#this>, <${admin_origin}/acl/groups/writers/#this>, <${admin_origin}/acl/groups/readers/#this> . + acl:agentGroup , , . + +} + +# access endpoint + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Access description access" ; + foaf:primaryTopic . + + a acl:Authorization ; + rdfs:label "Access description access" ; + rdfs:comment "Allows non-authenticated access" ; + acl:accessTo <${end_user_origin}/access> ; + acl:mode acl:Read ; + acl:agentClass foaf:Agent, acl:AuthenticatedAgent . + +} + +# access request endpoint + + +{ + + a dh:Item ; + sioc:has_container ; + dct:title "Access request access" ; + foaf:primaryTopic . + + a acl:Authorization ; + rdfs:label "Access request access" ; + rdfs:comment "Allows non-authenticated access" ; + acl:accessTo <${end_user_origin}/access/request> ; + acl:mode acl:Append ; + acl:agentClass foaf:Agent, acl:AuthenticatedAgent . } diff --git a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/AuthorizationFilter.java b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/AuthorizationFilter.java index 2ddbda545..c2a3968e6 100644 --- a/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/AuthorizationFilter.java +++ b/src/main/java/com/atomgraph/linkeddatahub/server/filter/request/AuthorizationFilter.java @@ -43,7 +43,7 @@ import jakarta.ws.rs.container.ContainerRequestFilter; import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.core.Response; -import java.net.URI; +import org.apache.jena.irix.IRIx; import java.util.HashSet; import java.util.Set; import org.apache.jena.query.ParameterizedSparqlString; @@ -157,8 +157,6 @@ public void filter(ContainerRequestContext request) throws IOException public Model authorize(ContainerRequestContext request, Resource agent, Resource accessMode) { Resource accessTo = ResourceFactory.createResource(request.getUriInfo().getAbsolutePath().toString()); - QuerySolutionMap thisQsm = new QuerySolutionMap(); - thisQsm.add(SPIN.THIS_VAR_NAME, accessTo); Model authorizations = ModelFactory.createDefaultModel(); // the agent is the owner of the requested document - automatically grant acl:Read/acl:Append/acl:Write access. @@ -169,46 +167,53 @@ public Model authorize(ContainerRequestContext request, Resource agent, Resource createOwnerAuthorization(authorizations, accessTo, agent); } - ResultSetRewindable docTypesResult = loadResultSet(getApplication().get().getService(), getDocumentTypeQuery(), thisQsm); + QuerySolutionMap thisQsm = new QuerySolutionMap(); + thisQsm.add(SPIN.THIS_VAR_NAME, accessTo); + ResultSetRewindable docTypesResult = loadResultSet(getApplication().get().getService(), getDocumentTypeQuery(), thisQsm); try { - if (!docTypesResult.hasNext()) // if the document resource has no types, we assume the document does not exist + // special case for PUT requests: if the document does not exist, check acl:Write access on the *parent* URI instead + if (!docTypesResult.hasNext() && request.getMethod().equals(HttpMethod.PUT) && accessMode.equals(ACL.Write)) { - // special case for PUT requests to non-existing document: allow if the agent has acl:Write acess to the *parent* URI - if (request.getMethod().equals(HttpMethod.PUT) && accessMode.equals(ACL.Write)) - { - URI parentURI = URI.create(accessTo.getURI()).resolve(".."); - log.debug("Requested document <{}> not found, falling back to parent URI <{}>", accessTo, parentURI); - accessTo = ResourceFactory.createResource(parentURI.toString()); - - thisQsm = new QuerySolutionMap(); - thisQsm.add(SPIN.THIS_VAR_NAME, accessTo); - - docTypesResult.close(); - docTypesResult = loadResultSet(getApplication().get().getService(), getDocumentTypeQuery(), thisQsm); + // Use Jena's IRIx for RFC 3986-compliant resolution - java.net.URI.resolve("..") is non-compliant + // (RFC 3986 section 5.2.4 step 2D requires ".." to be removed, but java.net.URI leaves it literal) + IRIx parentURI = IRIx.create(accessTo.getURI()).resolve(".."); + Resource parent = ResourceFactory.createResource(parentURI.toString()); + log.debug("Requested document <{}> not found, falling back to parent URI <{}>", parent, parentURI); + QuerySolutionMap parentQsm = new QuerySolutionMap(); + parentQsm.add(SPIN.THIS_VAR_NAME, parent); + ResultSetRewindable parentTypesResult = loadResultSet(getApplication().get().getService(), getDocumentTypeQuery(), parentQsm); + try + { Set parentTypes = new HashSet<>(); - docTypesResult.forEachRemaining(qs -> parentTypes.add(qs.getResource("Type"))); + parentTypesResult.forEachRemaining(qs -> parentTypes.add(qs.getResource("Type"))); // only root and containers allow child documents. This needs to be checked before checking ownership if (Collections.disjoint(parentTypes, Set.of(Default.Root, DH.Container))) return null; - docTypesResult.reset(); // rewind result set to the beginning - it's used again later on - + // the agent is the owner of the requested document - automatically grant acl:Read/acl:Append/acl:Write access - if (agent != null && isOwner(accessTo, agent)) + if (agent != null && isOwner(parent, agent)) { - log.debug("Agent <{}> is the owner of <{}>, granting acl:Read/acl:Append/acl:Write access", agent, accessTo); - createOwnerAuthorization(authorizations, accessTo, agent); + log.debug("Agent <{}> is the owner of <{}>, granting acl:Read/acl:Append/acl:Write access", agent, parent); + createOwnerAuthorization(authorizations, parent, agent); } + + accessTo = parent; // redirect ACL query to parent URI since the document does not exist yet + } + finally + { + parentTypesResult.close(); } - // access to non-existing documents is denied if the request method is not PUT *and* the agent has no Write access - else return null; } - + ParameterizedSparqlString pss = getApplication().get().canAs(EndUserApplication.class) ? getACLQuery() : getOwnerACLQuery(); - Query query = new SetResultSetValues().apply(pss.asQuery(), docTypesResult); - pss = new ParameterizedSparqlString(query.toString()); // make sure VALUES are now part of the query string - assert pss.toString().contains("VALUES"); + if (docTypesResult.hasNext()) + { + Query query = new SetResultSetValues().apply(pss.asQuery(), docTypesResult); + pss = new ParameterizedSparqlString(query.toString()); // make sure type VALUES are now part of the query string + assert pss.toString().contains("VALUES"); + } // note we're not setting the $mode value on the ACL queries as we want to provide the AuthorizationContext with all of the agent's authorizations authorizations.add(loadModel(getAdminService(), pss, new AuthorizationParams(getAdminBase(), accessTo, agent).get())); diff --git a/src/main/resources/com/atomgraph/linkeddatahub/ldh.ttl b/src/main/resources/com/atomgraph/linkeddatahub/ldh.ttl index caa46a07f..511871d81 100644 --- a/src/main/resources/com/atomgraph/linkeddatahub/ldh.ttl +++ b/src/main/resources/com/atomgraph/linkeddatahub/ldh.ttl @@ -657,7 +657,7 @@ sp:Query spin:constructor [ PREFIX xsd: PREFIX sp: PREFIX sd: - PREFIX : + PREFIX : CONSTRUCT { $this rdfs:label [ a xsd:string ] ;