Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
## [Unreleased]
### Changed
- Application ontologies resolved as a native ontapi `owl:imports` union graph (cached per ontology URI), no RDFS inference — replaces the manually flattened, RDFS-materialized model
- `Namespace` no-query GET serves the raw ontology graph from the shared repository instead of rebuilding one per request
- **BREAKING**: "Add data" and "Generate containers" orchestrated client-side over the Graph Store Protocol (POST-append via `?uri=` proxy; per-class container PUT fan-out embedding the view as `ldh:Object` → `rdf:value` → `ldh:View`), replacing the `/add` and `/generate` endpoints

### Fixed
- Raw ontology graphs no longer leak inferred `rdf:type rdfs:Resource` that broke View block rendering via multi-token `@typeof`

### Removed
- Linked Data proxy no longer serves ontology terms (now dumb transport: bundled-vocab file cache + SSRF-checked external fetch); ontology terms served by `/ns`
- **BREAKING**: `/add` and `/generate` server-side endpoints (`Add`/`Generate` JAX-RS resources), superseded by the client-orchestrated writes; removes their server-side fetch/SSRF surface (LNK-002); `/transform` retained until a client-side SPARQL engine lands

## [5.7.1] - 2026-08-06
### Changed
- RDFa editor: annotation overlay rebuilt on demand (`rdfa-editor/overlay.xsl`)
Expand Down
54 changes: 54 additions & 0 deletions http-tests/add/GET-proxied-source-POST-append.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/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"

# Exercises the client-orchestrated "Add data" flow that replaced the server-side /add endpoint:
# the browser GETs the external source through the same-origin ?uri= proxy as RDF/XML, then
# POSTs (appends) it to the target document. Two requests, no /add endpoint.

# add agent to the readers group (to read through the proxy) and the writers group (to append)

add-agent-to-group.sh \
-f "$OWNER_CERT_FILE" \
-p "$OWNER_CERT_PWD" \
--agent "$AGENT_URI" \
"${ADMIN_BASE_URL}acl/groups/readers/"

add-agent-to-group.sh \
-f "$OWNER_CERT_FILE" \
-p "$OWNER_CERT_PWD" \
--agent "$AGENT_URI" \
"${ADMIN_BASE_URL}acl/groups/writers/"

# create the target container

container=$(create-container.sh \
-f "$AGENT_CERT_FILE" \
-p "$AGENT_CERT_PWD" \
-b "$END_USER_BASE_URL" \
--title "Test" \
--slug "test" \
--parent "$END_USER_BASE_URL")

# step 1: fetch the external source through the LDH proxy, converted to RDF/XML

source_rdfxml=$(curl -k -f -s -G \
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
-H "Accept: application/rdf+xml" \
--data-urlencode "uri=https://orcid.org/0000-0003-1750-9906" \
"$END_USER_BASE_URL")

# step 2: append the fetched triples to the target container document (GSP append -> 204)

echo "$source_rdfxml" | curl -k -w "%{http_code}\n" -o /dev/null -s \
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
-X POST \
-H "Content-Type: application/rdf+xml" \
--data-binary @- \
"$container" \
| grep -q "$STATUS_NO_CONTENT"
41 changes: 0 additions & 41 deletions http-tests/add/POST-add.sh

This file was deleted.

82 changes: 82 additions & 0 deletions http-tests/add/PUT-generate-container.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/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"

# Exercises the client-orchestrated "Generate containers" flow that replaced the server-side
# /generate endpoint. The client builds one container document per checked class -- a dh:Container
# whose content block is an ldh:Object wrapping an ldh:View over a $type-parameterized SELECT -- and
# PUTs it. This test PUTs one such container (shaped exactly like ldh:generate-container-doc output)
# and verifies: creation succeeds (the Object-wrapped block passes ldh:InvalidContentBlockType /
# MissingValue / MissingQuery validation), the server stamps metadata, and the block persists.

# 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/"

parent="$END_USER_BASE_URL"
uuid=$(uuidgen | tr '[:upper:]' '[:lower:]')
container="${parent}${uuid}/"
class="https://www.w3.org/ns/ldt/document-hierarchy#Container"

# PUT the generated container document (blank nodes are skolemized server-side)

http_code=$(curl -k -s -o /dev/null -w "%{http_code}" \
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
-X PUT \
-H "Content-Type: application/rdf+xml" \
--data-binary @- \
"$container" <<EOF
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dct="http://purl.org/dc/terms/" xmlns:sioc="http://rdfs.org/sioc/ns#" xmlns:dh="https://www.w3.org/ns/ldt/document-hierarchy#" xmlns:ldh="https://w3id.org/atomgraph/linkeddatahub#" xmlns:spin="http://spinrdf.org/spin#" xmlns:sp="http://spinrdf.org/sp#">
<rdf:Description rdf:about="${container}">
<rdf:type rdf:resource="https://www.w3.org/ns/ldt/document-hierarchy#Container"/>
<sioc:has_parent rdf:resource="${parent}"/>
<dct:title>Containers</dct:title>
<dh:slug>${uuid}</dh:slug>
<rdf:_1>
<rdf:Description>
<rdf:type rdf:resource="https://w3id.org/atomgraph/linkeddatahub#Object"/>
<rdf:value>
<rdf:Description>
<rdf:type rdf:resource="https://w3id.org/atomgraph/linkeddatahub#View"/>
<spin:query>
<rdf:Description>
<rdf:type rdf:resource="http://spinrdf.org/sp#Select"/>
<dct:title>Select Container</dct:title>
<sp:text>SELECT DISTINCT ?s WHERE { ?s a &lt;${class}&gt; ; ?p ?o }</sp:text>
</rdf:Description>
</spin:query>
</rdf:Description>
</rdf:value>
</rdf:Description>
</rdf:_1>
</rdf:Description>
</rdf:RDF>
EOF
)

[ "$http_code" = "$STATUS_CREATED" ]

# fetch the created container and verify the shape + server-stamped metadata

ntriples=$(curl -k -f -s \
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
-H "Accept: application/n-triples" \
"$container")

# parent link, generated title, and server-stamped creation date
echo "$ntriples" | grep -q "<http://rdfs.org/sioc/ns#has_parent> <${parent}>"
echo "$ntriples" | grep -q "<http://purl.org/dc/terms/title> \"Containers\""
echo "$ntriples" | grep -q "<http://purl.org/dc/terms/created>"

# content block persisted as an ldh:Object, and the SELECT carries the substituted class IRI
echo "$ntriples" | grep -q "<https://w3id.org/atomgraph/linkeddatahub#Object>"
echo "$ntriples" | grep "<http://spinrdf.org/sp#text>" | grep -q "${class}"
65 changes: 65 additions & 0 deletions http-tests/proxy/GET-proxied-mapped-vocab.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/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 readers group to be able to read documents

add-agent-to-group.sh \
-f "$OWNER_CERT_FILE" \
-p "$OWNER_CERT_PWD" \
--agent "$AGENT_URI" \
"${ADMIN_BASE_URL}acl/groups/readers/"

# well-known vocab terms that are statically prefix-mapped to bundled documents
# (src/main/resources/prefix-mapping.ttl), so the proxy serves them straight from
# that cache (isMapped branch) instead of dereferencing the network.
#
# The whole vocabulary graph is returned (tens of KiB), so assertions read from a
# here-string rather than `echo "$response" | grep -q`: `grep -q` closes the pipe on
# first match, and with `set -o pipefail` the SIGPIPE'd `echo` (write error: broken
# pipe) fails the whole pipeline whenever the response exceeds the ~64 KiB pipe buffer.
# Labels are language-tagged in the bundled documents, so the expected literal is
# matched in full including its tag.

# dct:title - slash-based namespace (http://purl.org/dc/terms/); the proxy request
# URI equals the term URI itself

dct_response=$(curl -k -f -s \
-G \
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
-H "Accept: application/n-triples" \
--data-urlencode "uri=http://purl.org/dc/terms/title" \
"$END_USER_BASE_URL")

grep -qF '<http://purl.org/dc/terms/title> <http://www.w3.org/2000/01/rdf-schema#label> "Title"@en-US' <<< "$dct_response"

# foaf:Person - also slash-based (http://xmlns.com/foaf/0.1/); label is a plain literal

foaf_response=$(curl -k -f -s \
-G \
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
-H "Accept: application/n-triples" \
--data-urlencode "uri=http://xmlns.com/foaf/0.1/Person" \
"$END_USER_BASE_URL")

grep -qF '<http://xmlns.com/foaf/0.1/Person> <http://www.w3.org/2000/01/rdf-schema#label> "Person"' <<< "$foaf_response"

# skos:Concept - hash-based namespace (http://www.w3.org/2004/02/skos/core#); the
# request carries a #fragment that ProxyRequestFilter strips before matching the
# mapped prefix, and the bundled document declares terms as relative (#Concept)
# under its own xml:base, so this also confirms that base resolves back to the
# full hash URI rather than leaking a bare fragment or the classpath location

skos_response=$(curl -k -f -s \
-G \
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
-H "Accept: application/n-triples" \
--data-urlencode "uri=http://www.w3.org/2004/02/skos/core#Concept" \
"$END_USER_BASE_URL")

grep -qF '<http://www.w3.org/2004/02/skos/core#Concept> <http://www.w3.org/2000/01/rdf-schema#label> "Concept"@en' <<< "$skos_response"
66 changes: 0 additions & 66 deletions http-tests/proxy/GET-proxied-ontology-ns.sh

This file was deleted.

37 changes: 37 additions & 0 deletions http-tests/proxy/POST-proxied-cross-origin-query.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/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"

# Execute a SPARQL query against a cross-origin endpoint (the admin app's SPARQL
# endpoint) using the end-user app as a proxy. Because admin.localhost is a different
# origin than the end-user app, the request goes through ProxyRequestFilter, which
# fetches the remote endpoint and re-serializes the SPARQL results back to the caller.
# The owner is used because the admin SPARQL endpoint is ACL-protected.

response_body=$(curl -k -s \
-X POST \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H 'Content-Type: application/sparql-query' \
-H 'Accept: application/sparql-results+xml' \
--url-query "uri=${ADMIN_BASE_URL}sparql" \
--data 'SELECT (COUNT(*) AS ?count) WHERE { ?s ?p ?o }' \
"$END_USER_BASE_URL")

http_code=$(curl -k -s -o /dev/null -w "%{http_code}" \
-X POST \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H 'Content-Type: application/sparql-query' \
-H 'Accept: application/sparql-results+xml' \
--url-query "uri=${ADMIN_BASE_URL}sparql" \
--data 'SELECT (COUNT(*) AS ?count) WHERE { ?s ?p ?o }' \
"$END_USER_BASE_URL")

# verify successful status and that the proxy re-serialized actual SPARQL results
if [ "$http_code" -ne 200 ] || [[ "$response_body" != *"http://www.w3.org/2005/sparql-results#"* ]]; then
exit 1
fi
Loading
Loading