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/22 11:51:33 UTC

[1/6] jena git commit: JENA-1623: Endpoint access control lists

Repository: jena
Updated Branches:
  refs/heads/master df9b41df5 -> 35c1c3655


http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorizedRequest.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorizedRequest.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorizedRequest.java
deleted file mode 100644
index 00a5e37..0000000
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorizedRequest.java
+++ /dev/null
@@ -1,110 +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.main.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/ca783936/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java
index 0b1e4cf..dc0ff32 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java
@@ -27,8 +27,9 @@ 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.auth.Auth;
+import org.apache.jena.fuseki.auth.AuthPolicy;
 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;
@@ -94,8 +95,8 @@ public class TestPasswordServices {
         
         DataService dSrv = new DataService(DatasetGraphFactory.createTxnMem());
         FusekiBuilder.populateStdServices(dSrv, false);
-        RequestAuthorization reqAuth = RequestAuthorization.policyAllowSpecific("user1");
-        dSrv.setAllowedUsers(reqAuth);
+        AuthPolicy reqAuth = Auth.policyAllowSpecific("user1");
+        dSrv.setAuthPolicy(reqAuth);
         
         fusekiServer =
             FusekiServer.create()

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-webapp/fuseki-dev
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/fuseki-dev b/jena-fuseki2/jena-fuseki-webapp/fuseki-dev
index 365a33a..6399b8e 100755
--- a/jena-fuseki2/jena-fuseki-webapp/fuseki-dev
+++ b/jena-fuseki2/jena-fuseki-webapp/fuseki-dev
@@ -58,7 +58,7 @@ then
 fi
 
 # Prepend any development directories here
-DEVDIRS="jena-core jena-tdb jena-arq jena-text"
+DEVDIRS="jena-core jena-tdb jena-arq jena-text jena-fuseki2/jena-fuseki-core jena-fuseki2/jena-fuseki-webapp"
 for X in $DEVDIRS
 do
     CPX="$FUSEKI_HOME/../../$X/target/classes"


[5/6] jena git commit: JENA-1623: Merge commit 'refs/pull/492/head' of https://github.com/apache/jena

Posted by an...@apache.org.
JENA-1623: Merge commit 'refs/pull/492/head' of https://github.com/apache/jena

This closes #492.


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

Branch: refs/heads/master
Commit: 78affd221010a51bbee43fe0c7d22ae0804e91d6
Parents: df9b41d 61e900e
Author: Andy Seaborne <an...@apache.org>
Authored: Thu Nov 22 11:45:24 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Thu Nov 22 11:45:24 2018 +0000

----------------------------------------------------------------------
 .../jena/riot/system/StreamRDFWriter.java       |  12 +-
 .../jena/fuseki/access/DataAccessCtl.java       |   2 +-
 .../org/apache/jena/fuseki/access/Users.java    |  35 --
 .../java/org/apache/jena/fuseki/auth/Auth.java  | 104 +++++
 .../org/apache/jena/fuseki/auth/AuthPolicy.java |  39 ++
 .../apache/jena/fuseki/auth/AuthPolicyList.java |  73 ++++
 .../apache/jena/fuseki/auth/AuthUserList.java   |  60 +++
 .../java/org/apache/jena/fuseki/auth/Users.java |  33 ++
 .../jena/fuseki/build/FusekiBuildLib.java       |   1 +
 .../apache/jena/fuseki/build/FusekiBuilder.java |  54 ++-
 .../apache/jena/fuseki/build/FusekiConfig.java  |   5 +-
 .../jena/fuseki/build/RequestAuthorization.java | 115 ------
 .../org/apache/jena/fuseki/ctl/ActionCtl.java   |   2 +-
 .../org/apache/jena/fuseki/ctl/ActionStats.java |   2 +-
 .../apache/jena/fuseki/ctl/JsonDescription.java |   2 +-
 .../jena/fuseki/jetty/FusekiErrorHandler.java   |  12 +-
 .../org/apache/jena/fuseki/jetty/JettyLib.java  |   3 +
 .../fuseki/server/DataAccessPointRegistry.java  |   2 +-
 .../apache/jena/fuseki/server/DataService.java  |  20 +-
 .../org/apache/jena/fuseki/server/Endpoint.java |  73 ++--
 .../apache/jena/fuseki/server/FusekiInfo.java   |   2 +-
 .../jena/fuseki/servlets/ActionService.java     |  85 +++-
 .../apache/jena/fuseki/servlets/AuthFilter.java |   2 +-
 .../jena/fuseki/servlets/FusekiFilter.java      |   2 +-
 .../apache/jena/fuseki/servlets/HttpAction.java |  20 +-
 .../jena/fuseki/servlets/ServiceRouter.java     | 157 +++----
 .../apache/jena/fuseki/servlets/ServletOps.java |   9 +-
 .../test/java/org/apache/jena/fuseki/Dummy.java |   4 +
 .../apache/jena/fuseki/main/FusekiServer.java   | 143 ++++---
 .../jena/fuseki/main/cmds/FusekiMain.java       |   2 +-
 .../jena/fuseki/main/TestFusekiTestServer.java  |   3 +-
 .../AbstractTestFusekiSecurityAssembler.java    |   2 +-
 .../fuseki/main/access/TS_SecurityFuseki.java   |   6 +-
 .../jena/fuseki/main/access/TestAuthorized.java | 111 +++++
 .../main/access/TestAuthorizedRequest.java      | 110 -----
 .../fuseki/main/access/TestPasswordServer.java  | 219 ----------
 .../main/access/TestPasswordServices.java       | 232 -----------
 .../main/access/TestSecurityBuilderSetup.java   | 229 +++++++++++
 .../fuseki/main/access/TestSecurityConfig.java  | 411 +++++++++++++++++++
 .../testing/Access/allowedUsers.ttl             |   6 +-
 .../testing/Access/assem-security-shared.ttl    |   3 +
 .../testing/Access/assem-security.ttl           |   3 +
 .../testing/Access/config-server-0.ttl          |  61 +++
 .../testing/Access/config-server-1.ttl          |   3 +
 .../testing/Access/config-server-2.ttl          |   3 +
 .../testing/Access/config-server-3.ttl          |  51 +++
 .../testing/Access/config-server-4.ttl          |  45 ++
 jena-fuseki2/jena-fuseki-webapp/fuseki-dev      |   2 +-
 48 files changed, 1603 insertions(+), 972 deletions(-)
----------------------------------------------------------------------



[6/6] jena git commit: JENA-1623: Comment about server section was not correct

Posted by an...@apache.org.
JENA-1623: Comment about server section was not correct


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

Branch: refs/heads/master
Commit: 35c1c36557a0c5366374e6ba2202b169b35d5023
Parents: 78affd2
Author: Andy Seaborne <an...@apache.org>
Authored: Thu Nov 22 11:47:04 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Thu Nov 22 11:47:04 2018 +0000

----------------------------------------------------------------------
 .../jena-fuseki-main/testing/Access/config-server-3.ttl         | 5 -----
 1 file changed, 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/35c1c365/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl
index dc63661..13cb76e 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl
@@ -25,11 +25,6 @@ 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#>
 
-# Defaults to 
-#[] rdf:type fuseki:Server ;
-#   fuseki:allowedUsers  "*";
-#   .
-   
 <#service> rdf:type fuseki:Service ;
     rdfs:label                "Access controlled dataset" ;
     fuseki:allowedUsers       "user1", "user3";


[4/6] jena git commit: Fix javadoc, add comment (response to review)

Posted by an...@apache.org.
Fix javadoc, add comment (response to review)


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

Branch: refs/heads/master
Commit: 61e900e87ede605aee0755b6d5c97b80b3f64244
Parents: 070fae1
Author: Andy Seaborne <an...@apache.org>
Authored: Wed Nov 21 16:47:05 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Wed Nov 21 16:47:05 2018 +0000

----------------------------------------------------------------------
 .../src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java      | 2 +-
 .../src/main/java/org/apache/jena/fuseki/build/FusekiBuilder.java  | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/61e900e8/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
index ef6d330..5a14025 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
@@ -30,7 +30,7 @@ public interface AuthPolicy {
 
     /**
      * Is the use denied for this resource? Both {@linkplain #isAllowed} and
-     * {@linkplain #isDenied} could be false if the policy does not knwo one way of the
+     * {@linkplain #isDenied} could be false if the policy does not know one way of the
      * other.
      */
     public default boolean isDenied(String user) {

http://git-wip-us.apache.org/repos/asf/jena/blob/61e900e8/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 1cb060a..dfeb8fd 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
@@ -82,6 +82,7 @@ public class FusekiBuilder
         ResultSet rs = FusekiBuildLib.query("SELECT * { ?svc " + p + " ?ep}", svc.getModel(), "svc", svc) ;
         for ( ; rs.hasNext() ; ) {
             QuerySolution soln = rs.next() ;
+            // No policy yet - set below if one is found.
             AuthPolicy requestAuth = null;
             RDFNode ep = soln.get("ep");
             String epName = null;


[2/6] jena git commit: JENA-1623: Endpoint access control lists

Posted by an...@apache.org.
JENA-1623: Endpoint access control lists


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

Branch: refs/heads/master
Commit: ca7839362f3ce720e6e457e00d84b2c3a55168b3
Parents: ba6b878
Author: Andy Seaborne <an...@apache.org>
Authored: Mon Nov 19 23:58:51 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Tue Nov 20 20:22:51 2018 +0000

----------------------------------------------------------------------
 .../jena/riot/system/StreamRDFWriter.java       |  12 +-
 .../jena/fuseki/access/DataAccessCtl.java       |   2 +-
 .../org/apache/jena/fuseki/access/Users.java    |  35 -----
 .../java/org/apache/jena/fuseki/auth/Auth.java  | 102 ++++++++++++
 .../org/apache/jena/fuseki/auth/AuthPolicy.java |  39 +++++
 .../apache/jena/fuseki/auth/AuthPolicyList.java |  73 +++++++++
 .../jena/fuseki/auth/RequestAuthorization.java  |  60 +++++++
 .../java/org/apache/jena/fuseki/auth/Users.java |  33 ++++
 .../jena/fuseki/build/FusekiBuildLib.java       |   1 +
 .../apache/jena/fuseki/build/FusekiBuilder.java |  43 +++--
 .../apache/jena/fuseki/build/FusekiConfig.java  |   5 +-
 .../jena/fuseki/build/RequestAuthorization.java | 115 --------------
 .../org/apache/jena/fuseki/ctl/ActionCtl.java   |   2 +-
 .../org/apache/jena/fuseki/ctl/ActionStats.java |   2 +-
 .../apache/jena/fuseki/ctl/JsonDescription.java |   2 +-
 .../jena/fuseki/jetty/FusekiErrorHandler.java   |  12 +-
 .../fuseki/server/DataAccessPointRegistry.java  |   2 +-
 .../apache/jena/fuseki/server/DataService.java  |  16 +-
 .../org/apache/jena/fuseki/server/Endpoint.java |  73 +++++----
 .../apache/jena/fuseki/server/FusekiInfo.java   |   2 +-
 .../jena/fuseki/servlets/ActionService.java     |  85 ++++++++--
 .../apache/jena/fuseki/servlets/AuthFilter.java |   2 +-
 .../jena/fuseki/servlets/FusekiFilter.java      |   2 +-
 .../apache/jena/fuseki/servlets/HttpAction.java |  20 ++-
 .../jena/fuseki/servlets/ServiceRouter.java     | 157 ++++++++-----------
 .../apache/jena/fuseki/servlets/ServletOps.java |   9 +-
 .../test/java/org/apache/jena/fuseki/Dummy.java |   4 +
 .../apache/jena/fuseki/main/FusekiServer.java   |  37 +++--
 .../jena/fuseki/main/cmds/FusekiMain.java       |   2 +-
 .../jena/fuseki/main/TestFusekiTestServer.java  |   3 +-
 .../fuseki/main/access/TS_SecurityFuseki.java   |   2 +-
 .../jena/fuseki/main/access/TestAuthorized.java | 111 +++++++++++++
 .../main/access/TestAuthorizedRequest.java      | 110 -------------
 .../main/access/TestPasswordServices.java       |   7 +-
 jena-fuseki2/jena-fuseki-webapp/fuseki-dev      |   2 +-
 35 files changed, 715 insertions(+), 469 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFWriter.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFWriter.java b/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFWriter.java
index e01237c..7fcd875 100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFWriter.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/system/StreamRDFWriter.java
@@ -40,7 +40,9 @@ import org.apache.jena.sparql.core.Quad ;
 
 /** Write RDF in a streaming fashion.
  *  {@link RDFDataMgr} operations do not provide this guarantee.
- *  See {@link  RDFWriterRegistry} for general purpose writers.
+ *  See {@link RDFWriterRegistry} for general purpose writers. 
+ *  {@link StreamRDFWriter} returns the same writer as {@link RDFWriterRegistry}
+ *  if the {@link RDFFormat} is a streaming format. 
  *  
  * @see RDFDataMgr 
  * @see RDFWriterRegistry 
@@ -152,11 +154,11 @@ public class StreamRDFWriter {
         register(RDFFormat.RDFNULL,         streamWriterFactoryNull) ;
     }
 
-    /** Get a StreamRDF destination that will output in syntax <tt>Lang</tt>
+    /** Get a StreamRDF destination that will output in syntax {@code Lang}
      *  and is guaranteed to do so in a scaling, streaming fashion.    
      * @param output OutputStream
      * @param lang   The syntax 
-     * @return       StreamRDF
+     * @return       StreamRDF, or null if Lang does not have a streaming format.
      * @see StreamOps#graphToStream
      * @see StreamOps#datasetToStream
      */
@@ -165,11 +167,11 @@ public class StreamRDFWriter {
         return getWriterStream(output, fmt) ;
     }
 
-    /** Get a StreamRDF destination that will output in syntax <tt>RDFFormat</tt>
+    /** Get a StreamRDF destination that will output in syntax {@code RDFFormat}
      *  and is guaranteed to do so in a scaling, streaming fashion.    
      * @param output OutputStream
      * @param format  The syntax (as an {@link RDFFormat}) 
-     * @return       StreamRDF
+     * @return       StreamRDF, or null if format is not registered for streaming.
      * @see StreamOps#graphToStream
      * @see StreamOps#datasetToStream
      */

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/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
index b97aeec..d49e2ab 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DataAccessCtl.java
@@ -54,7 +54,7 @@ public class DataAccessCtl {
     public static final Symbol   symAuthorizationService    = Symbol.create(VocabSecurity.getURI() + "authService");
 
     /** Get the user from the servlet context via {@link HttpServletRequest#getRemoteUser} */ 
-    public static final Function<HttpAction, String> requestUserServlet = (action)->action.request.getRemoteUser();
+    public static final Function<HttpAction, String> requestUserServlet = (action)->action.getUser();
 
     /**
      * Get the user from {@code ?user} query string parameter. Use carefully; for situations where the user name has

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Users.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Users.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Users.java
deleted file mode 100644
index 62e2bab..0000000
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Users.java
+++ /dev/null
@@ -1,35 +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 Users {
-    
-    
-    /**
-     * Reserved user role name: Name of the user role for any authenticated user of the system.
-     * In the servlet API, this equates to {@code getRemoteUser() != null}.
-     */
-    public static String UserAnyLoggedIn = "*" ; 
-
-    /** 
-     * Reserved user role name: Name of the user role for any authenticated user of the system
-     * In the servlet API, this includes {@code getRemoteUser() != null}.
-     */
-    public static String UserAny = "_" ; 
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
new file mode 100644
index 0000000..2e61129
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
@@ -0,0 +1,102 @@
+/*
+ * 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.auth;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.FusekiConfigException;
+
+/** Authorization Policies.
+ * See {@link Users} for special user names.
+ */
+public class Auth {
+    /** Any authenticated user. */
+    public static AuthPolicy ANY_USER = (user) -> user != null;
+
+    /** Any user, whether authenticated or not. */
+    public static AuthPolicy ANY_ANON = (user) -> true;
+
+    /** Never allow. */
+    public static AuthPolicy DENY     = (user) -> false;
+
+    /** A policy that allows specific users (convenience wrapped for {@link #policyAllowSpecific(Collection)}). */ 
+    public static AuthPolicy policyAllowSpecific(String... allowedUsers) {
+        return Auth.policyAllowSpecific(Arrays.asList(allowedUsers));
+    }
+
+    /** 
+     * A policy that allows specific users.
+     * <ul>
+     * <li>If any user is {@linkplain Users#UserAnyLoggedIn}, then this policy is the same as {@linkplain #ANY_USER}.
+     * <li>If any user is {@linkplain Users#UserAnyAnon}, then this policy is the same as {@linkplain #ANY_ANON}.
+     * </ul>
+     */ 
+    public static AuthPolicy policyAllowSpecific(Collection<String> allowedUsers) {
+        Objects.requireNonNull(allowedUsers, "allowedUsers");
+        if ( allowedUsers.contains(Users.UserAnyLoggedIn) ) {
+            if ( allowedUsers.size() > 1 )
+                Fuseki.configLog.warn("Both 'any user' and a list of users given");
+            return ANY_USER;
+        }
+        if ( allowedUsers.contains(Users.UserAnyAnon) ) {
+            if ( allowedUsers.size() > 1 )
+                Fuseki.configLog.warn("Both 'anon user' and a list of users given");
+            return ANY_ANON;
+        }
+
+        if ( allowedUsers.stream().anyMatch(Objects::isNull) )
+            throw new FusekiConfigException("null user found : "+allowedUsers);  
+        return new RequestAuthorization(allowedUsers);
+    }
+
+    /**
+     * Test whether a user (principal) is allowed by a authorization policy.  
+     * The policy can be null, meaning no restrictions, and the function returns true.
+     * {@code user} maybe null, meaning unauthenticated and any policy must deal with this. 
+     * @param user
+     * @param policy
+     * @return boolean True if the policy is null or allows the user.
+     */
+    public static boolean allow(String user, AuthPolicy policy) {
+        if ( policy == null )
+            return true;
+        return policy.isAllowed(user);
+    }
+    
+    /**
+     * Test whether a user (principal) is allowed by a authorization policy
+     * and perform an action if the policy does not allow the user.
+     * The action can throw an exception.
+     * Additional, return true/false - see {@link #allow(String, AuthPolicy)}.
+     * The policy can be null, meaning no restrictions, and the function returns true.
+     * {@code user} maybe null, meaning unauthenticated and any policy must deal with this. 
+     * @param user
+     * @param policy
+     * @param notAllowed Runnable to execute if the policy does not allow the user.
+     */
+    public static boolean allow(String user, AuthPolicy policy, Runnable notAllowed) {
+        if ( allow(user, policy) )
+            return true;
+        notAllowed.run();
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
new file mode 100644
index 0000000..ef6d330
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicy.java
@@ -0,0 +1,39 @@
+/*
+ * 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.auth;
+
+/**
+ * Policy for authorization to a resource.
+ * Assumes the user has already been authenticated.
+ */
+public interface AuthPolicy {
+    /** 
+     * Is the use authorized for this resource?
+     */
+    public boolean isAllowed(String user);
+
+    /**
+     * Is the use denied for this resource? Both {@linkplain #isAllowed} and
+     * {@linkplain #isDenied} could be false if the policy does not knwo one way of the
+     * other.
+     */
+    public default boolean isDenied(String user) {
+        return !isAllowed(user);
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicyList.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicyList.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicyList.java
new file mode 100644
index 0000000..1e00320
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthPolicyList.java
@@ -0,0 +1,73 @@
+/*
+ * 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.auth;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/** 
+ * An {@link AuthPolicy} that combines a number of {@link AuthPolicy AuthPolicies}.
+ * All policies must authorize access for this policy to allow access.
+ */ 
+public class AuthPolicyList implements AuthPolicy {
+
+    // Thread safe.
+    // Use a 
+    private final Queue<AuthPolicy> policies = new ConcurrentLinkedQueue<>();
+    
+    /**
+     * Merge {@link AuthPolicy AuthPolicies}, returning a combination of the two if both are non-null.
+     * If either is null, return the other.
+     * If both null, return null.
+     */
+    public static AuthPolicy merge(AuthPolicy policy1, AuthPolicy policy2) {
+        if ( policy1 == null )
+            return policy2 ;
+        if ( policy2 == null )
+            return policy1;
+        if ( policy1 instanceof AuthPolicyList) {
+            AuthPolicyList x = new AuthPolicyList((AuthPolicyList)policy1);
+            x.add(policy2);
+            return x;
+        }
+        AuthPolicyList x = new AuthPolicyList();
+        x.add(policy1);
+        x.add(policy2);
+        return x;
+    }
+    
+    private AuthPolicyList(AuthPolicyList other) { 
+        policies.addAll(other.policies);
+    }
+    
+    public AuthPolicyList() { }
+    
+    public void add(AuthPolicy policy) {
+        policies.add(policy);
+    }
+
+    @Override
+    public boolean isAllowed(String user) {
+        for ( AuthPolicy policy : policies ) {
+            if ( ! policy.isAllowed(user) )
+                return false;
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/RequestAuthorization.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/RequestAuthorization.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/RequestAuthorization.java
new file mode 100644
index 0000000..2a5d11a
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/RequestAuthorization.java
@@ -0,0 +1,60 @@
+/*
+ * 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.auth;
+
+import java.util.*;
+
+/**
+ * Policy for allowing users to execute a request. 
+ * Assumes the user has been authenticated.
+ */
+class RequestAuthorization implements AuthPolicy {
+
+    private final Set<String>  allowedUsers;
+
+    /*package*/ RequestAuthorization(Collection<String> allowed) {
+        this.allowedUsers = (allowed == null) ? Collections.emptySet() : new HashSet<>(allowed);
+    }
+    
+    @Override
+    public boolean isAllowed(String user) {
+        if ( user == null )
+            return false;
+        if ( contains(allowedUsers, user) )
+            return true;
+        return false;
+    }
+
+    @Override
+    public boolean isDenied(String user) {
+        return !isAllowed(user);
+    }
+
+    static <T> boolean isNullOrEmpty(Collection<T> collection) {
+        if ( collection == null )
+            return true;
+        return collection.isEmpty(); 
+    }
+    
+    static <T> boolean contains(Collection<T> collection, T obj) {
+        if ( collection == null )
+            return false;
+        return collection.contains(obj); 
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Users.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Users.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Users.java
new file mode 100644
index 0000000..7b6c5fd
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Users.java
@@ -0,0 +1,33 @@
+/*
+ * 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.auth;
+
+public class Users {
+    /**
+     * Reserved user role name: Name of the user role for any authenticated user of the system.
+     * In the servlet API, this equates to {@code getRemoteUser() != null}.
+     */
+    public static String UserAnyLoggedIn = "*"; 
+
+    /** 
+     * Reserved user role name: Name of the user role for any authenticated user of the system
+     * In the servlet API, this includes {@code getRemoteUser() == null}
+     */
+    public static String UserAnyAnon = "_" ; 
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/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 5bad1ce..81a0563 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
@@ -118,6 +118,7 @@ public class FusekiBuildLib {
                 RDFList list = n.as(RDFList.class);
                 results.addAll(list.asJavaList());
             } catch (JenaException x) {
+                // Not a list.
                 results.add(n);
             }
         });

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/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 a6fdd8a..bfcb3a5 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
@@ -26,13 +26,13 @@ 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.fuseki.auth.Auth;
+import org.apache.jena.fuseki.auth.AuthPolicy;
+import org.apache.jena.fuseki.server.*;
 import org.apache.jena.graph.Node;
 import org.apache.jena.query.QuerySolution ;
 import org.apache.jena.query.ResultSet ;
+import org.apache.jena.rdf.model.Literal;
 import org.apache.jena.rdf.model.Property ;
 import org.apache.jena.rdf.model.RDFNode;
 import org.apache.jena.rdf.model.Resource ;
@@ -45,7 +45,7 @@ import org.apache.jena.sparql.core.DatasetGraph ;
  */
 public class FusekiBuilder
 {
-    /** Build a DataService starting at Resource svc, with the standard (default) set of services */
+    /** Build a DataService starting at Resource svc, with the standard (default) set of services. */
     public static DataService buildDataServiceStd(DatasetGraph dsg, boolean allowUpdate) {
         DataService dataService = new DataService(dsg) ;
         populateStdServices(dataService, allowUpdate);
@@ -70,7 +70,12 @@ public class FusekiBuilder
 
     /** Add an operation to a {@link DataService} with a given endpoint name */
     public static void addServiceEP(DataService dataService, Operation operation, String endpointName) {
-        dataService.addEndpoint(operation, endpointName) ; 
+        dataService.addEndpoint(operation, endpointName) ;
+    }
+
+    /** Add an operation to a {@link DataService} with a given endpoint name */
+    public static void addServiceEP(DataService dataService, Operation operation, String endpointName, AuthPolicy requestAuth) {
+        dataService.addEndpoint(operation, endpointName, requestAuth) ;
     }
 
     public static void addServiceEP(DataService dataService, Operation operation, Resource svc, Property property) {
@@ -78,8 +83,24 @@ public class FusekiBuilder
         ResultSet rs = FusekiBuildLib.query("SELECT * { ?svc " + p + " ?ep}", svc.getModel(), "svc", svc) ;
         for ( ; rs.hasNext() ; ) {
             QuerySolution soln = rs.next() ;
-            String epName = soln.getLiteral("ep").getLexicalForm() ;
-            addServiceEP(dataService, operation, epName); 
+            AuthPolicy requestAuth = null;
+            RDFNode ep = soln.get("ep");
+            String epName = null;
+            if ( ep.isLiteral() )
+                epName = soln.getLiteral("ep").getLexicalForm() ;
+            else if ( ep.isResource() ) {
+                Resource r = (Resource)ep;
+                try {
+                    // [AuthAll]
+                    // [ fuseki:name "" ; fuseki:allowedUsers ( "" "" ) ]
+                    Resource x = r.getProperty(FusekiVocab.pAllowedUsers).getResource();
+                    requestAuth = FusekiBuilder.allowedUsers(x);
+                    epName = ((Literal)r.getProperty(FusekiVocab.pServiceName)).getLexicalForm();
+                } catch(Exception x) {}                
+            } else {
+                throw new FusekiConfigException("Unrecognized: "+ep);
+            }
+            addServiceEP(dataService, operation, epName, requestAuth); 
             //log.info("  " + operation.name + " = " + dataAccessPoint.getName() + "/" + epName) ;
         }
     }
@@ -112,13 +133,13 @@ public class FusekiBuilder
         dataAccessPoints.remove(name);
     }
 
-    /** Get the allowed users on some resources.
+    /** Get the allowed users on a resource.
      *  Returns null if the resource is null or if there were no settings. 
      *  
      * @param resource
      * @return RequestAuthorization
      */
-    public static RequestAuthorization allowedUsers(Resource resource) {
+    public static AuthPolicy allowedUsers(Resource resource) {
         if ( resource == null )
             return null;
         Collection<RDFNode> allowedUsers = FusekiBuildLib.getAll(resource, "fu:"+pAllowedUsers.getLocalName());
@@ -140,7 +161,7 @@ public class FusekiBuilder
             .map(RDFNode::asNode)
             .map(Node::getLiteralLexicalForm)
             .collect(toList());
-        return RequestAuthorization.policyAllowSpecific(userNames);
+        return Auth.policyAllowSpecific(userNames);
     }
 }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/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 5dc34e3..c42207b 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
@@ -39,6 +39,7 @@ import org.apache.jena.atlas.lib.StrUtils ;
 import org.apache.jena.datatypes.xsd.XSDDatatype;
 import org.apache.jena.fuseki.Fuseki ;
 import org.apache.jena.fuseki.FusekiConfigException ;
+import org.apache.jena.fuseki.auth.AuthPolicy;
 import org.apache.jena.fuseki.server.*;
 import org.apache.jena.query.Dataset ;
 import org.apache.jena.query.QuerySolution ;
@@ -286,8 +287,8 @@ public class FusekiConfig {
         String name = object.getLexicalForm() ;
         name = DataAccessPoint.canonical(name) ;
         DataService dataService = buildDataService(svc, dsDescMap) ;
-        RequestAuthorization allowedUsers = FusekiBuilder.allowedUsers(svc);
-        dataService.setAllowedUsers(allowedUsers);
+        AuthPolicy allowedUsers = FusekiBuilder.allowedUsers(svc);
+        dataService.setAuthPolicy(allowedUsers);
         DataAccessPoint dataAccess = new DataAccessPoint(name, dataService) ;
         return dataAccess ;
     }

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RequestAuthorization.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RequestAuthorization.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RequestAuthorization.java
deleted file mode 100644
index 16a1616..0000000
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RequestAuthorization.java
+++ /dev/null
@@ -1,115 +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.build;
-
-import java.util.*;
-
-import org.apache.jena.fuseki.Fuseki;
-
-/**
- * Policy for allowing users to execute a request. Assumes the user has been
- * authenticated.
- */
-public class RequestAuthorization {
-
-    private static String ANY_USER  = "*";
-    private final Set<String>        allowedUsers;
-    private final boolean            allowAllUsers;
-    private final boolean            allowAnon;
-
-    /** Allow specific users */ 
-    public static RequestAuthorization policyAllowSpecific(String... allowedUsers) {
-        return new RequestAuthorization(Arrays.asList(allowedUsers), false, false);
-    }
-
-    /** Allow specific users */ 
-    public static RequestAuthorization policyAllowSpecific(Collection<String> allowedUsers) {
-        return new RequestAuthorization(allowedUsers, false, false);
-    }
-
-    /** Allow authenticated (logged in) user. */ 
-    public static RequestAuthorization policyAllowAuthenticated() {
-        return new RequestAuthorization(null, true, false);
-    }
-
-    /** Allow without authentication */ 
-    public static RequestAuthorization policyAllowAnon() {
-        return new RequestAuthorization(null, true, true);
-    }
-
-    /** Allow without authentication */ 
-    public static RequestAuthorization policyNoAccess() {
-        return new RequestAuthorization(Collections.emptySet(), false, false);
-    }
-
-    public RequestAuthorization(Collection<String> allowed, final boolean allowAllUsers, final boolean allowAnon) {
-        // -- anon.
-        if ( allowAnon ) {
-            if ( !isNullOrEmpty(allowed) ) {
-                //warn
-            }
-            this.allowAnon = true;
-            this.allowAllUsers = true;
-            this.allowedUsers = Collections.emptySet();
-            return;
-        }
-        this.allowAnon = false;
-        
-        // -- "any user"
-        if ( allowAllUsers || contains(allowed, ANY_USER) ) {
-            if ( allowed != null && allowed.size() > 1 )
-                Fuseki.configLog.warn("Both 'any user' and a list of users given");
-            this.allowAllUsers = true;
-            this.allowedUsers = Collections.emptySet();
-            return ;
-        }
-
-        // -- List of users
-        this.allowedUsers = (allowed == null) ? Collections.emptySet() : new HashSet<>(allowed);
-        this.allowAllUsers = false;
-    }
-    
-    public boolean isAllowed(String user) {
-        if ( allowAnon )
-            return true;
-        if ( user == null )
-            return false;
-        if ( allowAllUsers )
-            return true;
-        if ( contains(allowedUsers, user) )
-            return true;
-        return false;
-    }
-
-    public boolean isDenied(String user) {
-        return !isAllowed(user);
-    }
-
-    static <T> boolean isNullOrEmpty(Collection<T> collection) {
-        if ( collection == null )
-            return true;
-        return collection.isEmpty(); 
-    }
-    
-    static <T> boolean contains(Collection<T> collection, T obj) {
-        if ( collection == null )
-            return false;
-        return collection.contains(obj); 
-    }
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java
index 756d948..9ed6293 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionCtl.java
@@ -49,7 +49,7 @@ public abstract class ActionCtl extends ActionBase {
         }
         
         action.setControlRequest(dataAccessPoint, datasetUri) ;
-        action.setEndpoint(null, null) ;   // No operation or service name.
+        action.setEndpoint(null) ;   // No operation or service name.
         executeAction(action) ;
     }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java
index f9ab32f..f5bec6b 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionStats.java
@@ -102,7 +102,7 @@ public class ActionStats extends ActionContainerItem
             
             for ( Endpoint endpoint : endpoints ) {
                 // Endpoint names are unique for a given service.
-                builder.key(endpoint.getEndpoint()) ;
+                builder.key(endpoint.getName()) ;
                 builder.startObject() ;
                 
                 operationCounters(builder, endpoint);

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java
index d4d4ad5..e74b0a4 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/JsonDescription.java
@@ -64,7 +64,7 @@ public class JsonDescription {
         builder.key(ServerConst.srvEndpoints) ;
         builder.startArray() ;
         for ( Endpoint endpoint : endpoints )
-            builder.value(endpoint.getEndpoint()) ;
+            builder.value(endpoint.getName()) ;
         builder.finishArray() ;
 
         builder.finishObject() ;

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
index 5ec3882..84990cc 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
@@ -26,7 +26,6 @@ import javax.servlet.http.HttpServletRequest ;
 import javax.servlet.http.HttpServletResponse ;
 
 import org.apache.jena.atlas.io.IO ;
-import org.apache.jena.fuseki.Fuseki ;
 import org.apache.jena.fuseki.servlets.ServletOps ;
 import org.apache.jena.web.HttpSC ;
 import org.eclipse.jetty.http.HttpMethod ;
@@ -58,19 +57,10 @@ public class FusekiErrorHandler extends ErrorHandler
         
         ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024) ;
         try ( Writer writer = IO.asUTF8(bytes) ) {
-            String reason=(response instanceof Response)?((Response)response).getReason():null;
+            String reason = (response instanceof Response) ? ((Response)response).getReason() : null;
             handleErrorPage(request, writer, response.getStatus(), reason) ;
-
-            if ( ! Fuseki.VERSION.equalsIgnoreCase("development") &&
-                 ! Fuseki.VERSION.equals("${project.version}") )
-            {
-                writer.write("\n") ;
-                writer.write("\n") ;
-                writer.write(format("Fuseki - version %s (Build date: %s)\n", Fuseki.VERSION, Fuseki.BUILD_DATE)) ;
-            }
             writer.flush();
             response.setContentLength(bytes.size()) ;
-            // Copy :-(
             response.getOutputStream().write(bytes.toByteArray()) ;
         }
     }

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
index 4c07c69..f85453d 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
@@ -53,7 +53,7 @@ public class DataAccessPointRegistry extends Registry<String, DataAccessPoint>
             System.out.printf("  (key=%s, ref=%s)\n", k, ref.getName()) ;
             ref.getDataService().getOperations().forEach((op)->{
                 ref.getDataService().getEndpoints(op).forEach(ep->{
-                    System.out.printf("     %s : %s\n", op, ep.getEndpoint()) ;
+                    System.out.printf("     %s : %s\n", op, ep.getName()) ;
                 });
             });
         }) ;

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
index 1236e24..8f02877 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
@@ -29,7 +29,7 @@ import org.apache.jena.ext.com.google.common.collect.ArrayListMultimap;
 import org.apache.jena.ext.com.google.common.collect.ListMultimap;
 import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.FusekiException;
-import org.apache.jena.fuseki.build.RequestAuthorization;
+import org.apache.jena.fuseki.auth.AuthPolicy;
 import org.apache.jena.query.TxnType;
 import org.apache.jena.query.text.DatasetGraphText;
 import org.apache.jena.sparql.core.DatasetGraph;
@@ -39,7 +39,7 @@ public class DataService {
 
     private ListMultimap<Operation, Endpoint> operations  = ArrayListMultimap.create();
     private Map<String, Endpoint> endpoints               = new HashMap<>();
-    private RequestAuthorization requestAuth              = null;
+    private AuthPolicy authPolicy                         = null;
 
     /**
      * Record which {@link DataAccessPoint DataAccessPoints} this {@code DataService} is
@@ -96,7 +96,11 @@ public class DataService {
     }
     
     public void addEndpoint(Operation operation, String endpointName) {
-        Endpoint endpoint = new Endpoint(operation, endpointName);
+        addEndpoint(operation, endpointName, null);
+    }
+    
+    public void addEndpoint(Operation operation, String endpointName, AuthPolicy authPolicy) {
+        Endpoint endpoint = new Endpoint(operation, endpointName, authPolicy);
         endpoints.put(endpointName, endpoint);
         operations.put(operation, endpoint);
     }
@@ -215,10 +219,10 @@ public class DataService {
             dataset.close();
     }
 
-    public void setAllowedUsers(RequestAuthorization allowedUsers) { this.requestAuth = allowedUsers; }
+    public void setAuthPolicy(AuthPolicy authPolicy) { this.authPolicy = authPolicy; }
     
-    /** Returning null implies no access control */
-    public RequestAuthorization allowedUsers() { return requestAuth; }
+    /** Returning null implies no authorization control */
+    public AuthPolicy authPolicy() { return authPolicy; }
 
 }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
index c54309d..9b95a78 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
@@ -18,51 +18,66 @@
 
 package org.apache.jena.fuseki.server;
 
-import org.apache.jena.atlas.lib.InternalErrorException ;
+import java.util.Objects;
 
+import org.apache.jena.atlas.lib.InternalErrorException;
+import org.apache.jena.fuseki.auth.AuthPolicy;
+
+/*
+ * An {@code Endpoint} is an instance of an {@link Operation} within a {@link DataService} and has counters.
+ * An {@code Endpoint} may have a name which is a path component. 
+ */
 public class Endpoint implements Counters {
-    
-    public final Operation operation ;
-    public final String endpointName ;
+
+    public final Operation   operation;
+    public final String      endpointName;
+    private final AuthPolicy authPolicy;
     // Endpoint-level counters.
-    private final CounterSet counters           = new CounterSet() ;
+    private final CounterSet counters = new CounterSet();
 
-    public Endpoint(Operation operation, String endpointName) {
-        this.operation = operation ;
+    public Endpoint(Operation operation, String endpointName, AuthPolicy requestAuth) {
+        this.operation = Objects.requireNonNull(operation, "operation");
         if ( operation == null )
-            throw new InternalErrorException("operation is null") ;
-        this.endpointName = endpointName ;
+            throw new InternalErrorException("operation is null");
+        this.endpointName = Objects.requireNonNull(endpointName, "endpointName");
+        this.authPolicy = requestAuth;
         // Standard counters - there may be others
-        counters.add(CounterName.Requests) ;
-        counters.add(CounterName.RequestsGood) ;
-        counters.add(CounterName.RequestsBad) ;
+        counters.add(CounterName.Requests);
+        counters.add(CounterName.RequestsGood);
+        counters.add(CounterName.RequestsBad);
     }
 
     @Override
-    public  CounterSet getCounters()    { return counters ; }
+    public CounterSet getCounters() {
+        return counters;
+    }
 
-    //@Override
-    public Operation getOperation()     { return operation ; }
-    
-    //@Override
-    public boolean isType(Operation operation) { 
-        return operation.equals(operation) ;
+    public Operation getOperation() {
+        return operation;
     }
 
-    public String getEndpoint()         { return endpointName ; }
-    
-    //@Override 
-    public long getRequests() { 
-        return counters.value(CounterName.Requests) ;
+    public boolean isType(Operation operation) {
+        return operation.equals(operation);
     }
-    //@Override
+
+    public String getName() {
+        return endpointName;
+    }
+
+    public AuthPolicy getAuthPolicy() {
+        return authPolicy;
+    }
+
+    public long getRequests() {
+        return counters.value(CounterName.Requests);
+    }
+
     public long getRequestsGood() {
-        return counters.value(CounterName.RequestsGood) ;
+        return counters.value(CounterName.RequestsGood);
     }
-    //@Override
+
     public long getRequestsBad() {
-        return counters.value(CounterName.RequestsBad) ;
+        return counters.value(CounterName.RequestsBad);
     }
 
 }
-

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java
index 7a5643a..8312d1a 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiInfo.java
@@ -74,7 +74,7 @@ public class FusekiInfo {
             DataService dSrv = dap.getDataService();
             dSrv.getOperations().forEach((op)->{
                 dSrv.getEndpoints(op).forEach(ep-> {
-                    String x = ep.getEndpoint();
+                    String x = ep.getName();
                     if ( x.isEmpty() )
                         x = "quads";
                     endpoints.add(x);   

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
index 933faf6..eb4e540 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
@@ -24,11 +24,14 @@ import static org.apache.jena.fuseki.server.CounterName.RequestsBad;
 import static org.apache.jena.fuseki.server.CounterName.RequestsGood;
 
 import java.io.IOException;
+import java.util.Collection;
 
 import javax.servlet.ServletException;
 
 import org.apache.jena.atlas.RuntimeIOException;
+import org.apache.jena.atlas.lib.InternalErrorException;
 import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.auth.Auth;
 import org.apache.jena.fuseki.server.*;
 import org.apache.jena.query.QueryCancelledException;
 import org.apache.jena.web.HttpSC;
@@ -50,6 +53,7 @@ public abstract class ActionService extends ActionBase {
      * executes the HTTP Action life cycle.
      */
     @Override
+    final
     protected void execCommonWorker(HttpAction action) {
         DataAccessPoint dataAccessPoint;
         DataService dSrv;
@@ -63,12 +67,7 @@ public abstract class ActionService extends ActionBase {
                 return;
             }
             dSrv = dataAccessPoint.getDataService();
-            
-            if ( dSrv.allowedUsers() != null ) {
-                String user = action.request.getRemoteUser();
-                if ( ! dSrv.allowedUsers().isAllowed(user) )
-                    ServletOps.errorForbidden();
-            }
+
             if ( !dSrv.isAcceptingRequests() ) {
                 ServletOps.error(HttpSC.SERVICE_UNAVAILABLE_503, "Dataset not currently active");
                 return;
@@ -81,42 +80,96 @@ public abstract class ActionService extends ActionBase {
         }
 
         action.setRequest(dataAccessPoint, dSrv);
+        // Endpoint Name is "" for GSP or quads.
+        // Endpoint name is not "", but unknown for GSP direct naming (which is usually disabled).
         String endpointName = mapRequestToOperation(action, dataAccessPoint);
-
+        
         // ServiceRouter dispatch
         Operation operation = null;
         if ( !endpointName.isEmpty() ) {
             operation = chooseOperation(action, dSrv, endpointName);
             if ( operation == null )
-                ServletOps.errorBadRequest(format("dataset=%s, service=%s", dataAccessPoint.getName(), endpointName));
-
+                if ( ! Fuseki.GSP_DIRECT_NAMING ) 
+                    ServletOps.errorBadRequest(format("dataset=%s, service=%s", dataAccessPoint.getName(), endpointName));
+                else
+                    throw new InternalErrorException("Inconsistent: GSP_DIRECT_NAMING but no operation");
         } else {
+            // Endpoint ""
             operation = chooseOperation(action, dSrv);
             if ( operation == null )
                 ServletOps.errorBadRequest(format("dataset=%s", dataAccessPoint.getName()));
         }
 
+        // ---- Auth checking.
+        // -- Server-level auhtorization.
+        // Checking was carried out by servlet filter AuthFilter.
+        // Need to check Data service and endpoint authorization policies.
+        String user = action.getUser();
+        // -- Data service level authorization
+        if ( dSrv.authPolicy() != null ) {
+            if ( ! dSrv.authPolicy().isAllowed(user) )
+                ServletOps.errorForbidden();
+        }
+        
+        // -- Endpoint level authorization
+        // Make sure all contribute authentication.
+        if ( action.getEndpoint() != null ) {
+            // Specific endpoint chosen.
+            Auth.allow(user, action.getEndpoint().getAuthPolicy(), ServletOps::errorForbidden);
+        } else {
+            // No Endpoint name given; there may be several endpoints for the operation.
+            // authorization is the AND of all endpoints.
+            Collection<Endpoint> x = getEndpoints(dSrv, operation);
+            if ( x.isEmpty() )
+                throw new InternalErrorException("Inconsistent: no endpoints for "+operation);
+            x.forEach(ep->{
+                Auth.allow(user, ep.getAuthPolicy(), ServletOps::errorForbidden);
+            });
+        }
+        // ---- End auth checking.
+
         ActionService handler = action.getServiceDispatchRegistry().findHandler(operation);
         if ( handler == null )
             ServletOps.errorBadRequest(format("dataset=%s: op=%s", dataAccessPoint.getName(), operation.getName()));
-        // XXX -- replace action.setEndpoint
-        Endpoint ep = dSrv.getEndpoint(endpointName);
-        action.setEndpoint(ep, endpointName);
         handler.executeLifecycle(action);
         return;
     }
 
-    // Overridden by the ServiceRouter.
-    protected Operation chooseOperation(HttpAction action, DataService dataService, String serviceName) {
+    // Find the endpoints for an operation.
+    // This is GSP_R/GSP_RW and Quads_R/Quads_RW aware.
+    // If asked for GSP_R and there are no endpoints for GSP_R, try GSP_RW.
+    // Ditto Quads_R -> Quads_RW.
+    private Collection<Endpoint> getEndpoints(DataService dSrv, Operation operation) {
+        Collection<Endpoint> x = dSrv.getEndpoints(operation);
+        if ( x == null || x.isEmpty() ) {
+            if ( operation == Operation.GSP_R )
+                x = dSrv.getEndpoints(Operation.GSP_RW);
+            else if ( operation == Operation.Quads_R )
+                x = dSrv.getEndpoints(Operation.Quads_RW);
+        }
+        return x;
+    }
+    
+    /**
+     * Return the operation that corresponds to the endpoint name for a given data service.
+     * Side effect: This operation should set the selected endpoint in the HttpAction
+     * if this operation is determined to be a specific endpoint.
+     */
+    protected Operation chooseOperation(HttpAction action, DataService dataService, String endpointName) {
+        // Overridden by the ServiceRouter.
         // This default implementation is plain service name to operation based on the
         // DataService as would be used by operation servlets bound by web.xml
         // except Fuseki can add and delete mapping while running.
-        Endpoint ep = dataService.getEndpoint(serviceName);
+        Endpoint ep = dataService.getEndpoint(endpointName);
         Operation operation = ep.getOperation();
+        action.setEndpoint(ep);
         return operation;
     }
 
-    // Overridden by the ServiceRouter.
+    /**
+     * Return the operation that corresponds to the request when there is no endpoint name. 
+     * This operation does not set the selected endpoint in the HttpAction.
+     */
     protected Operation chooseOperation(HttpAction action, DataService dataService) {
         // No default implementation for directly bound services operation servlets.
         return null;

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/AuthFilter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/AuthFilter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/AuthFilter.java
index fba3b50..37cc824 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/AuthFilter.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/AuthFilter.java
@@ -35,7 +35,7 @@ import org.eclipse.jetty.security.SecurityHandler;
  * filter chain.
  * <p>
  * Either the user from {@link HttpServletRequest#getRemoteUser() getRemoteUser} is null,
- * no authentication, or it has been validated. Failed authentication wil have been
+ * no authentication, or it has been validated. Failed authentication will have been
  * handled and rejected by the {@link SecurityHandler security handler} before they get to
  * the filter chain.
  */

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java
index ad2188e..ccd174c 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java
@@ -35,7 +35,7 @@ import org.slf4j.Logger ;
  */
 public class FusekiFilter implements Filter {
     private static Logger log = Fuseki.serverLog ;
-    private static ServiceRouter routerServlet = new ServiceRouter.AccessByConfig() ;
+    private static ServiceRouter routerServlet = new ServiceRouter() ;
     
     @Override
     public void init(FilterConfig filterConfig) {

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
index 0436c5e..8757527 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
@@ -57,7 +57,7 @@ public class HttpAction
     
     // -- Valid only for operational actions (e.g. SPARQL).
     
-    public  String          endpointName    = null ;        // Endpoint name srv was found under 
+    public  String          xendpointName    = null ;        // Endpoint name srv was found under 
     public  Endpoint        endpoint        = null ;
     private Transactional   transactional   = null ;
     private boolean         isTransactional = false ;
@@ -199,6 +199,16 @@ public class HttpAction
         return context ;
     }
 
+    /** Return the authenticated user this {@code HttpAction}.
+     * Return null for no authenticated user.
+     */
+    public String getUser() {
+        if ( request == null )
+            return null;
+        return request.getRemoteUser();
+        //Same as: return request.getUserPrincipal().getName();
+    }
+
     /**
      * Return the "Transactional" for this HttpAction.
      */
@@ -250,12 +260,10 @@ public class HttpAction
     }
     
     /** Set the endpoint and endpoint name that this is an action for. 
-     * @param srvRef {@link Endpoint}
-     * @param endpointName
+     * @param endpoint {@link Endpoint}
      */
-    public void setEndpoint(Endpoint srvRef, String endpointName) {
-        this.endpoint = srvRef ; 
-        this.endpointName = endpointName ;
+    public void setEndpoint(Endpoint endpoint) {
+        this.endpoint = endpoint ; 
     }
     
     /** Get the endpoint for the action (may be null) . */

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServiceRouter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServiceRouter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServiceRouter.java
index 8ec9cd1..6b8c279 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServiceRouter.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServiceRouter.java
@@ -52,89 +52,8 @@ import org.apache.jena.riot.web.HttpNames;
  * It work in conjunction with {@link ActionService#execCommonWorker} to decide where to
  * route requests.
  */
-public abstract class ServiceRouter extends ActionService {
-    protected abstract boolean allowQuery(HttpAction action);
-
-    protected abstract boolean allowUpdate(HttpAction action);
-
-    protected abstract boolean allowGSP_R(HttpAction action);
-
-    protected abstract boolean allowGSP_RW(HttpAction action);
-
-    protected abstract boolean allowQuads_R(HttpAction action);
-
-    protected abstract boolean allowQuads_RW(HttpAction action);
-
-//    public static class ReadOnly extends ServiceRouterServlet {
-//        public ReadOnly() { super() ; }
-//        @Override protected boolean allowQuery(HttpAction action) { return true ; }
-//        @Override protected boolean allowUpdate(HttpAction action) { return false ; }
-//        @Override protected boolean allowGSP_R(HttpAction action) { return true ; }
-//        @Override protected boolean allowGSP_RW(HttpAction action) { return false ; }
-//        @Override protected boolean allowQuads_R(HttpAction action) { return true ; }
-//        @Override protected boolean allowQuads_RW(HttpAction action) { return false ; }
-//    }
-//
-//    public static class ReadWrite extends ServiceRouterServlet {
-//        public ReadWrite() { super() ; }
-//        @Override protected boolean allowQuery(HttpAction action) { return true ; }
-//        @Override protected boolean allowUpdate(HttpAction action) { return true ; }
-//        @Override protected boolean allowGSP_R(HttpAction action) { return true ; }
-//        @Override protected boolean allowGSP_RW(HttpAction action) { return true ; }
-//        @Override protected boolean allowQuads_R(HttpAction action) { return true ; }
-//        @Override protected boolean allowQuads_RW(HttpAction action) { return true ; }
-//    }
-
-    public static class AccessByConfig extends ServiceRouter {
-        public AccessByConfig() {
-            super();
-        }
-
-        @Override
-        protected boolean allowQuery(HttpAction action) {
-            return isEnabled(action, Operation.Query);
-        }
-
-        @Override
-        protected boolean allowUpdate(HttpAction action) {
-            return isEnabled(action, Operation.Update);
-        }
-
-        @Override
-        protected boolean allowGSP_R(HttpAction action) {
-            return isEnabled(action, Operation.GSP_R) || isEnabled(action, Operation.GSP_RW);
-        }
-
-        @Override
-        protected boolean allowGSP_RW(HttpAction action) {
-            return isEnabled(action, Operation.GSP_RW);
-        }
-
-        @Override
-        protected boolean allowQuads_R(HttpAction action) {
-            return isEnabled(action, Operation.Quads_R) || isEnabled(action, Operation.Quads_RW);
-        }
-
-        @Override
-        protected boolean allowQuads_RW(HttpAction action) {
-            return isEnabled(action, Operation.Quads_RW);
-        }
-
-        /**
-         * Test whether there is a configuration that allows this action as the operation
-         * given. Ignores the operation in the action which is set due to parsing - it
-         * might be "quads" which is the generic operation when just the dataset is
-         * specified.
-         */
-        private boolean isEnabled(HttpAction action, Operation operation) {
-            // Disregard the operation name of the action
-            DataService dSrv = action.getDataService();
-            if ( dSrv == null )
-                return false;
-            return !dSrv.getEndpoints(operation).isEmpty();
-        }
-    }
-
+public class ServiceRouter extends ActionService {
+    
     public ServiceRouter() {
         super();
     }
@@ -165,14 +84,19 @@ public abstract class ServiceRouter extends ActionService {
      * {@link Fuseki#GSP_DIRECT_NAMING}.
      */
     @Override
-    protected Operation chooseOperation(HttpAction action, DataService dataService, String serviceName) {
-        // Check enabled happens here. 
-        // Must be enabled by configuration to be in the lookup.
-        Endpoint ep = dataService.getEndpoint(serviceName);
+    protected Operation chooseOperation(HttpAction action, DataService dataService, String endpointName) {
+        // Default implementation in ActionService:
+//        Endpoint ep = dataService.getEndpoint(endpointName);
+//        Operation operation = ep.getOperation();
+//        action.setEndpoint(ep);
+        
+        Endpoint ep = dataService.getEndpoint(endpointName);
         if ( ep != null ) {
             Operation operation = ep.getOperation();
-            if ( operation != null ) {
-                // If GSP, no params means Quads operation.
+            action.setEndpoint(ep);
+            if ( operation != null ) { 
+                // Can this be null?
+                // If a GSP operation, then no params means Quads operation.
                 if ( operation.equals(Operation.GSP_R) || operation.equals(Operation.GSP_RW) ) {
                     // Look for special case. Quads on the GSP service endpoint.
                     boolean hasParamGraph = action.request.getParameter(HttpNames.paramGraph) != null;
@@ -186,12 +110,14 @@ public abstract class ServiceRouter extends ActionService {
                 }
                 return operation;
             }
+            System.err.printf("Notice: endpoint %s but no operation", endpointName);
         }
 
+        // No endpoint.
         // There is a trailing part - unrecognized service name ==> GSP direct naming.
         if ( !Fuseki.GSP_DIRECT_NAMING )
             ServletOps.errorNotFound(
-                "Not found: dataset='" + printName(action.getDataAccessPoint().getName()) + "' service='" + printName(serviceName) + "'");
+                "Not found: dataset='" + printName(action.getDataAccessPoint().getName()) + "' endpoint='" + printName(endpointName) + "'");
         // GSP Direct naming - the servlets handle direct and indirct naming.
         return gspOperation(action, action.request);
     }
@@ -207,9 +133,8 @@ public abstract class ServiceRouter extends ActionService {
      * <li>Content type</li>
      * </ul>
      */
-    @Override
+    @Override final
     protected Operation chooseOperation(HttpAction action, DataService dataService) {
-        Endpoint ep = dataService.getEndpoint("");
         HttpServletRequest request = action.getRequest();
 
         // ---- Dispatch based on HttpParams : Query, Update, GSP.
@@ -351,8 +276,8 @@ public abstract class ServiceRouter extends ActionService {
         boolean isHEAD = method.equals(HttpNames.METHOD_HEAD);
         return isGET || isHEAD;
     }
-
-    private String printName(String x) {
+    
+    private static String printName(String x) {
         if ( x.startsWith("/") )
             return x.substring(1);
         return x;
@@ -388,4 +313,48 @@ public abstract class ServiceRouter extends ActionService {
     protected void doDelete(HttpServletRequest request, HttpServletResponse response) {
         doCommon(request, response);
     }
+
+    // Check whether an operation is allowed by the setup.
+    // This is used when the operation/endpoint is not named directly
+    // e.g. http://host:port/dataset?query= 
+    //   is implicitly a call on 
+    // http://host:port/dataset/sparql?query=
+    
+    protected boolean allowQuery(HttpAction action) {
+        return isEnabled(action, Operation.Query);
+    }
+
+    protected boolean allowUpdate(HttpAction action) {
+        return isEnabled(action, Operation.Update);
+    }
+
+    protected boolean allowGSP_R(HttpAction action) {
+        return isEnabled(action, Operation.GSP_R) || isEnabled(action, Operation.GSP_RW);
+    }
+
+    protected boolean allowGSP_RW(HttpAction action) {
+        return isEnabled(action, Operation.GSP_RW);
+    }
+
+    protected boolean allowQuads_R(HttpAction action) {
+        return isEnabled(action, Operation.Quads_R) || isEnabled(action, Operation.Quads_RW);
+    }
+
+    protected boolean allowQuads_RW(HttpAction action) {
+        return isEnabled(action, Operation.Quads_RW);
+    }
+
+    /**
+     * Test whether there is a configuration that allows this action as the operation
+     * given. Ignores the operation in the action which is set due to parsing - it
+     * might be "quads" which is the generic operation when just the dataset is
+     * specified.
+     */
+    private boolean isEnabled(HttpAction action, Operation operation) {
+        // Disregard the operation name of the action.
+        DataService dSrv = action.getDataService();
+        if ( dSrv == null )
+            return false;
+        return !dSrv.getEndpoints(operation).isEmpty();
+    }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
index 0fa6add..c9f9029 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ServletOps.java
@@ -24,6 +24,7 @@ import java.io.PrintWriter ;
 import javax.servlet.ServletOutputStream ;
 import javax.servlet.http.HttpServletResponse ;
 
+import org.apache.jena.atlas.io.IndentedWriter;
 import org.apache.jena.atlas.json.JSON ;
 import org.apache.jena.atlas.json.JsonValue ;
 import org.apache.jena.fuseki.system.UploadDetails;
@@ -193,8 +194,12 @@ public class ServletOps {
             ServletOutputStream out = response.getOutputStream() ;
             response.setContentType(WebContent.contentTypeJSON);
             response.setCharacterEncoding(WebContent.charsetUTF8) ;
-            JSON.write(out, v) ;
-            out.println() ; 
+            
+            IndentedWriter iOut = new IndentedWriter(out) ;
+            JSON.write(iOut, v) ;
+            // Make sure we end with a newline.
+            iOut.ensureStartOfLine();
+            iOut.flush() ;
             out.flush() ;
         } catch (IOException ex) { ServletOps.errorOccurred(ex) ; }
     }

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/Dummy.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/Dummy.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/Dummy.java
index 50255c0..0969d76 100644
--- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/Dummy.java
+++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/Dummy.java
@@ -18,6 +18,10 @@
 
 package org.apache.jena.fuseki;
 
+/** 
+ * Tests in jena-fuseki-main and jena-fuseki-webapp
+ * because so many rely on having a server to run.
+ */  
 public class Dummy {
 
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
index 4f63c84..3d298e8 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
@@ -34,9 +34,10 @@ import org.apache.jena.fuseki.Fuseki;
 import org.apache.jena.fuseki.FusekiConfigException;
 import org.apache.jena.fuseki.FusekiException;
 import org.apache.jena.fuseki.access.DataAccessCtl;
+import org.apache.jena.fuseki.auth.Auth;
+import org.apache.jena.fuseki.auth.AuthPolicy;
 import org.apache.jena.fuseki.build.FusekiBuilder;
 import org.apache.jena.fuseki.build.FusekiConfig;
-import org.apache.jena.fuseki.build.RequestAuthorization;
 import org.apache.jena.fuseki.ctl.ActionPing;
 import org.apache.jena.fuseki.ctl.ActionStats;
 import org.apache.jena.fuseki.jetty.FusekiErrorHandler1;
@@ -235,7 +236,7 @@ public class FusekiServer {
         private boolean                  verbose            = false;
         private boolean                  withStats          = false;
         private boolean                  withPing           = false;
-        private RequestAuthorization     serverAllowedUsers = null;
+        private AuthPolicy               serverAuth         = null;
         private String                   passwordFile       = null;
         private String                   realm              = null;
         private AuthScheme               authScheme             = null;
@@ -457,7 +458,8 @@ public class FusekiServer {
             Model model = AssemblerUtils.readAssemblerFile(filename);
 
             Resource server = FusekiConfig.findServer(model);
-            processServerLevel(server);
+            // [AuthAll]
+            processServerLevel(server); 
             
             // Process server and services, whether via server ja:services or, if absent, by finding by type.
             // Side effect - sets global context.
@@ -486,9 +488,20 @@ public class FusekiServer {
             String authStr = GraphUtils.getAsStringValue(server, FusekiVocab.pAuth);
             authScheme = AuthScheme.scheme(authStr);
             
-            serverAllowedUsers = FusekiBuilder.allowedUsers(server);
+            serverAuth = FusekiBuilder.allowedUsers(server);
         }
 
+        /** Process password file, auth and rela settings on the server description. **/
+        private void processAuthentication(Resource server) {
+            passwordFile = GraphUtils.getAsStringValue(server, FusekiVocab.pPasswordFile);
+            if ( passwordFile != null )
+                passwordFile(passwordFile);
+            realm = GraphUtils.getAsStringValue(server, FusekiVocab.pRealm);
+            if ( realm == null )
+                realm = "TripleStore"; 
+            realm(realm);
+        }
+        
         /**
          * Choose the HTTP authentication scheme. 
          */
@@ -656,19 +669,19 @@ public class FusekiServer {
                     .map(name-> dataAccessPoints.get(name).getDataService().getDataset())
                     .anyMatch(DataAccessCtl::isAccessControlled);
 
-            hasAllowedUsers = ( serverAllowedUsers != null );
+            hasAllowedUsers = ( serverAuth != null );
             if ( ! hasAllowedUsers ) {
                 // Any datasets with allowedUsers?
                 hasAllowedUsers =
                     dataAccessPoints.keys().stream()
                         .map(name-> dataAccessPoints.get(name).getDataService())
-                        .anyMatch(dSvc->dSvc.allowedUsers() != null);
+                        .anyMatch(dSvc->dSvc.authPolicy() != null);
             }
             
             // If only a password file given, and nothing else, set the server to allowedUsers="*" (must log in).  
             if ( passwordFile != null && ! hasAllowedUsers ) {
                 hasAllowedUsers = true;
-                serverAllowedUsers = RequestAuthorization.policyAllowAuthenticated();
+                serverAuth = Auth.ANY_USER;
             }
         }
         
@@ -729,12 +742,12 @@ public class FusekiServer {
             // -- Access control
             if ( securityHandler != null && securityHandler instanceof ConstraintSecurityHandler ) {
                 ConstraintSecurityHandler csh = (ConstraintSecurityHandler)securityHandler;
-                if ( serverAllowedUsers != null )
+                if ( serverAuth != null )
                     JettyLib.addPathConstraint(csh, "/*");
                 else {
                     DataAccessPointRegistry.get(cxt).forEach((name, dap)-> {
                         DatasetGraph dsg = dap.getDataService().getDataset();
-                        if ( dap.getDataService().allowedUsers() != null ) {
+                        if ( dap.getDataService().authPolicy() != null ) {
                             // Not need if whole server ACL'ed.
                             JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name));
                             JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name)+"/*");
@@ -756,7 +769,7 @@ public class FusekiServer {
             context.setContextPath(contextPath);
             if ( securityHandler != null ) {
                 context.setSecurityHandler(securityHandler);
-                if ( serverAllowedUsers != null ) {
+                if ( serverAuth != null ) {
                     ConstraintSecurityHandler csh = (ConstraintSecurityHandler)securityHandler;
                     JettyLib.addPathConstraint(csh, "/*");
                 }
@@ -768,8 +781,8 @@ public class FusekiServer {
         private void servletsAndFilters(ServletContextHandler context) {
             // Fuseki dataset services filter
             // First in chain.
-            if ( serverAllowedUsers != null ) {
-                Predicate<String> auth = serverAllowedUsers::isAllowed; 
+            if ( serverAuth != null ) {
+                Predicate<String> auth = serverAuth::isAllowed; 
                 AuthFilter authFilter = new AuthFilter(auth);
                 addFilter(context, "/*", authFilter);
                 //JettyLib.addPathConstraint(null, contextPath);

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
index e1505b1..f6ba99b 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
@@ -547,7 +547,7 @@ public class FusekiMain extends CmdARQ {
                 DataService dSrv = dap.getDataService();
                 dSrv.getOperations().forEach((op)->{
                     dSrv.getEndpoints(op).forEach(ep-> {
-                        String x = ep.getEndpoint();
+                        String x = ep.getName();
                         if ( x.isEmpty() )
                             x = "quads";
                         endpoints.add(x);   

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestServer.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestServer.java
index 8637d36..ebed98f 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestServer.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestServer.java
@@ -53,7 +53,8 @@ public class TestFusekiTestServer {
         // No auth set - should work.
         try ( TypedInputStream in = HttpOp.execHttpGet(FusekiTestServer.urlDataset(), "*/*") ) {}
         catch (HttpException ex) {
-            Assert.assertTrue(ex.getResponseCode() == HttpSC.FORBIDDEN_403 || ex.getResponseCode() == HttpSC.UNAUTHORIZED_401 );
+            if ( ex.getResponseCode() == HttpSC.FORBIDDEN_403 || ex.getResponseCode() == HttpSC.UNAUTHORIZED_401 ) 
+                return;
             throw ex;
         }
     }

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java
index d34023c..e344d60 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java
@@ -26,7 +26,7 @@ import org.junit.runners.Suite;
 
 @RunWith(Suite.class)
 @Suite.SuiteClasses( {
-    TestAuthorizedRequest.class
+    TestAuthorized.class
     , TestSecurityFilterFuseki.class
     , TestFusekiSecurityAssemblerSeparate.class
     , TestFusekiSecurityAssemblerShared.class

http://git-wip-us.apache.org/repos/asf/jena/blob/ca783936/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorized.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorized.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorized.java
new file mode 100644
index 0000000..0562c49
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorized.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.main.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.auth.Auth;
+import org.apache.jena.fuseki.auth.AuthPolicy;
+import org.apache.jena.fuseki.build.FusekiBuilder;
+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 TestAuthorized {
+    
+    static Model model = RDFDataMgr.loadModel("testing/Access/allowedUsers.ttl");
+    
+    @Test public void auth_anon() {
+        AuthPolicy auth = Auth.ANY_ANON;
+        assertTrue(auth.isAllowed(null));
+        assertTrue(auth.isAllowed("user1"));
+    }
+    
+    @Test public void auth_anyLoggedIn_1() {
+        AuthPolicy auth = Auth.ANY_USER;
+        assertFalse(auth.isAllowed(null));
+        assertTrue(auth.isAllowed("user1"));
+    }
+    
+    @Test public void auth_anyLoggedIn_2() {
+        AuthPolicy auth = Auth.policyAllowSpecific("*");
+        assertFalse(auth.isAllowed(null));
+        assertTrue(auth.isAllowed("user1"));
+    }
+
+    @Test public void auth_noOne() {
+        AuthPolicy auth = Auth.DENY;
+        assertFalse(auth.isAllowed(null));
+        assertFalse(auth.isAllowed("user1"));
+    }
+
+
+    @Test public void auth_user_1() {
+        AuthPolicy auth = Auth.policyAllowSpecific("user1", "user2");
+        assertFalse(auth.isAllowed(null));
+        assertTrue(auth.isAllowed("user1"));
+        assertTrue(auth.isAllowed("user2"));
+        assertFalse(auth.isAllowed("user3"));
+    }
+    
+    @Test public void auth_parse_no_info_1() {
+        Resource r = model.createResource("http://example/notInData");
+        AuthPolicy auth = FusekiBuilder.allowedUsers(r);
+        assertNull(auth);
+    }
+
+    @Test public void auth_parse_no_info_2() {
+        Resource r = model.createResource("http://example/none");
+        AuthPolicy auth = FusekiBuilder.allowedUsers(r);
+        assertNull(auth);
+    }
+
+    @Test public void auth_parse_1() {
+        Resource r = model.createResource("http://example/r1");
+        AuthPolicy auth = FusekiBuilder.allowedUsers(r);
+        assertNotNull(auth);
+        assertFalse(auth.isAllowed(null));
+        assertTrue(auth.isAllowed("user1"));
+        assertTrue(auth.isAllowed("user2"));
+        assertFalse(auth.isAllowed("user3"));
+    }
+    
+    @Test public void auth_parse_2() {
+        Resource r = model.createResource("http://example/r2");
+        AuthPolicy auth = FusekiBuilder.allowedUsers(r);
+        assertNotNull(auth);
+        assertFalse(auth.isAllowed(null));
+        assertTrue(auth.isAllowed("user1"));
+        assertTrue(auth.isAllowed("user2"));
+        assertFalse(auth.isAllowed("user3"));
+    }
+    
+    @Test public void auth_parse_loggedIn() {
+        Resource r = model.createResource("http://example/rLoggedIn");
+        AuthPolicy auth = FusekiBuilder.allowedUsers(r);
+        assertNotNull(auth);
+        assertFalse(auth.isAllowed(null));
+        assertTrue(auth.isAllowed("user1"));
+        assertTrue(auth.isAllowed("user3"));
+    }
+}


[3/6] jena git commit: JENA-1623: Endpoint access control lists tests

Posted by an...@apache.org.
JENA-1623: Endpoint access control lists tests


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

Branch: refs/heads/master
Commit: 070fae10908c46508b3454727e4a388640fbdb97
Parents: ca78393
Author: Andy Seaborne <an...@apache.org>
Authored: Tue Nov 20 20:21:43 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Tue Nov 20 20:56:50 2018 +0000

----------------------------------------------------------------------
 .../java/org/apache/jena/fuseki/auth/Auth.java  |   6 +-
 .../apache/jena/fuseki/auth/AuthUserList.java   |  60 +++
 .../jena/fuseki/auth/RequestAuthorization.java  |  60 ---
 .../apache/jena/fuseki/build/FusekiBuilder.java |  22 +-
 .../org/apache/jena/fuseki/jetty/JettyLib.java  |   3 +
 .../apache/jena/fuseki/server/DataService.java  |   4 +
 .../jena/fuseki/servlets/ActionService.java     |   2 +-
 .../apache/jena/fuseki/main/FusekiServer.java   | 138 ++++---
 .../AbstractTestFusekiSecurityAssembler.java    |   2 +-
 .../fuseki/main/access/TS_SecurityFuseki.java   |   4 +-
 .../fuseki/main/access/TestPasswordServer.java  | 219 ----------
 .../main/access/TestPasswordServices.java       | 233 -----------
 .../main/access/TestSecurityBuilderSetup.java   | 229 +++++++++++
 .../fuseki/main/access/TestSecurityConfig.java  | 411 +++++++++++++++++++
 .../testing/Access/allowedUsers.ttl             |   6 +-
 .../testing/Access/assem-security-shared.ttl    |   3 +
 .../testing/Access/assem-security.ttl           |   3 +
 .../testing/Access/config-server-0.ttl          |  61 +++
 .../testing/Access/config-server-1.ttl          |   3 +
 .../testing/Access/config-server-2.ttl          |   3 +
 .../testing/Access/config-server-3.ttl          |  51 +++
 .../testing/Access/config-server-4.ttl          |  45 ++
 22 files changed, 976 insertions(+), 592 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
index 2e61129..c0b1d52 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/Auth.java
@@ -29,6 +29,8 @@ import org.apache.jena.fuseki.FusekiConfigException;
  * See {@link Users} for special user names.
  */
 public class Auth {
+    public static final String dftRealm = "TripleStore";   
+
     /** Any authenticated user. */
     public static AuthPolicy ANY_USER = (user) -> user != null;
 
@@ -42,7 +44,7 @@ public class Auth {
     public static AuthPolicy policyAllowSpecific(String... allowedUsers) {
         return Auth.policyAllowSpecific(Arrays.asList(allowedUsers));
     }
-
+    
     /** 
      * A policy that allows specific users.
      * <ul>
@@ -65,7 +67,7 @@ public class Auth {
 
         if ( allowedUsers.stream().anyMatch(Objects::isNull) )
             throw new FusekiConfigException("null user found : "+allowedUsers);  
-        return new RequestAuthorization(allowedUsers);
+        return new AuthUserList(allowedUsers);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthUserList.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthUserList.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthUserList.java
new file mode 100644
index 0000000..55aae72
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/AuthUserList.java
@@ -0,0 +1,60 @@
+/*
+ * 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.auth;
+
+import java.util.*;
+
+/**
+ * Policy for allowing users to execute a request. 
+ * Assumes the user has been authenticated.
+ */
+class AuthUserList implements AuthPolicy {
+
+    private final Set<String>  allowedUsers;
+
+    /*package*/ AuthUserList(Collection<String> allowed) {
+        this.allowedUsers = (allowed == null) ? Collections.emptySet() : new HashSet<>(allowed);
+    }
+    
+    @Override
+    public boolean isAllowed(String user) {
+        if ( user == null )
+            return false;
+        if ( contains(allowedUsers, user) )
+            return true;
+        return false;
+    }
+
+    @Override
+    public boolean isDenied(String user) {
+        return !isAllowed(user);
+    }
+
+    static <T> boolean isNullOrEmpty(Collection<T> collection) {
+        if ( collection == null )
+            return true;
+        return collection.isEmpty(); 
+    }
+    
+    static <T> boolean contains(Collection<T> collection, T obj) {
+        if ( collection == null )
+            return false;
+        return collection.contains(obj); 
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/RequestAuthorization.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/RequestAuthorization.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/RequestAuthorization.java
deleted file mode 100644
index 2a5d11a..0000000
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/auth/RequestAuthorization.java
+++ /dev/null
@@ -1,60 +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.auth;
-
-import java.util.*;
-
-/**
- * Policy for allowing users to execute a request. 
- * Assumes the user has been authenticated.
- */
-class RequestAuthorization implements AuthPolicy {
-
-    private final Set<String>  allowedUsers;
-
-    /*package*/ RequestAuthorization(Collection<String> allowed) {
-        this.allowedUsers = (allowed == null) ? Collections.emptySet() : new HashSet<>(allowed);
-    }
-    
-    @Override
-    public boolean isAllowed(String user) {
-        if ( user == null )
-            return false;
-        if ( contains(allowedUsers, user) )
-            return true;
-        return false;
-    }
-
-    @Override
-    public boolean isDenied(String user) {
-        return !isAllowed(user);
-    }
-
-    static <T> boolean isNullOrEmpty(Collection<T> collection) {
-        if ( collection == null )
-            return true;
-        return collection.isEmpty(); 
-    }
-    
-    static <T> boolean contains(Collection<T> collection, T obj) {
-        if ( collection == null )
-            return false;
-        return collection.contains(obj); 
-    }
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/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 bfcb3a5..1cb060a 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
@@ -32,12 +32,11 @@ import org.apache.jena.fuseki.server.*;
 import org.apache.jena.graph.Node;
 import org.apache.jena.query.QuerySolution ;
 import org.apache.jena.query.ResultSet ;
-import org.apache.jena.rdf.model.Literal;
-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.*;
 import org.apache.jena.rdf.model.impl.Util;
+import org.apache.jena.shared.JenaException;
 import org.apache.jena.sparql.core.DatasetGraph ;
+import org.apache.jena.sparql.util.graph.GraphUtils;
 
 /**
  * Helper functions use to construct Fuseki servers.
@@ -91,12 +90,17 @@ public class FusekiBuilder
             else if ( ep.isResource() ) {
                 Resource r = (Resource)ep;
                 try {
-                    // [AuthAll]
+                    // Look for possible:
                     // [ fuseki:name "" ; fuseki:allowedUsers ( "" "" ) ]
-                    Resource x = r.getProperty(FusekiVocab.pAllowedUsers).getResource();
-                    requestAuth = FusekiBuilder.allowedUsers(x);
-                    epName = ((Literal)r.getProperty(FusekiVocab.pServiceName)).getLexicalForm();
-                } catch(Exception x) {}                
+                    epName = r.getProperty(FusekiVocab.pServiceName).getString();
+                    List<RDFNode> x = GraphUtils.multiValue(r, FusekiVocab.pAllowedUsers);
+                    if ( x.size() > 1 )
+                        throw new FusekiConfigException("Multiple fuseki:"+FusekiVocab.pAllowedUsers.getLocalName()+" for "+r); 
+                    if ( ! x.isEmpty() )
+                        requestAuth = FusekiBuilder.allowedUsers(r);
+                } catch(JenaException | ClassCastException ex) {
+                    throw new FusekiConfigException("Failed to parse endpoint: "+r);
+                }
             } else {
                 throw new FusekiConfigException("Unrecognized: "+ep);
             }

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
index ae5ce65..005914c 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyLib.java
@@ -93,6 +93,9 @@ public class JettyLib {
         Objects.requireNonNull(userStore);
         Objects.requireNonNull(role);
         
+        if ( authMode == null )
+            authMode = dftAuthMode;
+        
         ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
 
         IdentityService identService = new DefaultIdentityService();

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
index 8f02877..3759da0 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataService.java
@@ -109,6 +109,10 @@ public class DataService {
         return endpoints.get(endpointName);
     }
 
+    public Collection<Endpoint> getEndpoints() {
+        return operations.values();
+    }
+    
     public List<Endpoint> getEndpoints(Operation operation) {
         List<Endpoint> x = operations.get(operation);
         if ( x == null )

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
index eb4e540..fe87407 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionService.java
@@ -101,7 +101,7 @@ public abstract class ActionService extends ActionBase {
         }
 
         // ---- Auth checking.
-        // -- Server-level auhtorization.
+        // -- Server-level authorization.
         // Checking was carried out by servlet filter AuthFilter.
         // Need to check Data service and endpoint authorization policies.
         String user = action.getUser();

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
index 3d298e8..794b4b7 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java
@@ -236,7 +236,7 @@ public class FusekiServer {
         private boolean                  verbose            = false;
         private boolean                  withStats          = false;
         private boolean                  withPing           = false;
-        private AuthPolicy               serverAuth         = null;
+        private AuthPolicy               serverAuth         = null;     // Server level policy.
         private String                   passwordFile       = null;
         private String                   realm              = null;
         private AuthScheme               authScheme             = null;
@@ -458,8 +458,7 @@ public class FusekiServer {
             Model model = AssemblerUtils.readAssemblerFile(filename);
 
             Resource server = FusekiConfig.findServer(model);
-            // [AuthAll]
-            processServerLevel(server); 
+            processServerLevel(server);
             
             // Process server and services, whether via server ja:services or, if absent, by finding by type.
             // Side effect - sets global context.
@@ -477,29 +476,27 @@ public class FusekiServer {
                 return;
             // Extract settings - the server building is done in buildSecurityHandler,
             // buildAccessControl.  Dataset and graph level happen in assemblers. 
-            passwordFile = GraphUtils.getAsStringValue(server, FusekiVocab.pPasswordFile);
-            if ( passwordFile != null )
-                passwordFile(passwordFile);
-            realm = GraphUtils.getAsStringValue(server, FusekiVocab.pRealm);
-            if ( realm == null )
-                realm = "TripleStore"; 
-            realm(realm);
+            String passwdFile = GraphUtils.getAsStringValue(server, FusekiVocab.pPasswordFile);
+            if ( passwdFile != null )
+                passwordFile(passwdFile);
+            String realmStr = GraphUtils.getAsStringValue(server, FusekiVocab.pRealm);
+            if ( realmStr != null )
+                realm(realmStr);
             
             String authStr = GraphUtils.getAsStringValue(server, FusekiVocab.pAuth);
-            authScheme = AuthScheme.scheme(authStr);
-            
+            if ( authStr != null )
+                auth(AuthScheme.scheme(authStr));
             serverAuth = FusekiBuilder.allowedUsers(server);
         }
 
-        /** Process password file, auth and rela settings on the server description. **/
+        /** Process password file, auth and realm settings on the server description. **/
         private void processAuthentication(Resource server) {
-            passwordFile = GraphUtils.getAsStringValue(server, FusekiVocab.pPasswordFile);
-            if ( passwordFile != null )
-                passwordFile(passwordFile);
-            realm = GraphUtils.getAsStringValue(server, FusekiVocab.pRealm);
-            if ( realm == null )
-                realm = "TripleStore"; 
-            realm(realm);
+            String passwdFile = GraphUtils.getAsStringValue(server, FusekiVocab.pPasswordFile);
+            if ( passwdFile != null )
+                passwordFile(passwdFile);
+            String realmStr = GraphUtils.getAsStringValue(server, FusekiVocab.pRealm);
+            if ( realmStr != null )
+                realm(realmStr);
         }
         
         /**
@@ -512,7 +509,6 @@ public class FusekiServer {
 
         /**
          * Set the realm used for HTTP digest authentication.
-         * The default is "TripleStore". 
          */
         public Builder realm(String realm) {
             this.realm = realm;
@@ -645,23 +641,31 @@ public class FusekiServer {
                 }
                 if ( networkLoopback ) 
                     applyLocalhost(server);
-                boolean accessCtlRequest = securityHandler != null;
-                boolean accessCtlData = false;
                 return new FusekiServer(serverPort, serverHttpsPort, server, handler.getServletContext());
             } finally {
                 buildFinish();
             }
         }
         
+        private ConstraintSecurityHandler buildSecurityHandler() {
+            if ( passwordFile == null )
+                return null;
+            UserStore userStore = JettyLib.makeUserStore(passwordFile);
+            return JettyLib.makeSecurityHandler(realm, userStore, authScheme);
+        }
+
         // Only valid during the build() process.
-        private boolean hasAuthenication = false;
-        private boolean hasAllowedUsers = false;
+        private boolean hasAuthenticationHandler = false;
+        private boolean hasAuthenticationUse = false;
         private boolean hasDataAccessControl = false;
         private List<DatasetGraph> datasets = null;
-
+        
         private void buildStart() {
             // -- Server, dataset authentication 
-            hasAuthenication = (passwordFile != null) || (securityHandler != null) ;
+            hasAuthenticationHandler = (passwordFile != null) || (securityHandler != null) ;
+
+            if ( realm == null )
+                realm = Auth.dftRealm;
             
             // See if there are any DatasetGraphAccessControl.
             hasDataAccessControl = 
@@ -669,32 +673,38 @@ public class FusekiServer {
                     .map(name-> dataAccessPoints.get(name).getDataService().getDataset())
                     .anyMatch(DataAccessCtl::isAccessControlled);
 
-            hasAllowedUsers = ( serverAuth != null );
-            if ( ! hasAllowedUsers ) {
+            // Server level.
+            hasAuthenticationUse = ( serverAuth != null );
+            // Dataset level.
+            if ( ! hasAuthenticationUse ) {
                 // Any datasets with allowedUsers?
-                hasAllowedUsers =
-                    dataAccessPoints.keys().stream()
+                hasAuthenticationUse = dataAccessPoints.keys().stream()
                         .map(name-> dataAccessPoints.get(name).getDataService())
                         .anyMatch(dSvc->dSvc.authPolicy() != null);
             }
+            // Endpoint level.
+            if ( ! hasAuthenticationUse ) {
+                hasAuthenticationUse = dataAccessPoints.keys().stream()
+                    .map(name-> dataAccessPoints.get(name).getDataService())
+                    .flatMap(dSrv->dSrv.getEndpoints().stream())
+                    .anyMatch(ep->ep.getAuthPolicy()!=null);
+            }
             
             // If only a password file given, and nothing else, set the server to allowedUsers="*" (must log in).  
-            if ( passwordFile != null && ! hasAllowedUsers ) {
-                hasAllowedUsers = true;
-                serverAuth = Auth.ANY_USER;
-            }
+            if ( passwordFile != null && ! hasAuthenticationUse )
+                hasAuthenticationUse = true;
         }
         
         private void buildFinish() {
-            hasAuthenication = false;
+            hasAuthenticationHandler = false;
             hasDataAccessControl = false;
             datasets = null;
         }
         
         /** Do some checking to make sure setup is consistent. */ 
         private void validate() {
-            if ( ! hasAuthenication ) {
-                if ( hasAllowedUsers ) {
+            if ( ! hasAuthenticationHandler ) {
+                if ( hasAuthenticationUse ) {
                     Fuseki.configLog.warn("'allowedUsers' is set but there is no authentication setup (e.g. password file)");
                 }
                 
@@ -719,7 +729,7 @@ public class FusekiServer {
             DataAccessPointRegistry.set(cxt, dapRegistry);
             JettyLib.setMimeTypes(handler);
             servletsAndFilters(handler);
-            buildAccessControl(cxt);
+            buildAccessControl(handler);
             
             if ( hasDataAccessControl ) {
                 // Consider making this "always" and changing the standard operation bindings. 
@@ -731,28 +741,34 @@ public class FusekiServer {
             return handler;
         }
         
-        private ConstraintSecurityHandler buildSecurityHandler() {
-            if ( passwordFile == null )
-                return null;
-            UserStore userStore = JettyLib.makeUserStore(passwordFile);
-            return JettyLib.makeSecurityHandler(realm, userStore, authScheme);
-        }
-        
-        private void buildAccessControl(ServletContext cxt) {
+        private void buildAccessControl(ServletContextHandler cxt) {
             // -- Access control
-            if ( securityHandler != null && securityHandler instanceof ConstraintSecurityHandler ) {
-                ConstraintSecurityHandler csh = (ConstraintSecurityHandler)securityHandler;
-                if ( serverAuth != null )
-                    JettyLib.addPathConstraint(csh, "/*");
-                else {
-                    DataAccessPointRegistry.get(cxt).forEach((name, dap)-> {
-                        DatasetGraph dsg = dap.getDataService().getDataset();
-                        if ( dap.getDataService().authPolicy() != null ) {
-                            // Not need if whole server ACL'ed.
-                            JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name));
-                            JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name)+"/*");
-                        }
-                    });
+            if ( securityHandler != null ) {
+                cxt.setSecurityHandler(securityHandler);
+                if ( securityHandler instanceof ConstraintSecurityHandler ) {
+                    ConstraintSecurityHandler csh = (ConstraintSecurityHandler)securityHandler;
+                    if ( serverAuth != null )
+                        JettyLib.addPathConstraint(csh, "/*");
+                    else {
+                        // Find datatsets than need filters. 
+                        DataAccessPointRegistry.get(cxt.getServletContext()).forEach((name, dap)-> {
+                            DatasetGraph dsg = dap.getDataService().getDataset();
+                            if ( dap.getDataService().authPolicy() != null ) {
+                                JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name));
+                                JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name)+"/*");
+                            }
+                            else {
+                                // Endpoint level.
+                                dap.getDataService().getEndpoints().forEach(ep->{
+                                   if ( ep.getAuthPolicy()!=null ) {
+                                       JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name)+"/"+ep.getName());
+                                       if ( Fuseki.GSP_DIRECT_NAMING )
+                                           JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name)+"/"+ep.getName()+"/*");
+                                   }
+                                });
+                            }
+                        });
+                    }
                 }
             }
         }
@@ -787,7 +803,7 @@ public class FusekiServer {
                 addFilter(context, "/*", authFilter);
                 //JettyLib.addPathConstraint(null, contextPath);
             }
-            // Second in chain.
+            // Second in chain?.
             FusekiFilter ff = new FusekiFilter();
             addFilter(context, "/*", ff);
 

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java
index 7369c4f..2304a6e 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java
@@ -53,7 +53,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 /**
- * Test on the assembler for data access control.
+ * Tests 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.

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java
index e344d60..efc29a1 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java
@@ -31,8 +31,8 @@ import org.junit.runners.Suite;
     , TestFusekiSecurityAssemblerSeparate.class
     , TestFusekiSecurityAssemblerShared.class
     
-    , TestPasswordServer.class
-    , TestPasswordServices.class
+    , TestSecurityConfig.class
+    , TestSecurityBuilderSetup.class
 })
 
 public class TS_SecurityFuseki {

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServer.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServer.java
deleted file mode 100644
index d24c701..0000000
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServer.java
+++ /dev/null
@@ -1,219 +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.main.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.LibSec;
-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.AuthSetup;
-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/070fae10/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java
deleted file mode 100644
index dc0ff32..0000000
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswordServices.java
+++ /dev/null
@@ -1,233 +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.main.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.auth.Auth;
-import org.apache.jena.fuseki.auth.AuthPolicy;
-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.rdfconnection.LibSec;
-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.AuthSetup;
-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);
-        AuthPolicy reqAuth = Auth.policyAllowSpecific("user1");
-        dSrv.setAuthPolicy(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/070fae10/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityBuilderSetup.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityBuilderSetup.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityBuilderSetup.java
new file mode 100644
index 0000000..2938455
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityBuilderSetup.java
@@ -0,0 +1,229 @@
+/*
+ * 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.main.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.auth.Auth;
+import org.apache.jena.fuseki.auth.AuthPolicy;
+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.rdfconnection.LibSec;
+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.AuthSetup;
+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 access to services using programmatic setup.
+ * <p>
+ * See {@link TestSecurityConfig} for tests with server-level access control and also
+ * access control by assembler
+ * <p>
+ * See {@link TestSecurityFilterFuseki} for graph-level access control.
+ */
+public class TestSecurityBuilderSetup {
+
+    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);
+        AuthPolicy reqAuth = Auth.policyAllowSpecific("user1");
+        dSrv.setAuthPolicy(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/070fae10/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityConfig.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityConfig.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityConfig.java
new file mode 100644
index 0000000..370c188
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityConfig.java
@@ -0,0 +1,411 @@
+/*
+ * 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.main.access;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.function.Consumer;
+
+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.main.FusekiServer;
+import org.apache.jena.rdfconnection.LibSec;
+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.AuthSetup;
+import org.apache.jena.web.HttpSC;
+import org.junit.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests for security of the server, services and endpoints using configuration file setup.
+ */
+public class TestSecurityConfig {
+
+    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 static String datasetURL(FusekiServer server, String dsName) {
+        if ( dsName.startsWith("/") )
+            dsName = dsName.substring(1);
+        return "http://localhost:"+server.getPort()+"/"+dsName;
+    }
+
+    private static FusekiServer fusekiServer(String configFile) {
+        int port = WebLib.choosePort();
+        FusekiServer fusekiServer =
+            FusekiServer.create()
+                .port(port)
+                .parseConfigFile(configFile)
+                .passwordFile("testing/Access/passwd")
+                .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() {
+        HttpClient hc = HttpOp.createDefaultHttpClient();
+        HttpOp.setDefaultHttpClient(hc);
+    }
+
+    private static void expectQuery403(Runnable action) {
+        expectQuery(action, HttpSC.FORBIDDEN_403);
+    }
+
+    private static void expectQuery401(Runnable action) {
+        expectQuery(action, HttpSC.UNAUTHORIZED_401);
+    }
+
+    private static void expectQuery(Runnable action, int expected) {
+        try {
+            action.run();
+            throw new HttpException("action completed");
+        } catch (QueryExceptionHTTP ex) {
+            if ( ex.getResponseCode() != expected )
+                throw ex;
+        }
+    }
+
+    private static void test(String configFile, Consumer<FusekiServer> action) {
+        FusekiServer fusekiServer = fusekiServer(configFile);
+        try {
+            action.accept(fusekiServer);
+        } finally {
+            fusekiServer.stop();
+            fusekiServer = null;
+        }
+    }
+
+    // config-server-0.ttl : service level ACL
+    @Test public void access_serverNone() {
+        test("testing/Access/config-server-0.ttl", fusekiServer -> {
+            // Server access.
+            try (TypedInputStream in = HttpOp.execHttpGet(serverURL(fusekiServer))) {
+                assertNotNull(in);
+            } catch (HttpException ex) {
+                // 404 is OK - no static file area.
+                if ( ex.getResponseCode() != HttpSC.NOT_FOUND_404 )
+                    throw ex;
+            }
+        });
+    }    
+
+    @Test public void access_serverNone_db1() {
+        test("testing/Access/config-server-0.ttl", (fusekiServer)->{
+            // db1 - secured - try no user
+            try ( RDFConnection conn = RDFConnectionRemote.create().destination(datasetURL(fusekiServer, "database1"))
+                    .build() ) {
+                expectQuery401(()->conn.queryAsk("ASK{}"));
+            }
+            // db1 - secured - try wrong user
+            HttpClient hcUser2 = LibSec.httpClient(authSetup2(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote.create().destination(datasetURL(fusekiServer, "database1"))
+                    .httpClient(hcUser2)
+                    .build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+            
+            // db1 - secured - with user
+            HttpClient hcUser1 = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote.create().destination(datasetURL(fusekiServer, "database1"))
+                    .httpClient(hcUser1)
+                    .build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        });
+    }
+
+    @Test public void access_serverNone_db2() {
+        test("testing/Access/config-server-0.ttl", (fusekiServer)->{
+            try ( RDFConnection conn = RDFConnectionRemote.create()
+                    .destination(datasetURL(fusekiServer, "database2"))
+                    // No HttpClient.
+                    .build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        });
+    }
+
+    // config-server-1.ttl : server=*; service level ACL
+    @Test public void access_serverAny_user1() {
+        test("testing/Access/config-server-1.ttl", fusekiServer->{
+            // 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;
+            }
+        });
+    }
+
+    @Test public void access_dataset_db1() {
+        test("testing/Access/config-server-1.ttl", fusekiServer->{
+            // Must be logged in.
+            HttpClient hc = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "database1")).httpClient(hc).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        });
+    }
+
+    @Test public void access_dataset_db2() {
+        test("testing/Access/config-server-1.ttl", fusekiServer->{
+            // Must be logged in.
+            HttpClient hc = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "database2")).httpClient(hc).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        });
+    }
+
+    @Test public void access_dataset_db1_wrongUser() {
+        test("testing/Access/config-server-1.ttl", fusekiServer->{
+            // Must be logged in.
+            HttpClient hc = LibSec.httpClient(authSetup2(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                        .create().destination(datasetURL(fusekiServer, "database1")).httpClient(hc).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+
+    // config-server-2.ttl : server=user1,user2; service level ACL
+    @Test public void access_dataset_user1() {
+        test("testing/Access/config-server-2.ttl", fusekiServer->{
+            // Must be logged in as user1
+            HttpClient hc = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "database1")).httpClient(hc).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        });
+    }
+
+    // Specific server user.
+    @Test public void access_dataset_user2() {
+        test("testing/Access/config-server-2.ttl", fusekiServer->{ 
+            // user2 does not have service access 
+            HttpClient hc = LibSec.httpClient(authSetup2(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "database1")).httpClient(hc).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+
+    // Specific server user.
+    @Test public void access_dataset_user3() {
+        test("testing/Access/config-server-2.ttl", fusekiServer->{
+            // user3 does not have server access 
+            HttpClient hc = LibSec.httpClient(authSetup3(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "database1")).httpClient(hc).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+
+    // config-server-3.ttl : service and endpoint
+    @Test public void serviceAndEndpoint_anon() {
+        test("testing/Access/config-server-3.ttl", fusekiServer->{
+            try ( RDFConnection conn = RDFConnectionRemote
+                .create().destination(datasetURL(fusekiServer, "db")).build() ) {
+                expectQuery401(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+
+    @Test public void serviceAndEndpoint_unknownUser() {
+        test("testing/Access/config-server-3.ttl", fusekiServer->{
+            HttpClient hc = LibSec.httpClient(authSetupX(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                .create().destination(datasetURL(fusekiServer, "db")).httpClient(hc).build() ) {
+                // Fails authentication.
+                expectQuery401(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+
+    // Deny - not in every endpoint
+    @Test public void serviceAndEndpoint_user1() {
+        test("testing/Access/config-server-3.ttl", fusekiServer->{
+            HttpClient hc1 = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "db"))
+                    .httpClient(hc1).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+    
+    // Go to endpoint.
+    @Test public void serviceAndEndpointDirect_user1() {
+        test("testing/Access/config-server-3.ttl", fusekiServer->{
+            HttpClient hc1 = LibSec.httpClient(authSetup1(fusekiServer));
+            
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "db"))
+                    .queryEndpoint(datasetURL(fusekiServer, "db")+"/query1")
+                    .httpClient(hc1).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        });
+    }
+
+    @Test public void serviceAndEndpoint_user2() {
+        test("testing/Access/config-server-3.ttl", fusekiServer->{
+            HttpClient hc2 = LibSec.httpClient(authSetup2(fusekiServer));
+            // -- Dataset query. User2 is not in dataset.
+            try ( RDFConnection conn = RDFConnectionRemote
+                .create().destination(datasetURL(fusekiServer, "db")).httpClient(hc2).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+    
+    // Still "no" - dataset excludes.
+    @Test public void serviceAndEndpointDirect_user2() {
+        test("testing/Access/config-server-3.ttl", fusekiServer->{
+            HttpClient hc2 = LibSec.httpClient(authSetup2(fusekiServer));
+            // -- Dataset query. User2 is not in dataset.
+            try ( RDFConnection conn = RDFConnectionRemote
+                .create().destination(datasetURL(fusekiServer, "db"))
+                .queryEndpoint(datasetURL(fusekiServer, "db")+"/query2")
+                .httpClient(hc2).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+
+    @Test public void serviceAndEndpoint_user3() {
+        // Not user3 - it is not in every endpoint.
+        test("testing/Access/config-server-3.ttl", fusekiServer->{
+            HttpClient hc3 = LibSec.httpClient(authSetup3(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                .create().destination(datasetURL(fusekiServer, "db")).httpClient(hc3).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+    
+    // config-server-4.ttl : endpoint only.
+    // Deny - not in every endpoint
+    @Test public void endpoint_user1() {
+        test("testing/Access/config-server-4.ttl", fusekiServer->{
+            HttpClient hc1 = LibSec.httpClient(authSetup1(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "db2"))
+                    .httpClient(hc1).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+    
+
+    // Go to endpoint.
+    @Test public void endpointDirect_user1() {
+        test("testing/Access/config-server-4.ttl", fusekiServer->{
+            HttpClient hc1 = LibSec.httpClient(authSetup1(fusekiServer));
+            
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "db2"))
+                    .queryEndpoint(datasetURL(fusekiServer, "db2")+"/query1")
+                    .httpClient(hc1).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        });
+    }
+
+    @Test public void endpoint_user2() {
+        test("testing/Access/config-server-4.ttl", fusekiServer->{
+            HttpClient hc2 = LibSec.httpClient(authSetup2(fusekiServer));
+            // -- Dataset query. User2 is not in dataset.
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "db2")).httpClient(hc2).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+    
+    // Yes by endpoint only.
+    @Test public void endpointDirect_user2() {
+        test("testing/Access/config-server-4.ttl", fusekiServer->{
+            HttpClient hc2 = LibSec.httpClient(authSetup2(fusekiServer));
+            // -- Dataset query. User2 is onthis specific endpoint.
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "db2"))
+                    .queryEndpoint(datasetURL(fusekiServer, "db2")+"/query2")
+                    .httpClient(hc2).build() ) {
+                conn.queryAsk("ASK{}");
+            }
+        });
+    }
+    
+    // No - not at this endpoint.
+    @Test public void endpointDirect_user2a() {
+        test("testing/Access/config-server-4.ttl", fusekiServer->{
+            HttpClient hc2 = LibSec.httpClient(authSetup2(fusekiServer));
+            // -- Dataset query. User2 is onthis specific endpoint.
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "db2"))
+                    .queryEndpoint(datasetURL(fusekiServer, "db2")+"/query3")
+                    .httpClient(hc2).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+
+    @Test public void endpoint_user3() {
+        // Not user3 - it is not in every endpoint.
+        test("testing/Access/config-server-4.ttl", fusekiServer->{
+            HttpClient hc3 = LibSec.httpClient(authSetup3(fusekiServer));
+            try ( RDFConnection conn = RDFConnectionRemote
+                    .create().destination(datasetURL(fusekiServer, "db2")).httpClient(hc3).build() ) {
+                expectQuery403(()->conn.queryAsk("ASK{}"));
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/testing/Access/allowedUsers.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/allowedUsers.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/allowedUsers.ttl
index b45fecc..679d43b 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Access/allowedUsers.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/allowedUsers.ttl
@@ -1,12 +1,11 @@
 # Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0
 
+# Data for testing parsing "fuseki:allowedUsers" : TestAuthorized
+
 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 .
 
@@ -14,4 +13,3 @@ PREFIX access:  <http://jena.apache.org/access#>
 :r2 fuseki:allowedUsers ( "user1" "user2" ) .
 
 :rLoggedIn fuseki:allowedUsers "*" .
-

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl
index c16f66c..210ff9e 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security-shared.ttl
@@ -14,6 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Use in TestFusekiSecurityAssemblerShared
+# Tests on the assembler for graph-level data access control.
+
 PREFIX :        <#>
 PREFIX fuseki:  <http://jena.apache.org/fuseki#>
 PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security.ttl
index 286ab2e..8c6f3d3 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/assem-security.ttl
@@ -14,6 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Use in TestFusekiSecurityAssemblerSeparate
+# Tests on the assembler for graph-level data access control.
+
 PREFIX :        <#>
 PREFIX fuseki:  <http://jena.apache.org/fuseki#>
 PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-0.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-0.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-0.ttl
new file mode 100644
index 0000000..1432b7d
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-0.ttl
@@ -0,0 +1,61 @@
+# 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.
+
+# Used by TestSecurityConfig
+# No server level security, only service level.
+
+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: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/070fae10/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-1.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-1.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-1.ttl
index e06b687..bb12dca 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-1.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-1.ttl
@@ -14,6 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Used by TestSecurityConfig
+# Server is "*"
+
 PREFIX :        <#>
 PREFIX fuseki:  <http://jena.apache.org/fuseki#>
 PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-2.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-2.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-2.ttl
index d08ad90..a1df3cf 100644
--- a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-2.ttl
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-2.ttl
@@ -14,6 +14,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Used by TestSecurityConfig
+# Server is "user1", "user2
+
 PREFIX :        <#>
 PREFIX fuseki:  <http://jena.apache.org/fuseki#>
 PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl
new file mode 100644
index 0000000..dc63661
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-3.ttl
@@ -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.
+
+# Used by TestSecurityConfig
+# Endpoint ACLs.
+
+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#>
+
+# Defaults to 
+#[] rdf:type fuseki:Server ;
+#   fuseki:allowedUsers  "*";
+#   .
+   
+<#service> rdf:type fuseki:Service ;
+    rdfs:label                "Access controlled dataset" ;
+    fuseki:allowedUsers       "user1", "user3";
+    fuseki:name               "db" ;
+    fuseki:serviceQuery       [ fuseki:name "query1" ;
+                                fuseki:allowedUsers ("user1" "user2")
+                              ] ;
+    fuseki:serviceQuery       [ fuseki:name "query2" ;
+                                # Not accessible - service is 1&3
+                                fuseki:allowedUsers ("user1" "user2")
+                              ] ;
+    fuseki:serviceQuery       [ fuseki:name "query3" ;
+                                fuseki:allowedUsers ("user3")
+                              ] ;
+    fuseki:serviceQuery       [ fuseki:name "open" ;
+                                fuseki:allowedUsers ("*")
+                              ] ;
+    fuseki:dataset            [ rdf:type  ja:MemoryDataset ];
+    .

http://git-wip-us.apache.org/repos/asf/jena/blob/070fae10/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-4.ttl
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-4.ttl b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-4.ttl
new file mode 100644
index 0000000..b5ef5d5
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/testing/Access/config-server-4.ttl
@@ -0,0 +1,45 @@
+# 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.
+
+# Used by TestSecurityConfig
+# Endpoint ACLs only.
+
+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#>
+
+<#service> rdf:type fuseki:Service ;
+    rdfs:label                "Access controlled dataset" ;
+    fuseki:name               "db2" ;
+    fuseki:serviceQuery       [ fuseki:name "query1" ;
+                                fuseki:allowedUsers ("user1" "user2")
+                              ] ;
+    fuseki:serviceQuery       [ fuseki:name "query2" ;
+                                # Not accessible - service is 1&3
+                                fuseki:allowedUsers ("user1" "user2")
+                              ] ;
+    fuseki:serviceQuery       [ fuseki:name "query3" ;
+                                fuseki:allowedUsers ("user3")
+                              ] ;
+    fuseki:serviceQuery       [ fuseki:name "open" ;
+                                fuseki:allowedUsers ("*")
+                              ] ;
+    fuseki:dataset            [ rdf:type  ja:MemoryDataset ];
+    .