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

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

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RefCountingMap.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RefCountingMap.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RefCountingMap.java
deleted file mode 100644
index b960905..0000000
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RefCountingMap.java
+++ /dev/null
@@ -1,185 +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.Collection;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * A key {@literal ->} value 'map' which reference counts entries.  
- * 
- * <p>
- *   The same (key,value) pair can be added to the map several times and then 
- *   removed several times.  A reference count is incremented for each addition
- *   and, provided the count is greater than 0, decremented on removal.
- * <p>
- * 
- * <p>
- *   The pair is removed from the map when a remove decrements the reference count to 0.
- * </p>
- * 
- * <p>
- *   This class is thread safe.
- * </p>
- * 
- * @param <K>
- * @param <T>
- */
-public class RefCountingMap<K, T> {
-	
-	/*
-	 * Uses CountedRef instances which are pairs of an integer and
-	 * and a reference.  These instances are immutable - a new instance
-	 * is created on each increment/decrement operation.  This could
-	 * result in churn in the garbage collector under heavy use.
-	 */
-	protected Map<K, CountedRef<T>> map = new ConcurrentHashMap<>() ;
-	   
-    public RefCountingMap() {}   
-
-    public boolean contains(K key)         { return map.containsKey(key) ; }
-    public Collection<K> keys()            { return map.keySet() ; }
-    public int size()                      { return map.size() ; }
-    public boolean isEmpty()               { return map.isEmpty() ; }
-
-    /** Clear the map of all keys regardless of reference counts. */
-    public void clear()                    { map.clear() ; }
-    
-	public Set<K> keySet()                 { return map.keySet(); }
-	public boolean containsKey(Object key) { return map.containsKey(key); }
-    
-	/**
-	 * Add a key value pair to the map.
-	 * 
-	 * <p>
-	 *   if there is no entry in the map for the key, then a key value pair is added
-	 *   to the map with a reference count of 1.
-	 * </p>
-	 * 
-	 * <p>
-	 *   If there is already an entry in the map for the same key and value,
-	 *   the reference count for that entry is incremented.
-	 * </p>
-	 * 
-	 * <p>
-	 *   if there is an entry in the map for the key, but with a different value,
-	 *   then that entry is replaced with a new entry for the key and value with
-	 *   a reference count of 1.
-	 * </p>
-	 * 
-	 * @param key
-	 * @param value
-	 */
-    public void add(K key, T value) {
-    	// map.compute is atomic
-    	map.compute(key,
-    			(k, v) -> {
-    				int refCount = 1 ;
-    				if (v != null && ( v.getRef().equals(value) ) ) {
-    					refCount = v.getCount() + 1;
-    				}
-    				return new CountedRef<>(value, refCount );
-    			});
-    }
-    
-    /**
-     * Decrement the reference count for a key, and remove the corresponding entry from the map
-     * if the result is 0.
-     * 
-     * <p>
-     *   Do nothing if there is no entry in the map corresponding to the key.
-     * </p>
-     * @param key
-     */
-    public void remove(K key) {
-    	// map.compute is atomic
-    	map.compute(key, 
-    			(k, v) -> {
-    				if (v == null)
-    					return null ;
-    				int refCount = v.getCount() - 1 ;
-    				if ( refCount == 0 )
-    				    return null ;
-    				else
-    				    return new CountedRef<>(v.getRef(), refCount);
-    			});
-    }
-    
-    /**
-     * Remove the entry corresponding to the key from the map completely.
-     * 
-     * <p>
-     *   This method ignores the reference count.
-     * </p>
-     * 
-     * @param key
-     */
-    public void removeAll(K key) {
-    	map.remove(key);
-    }
-
-    /**
-     * Return the reference count for the entry corresponding to a key in the map.
-     * 
-     * <p>
-     *   Returns 0 if there is no entry in the map corresponding to the key.
-     * </p>
-     * @param key
-     * @return the reference count for the entry corresponding to key in the map,
-     *         or 0 if there is no corresponding entry.
-     */
-    public int refCount(K key) { 
-    	CountedRef<T> ref = map.get(key);
-    	if (ref == null) {
-    		return 0 ;
-    	} else {
-    		return ref.getCount();
-    	}
-    }
-
-    /**
-     * Return the value associated with a key in the map.
-     * 
-     * @param key
-     * @return the value associated with the key, or null if there is no such value.
-     */
-	public T get(Object key) {
-		CountedRef<T> ref = map.get(key);
-		if ( ref == null ) return null ;
-		return ref.getRef();
-	}
-
-	/*
-	 * An immutable pair of an integer count and an object reference
-	 */
-    class CountedRef<R> {
-  	    final int refCount;
-	    final R    ref;
-	
-	    CountedRef(R ref, int refCount) {
-	    	this.refCount = refCount;
-	    	this.ref = ref;
-	    }
-	
-	    int getCount()  { return refCount ; }
-	    R   getRef()    { return ref; }
-    }
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/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
new file mode 100644
index 0000000..16a1616
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/RequestAuthorization.java
@@ -0,0 +1,115 @@
+/*
+ * 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/fcbcc78e/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 63f323e..1236e24 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,6 +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.query.TxnType;
 import org.apache.jena.query.text.DatasetGraphText;
 import org.apache.jena.sparql.core.DatasetGraph;
@@ -38,7 +39,7 @@ public class DataService {
 
     private ListMultimap<Operation, Endpoint> operations  = ArrayListMultimap.create();
     private Map<String, Endpoint> endpoints               = new HashMap<>();
-    private Collection<String> allowedUsers             = null;
+    private RequestAuthorization requestAuth              = null;
 
     /**
      * Record which {@link DataAccessPoint DataAccessPoints} this {@code DataService} is
@@ -214,10 +215,10 @@ public class DataService {
             dataset.close();
     }
 
-    public void setAllowedUsers(Collection<String> allowedUsers) { this.allowedUsers= allowedUsers; }
+    public void setAllowedUsers(RequestAuthorization allowedUsers) { this.requestAuth = allowedUsers; }
     
     /** Returning null implies no access control */
-    public Collection<String> getAllowedUsers() { return allowedUsers; }
+    public RequestAuthorization allowedUsers() { return requestAuth; }
 
 }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/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 4da1e42..933faf6 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
@@ -64,13 +64,10 @@ public abstract class ActionService extends ActionBase {
             }
             dSrv = dataAccessPoint.getDataService();
             
-            if ( dSrv.getAllowedUsers() != null ) {
+            if ( dSrv.allowedUsers() != null ) {
                 String user = action.request.getRemoteUser();
-                if ( user == null )
+                if ( ! dSrv.allowedUsers().isAllowed(user) )
                     ServletOps.errorForbidden();
-                if ( ! dSrv.getAllowedUsers().contains(user) ) {
-                    ServletOps.errorForbidden();
-                }
             }
             if ( !dSrv.isAcceptingRequests() ) {
                 ServletOps.error(HttpSC.SERVICE_UNAVAILABLE_503, "Dataset not currently active");

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/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
new file mode 100644
index 0000000..fba3b50
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/AuthFilter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.servlets;
+
+import java.io.IOException;
+import java.util.function.Predicate;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.web.HttpSC;
+import org.eclipse.jetty.security.SecurityHandler;
+
+/**
+ * Servlet filter that applies a predicate to incoming requests and rejects with with 403
+ * "Forbidden" if the predicate returns false, otherwise it passes the request down the
+ * 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
+ * handled and rejected by the {@link SecurityHandler security handler} before they get to
+ * the filter chain.
+ */
+public class AuthFilter implements Filter {
+
+    private final Predicate<String> predicate;
+
+    public AuthFilter(Predicate<String> allowAccess) {
+        predicate = allowAccess;
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filter) throws IOException, ServletException {
+        try {
+            HttpServletRequest httpRequest = (HttpServletRequest)request;
+            HttpServletResponse httpResponse = (HttpServletResponse)response;
+            String user = httpRequest.getRemoteUser();
+            boolean allowed = predicate.test(user);
+            if ( !allowed ) {
+                // No action id allocated this early.
+                // Fuseki.actionLog.info("Response 403: "+httpRequest.getRequestURI());
+                httpResponse.sendError(HttpSC.FORBIDDEN_403);
+                return;
+            }
+            // HTTP only.
+            filter.doFilter(httpRequest, httpResponse);
+        } catch (ClassCastException ex) {
+            Fuseki.actionLog.error(ex.getMessage());
+        }
+    }
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {}
+
+    @Override
+    public void destroy() {}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/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 e13fc04..ad2188e 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
@@ -91,7 +91,9 @@ public class FusekiFilter implements Filter {
                     }
                 } 
             }
-        } catch (Exception ex) {}
+        } catch (Exception ex) {
+            log.info("Filter: expected exception: "+ex.getMessage(),ex);
+        }
         
         if ( LogFilter )
             log.info("Filter: pass to chain") ;

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java
index 36598b7..75f35cb 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiLib.java
@@ -27,17 +27,10 @@ import java.util.stream.Stream;
 import org.apache.jena.fuseki.build.FusekiBuilder;
 import org.apache.jena.fuseki.server.DataAccessPointRegistry;
 import org.apache.jena.fuseki.server.DataService;
-import org.apache.jena.fuseki.system.FusekiNetLib;
 import org.apache.jena.sparql.core.DatasetGraph;
 
 /** Actions on a {@link FusekiServer} */
 public class FusekiLib {
-
-    /** Choose a free port */
-    public static int choosePort() {
-        return FusekiNetLib.choosePort();
-    }
-
     /**
      * Return a collection of the names registered. This collection does not change as the
      * server changes.

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/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 b3906f0..23469b1 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
@@ -21,6 +21,7 @@ package org.apache.jena.fuseki.main;
 import static java.util.Objects.requireNonNull;
 
 import java.util.*;
+import java.util.function.Predicate;
 
 import javax.servlet.Filter;
 import javax.servlet.ServletContext;
@@ -32,6 +33,7 @@ import org.apache.jena.fuseki.FusekiConfigException;
 import org.apache.jena.fuseki.FusekiException;
 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;
@@ -41,12 +43,15 @@ 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.servlets.ActionService;
+import org.apache.jena.fuseki.servlets.AuthFilter;
 import org.apache.jena.fuseki.servlets.FusekiFilter;
 import org.apache.jena.fuseki.servlets.ServiceDispatchRegistry;
 import org.apache.jena.query.Dataset;
 import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Resource;
 import org.apache.jena.sparql.core.DatasetGraph;
 import org.apache.jena.sparql.core.assembler.AssemblerUtils;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
 import org.eclipse.jetty.security.SecurityHandler;
 import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.Server;
@@ -189,6 +194,7 @@ public class FusekiServer {
         private boolean                  verbose            = false;
         private boolean                  withStats          = false;
         private boolean                  withPing           = false;
+        private RequestAuthorization     serverAllowedUsers = null;
         // Other servlets to add.
         private List<Pair<String, HttpServlet>> servlets    = new ArrayList<>();
         private List<Pair<String, Filter>> filters          = new ArrayList<>();
@@ -405,7 +411,10 @@ public class FusekiServer {
             requireNonNull(filename, "filename");
             Model model = AssemblerUtils.readAssemblerFile(filename);
 
-            // Process services, whether via server ja:services or, if absent, by finding by type.
+            Resource server = FusekiConfig.findServer(model);
+            serverAllowedUsers = FusekiBuilder.allowedUsers(server);
+            
+            // Process server and services, whether via server ja:services or, if absent, by finding by type.
             // Side effect - sets global context.
             List<DataAccessPoint> x = FusekiConfig.processServerConfiguration(model, Fuseki.getContext());
             x.forEach(dap->addDataAccessPoint(dap));
@@ -534,15 +543,27 @@ public class FusekiServer {
             context.setDisplayName(Fuseki.servletRequestLogName);
             context.setErrorHandler(new FusekiErrorHandler1());
             context.setContextPath(contextPath);
-            if ( securityHandler != null )
+            if ( securityHandler != null ) {
                 context.setSecurityHandler(securityHandler);
+                if ( serverAllowedUsers != null ) {
+                    ConstraintSecurityHandler csh = (ConstraintSecurityHandler)securityHandler;
+                    JettyLib.addPathConstraint(csh, "/*");
+                }
+            }
             return context;
         }
 
         /** Add servlets and servlet filters, including the {@link FusekiFilter} */ 
         private void servletsAndFilters(ServletContextHandler context) {
             // Fuseki dataset services filter
-            // This goes as the filter at the end of any filter chaining.
+            // First in chain.
+            if ( serverAllowedUsers != null ) {
+                Predicate<String> auth = serverAllowedUsers::isAllowed; 
+                AuthFilter authFilter = new AuthFilter(auth);
+                addFilter(context, "/*", authFilter);
+                //JettyLib.addPathConstraint(null, contextPath);
+            }
+            // Second in chain.
             FusekiFilter ff = new FusekiFilter();
             addFilter(context, "/*", ff);
 
@@ -552,6 +573,7 @@ public class FusekiServer {
                 addServlet(context, "/$/ping", new ActionPing());
 
             servlets.forEach(p->addServlet(context, p.getLeft(), p.getRight()));
+            // Order/place?
             filters.forEach (p-> addFilter(context, p.getLeft(), p.getRight()));
 
             if ( staticContentDir != null ) {

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.java
index 9e57a25..000186f 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.java
@@ -77,6 +77,8 @@ import org.junit.Assert;
  * large test suite may end up quite slow.
  */
 public class FusekiTestAuth {
+    // Old tests 
+    // Predates full auth support in Fuseki.
     private static int currentPort = WebLib.choosePort() ;
     
     public static int port() {
@@ -168,7 +170,7 @@ public class FusekiTestAuth {
     }
 
     /** Assert that an {@code HttpException} ias an authorization failure.
-     * This is normally 403.  401 indicates no retryu with credentials.
+     * This is normally 403.  401 indicates no retry with credentials.
      */
     public static HttpException assertAuthHttpException(HttpException ex) {
         int rc = ex.getResponseCode();

http://git-wip-us.apache.org/repos/asf/jena/blob/fcbcc78e/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestAuth.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestAuth.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestAuth.java
index 939725f..e24893e 100644
--- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestAuth.java
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiTestAuth.java
@@ -37,7 +37,9 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class TestFusekiTestAuth {
-    
+    // Old tests 
+    // Predates full access control support in Fuseki.
+    // Superseded.
     static {
         LogCtl.setLevel(Fuseki.serverLogName, "WARN");
         LogCtl.setLevel(Fuseki.actionLogName, "WARN");