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:44 UTC

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

JENA-1623: Service authorization by HTTP authentication.


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

Branch: refs/heads/master
Commit: 83858ba1145674ed14331e4c73863f72de75e99f
Parents: 34b89a6
Author: Andy Seaborne <an...@apache.org>
Authored: Fri Nov 2 17:21:19 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Tue Nov 13 15:39:14 2018 +0000

----------------------------------------------------------------------
 .../jena/fuseki/access/TestPasswordAccess.java  | 101 +++++++++++++++++--
 .../fuseki/access/TestSecurityFilterFuseki.java |  19 ++++
 .../jena/fuseki/build/FusekiBuildLib.java       |  28 +++++
 .../apache/jena/fuseki/build/FusekiConfig.java  |  56 ++++++++--
 .../apache/jena/fuseki/server/DataService.java  |   7 ++
 .../jena/fuseki/servlets/ActionService.java     |  10 ++
 6 files changed, 206 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/83858ba1/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
index d5506f8..d81de8c 100644
--- 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
@@ -20,18 +20,25 @@ package org.apache.jena.fuseki.access;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+
+import java.util.Arrays;
 
 import org.apache.http.client.HttpClient;
 import org.apache.jena.atlas.logging.LogCtl;
 import org.apache.jena.atlas.web.HttpException;
 import org.apache.jena.atlas.web.TypedInputStream;
 import org.apache.jena.atlas.web.WebLib;
+import org.apache.jena.fuseki.build.FusekiBuilder;
 import org.apache.jena.fuseki.jetty.JettyLib;
 import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.fuseki.server.DataService;
 import org.apache.jena.query.DatasetFactory;
 import org.apache.jena.riot.web.HttpCaptureResponse;
 import org.apache.jena.riot.web.HttpOp;
 import org.apache.jena.riot.web.HttpOp.CaptureInput;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
 import org.apache.jena.web.HttpSC;
 import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.UserStore;
@@ -40,13 +47,19 @@ import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-/** Tests for password access to a server. */
+/** Tests for password access to a server and to services. */
 public class TestPasswordAccess {
 
     private static FusekiServer fusekiServer = null;
     private static int port = WebLib.choosePort();
     private static String serverURL = "http://localhost:"+port+"/";
-    private static AuthSetup authSetup = new AuthSetup("localhost", port, "user1", "pw1", "TripleStore");
+    private static AuthSetup authSetup1 = new AuthSetup("localhost", port, "user1", "pw1", "TripleStore");
+    private static AuthSetup authSetup2 = new AuthSetup("localhost", port, "user2", "pw2", "TripleStore");
+    // Not in the user store.
+    private static AuthSetup authSetupX = new AuthSetup("localhost", port, "userX", "pwX", "TripleStore");
+    
+    // All users must log in to access any resource. Disables some "open" tests.
+    private static boolean ConstraintServer = true;
     
     @BeforeClass
     public static void beforeClass() {
@@ -54,18 +67,37 @@ public class TestPasswordAccess {
             // 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);
+        // Two authorized users.
+        UserStore userStore = new UserStore();
+        JettyLib.addUser(userStore, authSetup1.user, authSetup1.password);
+        JettyLib.addUser(userStore, authSetup2.user, authSetup2.password);
+        try { userStore.start(); }
+        catch (Exception ex) { throw new RuntimeException("UserStore", ex); }
+        
+        ConstraintSecurityHandler sh = JettyLib.makeSecurityHandler(authSetup1.realm, userStore);
         
         // Secure these areas.
         JettyLib.addPathConstraint(sh, "/ds");
+        // Allow auth control even through there isn't anything there 
         JettyLib.addPathConstraint(sh, "/nowhere");
+
+        JettyLib.addPathConstraint(sh, "/ctl");
+        // Not controlled: JettyLib.addPathConstraint(sh, "/open");
+        
+        // Server wide (breaks "open" tests)
+        if ( ConstraintServer )
+            JettyLib.addPathConstraint(sh, "/*");
+        
+        DataService dSrv = new DataService(DatasetGraphFactory.createTxnMem());
+        FusekiBuilder.populateStdServices(dSrv, false);
+        dSrv.setAllowedUsers(Arrays.asList("user1"));
         
         fusekiServer =
             FusekiServer.create()
                 .port(port)
                 .add("/ds", DatasetFactory.createTxnMem())
                 .add("/open", DatasetFactory.createTxnMem())
+                .add("/ctl", dSrv)
                 .securityHandler(sh)
                 //.staticFileBase(".")
                 .build();
@@ -87,9 +119,13 @@ public class TestPasswordAccess {
         HttpOp.setDefaultHttpClient(hc);
     }
     
+    // Server authentication.
+    
     @Test public void access_server() {
+        assumeFalse(ConstraintServer);
         try( TypedInputStream in = HttpOp.execHttpGet(serverURL) ) {
             assertNotNull(in);
+            fail("Didn't expect to succeed");
         } catch (HttpException ex) {
             // 404 is OK - no static file area. 
             if ( ex.getResponseCode() != HttpSC.NOT_FOUND_404 )
@@ -98,15 +134,32 @@ public class TestPasswordAccess {
     }
 
     @Test public void access_open() {
+        assumeFalse(ConstraintServer);
         try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"open") ) {
             assertNotNull(in);
         }
     }
 
+    @Test public void access_open_user1() {
+        // OK.
+        LibSec.withAuth(serverURL+"open", authSetup1, (conn)->{
+            conn.queryAsk("ASK{}");
+        });
+    }
+
+    @Test public void access_open_userX() {
+        assumeFalse(ConstraintServer);
+        // OK.
+        LibSec.withAuth(serverURL+"open", authSetupX, (conn)->{
+            conn.queryAsk("ASK{}");
+        });
+    }
+
+
     // Should fail.
     @Test public void access_deny_ds() {
         try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"ds") ) {
-            assertNotNull(in);
+            fail("Didn't expect to succeed");
         } catch (HttpException ex) {
             if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401 )
                 throw ex;
@@ -116,7 +169,7 @@ public class TestPasswordAccess {
     // Should be 401, not be 404.
     @Test public void access_deny_nowhere() {
         try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"nowhere") ) {
-            assertNotNull(in);
+            fail("Didn't expect to succeed");
         } catch (HttpException ex) {
             if ( ex.getResponseCode() != HttpSC.UNAUTHORIZED_401 )
                 throw ex;
@@ -124,7 +177,7 @@ public class TestPasswordAccess {
     }
     
     @Test public void access_allow_nowhere() {
-        HttpClient hc = LibSec.httpClient(authSetup);
+        HttpClient hc = LibSec.httpClient(authSetup1);
         HttpCaptureResponse<TypedInputStream> handler = new CaptureInput();
         try( TypedInputStream in = HttpOp.execHttpGet(serverURL+"nowhere", null, hc, null) ) {
             // null for 404.
@@ -136,10 +189,42 @@ public class TestPasswordAccess {
     }
     
     @Test public void access_allow_ds() {
-        HttpClient hc = LibSec.httpClient(authSetup);
+        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/83858ba1/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 4ec6bc4..91c2afb 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
@@ -203,6 +203,25 @@ public class TestSecurityFilterFuseki {
         query401("user0", "not-the-password", queryAll);
     }
 
+    // More queries. DevSecureNG.java // FROM, "GRAPH ?g" and non-graph (also TestSecurityFilterLocal)
+    
+//    query(connx, "SELECT * FROM <http://host/graphname1> { GRAPH ?g {} }");
+//    query(connx, "SELECT * FROM NAMED <http://host/graphname1> { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } }");
+//    query(connx, "SELECT * FROM NAMED <http://host/graphname1> { GRAPH <http://host/graphname1> { ?s ?p ?o } }");
+//    query(connx, "SELECT * { GRAPH ?g {} }");
+//    query(connx, "SELECT * { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } }");
+//    query(connx, "SELECT * FROM <http://host/graphname1> { ?s ?p ?o }");
+//    query(connx, "SELECT * FROM <http://host/graphname1> { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } }");
+//    query(connx, "SELECT * FROM NAMED <http://host/graphname1> { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } }");
+//    query(connx, "SELECT * { GRAPH <http://host/graphname1> { ?s ?p ?o }}");
+//    
+//    query(connx, "SELECT * FROM <http://host/graphname4> { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } }");
+//    query(connx, "SELECT * { GRAPH <http://host/graphname4> { ?s ?p ?o }}");
+//    
+//    query(connx, "SELECT * { GRAPH <"+Quad.unionGraph.getURI()+"> { ?s ?p ?o } }");
+//    query(connx, "SELECT * FROM <"+Quad.unionGraph.getURI()+"> { ?s ?p ?o }");
+
+    
     private Set<Node> gsp(String user, String password, String graphName) {
         Set<Node> results = new HashSet<>();
         try (RDFConnection conn = RDFConnectionFactory.connectPW(baseUrl, user, password)) {

http://git-wip-us.apache.org/repos/asf/jena/blob/83858ba1/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 a964c9d..07f5be6 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
@@ -18,12 +18,18 @@
 
 package org.apache.jena.fuseki.build;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 import org.apache.jena.fuseki.FusekiConfigException;
 import org.apache.jena.query.* ;
 import org.apache.jena.rdf.model.Literal ;
 import org.apache.jena.rdf.model.Model ;
+import org.apache.jena.rdf.model.RDFList;
 import org.apache.jena.rdf.model.RDFNode ;
 import org.apache.jena.rdf.model.Resource ;
+import org.apache.jena.shared.JenaException;
 import org.apache.jena.shared.PrefixMapping ;
 import org.apache.jena.vocabulary.RDFS ;
 
@@ -94,6 +100,28 @@ public class FusekiBuildLib {
         return x ;
     }
 
+    /**
+     * Get all object of a subject/property, whether repeated triples or RDF Lists or a
+     * mixture. If the subject/property isn't present, return null, so a caller can tell
+     * the difference between "not present" and an empty list value.
+     */
+    public static Collection<RDFNode> getAll(Resource svc, String property) {
+        ResultSet rs = FusekiBuildLib.query("SELECT * { ?svc " + property + " ?x}", svc.getModel(), "svc", svc) ;
+        if ( ! rs.hasNext() )
+            return null;
+        List<RDFNode> results = new ArrayList<>();
+        rs.forEachRemaining(qs->{
+            RDFNode n = qs.get("x");
+            try {
+                RDFList list = n.as(RDFList.class);
+                results.addAll(list.asJavaList());
+            } catch (JenaException x) {
+                results.add(n);
+            }
+        });
+        return results ;
+    }
+
     // Node presentation
     public static String nodeLabel(RDFNode n) {
         if ( n == null )

http://git-wip-us.apache.org/repos/asf/jena/blob/83858ba1/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 7bf5ea0..25d4f70 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
@@ -19,6 +19,7 @@
 package org.apache.jena.fuseki.build ;
 
 import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
 import static org.apache.jena.fuseki.server.FusekiVocab.*;
 import static org.apache.jena.riot.RDFLanguages.filenameToLang;
 import static org.apache.jena.riot.RDFParserRegistry.isRegistered;
@@ -31,6 +32,7 @@ import java.nio.file.Files ;
 import java.nio.file.Path ;
 import java.nio.file.Paths ;
 import java.util.ArrayList ;
+import java.util.Collection;
 import java.util.Collections ;
 import java.util.List ;
 
@@ -41,12 +43,23 @@ 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.server.* ;
+import org.apache.jena.fuseki.server.DataAccessPoint;
+import org.apache.jena.fuseki.server.DataService;
+import org.apache.jena.fuseki.server.DataServiceStatus;
+import org.apache.jena.fuseki.server.FusekiVocab;
+import org.apache.jena.fuseki.server.Operation;
+import org.apache.jena.graph.Node;
 import org.apache.jena.query.Dataset ;
 import org.apache.jena.query.QuerySolution ;
 import org.apache.jena.query.ReadWrite ;
 import org.apache.jena.query.ResultSet ;
-import org.apache.jena.rdf.model.* ;
+import org.apache.jena.rdf.model.Literal;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.Statement;
+import org.apache.jena.rdf.model.StmtIterator;
+import org.apache.jena.rdf.model.impl.Util;
 import org.apache.jena.riot.Lang;
 import org.apache.jena.sparql.core.assembler.AssemblerUtils ;
 import org.apache.jena.sparql.util.FmtUtils;
@@ -208,19 +221,49 @@ public class FusekiConfig {
         if ( ! n.isLiteral() )
             throw new FusekiConfigException("Not a literal for access point name: "+FmtUtils.stringForRDFNode(n));
         Literal object = n.asLiteral() ;
-
+        
         if ( object.getDatatype() != null && ! object.getDatatype().equals(XSDDatatype.XSDstring) )
             Fuseki.configLog.error(format("Service name '%s' is not a string", FmtUtils.stringForRDFNode(object)));
+
         String name = object.getLexicalForm() ;
         name = DataAccessPoint.canonical(name) ;
-
-        DataService dataService = buildDataServiceCustom(svc, dsDescMap) ;
+        DataService dataService = buildDataService(svc, dsDescMap) ;
+        Collection<String> allowedUsers = getAllowedUsers(svc);
+        dataService.setAllowedUsers(allowedUsers);
         DataAccessPoint dataAccess = new DataAccessPoint(name, dataService) ;
         return dataAccess ;
     }
+    
+    /** Get the allowed users on some resources.
+     *  
+     * @param resource
+     * @return Collection<String>
+     */
+    public static Collection<String> getAllowedUsers(Resource resource) {
+        Collection<RDFNode> allowedUsers = FusekiBuildLib.getAll(resource, "fu:"+pAllowedUsers.getLocalName());
+        Collection<String> userNames = null;
+        if ( allowedUsers != null ) {
+            // Check all values are simple strings  
+            List<String> bad = allowedUsers.stream()
+                .map(RDFNode::asNode)
+                .filter(rn -> ! Util.isSimpleString(rn))
+                .map(rn->rn.toString())
+                .collect(toList());
+            if ( ! bad.isEmpty() ) {
+                //Fuseki.configLog.error(format("User names must be a simple string: bad = %s", bad));
+                throw new FusekiConfigException(format("User names should be a simple string: bad = %s", bad));
+            }
+            // RDFNodes/literals to strings.
+            userNames = allowedUsers.stream()
+                .map(RDFNode::asNode)
+                .map(Node::getLiteralLexicalForm)
+                .collect(toList());
+        }
+        return userNames;
+    }
 
     /** Build a DatasetRef starting at Resource svc, having the services as described by the descriptions. */
-    private static DataService buildDataServiceCustom(Resource svc, DatasetDescriptionRegistry dsDescMap) {
+    private static DataService buildDataService(Resource svc, DatasetDescriptionRegistry dsDescMap) {
         Resource datasetDesc = ((Resource)FusekiBuildLib.getOne(svc, "fu:dataset")) ;
         Dataset ds = getDataset(datasetDesc, dsDescMap);
  
@@ -258,7 +301,6 @@ public class FusekiConfig {
 //                sDesc.maximumTimeoutOverride = (int)(svc.getProperty(FusekiVocab.pMaximumTimeoutOverride).getObject().asLiteral().getFloat() * 1000) ;
 //            }
 //        }
-
         return dataService ;
     }
     

http://git-wip-us.apache.org/repos/asf/jena/blob/83858ba1/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 af760c1..63f323e 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
@@ -38,6 +38,7 @@ public class DataService {
 
     private ListMultimap<Operation, Endpoint> operations  = ArrayListMultimap.create();
     private Map<String, Endpoint> endpoints               = new HashMap<>();
+    private Collection<String> allowedUsers             = null;
 
     /**
      * Record which {@link DataAccessPoint DataAccessPoints} this {@code DataService} is
@@ -212,5 +213,11 @@ public class DataService {
         } else
             dataset.close();
     }
+
+    public void setAllowedUsers(Collection<String> allowedUsers) { this.allowedUsers= allowedUsers; }
+    
+    /** Returning null implies no access control */
+    public Collection<String> getAllowedUsers() { return allowedUsers; }
+
 }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/83858ba1/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 443c212..4da1e42 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
@@ -57,11 +57,21 @@ public abstract class ActionService extends ActionBase {
         String datasetUri = mapRequestToDataset(action);
         if ( datasetUri != null ) {
             dataAccessPoint = action.getDataAccessPointRegistry().get(datasetUri);
+            
             if ( dataAccessPoint == null ) {
                 ServletOps.errorNotFound("No dataset for URI: " + datasetUri);
                 return;
             }
             dSrv = dataAccessPoint.getDataService();
+            
+            if ( dSrv.getAllowedUsers() != null ) {
+                String user = action.request.getRemoteUser();
+                if ( user == null )
+                    ServletOps.errorForbidden();
+                if ( ! dSrv.getAllowedUsers().contains(user) ) {
+                    ServletOps.errorForbidden();
+                }
+            }
             if ( !dSrv.isAcceptingRequests() ) {
                 ServletOps.error(HttpSC.SERVICE_UNAVAILABLE_503, "Dataset not currently active");
                 return;