Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class DatasetGraphTxnCtl extends DatasetGraphWrapper implements Transacti
* Use {@link #beginMultiMode}/{@link #endMultiMode} (normal
* operation), {@link #beingSingleMode}/{@link #endSingleMode} (exclusive mode)
**/
private ReadWriteLock exclusivitylock = new ReentrantReadWriteLock();
private ReadWriteLock exclusivityLock = new ReentrantReadWriteLock();

/**
* Lock to guarantee only readers are present. Writers and promote transaction
Expand Down Expand Up @@ -257,19 +257,19 @@ protected void finishPossibleWriter() {

@Override
public void startNonExclusiveMode() {
boolean b = beginMultiMode(exclusivitylock, true);
boolean b = beginMultiMode(exclusivityLock, true);
if ( !b )
throw new TransactionException("Can't start transaction at the moment.");
}

@Override
public boolean tryNonExclusiveMode(boolean canBlock) {
return beginMultiMode(exclusivitylock, canBlock);
return beginMultiMode(exclusivityLock, canBlock);
}

@Override
public void finishNonExclusiveMode() {
endMultiMode(exclusivitylock);
endMultiMode(exclusivityLock);
}

/**
Expand All @@ -295,10 +295,10 @@ public void startExclusiveMode() {
*/
private boolean startExclusiveMode(boolean canBlock) {
if ( canBlock ) {
exclusivitylock.writeLock().lock();
exclusivityLock.writeLock().lock();
return true;
}
return exclusivitylock.writeLock().tryLock();
return exclusivityLock.writeLock().tryLock();
}

/**
Expand Down Expand Up @@ -336,7 +336,7 @@ public boolean tryExclusiveMode(boolean canBlock) {
*/
@Override
public void finishExclusiveMode() {
exclusivitylock.writeLock().unlock();
exclusivityLock.writeLock().unlock();
}

// Lock abstraction.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,28 @@
import static java.lang.String.format;
import static org.apache.jena.fuseki.servlets.GraphTarget.determineTarget;

import java.util.List;

import org.apache.jena.atlas.web.MediaType;
import org.apache.jena.fuseki.DEF;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
import org.apache.jena.query.Query;
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.ShaclException;
import org.apache.jena.shacl.ShaclValidator;
import org.apache.jena.shacl.Shapes;
import org.apache.jena.shacl.ValidationReport;
import org.apache.jena.shacl.lib.ShLib;
import org.apache.jena.shacl.vocabulary.SHACL;
import org.apache.jena.sparql.syntax.ElementService;
import org.apache.jena.sparql.syntax.ElementVisitor;
import org.apache.jena.sparql.syntax.ElementVisitorBase;
import org.apache.jena.sparql.syntax.ElementWalker;
import org.apache.jena.system.G;
import org.apache.jena.vocabulary.OWL1;
import org.apache.jena.web.HttpSC;
Expand Down Expand Up @@ -68,17 +78,14 @@ protected void doPost(HttpAction action) {
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());
}

checkShapes(action, shapesGraph);

action.beginRead();
try {
GraphTarget graphTarget = determineTarget(action.getActiveDSG(), action);
Expand All @@ -95,13 +102,20 @@ protected void doPost(HttpAction action) {
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)
: ShaclValidator.get().validate(shapesGraph, data, targetNode);
ValidationReport report;
try {
// 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);
report = ( targetNode == null )
? ShaclValidator.get().validate(shapesGraph, data)
: ShaclValidator.get().validate(shapesGraph, data, targetNode);
} catch (Throwable th) {
// General catch-all.
ServletOps.errorBadRequest("SHACL Validation request error: "+th.getMessage());
throw th;
}

if ( report.conforms() )
action.log.info(format("[%d] shacl: conforms", action.id));
Expand All @@ -114,4 +128,61 @@ protected void doPost(HttpAction action) {
action.endRead();
}
}

// Check the graph in the request.
private static void checkShapes(HttpAction action, Graph shapesGraph) {
try {
checkForOwlImports(action, shapesGraph);
checkForService(shapesGraph);
} catch (ActionErrorException ex) {
throw ex;
} catch (ShaclException ex) {
action.log.error(format("[%d] shacl: bad request: %s", action.id, ex.getMessage()));
ServletOps.errorBadRequest("Bad SHACL validation request: " + ex.getMessage());
}
}

private static void checkForOwlImports(HttpAction action, Graph shapesGraph) {
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");
}
}

// The SHACL vocabulary that is used to give executable queries.
static Node[] predicates = { SHACL.select, SHACL.ask, SHACL.construct };
public static void checkForService(Graph shapesGraph) {
for ( Node p : predicates ) {
checkForService(shapesGraph, p);
}
}

/**
* Check a shapes graph for use of SERVICE in SPARQ-based extensions such as
* {@code sh:target [ sh:select ... ]}.
* This checking is regardless of the use of the query
* (e.g. it is not only applying to forms that do not allow SERVICE
* such as pre-binding rules).
*/
private static void checkForService(Graph shapesGraph, Node predicate) {
List<Node> queryNodes = G.listPO(shapesGraph, predicate, null);
G.iterSubjectsOfPredicate(shapesGraph, predicate).forEachRemaining(qNode->{
Query q = ShLib.extractSPARQLQuery(shapesGraph, qNode);
checkForService(q);
});
}

static void checkForService(Query query) {
//private static
ElementWalker.walk(query.getQueryPattern(), eltCheckServiceVisitor);
}

private static ElementVisitor eltCheckServiceVisitor = new ElementVisitorBase() {
@Override
public void visit(ElementService el) {
throw new ShaclException("SERVICE found in query");
}
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,15 @@ public void shacl_no_data_graph() {
withServer((datasetURL)->{
try ( RDFConnection conn = RDFConnection.connect(datasetURL)) {
conn.put(DIR+"data1.ttl");
try {
FusekiTestLib.expect404(()->{
ValidationReport report = validateReport(datasetURL+"/shacl?graph=urn:abc:noGraph", DIR+"shapes1.ttl");
});
} finally {
conn.update("CLEAR ALL");
}
LogCtl.withLevel(Fuseki.actionLog, "FATAL", ()->{
try {
FusekiTestLib.expect404(()->{
ValidationReport report = validateReport(datasetURL+"/shacl?graph=urn:abc:noGraph", DIR+"shapes1.ttl");
});
} finally {
conn.update("CLEAR ALL");
}
});
}
});
}
Expand All @@ -199,6 +201,25 @@ public void shacl_imports() {
});
}

@Test
public void shacl_SERVICE_not_included() {
LogCtl.withLevel(Fuseki.actionLog, "FATAL", ()->{
withServer((datasetURL)->{
FusekiTestLib.expect400(()->{
validateReport(datasetURL+"/shacl?default", DIR+"shapes-service.ttl");
});
});
});
}

@Test
public void shacl_no_SERVICE() {
// allowed
withServer((datasetURL)->{
validateReport(datasetURL+"/shacl?default", DIR+"shapes-no-service.ttl");
});
}

private static ValidationReport validateReport(String url, String shapesFile) {
Graph shapesGraph = RDFDataMgr.loadGraph(shapesFile);
Graph responseGraph = HttpRDF.httpPostGraphRtn(url, shapesGraph);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX sh: <http://www.w3.org/ns/shacl#>

PREFIX : <http://example/>

:NS
sh:declare [
sh:prefix "" ;
sh:namespace "http://example/"^^xsd:anyURI;
] .

[ ] rdf:type sh:NodeShape ;
sh:target [
a sh:SPARQLTarget ;
sh:prefixes :NS ;
sh:select "SELECT ?this { BIND(:x as ?this) }"
];
.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX sh: <http://www.w3.org/ns/shacl#>

PREFIX : <http://example/>

:NS
sh:declare [
sh:prefix "" ;
sh:namespace "http://example/"^^xsd:anyURI;
] .

[ ] rdf:type sh:NodeShape ;
sh:target [
a sh:SPARQLTarget ;
sh:prefixes :NS ;
sh:select "SELECT ?this { SERVICE :remote { } }"
];
.
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ public static String prefixes(Graph shapesGraph, Node sparqlNode) {
else if ( nNamespace.isURI() )
ns = nNamespace.getURI();
else
throw new ShaclParseException("sh:namespace is not a literal or URI");
throw new ShaclParseException("sh:namespace is not a literal or URI");
if ( seen.containsKey(prefix) ) {
if ( seen.get(prefix).equals(ns) )
continue;
Expand Down
Loading