You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2018/11/17 17:20:37 UTC

[11/34] jena git commit: JENA-1594: Add authentication support (digest or basic).

JENA-1594: Add authentication support (digest or basic).

Add support for FROM handling with graph-access-controlled datasets.
Code cleaning.


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

Branch: refs/heads/master
Commit: c6e9d3678a48a9bc4e6243c53f987e67df33e442
Parents: 2303bfa
Author: Andy Seaborne <an...@apache.org>
Authored: Thu Nov 1 19:55:42 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Tue Nov 13 15:39:14 2018 +0000

----------------------------------------------------------------------
 .../fuseki/access/AssemblerAccessDataset.java   |   8 +-
 .../apache/jena/fuseki/access/AuthSetup.java    |  36 ++++
 .../jena/fuseki/access/DataAccessCtl.java       |  51 ++++--
 .../access/DatasetGraphAccessControl.java       |   6 +-
 .../access/Filtered_SPARQL_QueryDataset.java    |  69 +++++++-
 .../fuseki/access/Filtered_SPARQL_Update.java   |  71 ++++++++
 .../org/apache/jena/fuseki/access/LibSec.java   | 177 +++++++++++++++++++
 .../jena/fuseki/access/SecurityContext.java     |  23 ++-
 .../fuseki/access/TS_SecurityFiltering.java     |   1 +
 .../jena/fuseki/access/TestPasswordAccess.java  | 145 +++++++++++++++
 .../fuseki/access/TestSecurityFilterFuseki.java |   5 +-
 .../org/apache/jena/fuseki/jetty/AuthMode.java  |  21 +++
 .../org/apache/jena/fuseki/jetty/JettyLib.java  |  86 ++++++---
 .../jena/fuseki/servlets/SPARQL_Query.java      |  20 +--
 .../fuseki/servlets/SPARQL_QueryDataset.java    |   7 +-
 .../fuseki/servlets/SPARQL_QueryGeneral.java    |  15 +-
 .../jena/fuseki/servlets/SPARQL_Update.java     |   2 +-
 .../apache/jena/fuseki/main/FusekiServer.java   |   2 +-
 18 files changed, 652 insertions(+), 93 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
index 71a89db..b379e2a 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AssemblerAccessDataset.java
@@ -53,10 +53,10 @@ public class AssemblerAccessDataset extends AssemblerBase {
         DatasetGraph dsg = new DatasetGraphAccessControl(ds.asDatasetGraph(), sr);
         ds = DatasetFactory.wrap(dsg);
         
-//        // Add marker
-//        ds.getContext().set(DataAccessCtl.symControlledAccess, true);
-//        // Add security registry
-//        ds.getContext().set(DataAccessCtl.symSecurityRegistry, sr);
+        // Add marker
+        // ds.getContext().set(DataAccessCtl.symControlledAccess, true);
+        // Add security registry : if this dadaset is wrapped then this means the AuthorizationService is still accessible.
+        ds.getContext().set(DataAccessCtl.symAuthorizationService, sr);
         return ds;
     }
     

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AuthSetup.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AuthSetup.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AuthSetup.java
new file mode 100644
index 0000000..e94ebff
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/AuthSetup.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/** Struct for the authentication information */
+public class AuthSetup {
+    public final String host;
+    public final int port;
+    public final String user;
+    public final String password;
+    public final String realm;
+    
+    public AuthSetup(String host, int port, String user, String password, String realm) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.realm = realm;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/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 625f282..2da63fe 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
@@ -42,16 +42,17 @@ import org.eclipse.jetty.security.SecurityHandler;
 public class DataAccessCtl {
     static { JenaSystem.init(); }
     
-    /**
-     * Flag for whether this is data access controlled or not - boolean false or undef for "not
-     * controlled". This is an alternative to {@link DatasetGraphAccessControl}.
-     */
-    public static final Symbol   symControlledAccess        = Symbol.create(VocabSecurity.getURI() + "controlled");
+//    /**
+//     * Flag for whether this is data access controlled or not - boolean false or undef for "not
+//     * controlled". This is an alternative to {@link DatasetGraphAccessControl}.
+//     * @see #isAccessControlled(DatasetGraph)
+//     */
+//    public static final Symbol   symControlledAccess        = Symbol.create(VocabSecurity.getURI() + "controlled");
     
     /**
-     * Symbol for the {@link AuthorizationService}. Must be present if
-     * {@link #symControlledAccess} indicates data access control.
+     * Symbol for the {@link AuthorizationService}.
      * This is an alternative to {@link DatasetGraphAccessControl}.
+     * @see #isAccessControlled(DatasetGraph)
      */
     public static final Symbol   symAuthorizationService    = Symbol.create(VocabSecurity.getURI() + "authService");
 
@@ -69,7 +70,7 @@ public class DataAccessCtl {
      * {@link DatasetGraph}'s {@link Context}.
      */
     private static void addAuthorizatonService(DatasetGraph dsg, AuthorizationService authService) {
-        dsg.getContext().set(symControlledAccess, true);
+        //dsg.getContext().set(symControlledAccess, true);
         dsg.getContext().set(symAuthorizationService, authService);
     }
 
@@ -118,6 +119,11 @@ public class DataAccessCtl {
         builder.registerOperation(Operation.Query, WebContent.contentTypeSPARQLQuery, new Filtered_SPARQL_QueryDataset(determineUser));
         builder.registerOperation(Operation.GSP_R, new Filtered_SPARQL_GSP_R(determineUser));
         builder.registerOperation(Operation.Quads_R, new Filtered_REST_Quads_R(determineUser));
+        
+        // Block updates
+        builder.registerOperation(Operation.Update, WebContent.contentTypeSPARQLUpdate, new Filtered_SPARQL_Update(determineUser));
+        builder.registerOperation(Operation.GSP_RW, null);
+        builder.registerOperation(Operation.Quads_RW, null);
         return builder;
     }
 
@@ -127,17 +133,28 @@ public class DataAccessCtl {
      * (It is better to create the server via {@link #DataAccessCtl.builder} first rather than modify afterwards.) 
      */
     public static void modifyForAccessCtl(FusekiServer server, Function<HttpAction, String> determineUser) {
-        /* 
-         * Reconfigure standard Jena Fuseki, replacing the default implementation of "query"
-         * with a filtering one.  This for this server only. 
+        /*
+         * Reconfigure standard Jena Fuseki, replacing the default implementations with
+         * filtering ones. This for this server only, not system-wide.
          */
         // The mapping operation to handler is in the ServiceDispatchRegistry and is per
-        // server (per servlet context). "registerOrReplace" would be a better name,
+        // server (per servlet context). "registerOrReplace" might be a better name,
         ActionService queryServletAccessFilter = new Filtered_SPARQL_QueryDataset(determineUser);
         ServletContext cxt = server.getServletContext();
-        ServiceDispatchRegistry.get(cxt).register(Operation.Query, WebContent.contentTypeSPARQLQuery, queryServletAccessFilter);
-        ServiceDispatchRegistry.get(cxt).register(Operation.GSP_R, null, new Filtered_SPARQL_GSP_R(determineUser));
-        ServiceDispatchRegistry.get(cxt).register(Operation.Quads_R, null, new Filtered_REST_Quads_R(determineUser));
+        ServiceDispatchRegistry reg = ServiceDispatchRegistry.get(cxt);
+        
+        if ( reg.isRegistered(Operation.Query) )
+            reg.register(Operation.Query, WebContent.contentTypeSPARQLQuery, queryServletAccessFilter);
+        if ( reg.isRegistered(Operation.GSP_R) )
+            reg.register(Operation.GSP_R, null, new Filtered_SPARQL_GSP_R(determineUser));
+        if ( reg.isRegistered(Operation.Quads_R) )
+            reg.register(Operation.Quads_R, null, new Filtered_REST_Quads_R(determineUser));
+        
+        // Block updates
+        //reg.register(Operation.Update, WebContent.contentTypeSPARQLUpdate, new Filtered_SPARQL_Update(determineUser));
+        reg.unregister(Operation.Update);
+        reg.unregister(Operation.GSP_RW);
+        reg.unregister(Operation.Quads_RW);
     }
     
     /**
@@ -147,8 +164,8 @@ public class DataAccessCtl {
     public static boolean isAccessControlled(DatasetGraph dsg) {
         if ( dsg instanceof DatasetGraphAccessControl )
             return true;
-        if ( dsg.getContext().isDefined(DataAccessCtl.symControlledAccess) )
-            return true;
+//        if ( dsg.getContext().isDefined(DataAccessCtl.symControlledAccess) )
+//            return true;
         if ( dsg.getContext().isDefined(DataAccessCtl.symAuthorizationService) )
             return true;
         return false;

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
index a518f75..2ccd538 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/DatasetGraphAccessControl.java
@@ -24,14 +24,14 @@ import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.DatasetGraphWrapper;
 
 /** DatasetGraph layer that carries an {@link AuthorizationService}. */ 
-class DatasetGraphAccessControl extends DatasetGraphWrapper {
+public class DatasetGraphAccessControl extends DatasetGraphWrapper {
     
     private AuthorizationService registry = null; 
 
-    public DatasetGraphAccessControl(DatasetGraph dsg, AuthorizationService authService) {
+    /*package*/ DatasetGraphAccessControl(DatasetGraph dsg, AuthorizationService authService) {
         super(Objects.requireNonNull(dsg));
         this.registry = Objects.requireNonNull(authService); 
-    }
+   }
     
     public AuthorizationService getAuthService() {
         return registry;

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java
index 06a5829..950c503 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_QueryDataset.java
@@ -20,15 +20,21 @@ package org.apache.jena.fuseki.access;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.List;
 import java.util.function.Function;
+import static java.util.stream.Collectors.toList;
 
 import org.apache.jena.fuseki.servlets.ActionService;
 import org.apache.jena.fuseki.servlets.HttpAction;
 import org.apache.jena.fuseki.servlets.SPARQL_QueryDataset;
-import org.apache.jena.query.Dataset;
+import org.apache.jena.fuseki.servlets.ServletOps;
 import org.apache.jena.query.Query;
 import org.apache.jena.query.QueryExecution;
+import org.apache.jena.sparql.core.DatasetDescription;
 import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphZero;
+import org.apache.jena.sparql.core.DynamicDatasets;
+import org.apache.jena.sparql.core.DynamicDatasets.DynamicDatasetGraph;
 
 /** A Query {@link ActionService} that inserts a security filter on each query. */
 final
@@ -39,24 +45,73 @@ public class Filtered_SPARQL_QueryDataset extends SPARQL_QueryDataset {
         this.requestUser = requestUser; 
     }
 
+    private static boolean ALLOW_FROM = true; 
+    
     @Override
     protected Collection<String> customParams() {
         // The additional ?user.
         return Collections.singletonList("user");
     }
     
+    /** Decide the dataset - this modifies the query
+     *  If the query has a dataset description.
+     */
+    @Override
+    protected DatasetGraph decideDataset(HttpAction action, Query query, String queryStringLog) {
+        DatasetGraph dsg = action.getActiveDSG() ;
+        DatasetDescription dsDesc = getDatasetDescription(action, query) ;
+        if ( dsDesc == null )
+            return dsg;
+        if ( ! ALLOW_FROM )
+            ServletOps.errorBadRequest("Use GRAPH. (FROM/FROM NAMED is not compatible with data access control.)");
+        
+        DatasetDescription dsDesc0 = getDatasetDescription(action, query);
+        if ( dsDesc0 == null )
+            return dsg;
+        // Filter the DatasetDescription by the SecurityContext
+        SecurityContext sCxt = DataAccessLib.getSecurityContext(action, dsg, requestUser);
+        DatasetDescription dsDesc1 = DatasetDescription.create(
+            mask(dsDesc0.getDefaultGraphURIs(), sCxt),
+            mask(dsDesc0.getNamedGraphURIs(),   sCxt));
+        // dsDesc1 != null.
+        if ( dsDesc1.isEmpty() )
+            return DatasetGraphZero.create();
+        
+        dsg = DynamicDatasets.dynamicDataset(dsDesc1, dsg, false) ;
+        if ( query.hasDatasetDescription() ) {
+            query.getGraphURIs().clear() ;
+            query.getNamedGraphURIs().clear() ;
+        }
+        return dsg ;
+    }
+
+    // Pass only those graphURIs in the security context.
+    private List<String> mask(List<String> graphURIs, SecurityContext sCxt) {
+        Collection<String> names = sCxt.visibleGraphNames();
+        return graphURIs.stream()
+            .filter(gn->names.contains(gn))
+            .collect(toList()) ;
+    }
+
     @Override
-    protected QueryExecution createQueryExecution(HttpAction action, Query query, Dataset dataset) {
-        // Server database, not the possibly dynamically built "dataset"
+    protected QueryExecution createQueryExecution(HttpAction action, Query query, DatasetGraph target) {
+        if ( target instanceof DynamicDatasetGraph ) {
+            // Protocol query/FROM should have been caught by decideDataset
+            // but specialised setups might have DynamicDatasetGraph as the base dataset.
+            if ( ! ALLOW_FROM )
+                ServletOps.errorBadRequest("FROM/FROM NAMED is not compatible with data access control.");
+        }
+        
+        // Database defined for this service, not the possibly dynamically built "dataset"
         DatasetGraph dsg = action.getDataset();
         if ( dsg == null )
-            return super.createQueryExecution(action, query, dataset);
+            return super.createQueryExecution(action, query, target);
         if ( ! DataAccessCtl.isAccessControlled(dsg) )
-            return super.createQueryExecution(action, query, dataset);
+            return super.createQueryExecution(action, query, target);
 
-        SecurityContext sCxt = DataAccessLib.getSecurityContext(action, dataset.asDatasetGraph(), requestUser);
+        SecurityContext sCxt = DataAccessLib.getSecurityContext(action, dsg, requestUser);
         // A QueryExecution for controlled access
-        QueryExecution qExec = sCxt.createQueryExecution(query, dsg);
+        QueryExecution qExec = sCxt.createQueryExecution(query, target);
         return qExec;
     }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_Update.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_Update.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_Update.java
new file mode 100644
index 0000000..9ca0294
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/Filtered_SPARQL_Update.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.io.InputStream;
+import java.util.function.Function;
+
+import org.apache.jena.fuseki.servlets.ActionService;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.SPARQL_Update;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.sparql.core.DatasetGraph;
+
+/** An Update {@link ActionService} that denies SPAQR Update in access controlled datasets. */
+final
+public class Filtered_SPARQL_Update extends SPARQL_Update {
+    private final Function<HttpAction, String> requestUser;
+
+    public Filtered_SPARQL_Update(Function<HttpAction, String> requestUser) {
+        this.requestUser = requestUser; 
+    }
+
+    @Override
+    protected void validate(HttpAction action) {
+        super.validate(action);
+        
+        DatasetGraph dsg = action.getActiveDSG() ;
+        if ( ! DataAccessCtl.isAccessControlled(dsg) )
+            return;
+        
+        ServletOps.errorBadRequest("SPARQL Update no supported");
+    }
+    
+    @Override
+    protected void perform(HttpAction action) {
+        DatasetGraph dsg = action.getActiveDSG() ;
+        if ( ! DataAccessCtl.isAccessControlled(dsg) ) {
+            super.perform(action);
+            return;
+        }
+        
+        ServletOps.errorBadRequest("SPARQL Update no supported");
+    }
+    
+    @Override
+    protected void execute(HttpAction action, InputStream input) {
+        DatasetGraph dsg = action.getActiveDSG() ;
+        if ( ! DataAccessCtl.isAccessControlled(dsg) ) {
+            super.execute(action, input);
+            return;
+        }
+        ServletOps.errorBadRequest("SPARQL Update no supported");
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/LibSec.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/LibSec.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/LibSec.java
new file mode 100644
index 0000000..2d34b11
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/LibSec.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.auth.DigestScheme;
+import org.apache.http.impl.auth.RFC2617Scheme;
+import org.apache.http.impl.client.BasicAuthCache;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.protocol.HttpContext;
+import org.apache.jena.atlas.lib.InternalErrorException;
+import org.apache.jena.fuseki.jetty.AuthMode;
+import org.apache.jena.fuseki.jetty.JettyLib;
+import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.rdfconnection.RDFConnection;
+import org.apache.jena.rdfconnection.RDFConnectionRemote;
+import org.apache.jena.riot.web.HttpOp;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.UserStore;
+
+/** Library for data access security */ 
+public class LibSec {
+    // See also DataAccessLib (package lib)
+    
+    /** Create a {@link FusekiServer} - setup with one dataset. The returned server has not been started. */
+    public static FusekiServer fuseki(int port, String dsName, Dataset ds, AuthorizationService reg, UserStore userStore) {
+        Objects.requireNonNull(dsName); 
+        Objects.requireNonNull(ds); 
+        Objects.requireNonNull(reg);
+        Objects.requireNonNull(userStore);
+        
+        Dataset dsx = DataAccessCtl.controlledDataset(ds, reg);
+        FusekiServer.Builder builder = DataAccessCtl.fusekiBuilder(DataAccessCtl.requestUserServlet)
+            .port(port)
+            .add(dsName, dsx, false);
+        
+        if ( dsName.startsWith("/") )
+            dsName = dsName.substring(1);
+        ConstraintSecurityHandler sh = JettyLib.makeSecurityHandler("Dataset:"+dsName, userStore);
+        JettyLib.addPathConstraint(sh, "/"+dsName);
+        builder.securityHandler(sh);
+        return builder.build();
+    }
+
+    // HttpClientLibSec.
+    
+    // [AuthScheme] default
+    public static AuthMode authMode = AuthMode.DIGEST;
+    
+    public static void withAuth(String urlStr, AuthSetup auth, Consumer<RDFConnection> action) {
+        CredentialsProvider credsProvider = credsProvider(auth);
+        HttpHost target = new HttpHost(auth.host, auth.port, "http");
+        // --- AuthCache : not necessary
+        // Create AuthCache instance - necessary for non-repeatable request entity. (i.e. streaming)
+        
+        // [AuthScheme]
+        AuthCache authCache = new BasicAuthCache();
+        if ( LibSec.authMode == AuthMode.BASIC ) {
+            RFC2617Scheme authScheme = authScheme(auth.realm);
+            // Can force the client to use basic first time by setting authCache.
+            // This does not work for digest because the nonce's will be wrong.
+            authCache.put(target, authScheme);
+        }
+        
+        HttpContext httpContext = httpContext(authCache, credsProvider);
+        HttpClient httpClient = httpClient(auth); 
+        
+        // Needs retryable mods to RDFConnectionRemote??
+        try ( RDFConnection conn = RDFConnectionRemote.create()
+                .destination(urlStr)
+                .httpClient(httpClient)
+                .httpContext(httpContext)
+                .build() ) {
+            action.accept(conn);
+        }
+    }
+
+    /** Create digest auth {@link DigestScheme} */
+    private static RFC2617Scheme authScheme(String realm) {
+        switch (authMode) {
+            case BASIC: return authBasicScheme(realm);
+            case DIGEST : return authDigestScheme(realm);
+            default:
+                throw new InternalErrorException("RFC2617 auth scheme not reocgnized: "+authMode);
+        }
+    }
+    
+    /** Create digest auth {@link DigestScheme} */
+    public static DigestScheme authDigestScheme(String realm) {
+        //Objects.requireNonNull(realm);
+        DigestScheme authScheme = new DigestScheme();
+        authScheme.overrideParamter("realm", realm);
+        authScheme.overrideParamter("nonce", "whatever");
+        return authScheme;
+    }
+
+    /** Create basic auth {@link BasicScheme} */
+    public static BasicScheme authBasicScheme(String realm) {
+        BasicScheme authScheme = new BasicScheme();
+        return authScheme;
+    }
+
+    public static HttpClient httpClient(AuthSetup auth) {
+        // HttpClient with password.
+        CredentialsProvider credsProvider = credsProvider(auth);
+        Credentials credentials = new UsernamePasswordCredentials(auth.user, auth.password);
+        
+        String schemeAuthScope = authMode == AuthMode.BASIC ? "basic" : "digest";  
+        AuthScope authScope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM, schemeAuthScope);
+        //AuthScope authScope = AuthScope.ANY;
+        
+        credsProvider.setCredentials(AuthScope.ANY, credentials);
+        HttpClient client = HttpOp.createPoolingHttpClientBuilder()
+            .setDefaultCredentialsProvider(credsProvider)
+            .build();
+        return client;
+    }
+    
+    public static HttpClient httpClient(String host, int port, String user, String password, String realm) {
+        AuthSetup auth = new AuthSetup(host, port, user, password, realm);
+        return httpClient(auth);
+    }
+
+    public static HttpClientContext httpContext(AuthCache authCache, CredentialsProvider provider) {
+        // Add AuthCache to the execution context
+        HttpClientContext localContext = HttpClientContext.create();
+        return httpContext(localContext, authCache, provider);
+    }
+
+    public static HttpClientContext httpContext(HttpClientContext localContext, AuthCache authCache, CredentialsProvider provider) {
+        // Add AuthCache to the execution context
+        if ( authCache != null )
+            localContext.setAuthCache(authCache);
+        localContext.setCredentialsProvider(provider);
+        return localContext;
+    }
+
+    public static CredentialsProvider credsProvider(AuthSetup auth) {
+        return credsProvider(auth.host, auth.port, auth.user, auth.password);
+    }
+    
+    private static CredentialsProvider credsProvider(String host, int port, String user, String password) {
+        CredentialsProvider credsProvider = new BasicCredentialsProvider();
+        credsProvider.setCredentials(
+            new AuthScope(host, port),
+            new UsernamePasswordCredentials(user, password));
+        return credsProvider;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java
index 2ffaea0..4d55417 100644
--- a/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java
+++ b/jena-fuseki2/jena-fuseki-access/src/main/java/org/apache/jena/fuseki/access/SecurityContext.java
@@ -18,11 +18,12 @@
 
 package org.apache.jena.fuseki.access;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import org.apache.jena.graph.Node;
 import org.apache.jena.query.Query;
@@ -45,14 +46,14 @@ public class SecurityContext {
     public static SecurityContext NONE = new SecurityContext();
     public static SecurityContext DFT_GRAPH = new SecurityContext(true);
 
-    private final Collection<Node> graphNames = ConcurrentHashMap.newKeySet();
+    private final Collection<Node> graphNames = new ArrayList<>();
     private final boolean matchDefaultGraph;
     
-    public SecurityContext() {
+    private SecurityContext() {
         this(false);
     }
 
-    public SecurityContext(boolean matchDefaultGraph) {
+    private SecurityContext(boolean matchDefaultGraph) {
         this.matchDefaultGraph = matchDefaultGraph;
     }
 
@@ -76,6 +77,12 @@ public class SecurityContext {
     public Collection<Node> visibleGraphs() {
         return Collections.unmodifiableCollection(graphNames);
     }
+    public Collection<String> visibleGraphNames() {
+        return graphNames.stream()
+                .filter(Node::isURI)
+                .map(Node::getURI)
+                .collect(Collectors.toList()) ;
+    }
     
     /**
      * Apply a filter suitable for the TDB-backed {@link DatasetGraph}, to the {@link Context} of the
@@ -91,7 +98,10 @@ public class SecurityContext {
     }
     
     public QueryExecution createQueryExecution(Query query, DatasetGraph dsg) {
-        if ( ! ( dsg instanceof DatasetGraphAccessControl ) ) {
+        if ( ! DataAccessCtl.isAccessControlled(dsg) ) {
+//            throw new InternalErrorException("SecurityContext.createQueryExecution called on an unsecured DatasetGraph");
+//            // Internal error?
+            // Already setup or no security context.
             return QueryExecutionFactory.create(query, dsg);
         }
         if ( isAccessControlledTDB(dsg) ) {
@@ -100,8 +110,9 @@ public class SecurityContext {
             return qExec;
         }
         
+        // XXX Does not work on GRAPH ?g {}
         DatasetGraph dsgA = DataAccessCtl.filteredDataset(dsg, this);
-        return QueryExecutionFactory.create(query, dsgA); 
+        return QueryExecutionFactory.create(query, dsgA);
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java
index 715054d..3face49 100644
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TS_SecurityFiltering.java
@@ -31,6 +31,7 @@ import org.junit.runners.Suite;
     , TestSecurityAssemblerBuild.class
     , TestAssemblerSeparate.class
     , TestAssemblerShared.class
+    , TestPasswordAccess.class
 })
 
 public class TS_SecurityFiltering {

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordAccess.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordAccess.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordAccess.java
new file mode 100644
index 0000000..d5506f8
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestPasswordAccess.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.access;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import 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.jetty.JettyLib;
+import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.riot.web.HttpCaptureResponse;
+import org.apache.jena.riot.web.HttpOp;
+import org.apache.jena.riot.web.HttpOp.CaptureInput;
+import org.apache.jena.web.HttpSC;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.UserStore;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/** Tests for password access to a server. */
+public class TestPasswordAccess {
+
+    private static FusekiServer fusekiServer = null;
+    private static int port = WebLib.choosePort();
+    private static String serverURL = "http://localhost:"+port+"/";
+    private static AuthSetup authSetup = new AuthSetup("localhost", port, "user1", "pw1", "TripleStore");
+    
+    @BeforeClass
+    public static void beforeClass() {
+        if ( false )
+            // To watch the HTTP headers
+            LogCtl.enable("org.apache.http.headers");
+        
+        UserStore userStore = JettyLib.makeUserStore(authSetup.user, authSetup.password);
+        ConstraintSecurityHandler sh = JettyLib.makeSecurityHandler(authSetup.realm, userStore);
+        
+        // Secure these areas.
+        JettyLib.addPathConstraint(sh, "/ds");
+        JettyLib.addPathConstraint(sh, "/nowhere");
+        
+        fusekiServer =
+            FusekiServer.create()
+                .port(port)
+                .add("/ds", DatasetFactory.createTxnMem())
+                .add("/open", DatasetFactory.createTxnMem())
+                .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);
+    }
+    
+    @Test public void access_server() {
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL) ) {
+            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_open() {
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"open") ) {
+            assertNotNull(in);
+        }
+    }
+
+    // Should fail.
+    @Test public void access_deny_ds() {
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ds") ) {
+            assertNotNull(in);
+        } 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") ) {
+            assertNotNull(in);
+        } catch (HttpException ex) {
+            if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401 )
+                throw ex;
+        }
+    }
+    
+    @Test public void access_allow_nowhere() {
+        HttpClient hc = LibSec.httpClient(authSetup);
+        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(authSetup);
+        HttpCaptureResponse<TypedInputStream> handler = new CaptureInput();
+        try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ds", null, hc, null) ) {
+            assertNotNull(in);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java
index fb236ab..4ec6bc4 100644
--- a/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java
+++ b/jena-fuseki2/jena-fuseki-access/src/test/java/org/apache/jena/fuseki/access/TestSecurityFilterFuseki.java
@@ -49,8 +49,8 @@ import org.apache.jena.sparql.core.Quad;
 import org.apache.jena.sparql.engine.http.QueryExceptionHTTP;
 import org.apache.jena.tdb.TDBFactory;
 import org.apache.jena.tdb2.DatabaseMgr;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.PropertyUserStore;
-import org.eclipse.jetty.security.SecurityHandler;
 import org.eclipse.jetty.security.UserStore;
 import org.eclipse.jetty.util.security.Credential;
 import org.eclipse.jetty.util.security.Password;
@@ -98,7 +98,8 @@ public class TestSecurityFilterFuseki {
         testdsg3 = DataAccessCtl.controlledDataset(testdsg3, reg);
         
         UserStore userStore = userStore();
-        SecurityHandler sh = JettyLib.makeSecurityHandler("/*", "DatasetRealm", userStore);
+        ConstraintSecurityHandler sh = JettyLib.makeSecurityHandler("*", userStore);
+        JettyLib.addPathConstraint(sh, "/*");
         
         fusekiServer = DataAccessCtl.fusekiBuilder(sh,  DataAccessCtl.requestUserServlet)
             .port(port)

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java
new file mode 100644
index 0000000..2c6e30e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/AuthMode.java
@@ -0,0 +1,21 @@
+/*
+ * 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.jetty;
+
+public enum AuthMode { BASIC, DIGEST }

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/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 efdbd2f..43d84d0 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
@@ -24,6 +24,9 @@ import org.apache.jena.riot.WebContent;
 import org.eclipse.jetty.http.MimeTypes;
 import org.eclipse.jetty.security.*;
 import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.security.authentication.DigestAuthenticator;
+//import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+//import org.eclipse.jetty.security.authentication.DigestAuthenticator;
 import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.HandlerList;
@@ -42,47 +45,82 @@ import org.eclipse.jetty.util.security.Password;
  */
 public class JettyLib {
     
-    /** Create a Jetty {@link SecurityHandler} for basic authentication. */
-    public static SecurityHandler makeSecurityHandler(String pathSpec, String realm, UserStore userStore) {
-        return makeSecurityHandler(pathSpec, realm, userStore, "**");
+    /** Create a Jetty {@link SecurityHandler} for a specific pathSpace, e.g {@code /database}. */
+    public static SecurityHandler makeSecurityHandlerForPathspec(String pathSpec, String realm, UserStore userStore) {
+        ConstraintSecurityHandler sh = makeSecurityHandler(realm, userStore);
+        addPathConstraint(sh, pathSpec);
+        return sh;
     }
     
-    /** Create a Jetty {@link SecurityHandler} for basic authentication. */
-    public static SecurityHandler makeSecurityHandler(String pathSpec, String realm, UserStore userStore, String role) {
+//    /** Create a Jetty {@link SecurityHandler} for basic authentication. */
+//    @Deprecated
+//    public static SecurityHandler makeSecurityHandlerForPathspec(String pathSpec, String realm, UserStore userStore, String role) {
+//        ConstraintSecurityHandler securityHandler = makeSecurityHandler(realm, userStore, role);
+//        // Pathspec based.
+//        addDatasetConstraint(securityHandler, pathSpec);
+//        return securityHandler;
+//    }
+    
+    /** Create a Jetty {@link SecurityHandler} for basic authentication. 
+     * See {@linkplain #addPathConstraint(ConstraintSecurityHandler, String)}
+     * for adding the {@code pathspec} to apply it to.
+     */
+     public static ConstraintSecurityHandler makeSecurityHandler(String realm, UserStore userStore) {
+         return makeSecurityHandler(realm, userStore, "**");
+     }
+
+    /**
+     * Digest requires an extra round trip so it is unfriendly to API
+     * or scripts that stream.
+     */
+     // [AuthScheme] Default
+     public static AuthMode authMode = AuthMode.DIGEST; 
+
+      /** Create a Jetty {@link SecurityHandler} for basic authentication. 
+     * See {@linkplain #addPathConstraint(ConstraintSecurityHandler, String)}
+     * for adding the {@code pathspec} to apply it to.
+     */
+     public static ConstraintSecurityHandler makeSecurityHandler(String realm, UserStore userStore, String role) {
         // role can be "**" for any authenticated user.
-        Objects.requireNonNull(pathSpec);
         Objects.requireNonNull(userStore);
         Objects.requireNonNull(role);
         
-        Constraint constraint = new Constraint();
-        constraint.setName(Constraint.__BASIC_AUTH);
-        String[] roles = new String[]{role};
-        constraint.setRoles(roles);
-        constraint.setAuthenticate(true);
-
-        ConstraintMapping mapping = new ConstraintMapping();
-        mapping.setConstraint(constraint);
-        mapping.setPathSpec(pathSpec);
+        ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
 
         IdentityService identService = new DefaultIdentityService();
-        ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
-        securityHandler.addConstraintMapping(mapping);
         securityHandler.setIdentityService(identService);
-        
+
         // ---- HashLoginService
-        
-        HashLoginService loginService = new HashLoginService("Authentication");
+        HashLoginService loginService = new HashLoginService(realm);
         loginService.setUserStore(userStore);
         loginService.setIdentityService(identService);
         
-        // ----
+        // [AuthScheme]
         securityHandler.setLoginService(loginService);
-        securityHandler.setAuthenticator(new BasicAuthenticator());
+        securityHandler.setAuthenticator( authMode == AuthMode.BASIC ? new BasicAuthenticator() : new DigestAuthenticator() );
         if ( realm != null )
             securityHandler.setRealmName(realm);
-        
         return securityHandler;
     }
+    
+     public static void addPathConstraint(ConstraintSecurityHandler securityHandler, String pathSpec) {
+         addPathConstraint(securityHandler, pathSpec, "**");
+     }
+     
+    public static void addPathConstraint(ConstraintSecurityHandler securityHandler, String pathSpec, String role) {
+        Objects.requireNonNull(securityHandler);
+        Objects.requireNonNull(pathSpec);
+        
+        ConstraintMapping mapping = new ConstraintMapping();
+        Constraint constraint = new Constraint();
+        String[] roles = new String[]{role};
+        constraint.setRoles(roles);
+        constraint.setName(securityHandler.getAuthenticator().getAuthMethod());
+        constraint.setAuthenticate(true);
+        mapping.setConstraint(constraint);
+        mapping.setPathSpec(pathSpec);
+        securityHandler.addConstraintMapping(mapping);
+    }
 
     /**
      * Make a {@link UserStore} from a password file.
@@ -91,7 +129,7 @@ public class JettyLib {
     public static UserStore makeUserStore(String passwordFile) {
         PropertyUserStore propertyUserStore = new PropertyUserStore();
         propertyUserStore.setConfig(passwordFile);
-        propertyUserStore.setHotReload(false);
+        propertyUserStore.setHotReload(true);
         try { propertyUserStore.start(); }
         catch (Exception ex) { throw new RuntimeException("UserStore", ex); }
         return propertyUserStore;

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java
index cb6424e..8692296 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java
@@ -44,6 +44,7 @@ import org.apache.jena.query.* ;
 import org.apache.jena.rdf.model.Model ;
 import org.apache.jena.riot.web.HttpNames ;
 import org.apache.jena.riot.web.HttpOp ;
+import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.Prologue ;
 import org.apache.jena.sparql.engine.EngineLib;
 import org.apache.jena.sparql.resultset.SPARQLResult ;
@@ -274,7 +275,7 @@ public abstract class SPARQL_Query extends SPARQL_Protocol
         // Assumes finished whole thing by end of sendResult.
         try {
             action.beginRead() ;
-            Dataset dataset = decideDataset(action, query, queryStringLog) ;
+            DatasetGraph dataset = decideDataset(action, query, queryStringLog) ;
             try ( QueryExecution qExec = createQueryExecution(action, query, dataset) ; ) {
                 SPARQLResult result = executeQuery(action, qExec, query, queryStringLog) ;
                 // Deals with exceptions itself.
@@ -301,24 +302,13 @@ public abstract class SPARQL_Query extends SPARQL_Protocol
     protected abstract void validateQuery(HttpAction action, Query query) ;
 
     /** Create the {@link QueryExecution} for this operation.
-     * @param query
-     * @param dataset
-     * @return QueryExecution
-     * @deprecated Use {@link #createQueryExecution(HttpAction, Query, Dataset)}
-     */
-    @Deprecated
-    protected QueryExecution createQueryExecution(Query query, Dataset dataset) {
-        return QueryExecutionFactory.create(query, dataset) ;
-    }
-
-    /** Create the {@link QueryExecution} for this operation.
      * @param action
      * @param query
      * @param dataset
      * @return QueryExecution
      */
-    protected QueryExecution createQueryExecution(HttpAction action, Query query, Dataset dataset) {
-        return createQueryExecution(query, dataset);
+    protected QueryExecution createQueryExecution(HttpAction action, Query query, DatasetGraph dataset) {
+        return QueryExecutionFactory.create(query, dataset) ;
     }
 
     /** Perform the {@link QueryExecution} once.
@@ -398,7 +388,7 @@ public abstract class SPARQL_Query extends SPARQL_Protocol
      * @param queryStringLog
      * @return {@link Dataset}
      */
-    protected abstract Dataset decideDataset(HttpAction action, Query query, String queryStringLog) ;
+    protected abstract DatasetGraph decideDataset(HttpAction action, Query query, String queryStringLog) ;
 
     /** Ship the results to the remote caller.
      * @param action

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java
index 325315a..668e563 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java
@@ -18,8 +18,6 @@
 
 package org.apache.jena.fuseki.servlets;
 
-import org.apache.jena.query.Dataset ;
-import org.apache.jena.query.DatasetFactory ;
 import org.apache.jena.query.Query ;
 import org.apache.jena.sparql.core.DatasetDescription ;
 import org.apache.jena.sparql.core.DatasetGraph ;
@@ -44,7 +42,7 @@ public class SPARQL_QueryDataset extends SPARQL_Query
      *  If the query has a dataset description.
      */
     @Override
-    protected Dataset decideDataset(HttpAction action, Query query, String queryStringLog) {
+    protected DatasetGraph decideDataset(HttpAction action, Query query, String queryStringLog) {
         DatasetGraph dsg = action.getActiveDSG() ;
         DatasetDescription dsDesc = getDatasetDescription(action, query) ;
         if ( dsDesc != null ) {
@@ -54,7 +52,6 @@ public class SPARQL_QueryDataset extends SPARQL_Query
                 query.getNamedGraphURIs().clear() ;
             }
         }
-
-        return DatasetFactory.wrap(dsg) ;
+        return dsg ;
     }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java
index d19b85c..c31174e 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java
@@ -33,6 +33,7 @@ import org.apache.jena.rdf.model.Model ;
 import org.apache.jena.rdf.model.ModelFactory ;
 import org.apache.jena.riot.RiotException ;
 import org.apache.jena.sparql.core.DatasetDescription ;
+import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.DatasetGraphZero;
 
 public class SPARQL_QueryGeneral extends SPARQL_Query {
@@ -64,22 +65,20 @@ public class SPARQL_QueryGeneral extends SPARQL_Query {
         return Operation.Query;
     }
 
-    private static Dataset ds = DatasetFactory.wrap(new DatasetGraphZero());
     @Override
-    protected Dataset decideDataset(HttpAction action, Query query, String queryStringLog) {
+    protected DatasetGraph decideDataset(HttpAction action, Query query, String queryStringLog) {
         DatasetDescription datasetDesc = getDatasetDescription(action, query) ;
         if ( datasetDesc == null )
             //ServletOps.errorBadRequest("No dataset description in protocol request or in the query string") ;
-            return ds;
+            return new DatasetGraphZero();
         return datasetFromDescriptionWeb(action, datasetDesc) ;
     }
 
     /**
-     * Construct a Dataset based on a dataset description. Loads graph from the
-     * web.
+     * Construct a Dataset based on a dataset description.
+     * Loads graph from the web.
      */
-
-    protected Dataset datasetFromDescriptionWeb(HttpAction action, DatasetDescription datasetDesc) {
+    protected DatasetGraph datasetFromDescriptionWeb(HttpAction action, DatasetDescription datasetDesc) {
         try {
             if ( datasetDesc == null )
                 return null ;
@@ -140,7 +139,7 @@ public class SPARQL_QueryGeneral extends SPARQL_Query {
                 }
             }
 
-            return dataset ;
+            return dataset.asDatasetGraph() ;
 
         }
         catch (ActionErrorException ex) {

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
index cb327c8..8099249 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
@@ -205,7 +205,7 @@ public class SPARQL_Update extends SPARQL_Protocol
         ServletOps.successPage(action,"Update succeeded") ;
     }
 
-    private void execute(HttpAction action, InputStream input) {
+    protected void execute(HttpAction action, InputStream input) {
         // OPTIONS
         if ( action.request.getMethod().equals(HttpNames.METHOD_OPTIONS) ) {
             // Share with update via SPARQL_Protocol.

http://git-wip-us.apache.org/repos/asf/jena/blob/c6e9d367/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 cd9b9e0..6bacd45 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
@@ -272,7 +272,7 @@ public class FusekiServer {
             return staticFileBase(directory);
         }
 
-        /** Set the location (filing system directory) to serve static file from. */
+        /** Set the location (filing system directory) to serve static files from. */
         public Builder staticFileBase(String directory) {
             requireNonNull(directory, "directory");
             this.staticContentDir = directory;