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);
+    }
+
+}