diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md
index 150f42c601a..2b304268a9a 100644
--- a/THREAT_MODEL.md
+++ b/THREAT_MODEL.md
@@ -47,7 +47,7 @@ limitations under the License.
| Stores + text index | `jena-tdb1`, `jena-tdb2`; **`jena-text` (Lucene)** | filesystem | **In.** On-disk store is operator-trusted and private to the owning process *(maintainer)*; the Lucene text index is reachable from SPARQL via `text:query` — an in-model query surface *(maintainer — afs flagged jena-text)* |
| IRI / langtag | `jena-iri3986`, `jena-langtag`, `jena-base` | none | **In (input parsing)** *(inferred)* |
| Extensions | `jena-geosparql`, `jena-serviceenhancer` | SERVICE | **In (reachable from queries)** *(inferred)* |
-| Validations | `jena-shacl`, `jena-shex` | HTTP GET requests (imports) | **In (import-fetch = SSRF surface)** *(maintainer — afs)* |
+| Validations | `jena-shacl` | HTTP GET requests | **In (no imports)** *(maintainer — afs)* |
| Client/API helpers | `jena-rdfconnection`, `jena-querybuilder`, `jena-rdfpatch`, `jena-commonsrdf`, `jena-ontapi` | none | **In as libraries (memory/correctness)** *(inferred)* |
| CLI tools | `jena-cmds` | filesystem | **In iff fed untrusted input; usually operator-run** *(inferred)* |
| Examples / tests / benchmarks | `jena-examples`, `jena-integration-tests`, `jena-benchmarks` | n/a | **Out** *(see §3)* |
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
index 0672b3cbc77..0fca12cb3a0 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
@@ -103,11 +103,13 @@ public static DatasetGraph controlledDataset(DatasetGraph dsgBase, Authorization
* {@link DatasetGraphAccessControl} or because it has the context settings.
*/
public static boolean isAccessControlled(DatasetGraph dsg) {
+ if ( dsg == null )
+ return false;
if ( dsg instanceof DatasetGraphAccessControl )
return true;
// if ( dsg.getContext().isDefined(DataAccessCtl.symControlledAccess) )
// return true;
- if ( dsg.getContext().isDefined(DataAccessCtl.symAuthorizationService) )
+ if ( dsg.getContext() != null && dsg.getContext().isDefined(DataAccessCtl.symAuthorizationService) )
return true;
return false;
}
diff --git a/jena-fuseki2/jena-fuseki-core/pom.xml b/jena-fuseki2/jena-fuseki-core/pom.xml
index 205ce65a417..82a825ee8a7 100644
--- a/jena-fuseki2/jena-fuseki-core/pom.xml
+++ b/jena-fuseki2/jena-fuseki-core/pom.xml
@@ -52,12 +52,6 @@
6.3.0-SNAPSHOT
-
- org.apache.jena
- jena-shex
- 6.3.0-SNAPSHOT
-
-
org.apache.jena
jena-tdb1
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
index d886d2fad3f..e8ab085b68a 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
@@ -203,13 +203,14 @@ public static boolean splitContains(String[] elts, String str) {
/**
* Parse RDF content from the body of the request of the action, ends the
* request, and sends a 400 if there is a parse error.
+ * Parse errors are logged as "fatal" and become 400/{@link ActionErrorException}
*
* @throws ActionErrorException ActionErrorException
*/
public static void parseOrError(HttpAction action, StreamRDF dest, Lang lang, String base) {
try {
parse(action, dest, lang, base);
- } catch (RiotParseException ex) {
+ } catch (RiotException ex) {
ActionLib.consumeBody(action);
ServletOps.errorParseError(ex);
}
@@ -218,7 +219,7 @@ public static void parseOrError(HttpAction action, StreamRDF dest, Lang lang, St
/**
* Parse RDF content. This wraps up the parse step reading from an action.
* It includes handling compression if the {@code Content-Encoding} header is present
- * @throws RiotParseException RiotParseException
+ * @throws RiotException
*/
public static void parse(HttpAction action, StreamRDF dest, Lang lang, String base) {
try {
@@ -229,7 +230,7 @@ public static void parse(HttpAction action, StreamRDF dest, Lang lang, String ba
/**
* Parse RDF content. This wraps up the parse step reading from an input stream.
- * @throws RiotParseException RiotParseException
+ * @throws RiotException RiotException
*/
public static void parse(HttpAction action, StreamRDF dest, InputStream input, Lang lang, String base) {
try {
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SHACL_Validation.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SHACL_Validation.java
index e901ef54592..536e77ddf4e 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SHACL_Validation.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SHACL_Validation.java
@@ -31,10 +31,13 @@
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFLanguages;
+import org.apache.jena.riot.RiotException;
import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.shacl.ShaclValidator;
import org.apache.jena.shacl.Shapes;
import org.apache.jena.shacl.ValidationReport;
+import org.apache.jena.system.G;
+import org.apache.jena.vocabulary.OWL1;
import org.apache.jena.web.HttpSC;
/**
@@ -48,6 +51,8 @@
*/
public class SHACL_Validation extends BaseActionREST { //ActionREST {
+ private static final Node owlImports = OWL1.imports.asNode();
+
public SHACL_Validation() {}
@Override
@@ -60,20 +65,39 @@ protected void doPost(HttpAction action) {
String targetNodeStr = action.getRequestParameter(HttpNames.paramTarget);
+ Graph shapesGraph;
+ try {
+ shapesGraph = ActionLib.readFromRequest(action, Lang.TTL);
+ if ( G.contains(shapesGraph, null, owlImports, null) ) {
+ action.log.error(format("[%d] shacl: owl:imports not supported for remote validation", action.id));
+ // Does not return.
+ ServletOps.errorBadRequest("owl:imports not allowed");
+ }
+ } catch (RiotException ex) {
+ shapesGraph = null;
+ // Does not return.
+ ServletOps.errorBadRequest(ex.getMessage());
+ }
+
action.beginRead();
try {
GraphTarget graphTarget = determineTarget(action.getActiveDSG(), action);
- if ( ! graphTarget.exists() )
+ if ( ! graphTarget.exists() ) {
+ action.log.error(format("[%d] shacl: No data graph", action.id));
+ // Does not return.
ServletOps.errorNotFound("No data graph: "+graphTarget.label());
- Graph data = graphTarget.graph();
- Graph shapesGraph = ActionLib.readFromRequest(action, Lang.TTL);
+ }
+ Graph data = graphTarget.graph();
Node targetNode = null;
if ( targetNodeStr != null ) {
String x = data.getPrefixMapping().expandPrefix(targetNodeStr);
targetNode = NodeFactory.createURI(x);
}
+ // This does not resolve owl:imports.
+ // Doing so would lead to SSRF (server-side request forgery)
+ // with the server making a URL access on the users behalf.
Shapes shapes = Shapes.parse(shapesGraph);
ValidationReport report = ( targetNode == null )
? ShaclValidator.get().validate(shapesGraph, data)
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
index 4fbeaa43a84..8e630af428c 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
@@ -28,7 +28,6 @@
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
-
import org.apache.jena.atlas.RuntimeIOException;
import org.apache.jena.atlas.io.IO;
import org.apache.jena.atlas.json.JSON;
@@ -40,7 +39,7 @@
import org.apache.jena.fuseki.system.ConNeg;
import org.apache.jena.fuseki.system.UploadDetails;
import org.apache.jena.fuseki.system.UploadDetails.PreState;
-import org.apache.jena.riot.RiotParseException;
+import org.apache.jena.riot.RiotException;
import org.apache.jena.riot.WebContent;
import org.apache.jena.riot.web.HttpNames;
import org.apache.jena.web.HttpSC;
@@ -211,7 +210,7 @@ public static void warning(HttpAction action, String string, Throwable thorwable
action.log.warn(string, thorwable);
}
- public static void errorParseError(RiotParseException ex) {
+ public static void errorParseError(RiotException ex) {
error(HttpSC.BAD_REQUEST_400, "Parse Error: "+ex.getMessage());
}
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiShaclValidation.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiShaclValidation.java
index 33472e66ef7..dfc01adf9ec 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiShaclValidation.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiShaclValidation.java
@@ -24,8 +24,12 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.apache.jena.atlas.logging.LogCtl;
+import org.apache.jena.fuseki.Fuseki;
import org.apache.jena.graph.Graph;
import org.apache.jena.http.HttpRDF;
import org.apache.jena.rdfconnection.RDFConnection;
@@ -184,6 +188,17 @@ public void shacl_no_data_graph() {
});
}
+ @Test
+ public void shacl_imports() {
+ LogCtl.withLevel(Fuseki.actionLog, "FATAL", ()->{
+ withServer((datasetURL)->{
+ FusekiTestLib.expect400(()->{
+ validateReport(datasetURL+"/shacl?default", DIR+"shapes-imports.ttl");
+ });
+ });
+ });
+ }
+
private static ValidationReport validateReport(String url, String shapesFile) {
Graph shapesGraph = RDFDataMgr.loadGraph(shapesFile);
Graph responseGraph = HttpRDF.httpPostGraphRtn(url, shapesGraph);
diff --git a/jena-fuseki2/jena-fuseki-main/testing/ShaclValidation/shapes-imports.ttl b/jena-fuseki2/jena-fuseki-main/testing/ShaclValidation/shapes-imports.ttl
new file mode 100644
index 00000000000..067cc98fe2c
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/ShaclValidation/shapes-imports.ttl
@@ -0,0 +1,21 @@
+PREFIX rdf:
+PREFIX rdfs:
+PREFIX owl:
+PREFIX xsd:
+PREFIX sh:
+
+PREFIX :
+PREFIX ex:
+PREFIX ns:
+
+[] rdf:type owl:Ontology;
+ owl:imports <.> .
+
+:nodeShape2 a sh:NodeShape ;
+ sh:targetSubjectsOf ns:p ;
+ sh:property [
+ sh:path ns:p;
+ sh:datatype xsd:string;
+ sh:maxCount 1 ;
+ sh:minCount 1 ;
+ ] .