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/11/17 17:20:46 UTC

[20/34] jena git commit: JENA-1623: Server authorization by HTTP authentication.

JENA-1623: Server authorization by HTTP authentication.


Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/fcbcc78e
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/fcbcc78e
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/fcbcc78e

Branch: refs/heads/master
Commit: fcbcc78e9be8f4eb04fab3207b61bc54ccb48fad
Parents: 6e8e074
Author: Andy Seaborne <an...@apache.org>
Authored: Sat Nov 3 16:57:13 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Tue Nov 13 15:39:14 2018 +0000

----------------------------------------------------------------------
 .../AbstractTestGraphSecurityAssembler.java     | 326 +++++++++++++++++++
 .../access/AbstractTestSecurityAssembler.java   | 326 -------------------
 .../fuseki/access/TS_SecurityFiltering.java     |  13 +-
 .../fuseki/access/TestAssemblerSeparate.java    |  26 --
 .../jena/fuseki/access/TestAssemblerShared.java |  26 --
 .../fuseki/access/TestAuthorizedRequest.java    | 111 +++++++
 .../TestGraphSecurityAssemblerSeparate.java     |  26 ++
 .../TestGraphSecurityAssemblerShared.java       |  26 ++
 .../jena/fuseki/access/TestPasswordAccess.java  | 230 -------------
 .../jena/fuseki/access/TestPasswordServer.java  | 217 ++++++++++++
 .../fuseki/access/TestPasswordServices.java     | 230 +++++++++++++
 .../access/TestSecurityAssemblerBuild.java      |   1 +
 .../testing/Access/allowedUsers.ttl             |  15 +
 .../testing/Access/assem-security.ttl           |   2 +-
 .../testing/Access/config-server-1.ttl          |  43 +++
 .../testing/Access/config-server-2.ttl          |  43 +++
 .../jena-fuseki-access/testing/Access/passwd    |   3 +
 .../jena/fuseki/build/FusekiBuildLib.java       |   4 +-
 .../apache/jena/fuseki/build/FusekiBuilder.java |  41 +++
 .../apache/jena/fuseki/build/FusekiConfig.java  |  83 ++---
 .../jena/fuseki/build/RefCountingMap.java       | 185 -----------
 .../jena/fuseki/build/RequestAuthorization.java | 115 +++++++
 .../apache/jena/fuseki/server/DataService.java  |   7 +-
 .../jena/fuseki/servlets/ActionService.java     |   7 +-
 .../apache/jena/fuseki/servlets/AuthFilter.java |  75 +++++
 .../jena/fuseki/servlets/FusekiFilter.java      |   4 +-
 .../org/apache/jena/fuseki/main/FusekiLib.java  |   7 -
 .../apache/jena/fuseki/main/FusekiServer.java   |  28 +-
 .../apache/jena/fuseki/main/FusekiTestAuth.java |   4 +-
 .../jena/fuseki/main/TestFusekiTestAuth.java    |   4 +-
 30 files changed, 1367 insertions(+), 861 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/AbstractTestGraphSecurityAssembler.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/AbstractTestGraphSecurityAssembler.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/AbstractTestGraphSecurityAssembler.java
new file mode 100644
index 0000000..1335702
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/AbstractTestGraphSecurityAssembler.java
@@ -0,0 +1,326 @@
+/*
+ * 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.AccessTestLib.assertSeen;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+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.SetUtils;
+import org.apache.jena.atlas.lib.StrUtils;
+import org.apache.jena.atlas.web.HttpException;
+import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.fuseki.system.FusekiNetLib;
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.QuerySolution;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+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.sse.SSE;
+import org.apache.jena.sys.JenaSystem;
+import org.apache.jena.system.Txn;
+import org.junit.AfterClass;
+import org.junit.Before;
+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>
+ * 
+ * @see TestSecurityFilterFuseki TestSecurityFilterFuseki for other HTTP tests.
+ */
+
+public abstract class AbstractTestGraphSecurityAssembler {
+    static { JenaSystem.init(); }
+    static final String DIR = "testing/Access/";
+
+//    @Parameters(name = "{index}: {0}")
+//    public static Iterable<Object[]> data() {
+//        Object[] obj1 = { DIR+"assem-security.ttl" , false };
+//        Object[] obj2 = { DIR+"assem-security-shared.ttl" , true };
+//        return Arrays.asList(obj1, obj2);
+//    }
+
+    private final String assemblerFile;
+    private static AtomicReference<String> user = new AtomicReference<>();
+
+    private boolean sharedDatabase;
+
+    // Parameterized tests don't provide a convenient way to run code at the start and end of each parameter run and access the parameters. 
+    private static FusekiServer server;
+    private FusekiServer getServer() {
+        if ( server == null )
+            server = setup(assemblerFile, false);
+        return server;
+    }
+    @AfterClass public static void afterClass() {
+        server.stop();
+        server = null;
+        user.set(null);
+    }
+    
+    @Before
+    public void before() {
+        user.set(null);
+    }
+    
+    private String getURL() {
+        getServer();
+        int port = server.getPort();
+        return "http://localhost:"+port+"/database";
+    }
+    
+    private static FusekiServer setup(String assembler, boolean sharedDatabase) {
+        int port = FusekiNetLib.choosePort();
+        FusekiServer server = DataAccessCtl.fusekiBuilder((a)->user.get())
+            .port(port)
+            .parseConfigFile(assembler)
+            .build();
+        server.start();
+        
+        if ( sharedDatabase ) {
+            String data = StrUtils.strjoinNL
+                ("PREFIX : <http://example/>"
+                ,"INSERT DATA {"
+                ,"   :s0 :p :o ."
+                ,"   GRAPH <http://host/graphname1> {:s1 :p :o}"
+                ,"   GRAPH <http://host/graphname3> {:s3 :p :o}"
+                ,"   GRAPH <http://host/graphname9> {:s9 :p :o}"
+                ,"}"
+                );
+            String plainUrl = "http://localhost:"+server.getPort()+"/plain";
+            try(RDFConnection conn = RDFConnectionFactory.connect(plainUrl)) {
+                conn.update(data);
+            }
+        } else {
+            DatasetGraph dsg = server.getDataAccessPointRegistry().get("/database").getDataService().getDataset();
+            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)"));
+            });
+        }
+        return server;
+    }
+    
+    protected AbstractTestGraphSecurityAssembler(String assemberFile, boolean sharedDatabase) {
+        this.assemblerFile = assemberFile;
+        this.sharedDatabase = sharedDatabase ;
+    }
+
+    private static Node s1 = SSE.parseNode(":s1"); 
+    private static Node s2 = SSE.parseNode(":s2"); 
+    private static Node s3 = SSE.parseNode(":s3");
+    private static Node s9 = SSE.parseNode(":s9"); 
+
+        // The access controlled dataset.
+
+//        { SecurityRegistry
+//            user1 -> dft:false / [http://host/graphname2, http://host/graphname1, http://host/graphname3]
+//            user2 -> dft:false / [http://host/graphname9]
+//            userZ -> dft:false / [http://host/graphnameZ]
+//            user3 -> dft:false / [http://host/graphname4, http://host/graphname3, http://host/graphname5]
+//          }
+        
+        
+    @Test public void query_user1() {         
+        user.set("user1");
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+            assertSeen(visible, s1, s3);
+        }
+    }
+
+    @Test public void query_userX() {
+        user.set("userX"); // No such user in the registry
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+            assertSeen(visible);
+        }
+    }
+    
+    @Test public void query_no_user() {
+        user.set(null); // No user.
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+            assertSeen(visible);
+        }
+    }
+    
+    @Test public void query_user2() {
+        user.set("user2");
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+            assertSeen(visible, s9);
+        }
+    }
+    
+    @Test public void query_userZ() {
+        user.set("userZ"); // No graphs with data.
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
+            assertSeen(visible);
+        }
+    }
+    
+    // GSP. "http://host/graphname1"
+    @Test public void gsp_dft_user1() {
+        user.set("user1");
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            Set<Node> visible = gsp(conn, null);
+            assertSeen(visible);
+        }
+    }
+    
+    @Test public void gsp_ng_user1() {
+        user.set("user1");
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            Set<Node> visible = gsp(conn, "http://host/graphname1");
+            assertSeen(visible, s1);
+        }
+    }
+    
+    @Test public void gsp_dft_user2() {
+        user.set("user2");
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            gsp404(conn, null);
+        }
+    }
+    
+    @Test public void gsp_ng_user2() {
+        user.set("user2");
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            gsp404(conn, "http://host/graphname1");
+        }
+    }
+    
+    @Test public void gsp_dft_userX() {
+        user.set("userX");
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            gsp404(conn, null);
+        }
+    }
+    
+    @Test public void gsp_ng_userX() {
+        user.set("userX");
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            gsp404(conn, "http://host/graphname1");
+        }
+    }
+
+    @Test public void gsp_dft_user_null() {
+        user.set(null);
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            gsp404(conn, null);
+        }
+    }
+    
+    @Test public void gsp_ng_user_null() {
+        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+            gsp404(conn, "http://host/graphname1");
+        }
+    }
+    
+//        // Quads
+//        user.set("user1");
+//        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+//            Set<Node> visible = dataset(conn);
+//            assertSeen(visible, s1, s3);
+//        }
+//        user.set("user2");
+//        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+//            Set<Node> visible = dataset(conn);
+//            assertSeen(visible, s9);
+//        }
+//        user.set("userX");
+//        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+//            Set<Node> visible = dataset(conn);
+//            assertSeen(visible);
+//        }
+//        user.set(null);
+//        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
+//            Set<Node> visible = dataset(conn);
+//            assertSeen(visible);
+//        }
+
+
+    private Set<Node> gsp(RDFConnection conn, String graphName) {
+        Set<Node> results = new HashSet<>();
+        Model model = graphName == null ? conn.fetch() : conn.fetch(graphName);
+        // Extract subjects.
+        Set<Node> seen = 
+            SetUtils.toSet(
+                Iter.asStream(model.listSubjects())
+                    .map(Resource::asNode)
+                );
+        return seen;
+    }
+
+    private void gsp404(RDFConnection conn, String graphName) {
+        gspHttp(conn, 404, graphName);
+    }
+
+    private void gspHttp(RDFConnection conn, int statusCode, String graphName) {
+        try {
+            gsp(conn, graphName);
+            if ( statusCode < 200 && statusCode > 299 ) 
+                fail("Should have responded with "+statusCode);
+        } catch (HttpException ex) {
+            assertEquals(statusCode, ex.getResponseCode());
+        }
+    }
+    
+    private Set<Node> dataset(RDFConnection conn) {
+        Dataset ds = conn.fetchDataset();
+        Set<Node> seen = 
+            SetUtils.toSet(
+                Iter.asStream(ds.asDatasetGraph().find())
+                    .map(Quad::getSubject)
+                    );
+        return seen;     
+    }
+
+    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/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/AbstractTestSecurityAssembler.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/AbstractTestSecurityAssembler.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/AbstractTestSecurityAssembler.java
deleted file mode 100644
index a7e5ac1..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/AbstractTestSecurityAssembler.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * 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.AccessTestLib.assertSeen;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-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.SetUtils;
-import org.apache.jena.atlas.lib.StrUtils;
-import org.apache.jena.atlas.web.HttpException;
-import org.apache.jena.fuseki.main.FusekiServer;
-import org.apache.jena.fuseki.system.FusekiNetLib;
-import org.apache.jena.graph.Node;
-import org.apache.jena.query.Dataset;
-import org.apache.jena.query.QuerySolution;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.RDFNode;
-import org.apache.jena.rdf.model.Resource;
-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.sse.SSE;
-import org.apache.jena.sys.JenaSystem;
-import org.apache.jena.system.Txn;
-import org.junit.AfterClass;
-import org.junit.Before;
-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>
- * 
- * @see TestSecurityFilterFuseki TestSecurityFilterFuseki for other HTTP tests.
- */
-
-public abstract class AbstractTestSecurityAssembler {
-    static { JenaSystem.init(); }
-    static final String DIR = "testing/Access/";
-
-//    @Parameters(name = "{index}: {0}")
-//    public static Iterable<Object[]> data() {
-//        Object[] obj1 = { DIR+"assem-security.ttl" , false };
-//        Object[] obj2 = { DIR+"assem-security-shared.ttl" , true };
-//        return Arrays.asList(obj1, obj2);
-//    }
-
-    private final String assemblerFile;
-    private static AtomicReference<String> user = new AtomicReference<>();
-
-    private boolean sharedDatabase;
-
-    // Parameterized tests don't provide a convenient way to run code at the start and end of each parameter run and access the parameters. 
-    private static FusekiServer server;
-    private FusekiServer getServer() {
-        if ( server == null )
-            server = setup(assemblerFile, false);
-        return server;
-    }
-    @AfterClass public static void afterClass() {
-        server.stop();
-        server = null;
-        user.set(null);
-    }
-    
-    @Before
-    public void before() {
-        user.set(null);
-    }
-    
-    private String getURL() {
-        getServer();
-        int port = server.getPort();
-        return "http://localhost:"+port+"/database";
-    }
-    
-    private static FusekiServer setup(String assembler, boolean sharedDatabase) {
-        int port = FusekiNetLib.choosePort();
-        FusekiServer server = DataAccessCtl.fusekiBuilder((a)->user.get())
-            .port(port)
-            .parseConfigFile(assembler)
-            .build();
-        server.start();
-        
-        if ( sharedDatabase ) {
-            String data = StrUtils.strjoinNL
-                ("PREFIX : <http://example/>"
-                ,"INSERT DATA {"
-                ,"   :s0 :p :o ."
-                ,"   GRAPH <http://host/graphname1> {:s1 :p :o}"
-                ,"   GRAPH <http://host/graphname3> {:s3 :p :o}"
-                ,"   GRAPH <http://host/graphname9> {:s9 :p :o}"
-                ,"}"
-                );
-            String plainUrl = "http://localhost:"+server.getPort()+"/plain";
-            try(RDFConnection conn = RDFConnectionFactory.connect(plainUrl)) {
-                conn.update(data);
-            }
-        } else {
-            DatasetGraph dsg = server.getDataAccessPointRegistry().get("/database").getDataService().getDataset();
-            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)"));
-            });
-        }
-        return server;
-    }
-    
-    protected AbstractTestSecurityAssembler(String assemberFile, boolean sharedDatabase) {
-        this.assemblerFile = assemberFile;
-        this.sharedDatabase = sharedDatabase ;
-    }
-
-    private static Node s1 = SSE.parseNode(":s1"); 
-    private static Node s2 = SSE.parseNode(":s2"); 
-    private static Node s3 = SSE.parseNode(":s3");
-    private static Node s9 = SSE.parseNode(":s9"); 
-
-        // The access controlled dataset.
-
-//        { SecurityRegistry
-//            user1 -> dft:false / [http://host/graphname2, http://host/graphname1, http://host/graphname3]
-//            user2 -> dft:false / [http://host/graphname9]
-//            userZ -> dft:false / [http://host/graphnameZ]
-//            user3 -> dft:false / [http://host/graphname4, http://host/graphname3, http://host/graphname5]
-//          }
-        
-        
-    @Test public void query_user1() {         
-        user.set("user1");
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
-            assertSeen(visible, s1, s3);
-        }
-    }
-
-    @Test public void query_userX() {
-        user.set("userX"); // No such user in the registry
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
-            assertSeen(visible);
-        }
-    }
-    
-    @Test public void query_no_user() {
-        user.set(null); // No user.
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
-            assertSeen(visible);
-        }
-    }
-    
-    @Test public void query_user2() {
-        user.set("user2");
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
-            assertSeen(visible, s9);
-        }
-    }
-    
-    @Test public void query_userZ() {
-        user.set("userZ"); // No graphs with data.
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            Set<Node> visible = query(conn, "SELECT * { GRAPH ?g { ?s ?p ?o }}");
-            assertSeen(visible);
-        }
-    }
-    
-    // GSP. "http://host/graphname1"
-    @Test public void gsp_dft_user1() {
-        user.set("user1");
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            Set<Node> visible = gsp(conn, null);
-            assertSeen(visible);
-        }
-    }
-    
-    @Test public void gsp_ng_user1() {
-        user.set("user1");
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            Set<Node> visible = gsp(conn, "http://host/graphname1");
-            assertSeen(visible, s1);
-        }
-    }
-    
-    @Test public void gsp_dft_user2() {
-        user.set("user2");
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            gsp404(conn, null);
-        }
-    }
-    
-    @Test public void gsp_ng_user2() {
-        user.set("user2");
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            gsp404(conn, "http://host/graphname1");
-        }
-    }
-    
-    @Test public void gsp_dft_userX() {
-        user.set("userX");
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            gsp404(conn, null);
-        }
-    }
-    
-    @Test public void gsp_ng_userX() {
-        user.set("userX");
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            gsp404(conn, "http://host/graphname1");
-        }
-    }
-
-    @Test public void gsp_dft_user_null() {
-        user.set(null);
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            gsp404(conn, null);
-        }
-    }
-    
-    @Test public void gsp_ng_user_null() {
-        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-            gsp404(conn, "http://host/graphname1");
-        }
-    }
-    
-//        // Quads
-//        user.set("user1");
-//        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-//            Set<Node> visible = dataset(conn);
-//            assertSeen(visible, s1, s3);
-//        }
-//        user.set("user2");
-//        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-//            Set<Node> visible = dataset(conn);
-//            assertSeen(visible, s9);
-//        }
-//        user.set("userX");
-//        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-//            Set<Node> visible = dataset(conn);
-//            assertSeen(visible);
-//        }
-//        user.set(null);
-//        try(RDFConnection conn = RDFConnectionFactory.connect(getURL())) {
-//            Set<Node> visible = dataset(conn);
-//            assertSeen(visible);
-//        }
-
-
-    private Set<Node> gsp(RDFConnection conn, String graphName) {
-        Set<Node> results = new HashSet<>();
-        Model model = graphName == null ? conn.fetch() : conn.fetch(graphName);
-        // Extract subjects.
-        Set<Node> seen = 
-            SetUtils.toSet(
-                Iter.asStream(model.listSubjects())
-                    .map(Resource::asNode)
-                );
-        return seen;
-    }
-
-    private void gsp404(RDFConnection conn, String graphName) {
-        gspHttp(conn, 404, graphName);
-    }
-
-    private void gspHttp(RDFConnection conn, int statusCode, String graphName) {
-        try {
-            gsp(conn, graphName);
-            if ( statusCode < 200 && statusCode > 299 ) 
-                fail("Should have responded with "+statusCode);
-        } catch (HttpException ex) {
-            assertEquals(statusCode, ex.getResponseCode());
-        }
-    }
-    
-    private Set<Node> dataset(RDFConnection conn) {
-        Dataset ds = conn.fetchDataset();
-        Set<Node> seen = 
-            SetUtils.toSet(
-                Iter.asStream(ds.asDatasetGraph().find())
-                    .map(Quad::getSubject)
-                    );
-        return seen;     
-    }
-
-    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/fcbcc78e/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
index 3face49..19987f8 100644
--- 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
@@ -26,12 +26,17 @@ import org.junit.runners.Suite;
 
 @RunWith(Suite.class)
 @Suite.SuiteClasses( {
-    TestSecurityFilterLocal.class
+    TestAuthorizedRequest.class
+    
+    , TestSecurityFilterLocal.class
     , TestSecurityFilterFuseki.class
+    
     , TestSecurityAssemblerBuild.class
-    , TestAssemblerSeparate.class
-    , TestAssemblerShared.class
-    , TestPasswordAccess.class
+    , TestGraphSecurityAssemblerSeparate.class
+    , TestGraphSecurityAssemblerShared.class
+    
+    , TestPasswordServer.class
+    , TestPasswordServices.class
 })
 
 public class TS_SecurityFiltering {

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAssemblerSeparate.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAssemblerSeparate.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAssemblerSeparate.java
deleted file mode 100644
index 78281f1..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAssemblerSeparate.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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;
-
-public class TestAssemblerSeparate extends AbstractTestSecurityAssembler {
-
-    public TestAssemblerSeparate() {
-        super(DIR+"assem-security.ttl", false);
-    }
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAssemblerShared.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAssemblerShared.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAssemblerShared.java
deleted file mode 100644
index 32d182b..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAssemblerShared.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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;
-
-public class TestAssemblerShared extends AbstractTestSecurityAssembler {
-
-    public TestAssemblerShared() {
-        super(DIR+"assem-security-shared.ttl", true);
-    }
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAuthorizedRequest.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAuthorizedRequest.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAuthorizedRequest.java
new file mode 100644
index 0000000..480708f
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestAuthorizedRequest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.jena.fuseki.build.FusekiBuilder;
+import org.apache.jena.fuseki.build.RequestAuthorization;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.riot.RDFDataMgr;
+import org.junit.Test;
+
+public class TestAuthorizedRequest {
+    
+    static Model model = RDFDataMgr.loadModel("testing/Access/allowedUsers.ttl");
+
+    
+    @Test public void authrequest_anon() {
+        RequestAuthorization req = RequestAuthorization.policyAllowAnon();
+        assertTrue(req.isAllowed(null));
+        assertTrue(req.isAllowed("user1"));
+    }
+    
+    @Test public void authrequest_anyLoggedIn_1() {
+        RequestAuthorization req = RequestAuthorization.policyAllowAuthenticated();
+        assertFalse(req.isAllowed(null));
+        assertTrue(req.isAllowed("user1"));
+    }
+    
+    @Test public void authrequest_anyLoggedIn_2() {
+        RequestAuthorization req = RequestAuthorization.policyAllowSpecific("*");
+        assertFalse(req.isAllowed(null));
+        assertTrue(req.isAllowed("user1"));
+    }
+
+    @Test public void authrequest_noOne() {
+        RequestAuthorization req = RequestAuthorization.policyNoAccess();
+        assertFalse(req.isAllowed(null));
+        assertFalse(req.isAllowed("user1"));
+    }
+
+
+    @Test public void authrequest_user_1() {
+        RequestAuthorization req = RequestAuthorization.policyAllowSpecific("user1", "user2");
+        assertFalse(req.isAllowed(null));
+        assertTrue(req.isAllowed("user1"));
+        assertTrue(req.isAllowed("user2"));
+        assertFalse(req.isAllowed("user3"));
+    }
+    
+    @Test public void authrequest_parse_no_info_1() {
+        Resource r = model.createResource("http://example/notInData");
+        RequestAuthorization req = FusekiBuilder.allowedUsers(r);
+        assertNull(req);
+    }
+
+    @Test public void authrequest_parse_no_info_2() {
+        Resource r = model.createResource("http://example/none");
+        RequestAuthorization req = FusekiBuilder.allowedUsers(r);
+        assertNull(req);
+    }
+
+    @Test public void authrequest_parse_1() {
+        Resource r = model.createResource("http://example/r1");
+        RequestAuthorization req = FusekiBuilder.allowedUsers(r);
+        assertNotNull(req);
+        assertFalse(req.isAllowed(null));
+        assertTrue(req.isAllowed("user1"));
+        assertTrue(req.isAllowed("user2"));
+        assertFalse(req.isAllowed("user3"));
+    }
+    
+    @Test public void authrequest_parse_2() {
+        Resource r = model.createResource("http://example/r2");
+        RequestAuthorization req = FusekiBuilder.allowedUsers(r);
+        assertNotNull(req);
+        assertFalse(req.isAllowed(null));
+        assertTrue(req.isAllowed("user1"));
+        assertTrue(req.isAllowed("user2"));
+        assertFalse(req.isAllowed("user3"));
+    }
+    
+    @Test public void authrequest_parse_loggedIn() {
+        Resource r = model.createResource("http://example/rLoggedIn");
+        RequestAuthorization req = FusekiBuilder.allowedUsers(r);
+        assertNotNull(req);
+        assertFalse(req.isAllowed(null));
+        assertTrue(req.isAllowed("user1"));
+        assertTrue(req.isAllowed("user3"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestGraphSecurityAssemblerSeparate.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestGraphSecurityAssemblerSeparate.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestGraphSecurityAssemblerSeparate.java
new file mode 100644
index 0000000..dbf0456
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestGraphSecurityAssemblerSeparate.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+public class TestGraphSecurityAssemblerSeparate extends AbstractTestGraphSecurityAssembler {
+
+    public TestGraphSecurityAssemblerSeparate() {
+        super(DIR+"assem-security.ttl", false);
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestGraphSecurityAssemblerShared.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestGraphSecurityAssemblerShared.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestGraphSecurityAssemblerShared.java
new file mode 100644
index 0000000..0f55302
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestGraphSecurityAssemblerShared.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+public class TestGraphSecurityAssemblerShared extends AbstractTestGraphSecurityAssembler {
+
+    public TestGraphSecurityAssemblerShared() {
+        super(DIR+"assem-security-shared.ttl", true);
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordAccess.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordAccess.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordAccess.java
deleted file mode 100644
index d81de8c..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordAccess.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * 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.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
-
-import java.util.Arrays;
-
-import org.apache.http.client.HttpClient;
-import org.apache.jena.atlas.logging.LogCtl;
-import org.apache.jena.atlas.web.HttpException;
-import org.apache.jena.atlas.web.TypedInputStream;
-import org.apache.jena.atlas.web.WebLib;
-import org.apache.jena.fuseki.build.FusekiBuilder;
-import org.apache.jena.fuseki.jetty.JettyLib;
-import org.apache.jena.fuseki.main.FusekiServer;
-import org.apache.jena.fuseki.server.DataService;
-import org.apache.jena.query.DatasetFactory;
-import org.apache.jena.riot.web.HttpCaptureResponse;
-import org.apache.jena.riot.web.HttpOp;
-import org.apache.jena.riot.web.HttpOp.CaptureInput;
-import org.apache.jena.sparql.core.DatasetGraphFactory;
-import org.apache.jena.web.HttpSC;
-import org.eclipse.jetty.security.ConstraintSecurityHandler;
-import org.eclipse.jetty.security.UserStore;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-/** Tests for password access to a server and to services. */
-public class TestPasswordAccess {
-
-    private static FusekiServer fusekiServer = null;
-    private static int port = WebLib.choosePort();
-    private static String serverURL = "http://localhost:"+port+"/";
-    private static AuthSetup authSetup1 = new AuthSetup("localhost", port, "user1", "pw1", "TripleStore");
-    private static AuthSetup authSetup2 = new AuthSetup("localhost", port, "user2", "pw2", "TripleStore");
-    // Not in the user store.
-    private static AuthSetup authSetupX = new AuthSetup("localhost", port, "userX", "pwX", "TripleStore");
-    
-    // All users must log in to access any resource. Disables some "open" tests.
-    private static boolean ConstraintServer = true;
-    
-    @BeforeClass
-    public static void beforeClass() {
-        if ( false )
-            // To watch the HTTP headers
-            LogCtl.enable("org.apache.http.headers");
-        
-        // Two authorized users.
-        UserStore userStore = new UserStore();
-        JettyLib.addUser(userStore, authSetup1.user, authSetup1.password);
-        JettyLib.addUser(userStore, authSetup2.user, authSetup2.password);
-        try { userStore.start(); }
-        catch (Exception ex) { throw new RuntimeException("UserStore", ex); }
-        
-        ConstraintSecurityHandler sh = JettyLib.makeSecurityHandler(authSetup1.realm, userStore);
-        
-        // Secure these areas.
-        JettyLib.addPathConstraint(sh, "/ds");
-        // Allow auth control even through there isn't anything there 
-        JettyLib.addPathConstraint(sh, "/nowhere");
-
-        JettyLib.addPathConstraint(sh, "/ctl");
-        // Not controlled: JettyLib.addPathConstraint(sh, "/open");
-        
-        // Server wide (breaks "open" tests)
-        if ( ConstraintServer )
-            JettyLib.addPathConstraint(sh, "/*");
-        
-        DataService dSrv = new DataService(DatasetGraphFactory.createTxnMem());
-        FusekiBuilder.populateStdServices(dSrv, false);
-        dSrv.setAllowedUsers(Arrays.asList("user1"));
-        
-        fusekiServer =
-            FusekiServer.create()
-                .port(port)
-                .add("/ds", DatasetFactory.createTxnMem())
-                .add("/open", DatasetFactory.createTxnMem())
-                .add("/ctl", dSrv)
-                .securityHandler(sh)
-                //.staticFileBase(".")
-                .build();
-        fusekiServer.start();
-    }
-    
-    @Before
-    public void before() {
-        // Reset before every test and after the suite.
-        HttpClient hc = HttpOp.createDefaultHttpClient();
-        HttpOp.setDefaultHttpClient(hc);
-    }
-    
-
-    @AfterClass
-    public static void afterClass() {
-        fusekiServer.stop();
-        HttpClient hc = HttpOp.createDefaultHttpClient();
-        HttpOp.setDefaultHttpClient(hc);
-    }
-    
-    // Server authentication.
-    
-    @Test public void access_server() {
-        assumeFalse(ConstraintServer);
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL) ) {
-            assertNotNull(in);
-            fail("Didn't expect to succeed");
-        } catch (HttpException ex) {
-            // 404 is OK - no static file area. 
-            if ( ex.getResponseCode() != HttpSC.NOT_FOUND_404 )
-                throw ex;
-        }
-    }
-
-    @Test public void access_open() {
-        assumeFalse(ConstraintServer);
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"open") ) {
-            assertNotNull(in);
-        }
-    }
-
-    @Test public void access_open_user1() {
-        // OK.
-        LibSec.withAuth(serverURL+"open", authSetup1, (conn)->{
-            conn.queryAsk("ASK{}");
-        });
-    }
-
-    @Test public void access_open_userX() {
-        assumeFalse(ConstraintServer);
-        // OK.
-        LibSec.withAuth(serverURL+"open", authSetupX, (conn)->{
-            conn.queryAsk("ASK{}");
-        });
-    }
-
-
-    // Should fail.
-    @Test public void access_deny_ds() {
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ds") ) {
-            fail("Didn't expect to succeed");
-        } catch (HttpException ex) {
-            if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401 )
-                throw ex;
-        }
-    }
-    
-    // Should be 401, not be 404.
-    @Test public void access_deny_nowhere() {
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"nowhere") ) {
-            fail("Didn't expect to succeed");
-        } catch (HttpException ex) {
-            if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401 )
-                throw ex;
-        }
-    }
-    
-    @Test public void access_allow_nowhere() {
-        HttpClient hc = LibSec.httpClient(authSetup1);
-        HttpCaptureResponse<TypedInputStream> handler = new CaptureInput();
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"nowhere", null, hc, null) ) {
-            // null for 404.
-            assertNull(in);
-        } catch (HttpException ex) {
-            if ( ex.getResponseCode() != HttpSC.NOT_FOUND_404)
-                throw ex;
-        }
-    }
-    
-    @Test public void access_allow_ds() {
-        HttpClient hc = LibSec.httpClient(authSetup1);
-        HttpCaptureResponse<TypedInputStream> handler = new CaptureInput();
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ds", null, hc, null) ) {
-            assertNotNull(in);
-        }
-    }
-    
-    // Service level : ctl.
-    
-    @Test public void access_service_ctl_user1() {
-        // user1 -- allowed.
-        HttpClient hc = LibSec.httpClient(authSetup1);
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ctl", null, hc, null) ) {
-            assertNotNull(in);
-        }
-    }
-    
-    @Test public void access_service_ctl_user2() {
-        // user2 -- can login, not allowed.
-        HttpClient hc = LibSec.httpClient(authSetup2);
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ctl", null, hc, null) ) {
-            fail("Didn't expect to succeed");
-        } catch (HttpException ex) {
-            if ( ex.getResponseCode() != HttpSC.FORBIDDEN_403)
-                throw ex;
-        }
-    }
-
-    @Test public void access_service_ctl_userX() {
-        // userX -- can't login, not allowed.
-        HttpClient hc = LibSec.httpClient(authSetupX);
-        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ctl", null, hc, null) ) {
-            fail("Didn't expect to succeed");
-        } catch (HttpException ex) {
-            if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401)
-                throw ex;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordServer.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordServer.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordServer.java
new file mode 100644
index 0000000..b160db2
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordServer.java
@@ -0,0 +1,217 @@
+/*
+ * 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.assertNull;
+
+import org.apache.http.client.HttpClient;
+import org.apache.jena.atlas.web.HttpException;
+import org.apache.jena.atlas.web.TypedInputStream;
+import org.apache.jena.atlas.web.WebLib;
+import org.apache.jena.fuseki.jetty.JettyLib;
+import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.rdfconnection.RDFConnection;
+import org.apache.jena.rdfconnection.RDFConnectionRemote;
+import org.apache.jena.riot.web.HttpOp;
+import org.apache.jena.sparql.engine.http.QueryExceptionHTTP;
+import org.apache.jena.web.HttpSC;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.UserStore;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Tests for
+ * <ul> 
+ * <li>password access at the server-level
+ * <li>assembler file 
+ * </ul> 
+ */
+public class TestPasswordServer {
+
+    // Per test.
+    private FusekiServer fusekiServer = null;
+    
+    private static String REALM = "TripleStore";
+    private AuthSetup authSetup1(FusekiServer server) { return new AuthSetup("localhost", server.getPort(), "user1", "pw1", REALM); }
+    private AuthSetup authSetup2(FusekiServer server) { return new AuthSetup("localhost", server.getPort(), "user2", "pw2", REALM); }
+    private AuthSetup authSetup3(FusekiServer server) { return new AuthSetup("localhost", server.getPort(), "user3", "pw3", REALM); }
+    // Not in the user store.
+    private AuthSetup authSetupX(FusekiServer server) { return new AuthSetup("localhost", server.getPort(), "userX", "pwX", REALM); }
+    
+    private static String serverURL(FusekiServer server) {
+        return "http://localhost:"+server.getPort()+"/";
+    }
+    
+    private FusekiServer fusekiServer(String configFile) {
+        int port = WebLib.choosePort();
+        // Read from assembler+passwd file.
+        UserStore userStore = JettyLib.makeUserStore("testing/Access/passwd");
+        ConstraintSecurityHandler sh = JettyLib.makeSecurityHandler(REALM, userStore);
+        
+        fusekiServer =
+            FusekiServer.create()
+                .port(port)
+                .parseConfigFile(configFile)
+                .securityHandler(sh)
+                //.staticFileBase(".")
+                .build();
+        fusekiServer.start();
+        return fusekiServer;
+    }
+    
+    @BeforeClass
+    public static void beforeClass() {
+        // Reset before every test and after the suite.
+        HttpClient hc = HttpOp.createDefaultHttpClient();
+        HttpOp.setDefaultHttpClient(hc);
+    }
+    
+    @After
+    public void after() {
+        if ( fusekiServer != null )
+            fusekiServer.stop();
+        HttpClient hc = HttpOp.createDefaultHttpClient();
+        HttpOp.setDefaultHttpClient(hc);
+    }
+    
+    private static void expectQuery403(Runnable action) {
+        try{
+            action.run();
+            throw new HttpException("action completed");
+        } catch (QueryExceptionHTTP ex) {
+            // 404 is OK - no static file area. 
+            if ( ex.getResponseCode() != HttpSC.FORBIDDEN_403 )
+                throw ex;
+        }
+    }
+    
+    // Server authentication.
+
+    @Test public void access_serverAny_user1() {
+        FusekiServer fusekiServer = fusekiServer("testing/Access/config-server-1.ttl");
+        try { 
+            // Must be logged in.
+            HttpClient hc = LibSec.httpClient(authSetup1(fusekiServer));
+            try( TypedInputStream in = HttpOp.execHttpGet(serverURL(fusekiServer), null, hc, null) ) {
+                assertNull(in);
+            } catch (HttpException ex) {
+                // 404 is OK - no static file area. 
+                if ( ex.getResponseCode() != HttpSC.NOT_FOUND_404 )
+                    throw ex;
+            }
+        } finally {
+            fusekiServer.stop();
+            fusekiServer = null;
+        }
+    }
+    
+    @Test public void access_serverAny_db1() {
+        FusekiServer fusekiServer = fusekiServer("testing/Access/config-server-1.ttl");
+        try { 
+            // Must be logged in.
+            HttpClient hc = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(serverURL(fusekiServer)+"/database1").httpClient(hc).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        } finally {
+            fusekiServer.stop();
+            fusekiServer = null;
+        }
+    }
+
+    @Test public void access_serverAny_db2() {
+        FusekiServer fusekiServer = fusekiServer("testing/Access/config-server-1.ttl");
+        try { 
+            // Must be logged in.
+            HttpClient hc = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(serverURL(fusekiServer)+"/database2").httpClient(hc).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        } finally {
+            fusekiServer.stop();
+            fusekiServer = null;
+        }
+    }
+    
+    @Test public void access_serverAny_db1_wrongUser() {
+        FusekiServer fusekiServer = fusekiServer("testing/Access/config-server-1.ttl");
+        try { 
+            // Must be logged in.
+            HttpClient hc = LibSec.httpClient(authSetup2(fusekiServer)); // 2
+            try ( RDFConnection conn = RDFConnectionRemote
+                        .create().destination(serverURL(fusekiServer)+"/database1").httpClient(hc).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        } finally {
+            fusekiServer.stop();
+            fusekiServer = null;
+        }
+    }
+
+    // Specific server user.
+    @Test public void access_serverUser_user1() {
+        FusekiServer fusekiServer = fusekiServer("testing/Access/config-server-2.ttl");
+        try { 
+            // Must be logged in as user1
+            HttpClient hc = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(serverURL(fusekiServer)+"/database1").httpClient(hc).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        } finally {
+            fusekiServer.stop();
+            fusekiServer = null;
+        }
+    }
+    
+    // Specific server user.
+    @Test public void access_serverUser_user2() {
+        FusekiServer fusekiServer = fusekiServer("testing/Access/config-server-2.ttl");
+        try { 
+            // user2 does not have service access 
+            HttpClient hc = LibSec.httpClient(authSetup2(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(serverURL(fusekiServer)+"/database1").httpClient(hc).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        } finally {
+            fusekiServer.stop();
+            fusekiServer = null;
+        }
+    }
+    
+    // Specific server user.
+    @Test public void access_serverUser_user3() {
+        FusekiServer fusekiServer = fusekiServer("testing/Access/config-server-2.ttl");
+        try { 
+            // user3 does not have server access 
+            HttpClient hc = LibSec.httpClient(authSetup3(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(serverURL(fusekiServer)+"/database1").httpClient(hc).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        } finally {
+            fusekiServer.stop();
+            fusekiServer = null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordServices.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordServices.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordServices.java
new file mode 100644
index 0000000..0183728
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordServices.java
@@ -0,0 +1,230 @@
+/*
+ * 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.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import org.apache.http.client.HttpClient;
+import org.apache.jena.atlas.logging.LogCtl;
+import org.apache.jena.atlas.web.HttpException;
+import org.apache.jena.atlas.web.TypedInputStream;
+import org.apache.jena.atlas.web.WebLib;
+import org.apache.jena.fuseki.build.FusekiBuilder;
+import org.apache.jena.fuseki.build.RequestAuthorization;
+import org.apache.jena.fuseki.jetty.JettyLib;
+import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.fuseki.server.DataService;
+import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.riot.web.HttpCaptureResponse;
+import org.apache.jena.riot.web.HttpOp;
+import org.apache.jena.riot.web.HttpOp.CaptureInput;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.web.HttpSC;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.UserStore;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests for password access to services when there is no server-level access control
+ * and also programmatic setup.
+ * <p>
+ * See {@link TestPasswordServer} for tests with server-level access control
+ * and also access control by assembler
+ * <p>
+ * See {@link TestSecurityFilterFuseki} for graph-level access control.
+ *
+ */
+public class TestPasswordServices {
+
+    private static FusekiServer fusekiServer = null;
+    private static int port = WebLib.choosePort();
+    private static String serverURL = "http://localhost:"+port+"/";
+    private static AuthSetup authSetup1 = new AuthSetup("localhost", port, "user1", "pw1", "TripleStore");
+    private static AuthSetup authSetup2 = new AuthSetup("localhost", port, "user2", "pw2", "TripleStore");
+    // Not in the user store.
+    private static AuthSetup authSetupX = new AuthSetup("localhost", port, "userX", "pwX", "TripleStore");
+    
+    @BeforeClass
+    public static void beforeClass() {
+        if ( false )
+            // To watch the HTTP headers
+            LogCtl.enable("org.apache.http.headers");
+        
+        // Two authorized users.
+        UserStore userStore = new UserStore();
+        JettyLib.addUser(userStore, authSetup1.user, authSetup1.password);
+        JettyLib.addUser(userStore, authSetup2.user, authSetup2.password);
+        try { userStore.start(); }
+        catch (Exception ex) { throw new RuntimeException("UserStore", ex); }
+        
+        ConstraintSecurityHandler sh = JettyLib.makeSecurityHandler(authSetup1.realm, userStore);
+        
+        // Secure these areas.
+        // User needs to be logged in.
+        JettyLib.addPathConstraint(sh, "/ds");
+        // Allow auth control even through there isn't anything there 
+        JettyLib.addPathConstraint(sh, "/nowhere");
+        // user1 only.
+        JettyLib.addPathConstraint(sh, "/ctl");
+        
+        // Not controlled: "/open"
+        
+        DataService dSrv = new DataService(DatasetGraphFactory.createTxnMem());
+        FusekiBuilder.populateStdServices(dSrv, false);
+        RequestAuthorization reqAuth = RequestAuthorization.policyAllowSpecific("user1");
+        dSrv.setAllowedUsers(reqAuth);
+        
+        fusekiServer =
+            FusekiServer.create()
+                .port(port)
+                .add("/ds", DatasetFactory.createTxnMem())
+                .add("/open", DatasetFactory.createTxnMem())
+                .add("/ctl", dSrv)
+                .securityHandler(sh)
+                //.staticFileBase(".")
+                .build();
+        fusekiServer.start();
+    }
+    
+    @Before
+    public void before() {
+        // Reset before every test and after the suite.
+        HttpClient hc = HttpOp.createDefaultHttpClient();
+        HttpOp.setDefaultHttpClient(hc);
+    }
+    
+
+    @AfterClass
+    public static void afterClass() {
+        fusekiServer.stop();
+        HttpClient hc = HttpOp.createDefaultHttpClient();
+        HttpOp.setDefaultHttpClient(hc);
+    }
+    
+    // Server authentication.
+    
+    @Test public void access_server() {
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL) ) {
+            assertNotNull(in);
+            fail("Didn't expect to succeed");
+        } catch (HttpException ex) {
+            // 404 is OK - no static file area. 
+            if ( ex.getResponseCode() != HttpSC.NOT_FOUND_404 )
+                throw ex;
+        }
+    }
+
+    @Test public void access_open() {
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"open") ) {
+            assertNotNull(in);
+        }
+    }
+
+    @Test public void access_open_user1() {
+        // OK.
+        LibSec.withAuth(serverURL+"open", authSetup1, (conn)->{
+            conn.queryAsk("ASK{}");
+        });
+    }
+
+    @Test public void access_open_userX() {
+        // OK.
+        LibSec.withAuth(serverURL+"open", authSetupX, (conn)->{
+            conn.queryAsk("ASK{}");
+        });
+    }
+
+
+    // Should fail.
+    @Test public void access_deny_ds() {
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ds") ) {
+            fail("Didn't expect to succeed");
+        } catch (HttpException ex) {
+            if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401 )
+                throw ex;
+        }
+    }
+    
+    // Should be 401, not be 404.
+    @Test public void access_deny_nowhere() {
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"nowhere") ) {
+            fail("Didn't expect to succeed");
+        } catch (HttpException ex) {
+            if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401 )
+                throw ex;
+        }
+    }
+    
+    @Test public void access_allow_nowhere() {
+        HttpClient hc = LibSec.httpClient(authSetup1);
+        HttpCaptureResponse<TypedInputStream> handler = new CaptureInput();
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"nowhere", null, hc, null) ) {
+            // null for 404.
+            assertNull(in);
+        } catch (HttpException ex) {
+            if ( ex.getResponseCode() != HttpSC.NOT_FOUND_404)
+                throw ex;
+        }
+    }
+    
+    @Test public void access_allow_ds() {
+        HttpClient hc = LibSec.httpClient(authSetup1);
+        HttpCaptureResponse<TypedInputStream> handler = new CaptureInput();
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ds", null, hc, null) ) {
+            assertNotNull(in);
+        }
+    }
+    
+    // Service level : ctl.
+    
+    @Test public void access_service_ctl_user1() {
+        // user1 -- allowed.
+        HttpClient hc = LibSec.httpClient(authSetup1);
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ctl", null, hc, null) ) {
+            assertNotNull(in);
+        }
+    }
+    
+    @Test public void access_service_ctl_user2() {
+        // user2 -- can login, not allowed.
+        HttpClient hc = LibSec.httpClient(authSetup2);
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ctl", null, hc, null) ) {
+            fail("Didn't expect to succeed");
+        } catch (HttpException ex) {
+            if ( ex.getResponseCode() != HttpSC.FORBIDDEN_403)
+                throw ex;
+        }
+    }
+
+    @Test public void access_service_ctl_userX() {
+        // userX -- can't login, not allowed.
+        HttpClient hc = LibSec.httpClient(authSetupX);
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ctl", null, hc, null) ) {
+            fail("Didn't expect to succeed");
+        } catch (HttpException ex) {
+            if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401)
+                throw ex;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssemblerBuild.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssemblerBuild.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssemblerBuild.java
index 54cab0d..ade838b 100644
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssemblerBuild.java
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityAssemblerBuild.java
@@ -25,6 +25,7 @@ import org.apache.jena.sparql.core.assembler.AssemblerUtils;
 import org.apache.jena.sys.JenaSystem;
 import org.junit.Test;
 
+/** Test parsing of assembers with security aspects */ 
 public class TestSecurityAssemblerBuild {
     static { JenaSystem.init(); }
     static final String DIR = "testing/Access/";

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/testing/Access/allowedUsers.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/testing/Access/allowedUsers.ttl b/jena-fuseki2/jena-fuseki-access/testing/Access/allowedUsers.ttl
new file mode 100644
index 0000000..d674a64
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/testing/Access/allowedUsers.ttl
@@ -0,0 +1,15 @@
+PREFIX :        <http://example/>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX access:  <http://jena.apache.org/access#>
+
+:none :p 123 .
+
+:r1 fuseki:allowedUsers "user1" ,"user2" .
+:r2 fuseki:allowedUsers ( "user1" "user2" ) .
+
+:rLoggedIn fuseki:allowedUsers "*" .
+

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/testing/Access/assem-security.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/testing/Access/assem-security.ttl b/jena-fuseki2/jena-fuseki-access/testing/Access/assem-security.ttl
index 8d04b8e..8506be3 100644
--- a/jena-fuseki2/jena-fuseki-access/testing/Access/assem-security.ttl
+++ b/jena-fuseki2/jena-fuseki-access/testing/Access/assem-security.ttl
@@ -52,7 +52,7 @@ PREFIX access:  <http://jena.apache.org/access#>
     fuseki:serviceQuery          "sparql";
     fuseki:serviceUpdate         "update";
     fuseki:serviceUpload         "upload" ;
-    fuseki:serviceReadGraphStore "data" ;
+    fuseki:serviceReadWriteGraphStore "data" ;
     fuseki:serviceReadGraphStore "get" ;
     fuseki:dataset <#tdb_dataset> ;
     .

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/testing/Access/config-server-1.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/testing/Access/config-server-1.ttl b/jena-fuseki2/jena-fuseki-access/testing/Access/config-server-1.ttl
new file mode 100644
index 0000000..ce42505
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/testing/Access/config-server-1.ttl
@@ -0,0 +1,43 @@
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX access:  <http://jena.apache.org/access#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:allowedUsers  "*";
+   fuseki:services (
+     <#service_tdb2>
+     <#service_plain>
+   ) .
+
+<#service_tdb2> rdf:type fuseki:Service ;
+    rdfs:label                      "Access controlled dataset" ;
+    fuseki:allowedUsers             "user1", "user3";
+    fuseki:name                     "database1" ;
+    fuseki:serviceQuery             "query" ;
+    fuseki:serviceQuery             "sparql" ;
+    fuseki:serviceReadGraphStore    "get" ;
+    fuseki:dataset                  <#dataset1>;
+    .
+
+## Own database
+<#dataset1> rdf:type  ja:MemoryDataset ;
+    .
+
+## Dataset 2
+## No service
+<#service_plain> rdf:type fuseki:Service ;
+    fuseki:name                  "database2";
+    fuseki:serviceQuery          "query";
+    fuseki:serviceQuery          "sparql";
+    fuseki:serviceReadGraphStore "get" ;
+    fuseki:dataset <#tdb_dataset> ;
+    .
+    
+<#tdb_dataset> rdf:type      tdb2:DatasetTDB2 ;
+    tdb2:location "--mem--" ;
+    .
+    
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/testing/Access/config-server-2.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/testing/Access/config-server-2.ttl b/jena-fuseki2/jena-fuseki-access/testing/Access/config-server-2.ttl
new file mode 100644
index 0000000..cf9a1fc
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/testing/Access/config-server-2.ttl
@@ -0,0 +1,43 @@
+PREFIX :        <#>
+PREFIX fuseki:  <http://jena.apache.org/fuseki#>
+PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>
+PREFIX tdb2:    <http://jena.apache.org/2016/tdb#>
+PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>
+PREFIX access:  <http://jena.apache.org/access#>
+
+[] rdf:type fuseki:Server ;
+   fuseki:allowedUsers  "user1", "user2";
+   fuseki:services (
+     <#service_tdb2>
+     <#service_plain>
+   ) .
+
+<#service_tdb2> rdf:type fuseki:Service ;
+    rdfs:label                      "Access controlled dataset" ;
+    fuseki:allowedUsers             "user1", "user3";
+    fuseki:name                     "database1" ;
+    fuseki:serviceQuery             "query" ;
+    fuseki:serviceQuery             "sparql" ;
+    fuseki:serviceReadGraphStore    "get" ;
+    fuseki:dataset                  <#dataset1>;
+    .
+
+## Own database
+<#dataset1> rdf:type  ja:MemoryDataset ;
+    .
+
+## Dataset 2
+## No service
+<#service_plain> rdf:type fuseki:Service ;
+    fuseki:name                  "database2";
+    fuseki:serviceQuery          "query";
+    fuseki:serviceQuery          "sparql";
+    fuseki:serviceReadGraphStore "get" ;
+    fuseki:dataset <#tdb_dataset> ;
+    .
+    
+<#tdb_dataset> rdf:type      tdb2:DatasetTDB2 ;
+    tdb2:location "--mem--" ;
+    .
+    
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-access/testing/Access/passwd
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/testing/Access/passwd b/jena-fuseki2/jena-fuseki-access/testing/Access/passwd
new file mode 100644
index 0000000..c856f0a
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/testing/Access/passwd
@@ -0,0 +1,3 @@
+user1 :pw1
+user2: pw2
+user3: pw3

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuildLib.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuildLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuildLib.java
index e23d74e..5bad1ce 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuildLib.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuildLib.java
@@ -33,7 +33,9 @@ import org.apache.jena.shared.JenaException;
 import org.apache.jena.shared.PrefixMapping ;
 import org.apache.jena.vocabulary.RDFS ;
 
-/** Library code for operations specific to building Fuseki servers and services. */
+/**
+ * Library code for operations related to building Fuseki servers and services.
+ */
 public class FusekiBuildLib {
 
     // ---- Helper code

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java
index c9fe706..a6fdd8a 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java
@@ -18,15 +18,25 @@
 
 package org.apache.jena.fuseki.build;
 
+import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
+import static org.apache.jena.fuseki.server.FusekiVocab.pAllowedUsers;
+
+import java.util.Collection;
+import java.util.List;
+
 import org.apache.jena.fuseki.FusekiConfigException;
 import org.apache.jena.fuseki.server.DataAccessPoint;
 import org.apache.jena.fuseki.server.DataAccessPointRegistry;
 import org.apache.jena.fuseki.server.DataService ;
 import org.apache.jena.fuseki.server.Operation ;
+import org.apache.jena.graph.Node;
 import org.apache.jena.query.QuerySolution ;
 import org.apache.jena.query.ResultSet ;
 import org.apache.jena.rdf.model.Property ;
+import org.apache.jena.rdf.model.RDFNode;
 import org.apache.jena.rdf.model.Resource ;
+import org.apache.jena.rdf.model.impl.Util;
 import org.apache.jena.sparql.core.DatasetGraph ;
 
 /**
@@ -101,5 +111,36 @@ public class FusekiBuilder
         name = DataAccessPoint.canonical(name);
         dataAccessPoints.remove(name);
     }
+
+    /** Get the allowed users on some resources.
+     *  Returns null if the resource is null or if there were no settings. 
+     *  
+     * @param resource
+     * @return RequestAuthorization
+     */
+    public static RequestAuthorization allowedUsers(Resource resource) {
+        if ( resource == null )
+            return null;
+        Collection<RDFNode> allowedUsers = FusekiBuildLib.getAll(resource, "fu:"+pAllowedUsers.getLocalName());
+        if ( allowedUsers == null )
+            // Indicate no settings.
+            return null;
+        // Check all values are simple strings  
+        List<String> bad = allowedUsers.stream()
+            .map(RDFNode::asNode)
+            .filter(rn -> ! Util.isSimpleString(rn))
+            .map(rn->rn.toString())
+            .collect(toList());
+        if ( ! bad.isEmpty() ) {
+            //Fuseki.configLog.error(format("User names must be a simple string: bad = %s", bad));
+            throw new FusekiConfigException(format("User names should be a simple string: bad = %s", bad));
+        }
+        // RDFNodes/literals to strings.
+        Collection<String> userNames = allowedUsers.stream()
+            .map(RDFNode::asNode)
+            .map(Node::getLiteralLexicalForm)
+            .collect(toList());
+        return RequestAuthorization.policyAllowSpecific(userNames);
+    }
 }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
index f6ab055..5dc34e3 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
@@ -19,7 +19,6 @@
 package org.apache.jena.fuseki.build ;
 
 import static java.lang.String.format;
-import static java.util.stream.Collectors.toList;
 import static org.apache.jena.fuseki.server.FusekiVocab.*;
 import static org.apache.jena.riot.RDFLanguages.filenameToLang;
 import static org.apache.jena.riot.RDFParserRegistry.isRegistered;
@@ -31,10 +30,7 @@ import java.nio.file.DirectoryStream ;
 import java.nio.file.Files ;
 import java.nio.file.Path ;
 import java.nio.file.Paths ;
-import java.util.ArrayList ;
-import java.util.Collection;
-import java.util.Collections ;
-import java.util.List ;
+import java.util.*;
 
 import org.apache.jena.assembler.Assembler;
 import org.apache.jena.assembler.JA ;
@@ -44,13 +40,11 @@ import org.apache.jena.datatypes.xsd.XSDDatatype;
 import org.apache.jena.fuseki.Fuseki ;
 import org.apache.jena.fuseki.FusekiConfigException ;
 import org.apache.jena.fuseki.server.*;
-import org.apache.jena.graph.Node;
 import org.apache.jena.query.Dataset ;
 import org.apache.jena.query.QuerySolution ;
 import org.apache.jena.query.ReadWrite ;
 import org.apache.jena.query.ResultSet ;
 import org.apache.jena.rdf.model.*;
-import org.apache.jena.rdf.model.impl.Util;
 import org.apache.jena.riot.Lang;
 import org.apache.jena.sparql.core.assembler.AssemblerUtils ;
 import org.apache.jena.sparql.util.Context;
@@ -85,6 +79,29 @@ public class FusekiConfig {
         return x;
     }
 
+    /**
+     * Process a configuration file, starting {@code server}.
+     * Return the {@link DataAccessPoint DataAccessPoints}
+     * set the context provided for server-wide settings.
+     * 
+     * This bundles together the steps:
+     * <ul>
+     * <li>{@link #findServer}
+     * <li>{@link #processContext}
+     * <li>{@link #processLoadClass} (legacy)
+     * <li>{@link #servicesAndDatasets}
+     * </ul>
+     */ 
+    public static List<DataAccessPoint> processServerConfiguration(Resource server, Context context) {
+        Objects.requireNonNull(server);
+        FusekiConfig.processContext(server, context);
+        FusekiConfig.processLoadClass(server);
+        // Process services, whether via server ja:services or, if absent, by finding by type.
+        List<DataAccessPoint> x = FusekiConfig.servicesAndDatasets(server);
+        return x;
+    }
+
+
     /* Find the server resource in a configuration file.
      * Returns null if there isn't one.
      * Raises {@link FusekiConfigException} is there are more than one.
@@ -144,24 +161,36 @@ public class FusekiConfig {
     /** Find and process datasets and services in a configuration file.
      * This can be a Fuseki server configuration file or a services-only configuration file.
      * It looks {@code fuseki:services ( .... )} then, if not found, all {@code rtdf:type fuseki:services}. 
-     * This is the main entry point to processing Fuseki configuration files.
+     * @see #processServerConfiguration
      */
     public static List<DataAccessPoint> servicesAndDatasets(Model model) {
         Resource server = findServer(model);
-        if ( server != null )
-            AssemblerUtils.setContext(server, Fuseki.getContext()) ;
-        
-        // Old style configuration file : server to services.
+        return servicesAndDatasets$(server, model);
+    }
+    
+    /** Find and process datasets and services in a configuration file
+     * starting from {@code server} which can have a {@code fuseki:services ( .... )}
+     * but, if not found, all {@code rtdf:type fuseki:services} are processed. 
+     */
+    public static List<DataAccessPoint> servicesAndDatasets(Resource server) {
+        Objects.requireNonNull(server);
+        return servicesAndDatasets$(server, server.getModel());
+    }   
+    
+    private static List<DataAccessPoint> servicesAndDatasets$(Resource server, Model model) {        
         DatasetDescriptionRegistry dsDescMap = new DatasetDescriptionRegistry();
         // ---- Services
+        // Server to services.
         ResultSet rs = FusekiBuildLib.query("SELECT * { ?s fu:services [ list:member ?service ] }", model, "s", server) ;
         List<DataAccessPoint> accessPoints = new ArrayList<>() ;
 
+        // If none, look for services by type.
         if ( ! rs.hasNext() )
             // No "fu:services ( .... )" so try looking for services directly.
             // This means Fuseki2, service configuration files (no server section) work for --conf. 
             rs = FusekiBuildLib.query("SELECT ?service { ?service a fu:Service }", model) ;
 
+        // rs is a result set of services to process.
         for ( ; rs.hasNext() ; ) {
             QuerySolution soln = rs.next() ;
             Resource svc = soln.getResource("service") ;
@@ -257,40 +286,12 @@ public class FusekiConfig {
         String name = object.getLexicalForm() ;
         name = DataAccessPoint.canonical(name) ;
         DataService dataService = buildDataService(svc, dsDescMap) ;
-        Collection<String> allowedUsers = getAllowedUsers(svc);
+        RequestAuthorization allowedUsers = FusekiBuilder.allowedUsers(svc);
         dataService.setAllowedUsers(allowedUsers);
         DataAccessPoint dataAccess = new DataAccessPoint(name, dataService) ;
         return dataAccess ;
     }
     
-    /** Get the allowed users on some resources.
-     *  
-     * @param resource
-     * @return Collection<String>
-     */
-    public static Collection<String> getAllowedUsers(Resource resource) {
-        Collection<RDFNode> allowedUsers = FusekiBuildLib.getAll(resource, "fu:"+pAllowedUsers.getLocalName());
-        Collection<String> userNames = null;
-        if ( allowedUsers != null ) {
-            // Check all values are simple strings  
-            List<String> bad = allowedUsers.stream()
-                .map(RDFNode::asNode)
-                .filter(rn -> ! Util.isSimpleString(rn))
-                .map(rn->rn.toString())
-                .collect(toList());
-            if ( ! bad.isEmpty() ) {
-                //Fuseki.configLog.error(format("User names must be a simple string: bad = %s", bad));
-                throw new FusekiConfigException(format("User names should be a simple string: bad = %s", bad));
-            }
-            // RDFNodes/literals to strings.
-            userNames = allowedUsers.stream()
-                .map(RDFNode::asNode)
-                .map(Node::getLiteralLexicalForm)
-                .collect(toList());
-        }
-        return userNames;
-    }
-
     /** Build a DatasetRef starting at Resource svc, having the services as described by the descriptions. */
     private static DataService buildDataService(Resource svc, DatasetDescriptionRegistry dsDescMap) {
         Resource datasetDesc = ((Resource)FusekiBuildLib.getOne(svc, "fu:dataset")) ;