Skip to content

Commit a86a546

Browse files
committed
Release version 5.6.0
2 parents 9b2fe8c + 289f975 commit a86a546

65 files changed

Lines changed: 2008 additions & 916 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Dockerfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ ENV SOURCE_COMMIT=$SOURCE_COMMIT
3838

3939
WORKDIR $CATALINA_HOME
4040

41-
ENV CACHE_MODEL_LOADS=true
42-
4341
ENV STYLESHEET=static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/layout.xsl
4442

4543
ENV CACHE_STYLESHEET=true

docker-compose.yml

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ services:
107107
fuseki-admin:
108108
image: atomgraph/fuseki:6.1.0
109109
user: root # otherwise fuseki user does not have permissions to the mounted folder which is owner by root
110+
mem_limit: 1536m # leave headroom above heap for TDB mmap/native memory
111+
restart: on-failure
112+
environment:
113+
- JAVA_OPTIONS=-Xmx768m -Xms768m
110114
expose:
111115
- 3030
112116
volumes:
@@ -116,6 +120,10 @@ services:
116120
fuseki-end-user:
117121
image: atomgraph/fuseki:6.1.0
118122
user: root # otherwise the fuseki user does not have permissions to the mounted folder which is owner by root
123+
mem_limit: 3072m # leave headroom above heap for TDB mmap/native memory
124+
restart: on-failure
125+
environment:
126+
- JAVA_OPTIONS=-Xmx1536m -Xms1536m
119127
expose:
120128
- 3030
121129
volumes:
@@ -384,28 +392,24 @@ configs:
384392
return (pass);
385393
}
386394
387-
if (req.http.Client-Cert) {
388-
# Authenticated HTML is user-specific → never cache
389-
if (req.http.Accept ~ "text/html" ||
390-
req.http.Accept ~ "application/xhtml+xml") {
391-
return (pass);
392-
}
393-
394-
# Conditional requests must reach backend for validation
395-
if (req.http.If-Match || req.http.If-None-Match ||
396-
req.http.If-Modified-Since || req.http.If-Unmodified-Since) {
397-
return (pass);
398-
}
399-
400-
# /access endpoint returns agent-specific group memberships
401-
if (req.url ~ "^/access") {
402-
return (pass);
403-
}
395+
# Delegated requests carry user identity in the On-Behalf-Of header.
396+
# The backend response echoes that identity in the Link header
397+
# (acl#agent). The cache key does not include the asserted identity,
398+
# so caching would let a later anonymous request to the same
399+
# URL+Accept read back the previous agent's WebID and ACL grant.
400+
if (req.http.On-Behalf-Of) {
401+
return (pass);
402+
}
404403
405-
# SPARQL referencing /acl/agents/ depends on agent identity → don't cache
406-
if (req.url ~ "%2Facl%2Fagents%2F") {
407-
return (pass);
408-
}
404+
# Authenticated responses get acl#agent stamped into the Link header by
405+
# the backend, regardless of representation (HTML, RDF/XML, Turtle,
406+
# JSON-LD, SPARQL results, …). The URL-keyed cache slot ignores the
407+
# asserting identity, so any such response would leak to anonymous
408+
# readers of the same URL. /static/* is the only safely-shared path —
409+
# it's served by Tomcat's default servlet (web.xml:365-371), bypasses
410+
# Jersey, and carries no identity-bearing headers.
411+
if (req.http.Client-Cert && req.url !~ "^/static/") {
412+
return (pass);
409413
}
410414
411415
if (req.http.Cookie) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
initialize_dataset "$END_USER_BASE_URL" "$TMP_END_USER_DATASET" "$END_USER_ENDPOINT_URL"
5+
initialize_dataset "$ADMIN_BASE_URL" "$TMP_ADMIN_DATASET" "$ADMIN_ENDPOINT_URL"
6+
purge_cache "$END_USER_VARNISH_SERVICE"
7+
purge_cache "$ADMIN_VARNISH_SERVICE"
8+
purge_cache "$FRONTEND_VARNISH_SERVICE"
9+
10+
# sp:Describe is declared only as rdfs:Class (not owl:Class) in sp.ttl.
11+
# OntologyFilter must promote rdfs:Class to owl:Class during materialization so
12+
# that OWL2 profiles recognise third-party vocab terms and return their SPIN constructors.
13+
14+
response=$(curl -k -f -s \
15+
-G \
16+
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
17+
-H "Accept: application/rdf+xml" \
18+
--data-urlencode "forClass=http://spinrdf.org/sp#Describe" \
19+
"${END_USER_BASE_URL}ns")
20+
21+
# response must be non-empty: sp:Describe must be recognised as an OntClass
22+
echo "$response" | grep -q "http://spinrdf.org/sp#Describe"
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
initialize_dataset "$END_USER_BASE_URL" "$TMP_END_USER_DATASET" "$END_USER_ENDPOINT_URL"
5+
initialize_dataset "$ADMIN_BASE_URL" "$TMP_ADMIN_DATASET" "$ADMIN_ENDPOINT_URL"
6+
purge_cache "$END_USER_VARNISH_SERVICE"
7+
purge_cache "$ADMIN_VARNISH_SERVICE"
8+
purge_cache "$FRONTEND_VARNISH_SERVICE"
9+
10+
# add agent to the writers group
11+
12+
add-agent-to-group.sh \
13+
-f "$OWNER_CERT_FILE" \
14+
-p "$OWNER_CERT_PWD" \
15+
--agent "$AGENT_URI" \
16+
"${ADMIN_BASE_URL}acl/groups/writers/"
17+
18+
# PATCH the root with an INSERT that introduces a blank node.
19+
# Use rdf:_99 to avoid colliding with existing rdf:_1..rdf:_8 in the test dataset.
20+
# Expected: 204 No Content; the blank node is skolemized to a hash URI before persisting.
21+
22+
update=$(cat <<EOF
23+
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
24+
PREFIX dct: <http://purl.org/dc/terms/>
25+
26+
INSERT
27+
{
28+
<${END_USER_BASE_URL}> rdf:_99 _:bnode0 .
29+
_:bnode0 dct:title "Blank node title"
30+
}
31+
WHERE
32+
{}
33+
EOF
34+
)
35+
36+
curl -k -w "%{http_code}\n" -o /dev/null -f -s \
37+
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
38+
-X PATCH \
39+
-H "Content-Type: application/sparql-update" \
40+
"$END_USER_BASE_URL" \
41+
--data-binary "$update" \
42+
| grep -q "$STATUS_NO_CONTENT"
43+
44+
# fetch the persisted graph and verify the blank node was skolemized to a hash URI
45+
46+
response=$(curl -k -f -s -G \
47+
-E "$AGENT_CERT_FILE":"$AGENT_CERT_PWD" \
48+
-H "Accept: application/n-triples" \
49+
"$END_USER_BASE_URL")
50+
51+
rdf_99_line=$(echo "$response" | grep -E "^<${END_USER_BASE_URL}> <http://www.w3.org/1999/02/22-rdf-syntax-ns#_99>" || true)
52+
53+
[ -n "$rdf_99_line" ] || exit 1
54+
55+
# object of rdf:_99 must be a URI (<...>), not a blank node label (_:...)
56+
! echo "$rdf_99_line" | grep -qE '_:[A-Za-z0-9]+ \.$'
57+
echo "$rdf_99_line" | grep -qE '<\S+> \.$'
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Regression: ProxyRequestFilter's server-side fetch attaches On-Behalf-Of
5+
# (via WebIDDelegationFilter), and the backend response carries the asserted
6+
# agent's WebID in the Link header (acl#agent). varnish-frontend must not
7+
# cache that response under a URL-keyed entry — otherwise a subsequent
8+
# anonymous request to the same URL+Accept replays the cached 200 and reads
9+
# back the previous agent's identity (and inherits whatever ACL grant they
10+
# had).
11+
12+
purge_cache "$END_USER_VARNISH_SERVICE"
13+
purge_cache "$ADMIN_VARNISH_SERVICE"
14+
purge_cache "$FRONTEND_VARNISH_SERVICE"
15+
16+
# Step A: authenticated owner fires a proxy request from the end-user
17+
# dataspace to the admin dataspace. This triggers WebIDDelegationFilter →
18+
# On-Behalf-Of on the server-side hop into varnish-frontend.
19+
20+
curl -k -f -s -o /dev/null \
21+
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
22+
-G \
23+
-H 'Accept: application/rdf+xml' \
24+
--data-urlencode "uri=${ADMIN_BASE_URL}" \
25+
"${END_USER_BASE_URL}"
26+
27+
# Step B: anonymous direct request to the admin URL with the same Accept.
28+
# If the cache was poisoned in Step A, this returns 200 with the owner's
29+
# WebID in the Link header. Expected after the fix: varnish-frontend should
30+
# pass on On-Behalf-Of and store nothing, so this goes to the backend
31+
# anonymously and gets 403.
32+
33+
response=$(curl -k -s -i -H 'Accept: application/rdf+xml' "${ADMIN_BASE_URL}")
34+
35+
status=$(printf '%s\n' "$response" | awk 'NR==1{print $2}' | tr -d '\r')
36+
link_leak=$(printf '%s\n' "$response" | tr -d '\r' | grep -i '^link:' | grep -c 'acl#agent' || true)
37+
38+
if [ "$status" != "$STATUS_FORBIDDEN" ]; then
39+
echo "Step B: expected $STATUS_FORBIDDEN, got $status"
40+
exit 1
41+
fi
42+
43+
if [ "$link_leak" != "0" ]; then
44+
echo "Step B: anonymous response leaks acl#agent (cache poisoning)"
45+
exit 1
46+
fi
47+
48+
# Step C: authenticated owner fetches the admin URL directly with cert at TLS,
49+
# Accept: application/rdf+xml. The Client-Cert header reaches varnish-frontend
50+
# (nginx-forwarded). The backend stamps acl#agent into the Link header for the
51+
# authenticated 200. varnish-frontend must NOT cache this response — its hash
52+
# key ignores identity, so a subsequent anonymous request would replay the 200.
53+
54+
purge_cache "$FRONTEND_VARNISH_SERVICE"
55+
56+
curl -k -f -s -o /dev/null \
57+
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
58+
-H 'Accept: application/rdf+xml' \
59+
"${ADMIN_BASE_URL}"
60+
61+
# Step D: anonymous direct fetch of the same URL. With the fix in place
62+
# (Client-Cert + non-/static/ path → pass in vcl_recv), Step C didn't store
63+
# anything, so this reaches the backend anonymously and gets 403.
64+
65+
response=$(curl -k -s -i -H 'Accept: application/rdf+xml' "${ADMIN_BASE_URL}")
66+
67+
status=$(printf '%s\n' "$response" | awk 'NR==1{print $2}' | tr -d '\r')
68+
link_leak=$(printf '%s\n' "$response" | tr -d '\r' | grep -i '^link:' | grep -c 'acl#agent' || true)
69+
70+
if [ "$status" != "$STATUS_FORBIDDEN" ]; then
71+
echo "Step D: expected $STATUS_FORBIDDEN, got $status"
72+
exit 1
73+
fi
74+
75+
if [ "$link_leak" != "0" ]; then
76+
echo "Step D: anonymous response leaks acl#agent (cache poisoning)"
77+
exit 1
78+
fi

platform/context.xsl

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ xmlns:orcid="&orcid;"
1717

1818
<xsl:output method="xml" indent="yes"/>
1919

20-
<xsl:param name="a:cacheModelLoads"/>
2120
<xsl:param name="ac:stylesheet"/>
2221
<xsl:param name="ac:cacheStylesheet"/>
2322
<xsl:param name="ac:resolvingUncached"/>
@@ -66,9 +65,6 @@ xmlns:orcid="&orcid;"
6665
<xsl:copy>
6766
<xsl:apply-templates select="@*"/>
6867

69-
<xsl:if test="$a:cacheModelLoads">
70-
<Parameter name="&a;cacheModelLoads" value="{$a:cacheModelLoads}" override="false"/>
71-
</xsl:if>
7268
<xsl:if test="$ac:stylesheet">
7369
<Parameter name="&ac;stylesheet" value="{$ac:stylesheet}" override="false"/>
7470
</xsl:if>

platform/entrypoint.sh

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,10 +1043,6 @@ if [ -n "$PROXY_PORT" ]; then
10431043
PROXY_PORT_PARAM="--stringparam ldhc:proxyPort '$PROXY_PORT' "
10441044
fi
10451045

1046-
if [ -n "$CACHE_MODEL_LOADS" ]; then
1047-
CACHE_MODEL_LOADS_PARAM="--stringparam a:cacheModelLoads '$CACHE_MODEL_LOADS' "
1048-
fi
1049-
10501046
# stylesheet URL must be relative to the base context URL
10511047
if [ -n "$STYLESHEET" ]; then
10521048
STYLESHEET_PARAM="--stringparam ac:stylesheet '$STYLESHEET' "
@@ -1166,7 +1162,6 @@ fi
11661162

11671163
transform="xsltproc \
11681164
--output conf/Catalina/localhost/ROOT.xml \
1169-
$CACHE_MODEL_LOADS_PARAM \
11701165
$STYLESHEET_PARAM \
11711166
$CACHE_STYLESHEET_PARAM \
11721167
$RESOLVING_UNCACHED_PARAM \

pom.xml

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<groupId>com.atomgraph</groupId>
55
<artifactId>linkeddatahub</artifactId>
6-
<version>5.5.4</version>
6+
<version>5.6.0</version>
77
<packaging>${packaging.type}</packaging>
88

99
<name>AtomGraph LinkedDataHub</name>
@@ -46,7 +46,7 @@
4646
<url>https://github.com/AtomGraph/LinkedDataHub</url>
4747
<connection>scm:git:git://github.com/AtomGraph/LinkedDataHub.git</connection>
4848
<developerConnection>scm:git:git@github.com:AtomGraph/LinkedDataHub.git</developerConnection>
49-
<tag>linkeddatahub-5.5.4</tag>
49+
<tag>linkeddatahub-5.6.0</tag>
5050
</scm>
5151

5252
<repositories>
@@ -93,6 +93,19 @@
9393
<groupId>org.glassfish.jersey.connectors</groupId>
9494
<artifactId>jersey-apache-connector</artifactId>
9595
<version>3.1.11</version>
96+
<exclusions>
97+
<!-- exclude commons-logging (via httpclient) - jcl-over-slf4j from Jena provides the JCL API. Otherwise duplicate org.apache.commons.logging classes on the classpath -->
98+
<exclusion>
99+
<groupId>commons-logging</groupId>
100+
<artifactId>commons-logging</artifactId>
101+
</exclusion>
102+
</exclusions>
103+
</dependency>
104+
<dependency>
105+
<!-- pin to the version jena-base 6.1.0 declares; nearest-wins would otherwise downgrade it to 1.18.0 via jersey-apache-connector -->
106+
<groupId>commons-codec</groupId>
107+
<artifactId>commons-codec</artifactId>
108+
<version>1.22.0</version>
96109
</dependency>
97110
<dependency>
98111
<groupId>org.glassfish.jersey.media</groupId>
@@ -112,7 +125,7 @@
112125
<dependency>
113126
<groupId>com.google.guava</groupId>
114127
<artifactId>guava</artifactId>
115-
<version>31.1-jre</version>
128+
<version>33.6.0-jre</version>
116129
</dependency>
117130
<dependency>
118131
<groupId>com.atomgraph.etl.csv</groupId>
@@ -146,14 +159,7 @@
146159
<dependency>
147160
<groupId>${project.groupId}</groupId>
148161
<artifactId>twirl</artifactId>
149-
<version>1.1.0</version>
150-
<exclusions>
151-
<!-- exclude slf4j-reload4j 1.7.x binding; replaced below with 2.0.x matching slf4j-api from Jena -->
152-
<exclusion>
153-
<groupId>org.slf4j</groupId>
154-
<artifactId>slf4j-reload4j</artifactId>
155-
</exclusion>
156-
</exclusions>
162+
<version>2.0.0</version>
157163
</dependency>
158164
<dependency>
159165
<groupId>org.slf4j</groupId>
@@ -163,13 +169,13 @@
163169
<dependency>
164170
<groupId>${project.groupId}</groupId>
165171
<artifactId>client</artifactId>
166-
<version>4.3.0</version>
172+
<version>5.0.2</version>
167173
<classifier>classes</classifier>
168174
</dependency>
169175
<dependency>
170176
<groupId>${project.groupId}</groupId>
171177
<artifactId>client</artifactId>
172-
<version>4.3.0</version>
178+
<version>5.0.2</version>
173179
<type>war</type>
174180
</dependency>
175181
<dependency>
@@ -185,24 +191,24 @@
185191
<dependency>
186192
<groupId>org.jsoup</groupId>
187193
<artifactId>jsoup</artifactId>
188-
<version>1.22.1</version>
194+
<version>1.22.2</version>
189195
</dependency>
190196
<dependency>
191197
<groupId>org.junit.jupiter</groupId>
192198
<artifactId>junit-jupiter</artifactId>
193-
<version>5.11.4</version>
199+
<version>6.1.0</version>
194200
<scope>test</scope>
195201
</dependency>
196202
<dependency>
197203
<groupId>org.mockito</groupId>
198204
<artifactId>mockito-core</artifactId>
199-
<version>5.12.0</version>
205+
<version>5.18.0</version>
200206
<scope>test</scope>
201207
</dependency>
202208
<dependency>
203209
<groupId>org.mockito</groupId>
204210
<artifactId>mockito-junit-jupiter</artifactId>
205-
<version>5.12.0</version>
211+
<version>5.18.0</version>
206212
<scope>test</scope>
207213
</dependency>
208214
</dependencies>

0 commit comments

Comments
 (0)