You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2018/08/31 12:04:52 UTC
[02/27] jena git commit: JENA-1594: Initial machinery with SPARQL
Query filtering
JENA-1594: Initial machinery with SPARQL Query filtering
Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/344ae5d8
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/344ae5d8
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/344ae5d8
Branch: refs/heads/master
Commit: 344ae5d841c8d22ce8c87e4a2fa466fe2a263582
Parents: 06f5912
Author: Andy Seaborne <an...@apache.org>
Authored: Thu Aug 23 17:13:00 2018 +0100
Committer: Andy Seaborne <an...@apache.org>
Committed: Thu Aug 23 17:15:05 2018 +0100
----------------------------------------------------------------------
jena-fuseki2/jena-fuseki-access/pom.xml | 104 ++++++
.../fuseki/access/AssemblerAccessDataset.java | 63 ++++
.../access/AssemblerSecurityRegistry.java | 130 +++++++
.../jena/fuseki/access/DataAccessCtl.java | 179 +++++++++
.../jena/fuseki/access/DataAccessLib.java | 58 +++
.../access/DatasetGraphAccessControl.java | 49 +++
.../fuseki/access/Filtered_REST_Quads_R.java | 51 +++
.../fuseki/access/Filtered_SPARQL_GSP_R.java | 65 ++++
.../access/Filtered_SPARQL_QueryDataset.java | 64 ++++
.../apache/jena/fuseki/access/GraphFilter.java | 76 ++++
.../jena/fuseki/access/GraphFilterTDB1.java | 70 ++++
.../jena/fuseki/access/GraphFilterTDB2.java | 70 ++++
.../apache/jena/fuseki/access/InitSecurity.java | 38 ++
.../jena/fuseki/access/SecurityPolicy.java | 116 ++++++
.../jena/fuseki/access/SecurityRegistry.java | 72 ++++
.../jena/fuseki/access/VocabSecurity.java | 55 +++
.../org.apache.jena.sys.JenaSubsystemLifecycle | 1 +
.../apache/jena/fuseki/access/GraphData.java | 59 +++
.../fuseki/access/TS_SecurityFiltering.java | 42 +++
.../fuseki/access/TestSecurityAssembler.java | 193 ++++++++++
.../fuseki/access/TestSecurityFilterFuseki.java | 191 ++++++++++
.../fuseki/access/TestSecurityFilterLocal.java | 320 ++++++++++++++++
.../src/test/resources/log4j.properties | 40 ++
.../testing/Access/assem-security-shared.ttl | 58 +++
.../testing/Access/assem-security.ttl | 63 ++++
jena-fuseki2/jena-fuseki-core/pom.xml | 2 +
.../org/apache/jena/fuseki/jetty/JettyLib.java | 169 +++++++++
.../apache/jena/fuseki/server/Operation.java | 6 +-
.../jena/fuseki/servlets/ActionService.java | 2 +-
.../servlets/ServiceDispatchRegistry.java | 10 +-
.../jena/fuseki/embedded/FusekiServer.java | 126 ++++---
.../jena/fuseki/embedded/JettyServer.java | 369 +++++++++++++++++++
jena-fuseki2/pom.xml | 1 +
.../rdfconnection/RDFConnectionFactory.java | 25 ++
34 files changed, 2874 insertions(+), 63 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/pom.xml
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/pom.xml b/jena-fuseki2/jena-fuseki-access/pom.xml
new file mode 100644
index 0000000..867a956
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/pom.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <name>Apache Jena - Fuseki Data Access Control</name>
+ <artifactId>jena-fuseki-access</artifactId>
+
+ <parent>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-fuseki</artifactId>
+ <version>3.9.0-SNAPSHOT</version>
+ </parent>
+
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-fuseki-embedded</artifactId>
+ <version>3.9.0-SNAPSHOT</version>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>attach-sources-test</id>
+ <goals>
+ <goal>test-jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>**/TS_*.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <configuration>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ </configuration>
+ </plugin>
+
+ </plugins>
+
+ </build>
+
+</project>
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
new file mode 100644
index 0000000..75cb155
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import org.apache.jena.assembler.Assembler;
+import org.apache.jena.assembler.Mode;
+import org.apache.jena.assembler.assemblers.AssemblerBase;
+import org.apache.jena.assembler.exceptions.AssemblerException;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.util.graph.GraphUtils;
+
+public class AssemblerAccessDataset extends AssemblerBase {
+
+ /*
+ * <#access_dataset> rdf:type access:AccessControlledDataset ;
+ * access:registry <#securityRegistry> ;
+ * access:dataset <#tdb_dataset_read> ;
+ * .
+ */
+ @Override
+ public Dataset open(Assembler a, Resource root, Mode mode) {
+ if ( ! GraphUtils.exactlyOneProperty(root, VocabSecurity.pSecurityRegistry) )
+ throw new AssemblerException(root, "Expected exactly one access:registry property");
+ if ( ! GraphUtils.exactlyOneProperty(root, VocabSecurity.pDataset) )
+ throw new AssemblerException(root, "Expected exactly one access:dataset property");
+
+ RDFNode rnRegistry = root.getProperty(VocabSecurity.pSecurityRegistry).getObject();
+ RDFNode rnDataset = root.getProperty(VocabSecurity.pDataset).getObject();
+
+ SecurityRegistry sr = (SecurityRegistry)a.open(rnRegistry.asResource()) ;
+ Dataset ds = (Dataset)a.open(rnDataset.asResource()) ;
+
+ DatasetGraph dsg = new DatasetGraphAccessControl(ds.asDatasetGraph(), sr);
+ ds = DatasetFactory.wrap(dsg);
+
+// // Add marker
+// ds.getContext().set(DataAccessCtl.symControlledAccess, true);
+// // Add security registry
+// ds.getContext().set(DataAccessCtl.symSecurityRegistry, sr);
+ return ds;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerSecurityRegistry.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerSecurityRegistry.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerSecurityRegistry.java
new file mode 100644
index 0000000..1e8a3e4
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerSecurityRegistry.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.List;
+
+import org.apache.jena.assembler.Assembler;
+import org.apache.jena.assembler.Mode;
+import org.apache.jena.assembler.assemblers.AssemblerBase;
+import org.apache.jena.assembler.exceptions.AssemblerException;
+import org.apache.jena.ext.com.google.common.collect.ArrayListMultimap;
+import org.apache.jena.ext.com.google.common.collect.Multimap;
+import org.apache.jena.graph.Node;
+import org.apache.jena.rdf.model.RDFList;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.StmtIterator;
+import org.apache.jena.rdf.model.impl.Util;
+import org.apache.jena.sparql.util.graph.GNode;
+import org.apache.jena.sparql.util.graph.GraphList;
+import org.apache.jena.sparql.util.graph.GraphUtils;
+
+public class AssemblerSecurityRegistry extends AssemblerBase {
+
+ /**
+ * SecurityRegistry.
+ * Builds a SecurityRegistry - a map fron user name to
+ *
+ * <#securityRegistry> rdf:type access:SecurityRegistry ;
+ * access:entry ("user1" <http://host/graphname1> <http://host/graphname2> ) ;) ;
+ * access:entry ("user1" <http://host/graphname3> ) ;
+ * access:entry ("user2" <http://host/graphname3> ) ;
+ * .
+ *
+ * access:entry [ :user "user2" ; :graphs (<http://host/graphname3> ) ] ;
+ */
+
+ @Override
+ public SecurityRegistry open(Assembler a, Resource root, Mode mode) {
+ SecurityRegistry registry = new SecurityRegistry();
+ // Java walking gives better error messages.
+ StmtIterator sIter = root.listProperties(VocabSecurity.pEntry);
+ if ( ! sIter.hasNext() )
+ throw new AssemblerException(root, "No access entries");
+ Multimap<String, Node> map = ArrayListMultimap.create();
+
+ sIter.forEachRemaining(s->{
+ RDFNode n = s.getObject();
+ if ( ! n.isResource())
+ throw new AssemblerException(root, "Found access:entry with non-resource");
+ Resource r = (Resource)n;
+ GNode entry = new GNode(root.getModel().getGraph(), n.asNode());
+ if ( GraphList.isListNode(entry) ) {
+ // Format:: access:entry ("user1" <http://host/graphname1> <http://host/graphname2> ) ;
+ parseList(map, root, entry);
+ } else if ( r.hasProperty(VocabSecurity.pUser) || r.hasProperty(VocabSecurity.pGraphs) ) {
+ // Format:: access:entry [ :user "user2" ; :graphs (<http://host/graphname3> ) ]
+ parseStruct(map, root, r);
+ } else
+ throw new AssemblerException(root, "Found access:entry but failed to parse the object: "+s.getSubject());
+ });
+
+ map.keySet().forEach(u->{
+ SecurityPolicy policy = new SecurityPolicy(map.get(u));
+ registry.put(u, policy);
+ });
+
+ return registry;
+ }
+
+ /**Format:: access:entry ("user1" <http://host/graphname1> <http://host/graphname2> ) ; */
+ private void parseList(Multimap<String, Node> map, Resource root, GNode entry) {
+ List<Node> members = GraphList.members(entry);
+ // string, then URIs.
+ if ( members.isEmpty() )
+ throw new AssemblerException(root, "Found access:entry with an empty list");
+ Node userNode = members.get(0);
+ if ( ! Util.isSimpleString(userNode) ) {}
+ String user = userNode.getLiteralLexicalForm();
+ for ( int i = 1 ; i < members.size() ; i++ ) {
+ Node gn = members.get(i);
+ //if ( gn.isBlank() )
+ if ( ! gn.isURI() ) { }
+ //System.out.printf("L: user %s : access : %s\n", user, gn);
+ map.put(user, gn);
+ }
+ }
+
+ /** Format:: access:entry [ :user "user2" ; :graphs (<http://host/graphname3> ) ] */
+ private void parseStruct(Multimap<String, Node> map, Resource root, Resource r) {
+ if ( ! GraphUtils.exactlyOneProperty(r, VocabSecurity.pUser) )
+ throw new AssemblerException(root, "Expected exactly one access:user property for "+r);
+ if ( ! GraphUtils.exactlyOneProperty(r, VocabSecurity.pGraphs) )
+ throw new AssemblerException(root, "Expected exactly one access:graphs property for "+r);
+
+ String user = GraphUtils.getStringValue(r, VocabSecurity.pUser);
+ r.listProperties(VocabSecurity.pGraphs).mapWith(s->s.getObject()).forEachRemaining(x->{
+ if ( x.isURIResource() ) {
+ //System.out.printf("S: user %s : access : %s\n", user, x.asNode());
+ map.put(user, x.asNode());
+ } else {
+ // List?
+ RDFList list = x.as(RDFList.class);
+ list.iterator().forEachRemaining(rn->{
+ if ( ! rn.isURIResource() )
+ throw new AssemblerException(root, "Not a graph name: "+rn);
+ //System.out.printf("S: user %s : access : %s\n", user, rn.asNode());
+ map.put(user, rn.asNode());
+ });
+ }
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..85b1f31
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.function.Function;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.jena.fuseki.embedded.FusekiServer;
+import org.apache.jena.fuseki.server.Operation;
+import org.apache.jena.fuseki.servlets.ActionService;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServiceDispatchRegistry;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.riot.WebContent;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.util.Context;
+import org.apache.jena.sparql.util.Symbol;
+import org.eclipse.jetty.security.SecurityHandler;
+
+/** A library of operations related to data acess sexurity for Fuseki */
+public class DataAccessCtl {
+
+ /**
+ * Flag for whether this is data access controlled or not - boolean false or undef for "not
+ * controlled".
+ */
+ public static final Symbol symControlledAccess = Symbol.create(VocabSecurity.getURI() + "controlled");
+
+ /**
+ * Symbol for the {@link SecurityRegistry}. Must be present if
+ * {@link #symControlledAccess} indicates data access control.
+ */
+ public static final Symbol symSecurityRegistry = Symbol.create(VocabSecurity.getURI() + "registry");
+
+ /** Get the user from the servlet context via {@link HttpServletRequest#getRemoteUser} */
+ public static final Function<HttpAction, String> requestUserServlet = (action)->action.request.getRemoteUser();
+
+// /** Build a fuseki server with controlled access enabled. */
+// public static FusekiServer controlledFuseki(int port, Function<HttpAction, String> determineUser, Consumer<FusekiServer.Builder> configure) {
+// FusekiServer.Builder b = controlledFuseki(port, determineUser);
+// configure.accept(b);
+// return b.build();
+// }
+//
+// /** Builder for a read-only Fuseki server with controlled access actions enabled. */
+// public static FusekiServer.Builder controlledFuseki(int port, Function<HttpAction, String> determineUser) {
+// ServiceDispatchRegistry sdr = new ServiceDispatchRegistry(false);
+// ActionService actionQuery = new Filtered_SPARQL_QueryDataset(determineUser);
+// ActionService actionGspR = new Filtered_SPARQL_GSP_R(determineUser);
+// ActionService actionQuadsR = new Filtered_REST_Quads_R(determineUser);
+//
+// // Create empty and only add "read" operations.
+// FusekiServer.Builder builder = FusekiServer.create(sdr)
+// .port(port)
+// .registerOperation(Operation.Query, WebContent.contentTypeSPARQLQuery, actionQuery)
+// .registerOperation(Operation.GSP_R, actionGspR)
+// .registerOperation(Operation.Quads_R, actionQuadsR);
+// return builder;
+// }
+
+ /**
+ * Enable data access control on a {@link DatasetGraph}. This modifies the
+ * {@link DatasetGraph}'s {@link Context}.
+ */
+ public static void controlledDataset(DatasetGraph dsg, SecurityRegistry reg) {
+ // Or wrapper.
+ dsg.getContext().set(symControlledAccess, true);
+ dsg.getContext().set(symSecurityRegistry, reg);
+ }
+
+ /**
+ * Enable data access control on a {@link Dataset}. This modifies the
+ * {@link Dataset}'s {@link Context}.
+ */
+ public static void controlledDataset(Dataset ds, SecurityRegistry reg) {
+ ds.getContext().set(symControlledAccess, true);
+ ds.getContext().set(symSecurityRegistry, reg);
+ }
+
+ /**
+ * Return a {@link DatasetGraph} with added data access control. Use of the original
+ * {@code DatasetGraph} is not controlled.
+ */
+ public static Dataset wrapControlledDataset(Dataset dsBase, SecurityRegistry reg) {
+ DatasetGraph dsg = wrapControlledDataset(dsBase.asDatasetGraph(), reg);
+ return DatasetFactory.wrap(dsg);
+ }
+
+ /**
+ * Return a {@link DatasetGraph} with added data access control. Use of the original
+ * {@code DatasetGraph} is not controlled.
+ */
+ public static DatasetGraph wrapControlledDataset(DatasetGraph dsgBase, SecurityRegistry reg) {
+ if ( dsgBase instanceof DatasetGraphAccessControl ) {
+ DatasetGraphAccessControl dsgx = (DatasetGraphAccessControl)dsgBase;
+ if ( reg == dsgx.getRegistry() )
+ return dsgx;
+ throw new IllegalArgumentException("DatasetGraph is alerady wrapped on a DatasetGraphAccessControl with a different SecurityRegistry");
+ }
+
+ DatasetGraphAccessControl dsg1 = new DatasetGraphAccessControl(dsgBase, reg);
+ return dsg1;
+ }
+
+ /**
+ * Return a {@code FusekiServer.Builder} setup for data access control
+ * but with no Jetty security handler.
+ */
+ public static FusekiServer.Builder fusekiBuilder(Function<HttpAction, String> determineUser) {
+ return fusekiBuilder(null, determineUser);
+ }
+
+ /**
+ * Return a {@code FusekiServer.Builder} setup for data access control and with a
+ * Jetty security handler.
+ */
+ public static FusekiServer.Builder fusekiBuilder(SecurityHandler securityHandler, Function<HttpAction, String> determineUser) {
+ FusekiServer.Builder builder = FusekiServer.create();
+ if ( securityHandler != null )
+ builder.securityHandler(securityHandler);
+ // Replace the standard operations with the SecurityRegistry processing ones.
+ builder.registerOperation(Operation.Query, WebContent.contentTypeSPARQLQuery, new Filtered_SPARQL_QueryDataset(determineUser));
+ builder.registerOperation(Operation.GSP_R, new Filtered_SPARQL_GSP_R(determineUser));
+ builder.registerOperation(Operation.Quads_R, new Filtered_REST_Quads_R(determineUser));
+ return builder;
+ }
+
+ /**
+ * Modify in-place an existing {@link FusekiServer} so that the read-operations for
+ * query/GSP/Quads go to the data-filtering versions of the {@link ActionService ActionServices}.
+ * (It is better to create the server via {@link #DataAccessCtl.builder} first rather than modify afterwards.)
+ */
+ public static void enable(FusekiServer server, Function<HttpAction, String> determineUser) {
+ /*
+ * Reconfigure standard Jena Fuseki, replacing the default implementation of "query"
+ * with a filtering one. This for this server only.
+ */
+ // The mapping operation to handler is in the ServiceDispatchRegistry and is per
+ // server (per servlet context). "registerOrReplace" would be a better name,
+ ActionService queryServletAccessFilter = new Filtered_SPARQL_QueryDataset(determineUser);
+ ServletContext cxt = server.getServletContext();
+ ServiceDispatchRegistry.get(cxt).register(Operation.Query, WebContent.contentTypeSPARQLQuery, queryServletAccessFilter);
+ ServiceDispatchRegistry.get(cxt).register(Operation.GSP_R, null, new Filtered_SPARQL_GSP_R(determineUser));
+ ServiceDispatchRegistry.get(cxt).register(Operation.Quads_R, null, new Filtered_REST_Quads_R(determineUser));
+ }
+
+ /**
+ * Return whether a {@code DatasetGraph} has access control, either because it is wrapped in
+ * {@link DatasetGraphAccessControl} or because it has the context settings.
+ */
+ public static boolean isAccessControlled(DatasetGraph dsg) {
+ if ( dsg instanceof DatasetGraphAccessControl )
+ return true;
+ if ( dsg.getContext().isDefined(DataAccessCtl.symControlledAccess) )
+ return true;
+ if ( dsg.getContext().isDefined(DataAccessCtl.symSecurityRegistry) )
+ return true;
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessLib.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessLib.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessLib.java
new file mode 100644
index 0000000..4329cc6
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessLib.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.function.Function;
+
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.sparql.core.DatasetGraph;
+
+/** Package-only operations */
+class DataAccessLib {
+
+ /** Determine the {@link SecurityPolicy} for this request */
+ static SecurityPolicy getSecurityPolicy(HttpAction action, DatasetGraph dataset, Function<HttpAction, String> requestUser) {
+ SecurityRegistry registry = getSecurityRegistry(action, dataset);
+ if ( registry == null )
+ ServletOps.errorOccurred("Internal Server Error");
+
+ SecurityPolicy sCxt = null;
+ String user = requestUser.apply(action);
+ sCxt = registry.get(user);
+ if ( sCxt == null )
+ sCxt = noSecurityPolicy();
+ return sCxt;
+ }
+
+
+ /** Get the {@link SecurityRegistry} for an action/query/dataset */
+ static SecurityRegistry getSecurityRegistry(HttpAction action, DatasetGraph dsg) {
+ if ( dsg instanceof DatasetGraphAccessControl )
+ return ((DatasetGraphAccessControl)dsg).getRegistry();
+ return dsg.getContext().get(DataAccessCtl.symSecurityRegistry);
+ }
+
+ static SecurityPolicy noSecurityPolicy() {
+ ServletOps.errorForbidden();
+ // Should not get here.
+ throw new InternalError();
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
new file mode 100644
index 0000000..d770ade
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.Objects;
+
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphWrapper;
+
+/** DatasetGraph layer that carries a SecurityRegistry. */
+class DatasetGraphAccessControl extends DatasetGraphWrapper {
+
+ private SecurityRegistry registry = null;
+
+ public DatasetGraphAccessControl(DatasetGraph dsg, SecurityRegistry registry) {
+ super(Objects.requireNonNull(dsg));
+ this.registry = Objects.requireNonNull(registry);
+ }
+
+ public SecurityRegistry getRegistry() {
+ return registry;
+ }
+
+ /**
+ * Return the underlying {@code DatasetGraph}. If the argument is not a
+ * {@code DatasetGraphAccessControl}, return the argument.
+ */
+ public static DatasetGraph unwrap(DatasetGraph dsg) {
+ if ( ! ( dsg instanceof DatasetGraphAccessControl ) )
+ return dsg;
+ return ((DatasetGraphAccessControl)dsg).getWrapped();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_REST_Quads_R.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_REST_Quads_R.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_REST_Quads_R.java
new file mode 100644
index 0000000..d698ecb
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_REST_Quads_R.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.function.Function;
+
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.REST_Quads_R;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphWrapper;
+import org.apache.jena.sparql.util.Context;
+
+public class Filtered_REST_Quads_R extends REST_Quads_R {
+ public Filtered_REST_Quads_R(Function<HttpAction, String> determineUser) {}
+
+ @Override
+ protected void validate(HttpAction action) {
+ super.validate(action);
+ }
+
+ @Override
+ protected void doGet(HttpAction action) {
+
+ DatasetGraph dsg0 = action.getActiveDSG();
+ DatasetGraph dsg = new DatasetGraphWrapper(dsg0) {
+ @Override public Context getContext() { return super.getContext(); }
+ };
+ // Replace datasetGraph
+ // XXX Implement access control for REST_Quads_R
+
+ HttpAction action2 = action;
+
+ super.doGet(action2);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_GSP_R.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_GSP_R.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_GSP_R.java
new file mode 100644
index 0000000..1eae1d1
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_GSP_R.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.function.Function;
+
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.SPARQL_GSP_R;
+import org.apache.jena.sparql.util.Context;
+
+public class Filtered_SPARQL_GSP_R extends SPARQL_GSP_R {
+
+ private final Function<HttpAction, String> requestUser;
+
+ public Filtered_SPARQL_GSP_R(Function<HttpAction, String> determineUser) {
+ this.requestUser = determineUser;
+ }
+
+ @Override
+ protected void doGet(HttpAction action) {
+ // For this, mask on target.
+
+ // Avoid doing twice?
+ Target target = determineTarget(action);
+
+// Node gn = target.graphName;
+// boolean dftGraph = target.isDefault;
+ // Yes/no based on graph.
+
+ // 1:: DatsetGraphWrapper and modify action.activeDGS;
+ // 2:: action.getContext().set(...
+
+
+ DataAccessLib.getSecurityPolicy(action, action.getActiveDSG(), requestUser);
+
+ Context context = action.getActiveDSG().getContext();
+ //action.getContext(); // Is this the DGS? No. copied.
+ action.getContext().set(DataAccessCtl.symControlledAccess, true);
+ // XXX Implement access control for GSP_R
+ SecurityRegistry securityRegistry = null;
+ String user = requestUser.apply(action);
+
+
+
+ action.getContext().set(DataAccessCtl.symSecurityRegistry, securityRegistry);
+ super.doGet(action);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java
new file mode 100644
index 0000000..cc260f2
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.function.Function;
+
+import org.apache.jena.fuseki.servlets.ActionService;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.SPARQL_QueryDataset;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.query.Query;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.sparql.core.DatasetGraph;
+
+/** A Query {@link ActionService} that inserts a security filter on each query. */
+final
+public class Filtered_SPARQL_QueryDataset extends SPARQL_QueryDataset {
+ private final Function<HttpAction, String> requestUser;
+
+ public Filtered_SPARQL_QueryDataset(Function<HttpAction, String> requestUser) {
+ this.requestUser = requestUser;
+ }
+
+ @Override
+ protected QueryExecution createQueryExecution(HttpAction action, Query query, Dataset dataset) {
+ // Server database, not the possibly dynamically built "dataset"
+ DatasetGraph dsg = action.getDataset();
+ if ( dsg == null )
+ return super.createQueryExecution(action, query, dataset);
+ if ( ! DataAccessCtl.isAccessControlled(dsg) )
+ return super.createQueryExecution(action, query, dataset);
+
+ SecurityPolicy sCxt = DataAccessLib.getSecurityPolicy(action, dataset.asDatasetGraph(), requestUser);
+ if ( dsg instanceof DatasetGraphAccessControl ) {
+ // Take off one layer.
+ dsg = DatasetGraphAccessControl.unwrap(dsg);
+ // Add back the Dataset for the createQueryExecution call.
+ dataset = DatasetFactory.wrap(dsg);
+ }
+
+ QueryExecution qExec = super.createQueryExecution(action, query, dataset);
+ if ( sCxt != null )
+ sCxt.filterTDB(dsg, qExec);
+ return qExec;
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilter.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilter.java
new file mode 100644
index 0000000..33283d6
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilter.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.apache.jena.atlas.lib.tuple.Tuple;
+import org.apache.jena.sparql.util.Symbol;
+
+/**
+ * A graph filter for TDB1 and TDB2 (by generic type).
+ * <p>
+ * This filter takes a collection of graph names and returns true from
+ * {@link #test(Tuple)} if the tuple graph slot is in the collection of graph names or
+ * matchDefaultGraph is true. It can be used as an "allow" filter; it can be negated to
+ * become a "deny" filter.
+ *
+ * @see GraphFilterTDB1#graphFilter
+ * @see GraphFilterTDB2#graphFilter
+ */
+public abstract class GraphFilter<X> implements Predicate<Tuple<X>> {
+ private final Set<X> graphs;
+ private final boolean matchDefaultGraph;
+// // This makes the GraphFilter stateful.
+// private X slot = null;
+
+ protected GraphFilter(Collection<X> matches, boolean matchDefaultGraph) {
+ this.graphs = new HashSet<X>(matches);
+ this.matchDefaultGraph = matchDefaultGraph;
+ }
+
+ public abstract Symbol getContextKey();
+
+ @Override
+ public boolean test(Tuple<X> t) {
+ if ( t.len() == 3 ) {
+ // Default graph.
+ return matchDefaultGraph;
+ }
+ X g = t.get(0);
+ boolean b = perGraphTest(g);
+ return b;
+ }
+
+ // The per graph test.
+ private boolean perGraphTest(X g) {
+ return graphs.contains(g);
+// if ( g == slot ) {
+// System.err.println("Slot hit");
+// return true;
+// }
+// boolean b = matches.contains(g);
+// if ( b )
+// slot = g ;
+// return b;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB1.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB1.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB1.java
new file mode 100644
index 0000000..d15c57b
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB1.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.jena.atlas.lib.ListUtils;
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.util.Symbol;
+import org.apache.jena.system.Txn;
+import org.apache.jena.tdb.store.NodeId;
+import org.apache.jena.tdb.store.nodetable.NodeTable;
+import org.apache.jena.tdb.sys.SystemTDB;
+import org.apache.jena.tdb.sys.TDBInternal;
+
+/** {@link GraphFilter} for TDB1 */
+class GraphFilterTDB1 extends GraphFilter<NodeId> {
+
+ private GraphFilterTDB1(Collection<NodeId> matches, boolean matchDefaultGraph) {
+ super(matches, matchDefaultGraph);
+ }
+
+ @Override
+ public Symbol getContextKey() {
+ return SystemTDB.symTupleFilter;
+ }
+
+ /**
+ * Create a graph filter for a TDB1 {@link DatasetGraph}. The filter matches (returns
+ * true) for Tuples where the graph slot in quad is in the collection or for triples in the default
+ * graph according the boolean.
+ *
+ * @see SecurityPolicy#filterTDB(DatasetGraph, QueryExecution)
+ */
+ public static GraphFilterTDB1 graphFilter(DatasetGraph dsg, Collection<Node> namedGraphs, boolean matchDefaultGraph) {
+ if ( ! TDBInternal.isTDB1(dsg) )
+ throw new IllegalArgumentException("DatasetGraph is not TDB1-backed");
+ List<NodeId> x =
+ Txn.calculateRead(dsg, ()->{
+ NodeTable nt = TDBInternal.getDatasetGraphTDB(dsg).getQuadTable().getNodeTupleTable().getNodeTable();
+ return
+ ListUtils.toList(
+ namedGraphs.stream()
+ .map(n->nt.getNodeIdForNode(n))
+ .filter(Objects::nonNull)
+ );
+ });
+ return new GraphFilterTDB1(x, matchDefaultGraph);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB2.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB2.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB2.java
new file mode 100644
index 0000000..cf95244
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/GraphFilterTDB2.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.jena.atlas.lib.ListUtils;
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.util.Symbol;
+import org.apache.jena.system.Txn;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.nodetable.NodeTable;
+import org.apache.jena.tdb2.sys.SystemTDB;
+import org.apache.jena.tdb2.sys.TDBInternal;
+
+/** {@link GraphFilter} for TDB2 */
+class GraphFilterTDB2 extends GraphFilter<NodeId> {
+
+ private GraphFilterTDB2(Collection<NodeId> matches, boolean matchDefaultGraph) {
+ super(matches, matchDefaultGraph);
+ }
+
+ @Override
+ public Symbol getContextKey() {
+ return SystemTDB.symTupleFilter;
+ }
+
+ /**
+ * Create a graph filter for a TDB2 {@link DatasetGraph}. The filter matches (returns
+ * true) for Tuples where the graph slot in quad is in the collection or for triples in the default
+ * graph according the boolean.
+ *
+ * @see SecurityPolicy#filterTDB(DatasetGraph, QueryExecution)
+ */
+ public static GraphFilterTDB2 graphFilter(DatasetGraph dsg, Collection<Node> namedGraphs, boolean matchDefaultGraph) {
+ if ( ! TDBInternal.isTDB2(dsg) )
+ throw new IllegalArgumentException("DatasetGraph is not TDB2-backed");
+ List<NodeId> x =
+ Txn.calculateRead(dsg, ()->{
+ NodeTable nt = TDBInternal.getDatasetGraphTDB(dsg).getQuadTable().getNodeTupleTable().getNodeTable();
+ return
+ ListUtils.toList(
+ namedGraphs.stream()
+ .map(n->nt.getNodeIdForNode(n))
+ .filter(Objects::nonNull)
+ );
+ });
+ return new GraphFilterTDB2(x, matchDefaultGraph);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/InitSecurity.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/InitSecurity.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/InitSecurity.java
new file mode 100644
index 0000000..55e760e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/InitSecurity.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import org.apache.jena.sys.JenaSubsystemLifecycle;
+import org.apache.jena.sys.JenaSystem;
+
+public class InitSecurity implements JenaSubsystemLifecycle {
+
+ @Override
+ public void start() {
+ JenaSystem.logLifecycle("InitSecurity - start") ;
+ VocabSecurity.init();
+ JenaSystem.logLifecycle("InitSecurity - finish") ;
+ }
+
+ @Override
+ public void stop() {}
+
+ @Override
+ public int level() { return 100; }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityPolicy.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityPolicy.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityPolicy.java
new file mode 100644
index 0000000..808e980
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityPolicy.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Predicate;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.util.Context;
+import org.apache.jena.sparql.util.NodeUtils;
+import org.apache.jena.tdb.TDBFactory;
+import org.apache.jena.tdb2.DatabaseMgr;
+
+/** A {@link SecurityPolicy} is the things actor (user, role) is allowed to do.
+ * Currently version: the set of graphs, by graph name, they can access.
+ * It can be inverted into a "deny" policy with {@link Predicate#negate()}.
+ */
+public class SecurityPolicy {
+
+ public static SecurityPolicy NONE = new SecurityPolicy();
+ public static SecurityPolicy DFT_GRAPH = new SecurityPolicy(true);
+
+ private final Collection<Node> graphNames = ConcurrentHashMap.newKeySet();
+ private final boolean matchDefaultGraph;
+
+ public SecurityPolicy() {
+ this(false);
+ }
+
+ public SecurityPolicy(boolean matchDefaultGraph) {
+ this.matchDefaultGraph = matchDefaultGraph;
+ }
+
+ public SecurityPolicy(String...graphNames) {
+ this(NodeUtils.convertToSetNodes(graphNames));
+ }
+
+ public SecurityPolicy(Node...graphNames) {
+ this(Arrays.asList(graphNames));
+ }
+
+ public SecurityPolicy(Collection<Node> graphNames) {
+ this.graphNames.addAll(graphNames);
+ this.matchDefaultGraph = graphNames.stream().anyMatch(Quad::isDefaultGraph);
+ }
+
+ /**
+ * Apply a filter suitable for the TDB-backed {@link DatasetGraph}, to the {@link Context} of the
+ * {@link QueryExecution}. This does not modify the {@link DatasetGraph}
+ */
+ public void filterTDB(DatasetGraph dsg, QueryExecution qExec) {
+ GraphFilter<?> predicate = predicate(dsg);
+ qExec.getContext().set(predicate.getContextKey(), predicate);
+ }
+
+ /** Modify the {@link Context} of the TDB-backed {@link DatasetGraph}. */
+ public void filterTDB(DatasetGraph dsg) {
+ GraphFilter<?> predicate = predicate(dsg);
+ dsg.getContext().set(predicate.getContextKey(), predicate);
+ }
+
+ @Override
+ public String toString() {
+ return "dft:"+matchDefaultGraph+" / "+graphNames.toString();
+ }
+
+ /**
+ * Create a GraphFilter for a TDB backed dataset.
+ *
+ * @return GraphFilter
+ * @throws IllegalArgumentException
+ * if not a TDB database, or a {@link DatasetGraphAccessControl} wrapped
+ * TDB database.
+ */
+ public GraphFilter<?> predicate(DatasetGraph dsg) {
+ dsg = DatasetGraphAccessControl.unwrap(dsg);
+ // dsg has to be the database dataset, not wrapped.
+ // DatasetGraphSwitchable is wrapped but should not be unwrapped.
+ if ( TDBFactory.isTDB1(dsg) )
+ return filterTDB1(dsg);
+ if ( DatabaseMgr.isTDB2(dsg) )
+ return filterTDB2(dsg);
+ throw new IllegalArgumentException("Not a TDB1 or TDB2 database: "+dsg.getClass().getSimpleName());
+ }
+
+ public GraphFilterTDB2 filterTDB2(DatasetGraph dsg) {
+ GraphFilterTDB2 f = GraphFilterTDB2.graphFilter(dsg, graphNames, matchDefaultGraph);
+ return f;
+ }
+
+ public GraphFilterTDB1 filterTDB1(DatasetGraph dsg) {
+ GraphFilterTDB1 f = GraphFilterTDB1.graphFilter(dsg, graphNames, matchDefaultGraph);
+ return f;
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityRegistry.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityRegistry.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityRegistry.java
new file mode 100644
index 0000000..1ad4098
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityRegistry.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.StringJoiner;
+
+import javax.servlet.ServletContext;
+
+import org.apache.jena.atlas.lib.Registry;
+import org.apache.jena.fuseki.Fuseki;
+
+/**
+ * A {@link SecurityRegistry} is mapping from a string (typically a user name or role
+ * name) to a {@link SecurityPolicy}, where the {@link SecurityPolicy}
+ * is the access control operations for the user/role.
+ */
+public class SecurityRegistry extends Registry<String, SecurityPolicy>{
+
+ public static SecurityRegistry get(ServletContext cxt) {
+ return (SecurityRegistry)cxt.getAttribute(Fuseki.attrSecurityRegistry);
+ }
+
+ public static void set(ServletContext cxt, SecurityRegistry securityRegistry) {
+ cxt.setAttribute(Fuseki.attrSecurityRegistry, securityRegistry);
+ }
+
+ public SecurityRegistry() {}
+
+ @Override
+ public SecurityPolicy get(String actor) {
+ if ( actor == null )
+ return SecurityPolicy.NONE;
+ SecurityPolicy policy = super.get(actor);
+ if ( policy == null )
+ policy = SecurityPolicy.NONE;
+ return policy;
+ }
+
+ @Override
+ public String toString() {
+ if ( true )
+ return "SecurityRegistry"+keys();
+ // Long form.
+ StringJoiner sj1 = new StringJoiner("\n", "{ SecurityRegistry\n", "\n}");
+ super.keys().forEach(u->{
+ SecurityPolicy x = super.get(u);
+ StringJoiner sj2 = new StringJoiner("");
+ sj2.add(" ")
+ .add(u)
+ .add(" -> ")
+ .add(x.toString());
+ sj1.add(sj2.toString());
+ });
+ return sj1.toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/VocabSecurity.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/VocabSecurity.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/VocabSecurity.java
new file mode 100644
index 0000000..14ed959
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/VocabSecurity.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.sparql.core.assembler.AssemblerUtils;
+import org.apache.jena.tdb.assembler.Vocab;
+
+public class VocabSecurity {
+ private static final String NS = "http://jena.apache.org/access#";
+
+ public static String getURI() { return NS ; }
+
+ // Types
+ public static final Resource tAccessControlledDataset = Vocab.type(NS, "AccessControlledDataset");
+ public static final Resource tSecurityRegistry = Vocab.type(NS, "SecurityRegistry");
+
+ // Make subproperty of.
+ public static final Property pDataset = Vocab.property(NS, "dataset");
+ public static final Property pSecurityRegistry = Vocab.property(NS, "registry");
+
+ public static final Property pEntry = Vocab.property(NS, "entry");
+ public static final Property pUser = Vocab.property(NS, "user");
+ public static final Property pGraphs = Vocab.property(NS, "graphs");
+
+ private static boolean initialized = false ;
+
+ static { init() ; }
+
+ static synchronized public void init() {
+ if ( initialized )
+ return;
+ initialized = true;
+ // AssemblerUtils.subProperty
+ AssemblerUtils.registerDataset(tAccessControlledDataset, new AssemblerAccessDataset());
+ AssemblerUtils.registerModel(tSecurityRegistry, new AssemblerSecurityRegistry());
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle b/jena-fuseki2/jena-fuseki-access/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle
new file mode 100644
index 0000000..e1699d2
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle
@@ -0,0 +1 @@
+org.apache.jena.fuseki.access.InitSecurity
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/GraphData.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/GraphData.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/GraphData.java
new file mode 100644
index 0000000..9712b71
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/GraphData.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import org.apache.jena.atlas.lib.StrUtils;
+import org.apache.jena.graph.Node;
+import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.RDFParser;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.system.Txn;
+
+/** Build graph data for the filter/security tests. */
+
+class GraphData {
+ private static String dataStr = StrUtils.strjoinNL
+ ("PREFIX : <http://test/>"
+ ,""
+ ,":s0 :p 0 ."
+ ,":g1 { :s1 :p 1 }"
+ ,":g2 { :s2 :p 2 }"
+ ,":g3 { :s3 :p 3 }"
+ ,":g4 { :s4 :p 4 }"
+ );
+
+
+ static Node s0 = SSE.parseNode("<http://test/s0>");
+ static Node s1 = SSE.parseNode("<http://test/s1>");
+ static Node s2 = SSE.parseNode("<http://test/s2>");
+ static Node s3 = SSE.parseNode("<http://test/s3>");
+ static Node s4 = SSE.parseNode("<http://test/s4>");
+
+ static Node g1 = SSE.parseNode("<http://test/g1>");
+ static Node g2 = SSE.parseNode("<http://test/g2>");
+ static Node g3 = SSE.parseNode("<http://test/g3>");
+ static Node g4 = SSE.parseNode("<http://test/g4>");
+
+ public static void fill(DatasetGraph dsg) {
+ Txn.executeWrite(dsg, ()->{
+ RDFParser.create().fromString(dataStr).lang(Lang.TRIG).parse(dsg);
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java
new file mode 100644
index 0000000..35cb7ed
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import org.apache.jena.atlas.logging.LogCtl;
+import org.apache.jena.fuseki.Fuseki;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses( {
+ TestSecurityFilterLocal.class
+ , TestSecurityFilterFuseki.class
+ , TestSecurityAssembler.class
+})
+
+public class TS_SecurityFiltering {
+ @BeforeClass public static void setupForFusekiServer() {
+ LogCtl.setLevel(Fuseki.serverLogName, "WARN");
+ LogCtl.setLevel(Fuseki.actionLogName, "WARN");
+ LogCtl.setLevel(Fuseki.requestLogName, "WARN");
+ LogCtl.setLevel(Fuseki.adminLogName, "WARN");
+ LogCtl.setLevel("org.eclipse.jetty", "WARN");
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssembler.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssembler.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssembler.java
new file mode 100644
index 0000000..eefdcd7
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssembler.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.atlas.lib.StrUtils;
+import org.apache.jena.fuseki.FusekiLib;
+import org.apache.jena.fuseki.embedded.FusekiServer;
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.QuerySolution;
+import org.apache.jena.query.ResultSetFormatter;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdfconnection.RDFConnection;
+import org.apache.jena.rdfconnection.RDFConnectionFactory;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.assembler.AssemblerUtils;
+import org.apache.jena.sparql.sse.SSE;
+import org.apache.jena.system.Txn;
+import org.junit.Test;
+
+/**
+ * Test on the assembler for data access control.
+ * <ul>
+ * <li>assem-security.ttl - two services "/database" and "/plain" each with their own dataset.
+ * <li>assem-security-shared.ttl - two services "/database" and "/plain" with a shared dataset.
+ * </ul>
+ */
+public class TestSecurityAssembler {
+ static final String DIR = "testing/Access/";
+
+ // Check the main test inputs.
+ @Test public void assembler1() {
+ Dataset ds = (Dataset)AssemblerUtils.build(DIR+"assem-security.ttl", VocabSecurity.tAccessControlledDataset);
+ }
+
+ @Test public void assembler2() {
+ Dataset ds = (Dataset)AssemblerUtils.build(DIR+"assem-security-shared.ttl", VocabSecurity.tAccessControlledDataset);
+ SecurityRegistry securityRegistry = ds.getContext().get(DataAccessCtl.symSecurityRegistry);
+ }
+
+ private static FusekiServer setup(String assembler, AtomicReference<String> user) {
+ int port = FusekiLib.choosePort();
+ FusekiServer server = DataAccessCtl.fusekiBuilder((a)->user.get())
+ .port(port)
+ .parseConfigFile(assembler)
+ .build();
+
+ return server;
+ }
+
+ // Two separate datasets
+ @Test public void assembler3() {
+ AtomicReference<String> user = new AtomicReference<>();
+ FusekiServer server = setup(DIR+"assem-security.ttl", user);
+ // Add data directly to the datasets.
+ DatasetGraph dsg = server.getDataAccessPointRegistry().get("/database").getDataService().getDataset();
+ //System.out.println(dsg.getContext());
+ Txn.executeWrite(dsg, ()->{
+ dsg.add(SSE.parseQuad("(<http://host/graphname1> :s1 :p :o)"));
+ dsg.add(SSE.parseQuad("(<http://host/graphname3> :s3 :p :o)"));
+ dsg.add(SSE.parseQuad("(<http://host/graphname9> :s9 :p :o)"));
+ });
+ server.start();
+ try {
+ testAssembler(server.getPort(), user);
+
+ // Access the uncontrolled dataset.
+ user.set(null);
+ String plainUrl = "http://localhost:"+server.getPort()+"/plain";
+ try(RDFConnection conn = RDFConnectionFactory.connect(plainUrl)) {
+ conn.update("INSERT DATA { <x:s> <x:p> 123 , 456 }");
+ conn.queryResultSet("SELECT * { ?s ?p ?o }",
+ rs->{
+ int x = ResultSetFormatter.consume(rs);
+ assertEquals(2, x);
+ });
+ }
+ } finally { server.stop(); }
+ }
+
+ // Shared dataset
+ @Test public void assembler4() {
+ AtomicReference<String> user = new AtomicReference<>();
+ FusekiServer server = setup(DIR+"assem-security-shared.ttl", user);
+
+ String x = StrUtils.strjoinNL
+ ("PREFIX : <http://example/>"
+ ,"INSERT DATA {"
+ ," GRAPH <http://host/graphname1> {:s1 :p :o}"
+ ," GRAPH <http://host/graphname3> {:s3 :p :o}"
+ ," GRAPH <http://host/graphname9> {:s9 :p :o}"
+ ,"}"
+ );
+
+ server.start();
+ try {
+ user.set(null);
+ String plainUrl = "http://localhost:"+server.getPort()+"/plain";
+ try(RDFConnection conn = RDFConnectionFactory.connect(plainUrl)) {
+ conn.update(x);
+ conn.queryResultSet("SELECT * { GRAPH ?g { ?s ?p ?o } }",
+ rs->{
+ int c = ResultSetFormatter.consume(rs);
+ assertEquals(3, c);
+ });
+ }
+ testAssembler(server.getPort(), user);
+ } finally { server.stop(); }
+ }
+
+ private void testAssembler(int port, AtomicReference<String> user) {
+ // The access controlled dataset.
+ String url = "http://localhost:"+port+"/database";
+
+ Node s1 = SSE.parseNode(":s1");
+ Node s3 = SSE.parseNode(":s3");
+ Node s9 = SSE.parseNode(":s9");
+
+ user.set("user1");
+ try(RDFConnection conn = RDFConnectionFactory.connect(url)) {
+ Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+ assertSeen(visible, s1, s3);
+ }
+
+ user.set("userX"); // No such user in the registry
+ try(RDFConnection conn = RDFConnectionFactory.connect(url)) {
+ Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+ assertSeen(visible);
+ }
+ user.set(null); // No user.
+ try(RDFConnection conn = RDFConnectionFactory.connect(url)) {
+ Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+ assertSeen(visible);
+ }
+
+ user.set("user2");
+ try(RDFConnection conn = RDFConnectionFactory.connect(url)) {
+ Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+ assertSeen(visible, s9);
+ }
+
+ user.set("userZ"); // No graphs with data.
+ try(RDFConnection conn = RDFConnectionFactory.connect(url)) {
+ Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+ assertSeen(visible);
+ }
+
+ }
+
+ private static void assertSeen(Set<Node> visible, Node ... expected) {
+ Set<Node> expectedNodes = new HashSet<>(Arrays.asList(expected));
+ assertEquals(expectedNodes, visible);
+ }
+
+ private Set<Node> query(RDFConnection conn, String queryString) {
+ Set<Node> results = new HashSet<>();
+ conn.queryResultSet(queryString, rs->{
+ List<QuerySolution> list = Iter.toList(rs);
+ list.stream()
+ .map(qs->qs.get("s"))
+ .filter(Objects::nonNull)
+ .map(RDFNode::asNode)
+ .forEach(n->results.add(n));
+ });
+ return results;
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/344ae5d8/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java
new file mode 100644
index 0000000..4eb8dd4
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import static org.apache.jena.fuseki.access.GraphData.s0;
+import static org.apache.jena.fuseki.access.GraphData.s1;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.jena.atlas.iterator.Iter;
+import org.apache.jena.fuseki.FusekiLib;
+import org.apache.jena.fuseki.embedded.FusekiServer;
+import org.apache.jena.fuseki.jetty.JettyLib;
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.QuerySolution;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdfconnection.RDFConnection;
+import org.apache.jena.rdfconnection.RDFConnectionFactory;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.engine.http.QueryExceptionHTTP;
+import org.apache.jena.tdb.TDBFactory;
+import org.apache.jena.tdb2.DatabaseMgr;
+import org.eclipse.jetty.security.PropertyUserStore;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.UserStore;
+import org.eclipse.jetty.util.security.Credential;
+import org.eclipse.jetty.util.security.Password;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TestSecurityFilterFuseki {
+
+ @Parameters(name = "{index}: {0}")
+ public static Iterable<Object[]> data() {
+ Object[] obj1 = { "TDB", "data1" };
+ Object[] obj2 = { "TDB2", "data2" };
+ return Arrays.asList(obj1, obj2);
+ }
+
+ private final String baseUrl;
+ private static final DatasetGraph testdsg1 = TDBFactory.createDatasetGraph();
+ private static final DatasetGraph testdsg2 = DatabaseMgr.createDatasetGraph();
+ private static FusekiServer fusekiServer;
+
+ // Set up Fuseki with two datasets, "data1" backed by TDB and "data2" backed by TDB2.
+ @BeforeClass public static void beforeClass() {
+ int port = FusekiLib.choosePort();
+ GraphData.fill(testdsg1);
+ GraphData.fill(testdsg2);
+
+ SecurityRegistry reg = new SecurityRegistry();
+ reg.put("userNone", SecurityPolicy.NONE);
+ reg.put("userDft", SecurityPolicy.DFT_GRAPH);
+ reg.put("user0", new SecurityPolicy(Quad.defaultGraphIRI.getURI()));
+ reg.put("user1", new SecurityPolicy("http://test/g1", Quad.defaultGraphIRI.getURI()));
+ reg.put("user2", new SecurityPolicy("http://test/g1", "http://test/g2", "http://test/g3"));
+
+ // XXXX Also need wrapped tests
+ DataAccessCtl.controlledDataset(testdsg1, reg);
+ DataAccessCtl.controlledDataset(testdsg2, reg);
+
+ UserStore userStore = userStore();
+ SecurityHandler sh = JettyLib.makeSecurityHandler("/*", "DatasetRealm", userStore);
+
+ fusekiServer = DataAccessCtl.fusekiBuilder(sh, DataAccessCtl.requestUserServlet)
+ .port(port)
+ .add("data1", testdsg1)
+ .add("data2", testdsg2)
+ .build();
+ fusekiServer.start();
+ }
+
+ @AfterClass public static void afterClass() {
+ fusekiServer.stop();
+ }
+
+ private static UserStore userStore() {
+ PropertyUserStore propertyUserStore = new PropertyUserStore();
+ String[] roles = new String[]{"**"};
+ addUserPassword(propertyUserStore, "user0", "pw0", roles);
+ addUserPassword(propertyUserStore, "user1", "pw1", roles);
+ addUserPassword(propertyUserStore, "user2", "pw2", roles);
+ return propertyUserStore;
+ }
+
+ private static void addUserPassword(PropertyUserStore propertyUserStore, String user, String password, String[] roles) {
+ Credential cred = new Password(password);
+ propertyUserStore.addUser(user, cred, roles);
+ }
+
+ public TestSecurityFilterFuseki(String label, String dsName) {
+ int port = fusekiServer.getPort();
+ baseUrl = "http://localhost:"+port+"/"+dsName;
+ }
+
+ private static void assertSeen(Set<Node> visible, Node ... expected) {
+ Set<Node> expectedNodes = new HashSet<>(Arrays.asList(expected));
+ assertEquals(expectedNodes, visible);
+ }
+
+ private static String queryAll = "SELECT * { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } }";
+ private static String queryDft = "SELECT * { ?s ?p ?o }";
+ private static String queryNamed = "SELECT * { GRAPH ?g { ?s ?p ?o } }";
+
+ private static String queryG2 = "SELECT * { GRAPH <http://test/graph2> { ?s ?p ?o } }";
+ private static String queryGraphNames = "SELECT * { GRAPH ?g { } }";
+
+ private Set<Node> query(String user, String password, String queryString) {
+ Set<Node> results = new HashSet<>();
+ try (RDFConnection conn = RDFConnectionFactory.connectPW(baseUrl, user, password)) {
+ conn.queryResultSet(queryString, rs->{
+ List<QuerySolution> list = Iter.toList(rs);
+ list.stream()
+ .map(qs->qs.get("s"))
+ .filter(Objects::nonNull)
+ .map(RDFNode::asNode)
+ .forEach(n->results.add(n));
+ });
+ }
+ return results;
+ }
+
+ private void query401(String user, String password, String queryString) {
+ queryHttp(401, user, password, queryString);
+ }
+
+ private void query403(String user, String password, String queryString) {
+ queryHttp(403, user, password, queryString);
+ }
+
+ private void queryHttp(int statusCode, String user, String password, String queryString) {
+ try {
+ query(user, password, queryString);
+ if ( statusCode < 200 && statusCode > 299 )
+ fail("Should have responded with "+statusCode);
+ } catch (QueryExceptionHTTP ex) {
+ assertEquals(statusCode, ex.getResponseCode());
+ }
+ }
+
+ @Test public void query_user0() {
+ Set<Node> results = query("user0", "pw0", queryAll);
+ assertSeen(results, s0);
+ }
+
+ @Test public void query_user1() {
+ Set<Node> results = query("user1", "pw1", queryAll);
+ assertSeen(results, s0, s1);
+ }
+
+ @Test public void query_userX() {
+ query401("userX", "pwX", queryAll);
+ }
+
+ @Test public void query_bad_user() {
+ query401("userX", "pwX", queryAll);
+ }
+
+ @Test public void query_bad_password() {
+ query401("user0", "not-the-password", queryAll);
+ }
+
+}