You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by rv...@apache.org on 2013/06/26 01:56:42 UTC

svn commit: r1496676 - in /jena/trunk/jena-fuseki: ./ src/main/java/org/apache/jena/fuseki/ src/main/java/org/apache/jena/fuseki/server/ src/test/java/org/apache/jena/fuseki/

Author: rvesse
Date: Tue Jun 25 23:56:41 2013
New Revision: 1496676

URL: http://svn.apache.org/r1496676
Log:
Add support for a --basic-auth argument in Fuseki that can be used to have basic auth configured using a Jetty realm file in lieu of a full blown Jetty config.  It configures a single role fuseki to which users must belong in order to access any services on the server.  If --jetty-config is used the --basic-auth argument is ignored.

With this functionality in place added unit tests that verify JENA-475

Added:
    jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TestAuth.java
Modified:
    jena/trunk/jena-fuseki/pom.xml
    jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/FusekiCmd.java
    jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java
    jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java
    jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/ServerTest.java
    jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java

Modified: jena/trunk/jena-fuseki/pom.xml
URL: http://svn.apache.org/viewvc/jena/trunk/jena-fuseki/pom.xml?rev=1496676&r1=1496675&r2=1496676&view=diff
==============================================================================
--- jena/trunk/jena-fuseki/pom.xml (original)
+++ jena/trunk/jena-fuseki/pom.xml Tue Jun 25 23:56:41 2013
@@ -191,6 +191,18 @@
       <artifactId>jetty-xml</artifactId>
       <version>${ver.jetty}</version>
     </dependency> 
+    
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-security</artifactId>
+      <version>${ver.jetty}</version>
+    </dependency> 
+    
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-util</artifactId>
+      <version>${ver.jetty}</version>
+    </dependency> 
 
     <dependency>
       <groupId>org.apache.velocity</groupId>

Modified: jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/FusekiCmd.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/FusekiCmd.java?rev=1496676&r1=1496675&r2=1496676&view=diff
==============================================================================
--- jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/FusekiCmd.java (original)
+++ jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/FusekiCmd.java Tue Jun 25 23:56:41 2013
@@ -121,6 +121,7 @@ public class FusekiCmd extends CmdARQ
     private static ArgDecl argJettyConfig   = new ArgDecl(ArgDecl.HasValue, "jetty-config") ;
     private static ArgDecl argGZip          = new ArgDecl(ArgDecl.HasValue, "gzip") ;
     private static ArgDecl argUber          = new ArgDecl(ArgDecl.NoValue,  "uber", "über") ;   // Use the überservlet (experimental)
+    private static ArgDecl argBasicAuth     = new ArgDecl(ArgDecl.HasValue, "basic-auth") ;
     
     private static ArgDecl argGSP           = new ArgDecl(ArgDecl.NoValue,  "gsp") ;    // GSP compliance mode
     
@@ -152,6 +153,7 @@ public class FusekiCmd extends CmdARQ
     private String fusekiConfigFile     = null ;
     private boolean enableCompression   = true ;
     private String jettyConfigFile      = null ;
+    private String authConfigFile       = null ;
     private String homeDir              = null ;
     private String pagesDir             = null ;
     
@@ -177,6 +179,7 @@ public class FusekiCmd extends CmdARQ
         add(argAllowUpdate, "--update",         "Allow updates (via SPARQL Update and SPARQL HTTP Update)") ;
         add(argFusekiConfig, "--config=",       "Use a configuration file to determine the services") ;
         add(argJettyConfig, "--jetty-config=",  "Set up the server (not services) with a Jetty XML file") ;
+        add(argBasicAuth, "--basic-auth",       "Configure basic auth using provided Jetty realm file, ignored if --jetty-config is used") ;
         add(argMgtPort, "--mgtPort=port",       "Enable the management commands on the given port") ; 
         add(argHome, "--home=DIR",              "Root of Fuseki installation (overrides environment variable FUSEKI_HOME)") ; 
         add(argGZip, "--gzip=on|off",           "Enable GZip compression (HTTP Accept-Encoding) if request header set") ;
@@ -344,7 +347,14 @@ public class FusekiCmd extends CmdARQ
         {
             jettyConfigFile = getValue(argJettyConfig) ;
             if ( !FileOps.exists(jettyConfigFile) )
-                throw new CmdException("No such file: : "+jettyConfigFile) ;
+                throw new CmdException("No such file: "+jettyConfigFile) ;
+        }
+        
+        if ( contains(argBasicAuth) )
+        {
+            authConfigFile = getValue(argBasicAuth) ;
+            if ( !FileOps.exists(authConfigFile) )
+                throw new CmdException("No such file: " + authConfigFile) ;
         }
         
         if ( contains(argHome) )
@@ -432,6 +442,7 @@ public class FusekiCmd extends CmdARQ
         serverConfig.pagesPort = port ;
         serverConfig.enableCompression = enableCompression ;
         serverConfig.jettyConfigFile = jettyConfigFile ;
+        serverConfig.authConfigFile = authConfigFile ;
         serverConfig.verboseLogging = ( super.isVerbose() || super.isDebug() ) ;
         
         SPARQLServer server = new SPARQLServer(serverConfig) ;

Modified: jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java?rev=1496676&r1=1496675&r2=1496676&view=diff
==============================================================================
--- jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java (original)
+++ jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java Tue Jun 25 23:56:41 2013
@@ -39,6 +39,12 @@ import org.apache.jena.fuseki.validation
 import org.apache.jena.fuseki.validation.UpdateValidator ;
 import org.apache.jena.riot.WebContent ;
 import org.eclipse.jetty.http.MimeTypes ;
+import org.eclipse.jetty.security.ConstraintMapping;
+import org.eclipse.jetty.security.ConstraintSecurityHandler;
+import org.eclipse.jetty.security.DefaultIdentityService;
+import org.eclipse.jetty.security.HashLoginService;
+import org.eclipse.jetty.security.IdentityService;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
 import org.eclipse.jetty.server.Connector ;
 import org.eclipse.jetty.server.Server ;
 import org.eclipse.jetty.server.nio.BlockingChannelConnector ;
@@ -46,6 +52,7 @@ import org.eclipse.jetty.servlet.Default
 import org.eclipse.jetty.servlet.ServletContextHandler ;
 import org.eclipse.jetty.servlet.ServletHolder ;
 import org.eclipse.jetty.servlets.GzipFilter ;
+import org.eclipse.jetty.util.security.Constraint;
 import org.eclipse.jetty.xml.XmlConfiguration ;
 
 import com.hp.hpl.jena.sparql.mgt.ARQMgt ;
@@ -135,6 +142,36 @@ public class SPARQLServer
         context.setErrorHandler(new FusekiErrorHandler()) ;
         // Increase form size.
         context.getServletContext().getContextHandler().setMaxFormContentSize(10*1000*1000) ;
+        
+        // Wire up authentication if appropriate
+        if (jettyConfig == null && serverConfig.authConfigFile != null) {
+             Constraint constraint = new Constraint();
+             constraint.setName(Constraint.__BASIC_AUTH);
+             constraint.setRoles(new String[] { "fuseki" });
+             constraint.setAuthenticate(true);
+             
+             ConstraintMapping mapping = new ConstraintMapping();
+             mapping.setConstraint(constraint);
+             mapping.setPathSpec("/*");
+             
+             IdentityService identService = new DefaultIdentityService();
+             
+             ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
+             securityHandler.addConstraintMapping(mapping);
+             securityHandler.setIdentityService(identService);
+             
+             HashLoginService loginService = new HashLoginService("Fuseki Authentication", serverConfig.authConfigFile);
+             loginService.setIdentityService(identService);
+             
+             securityHandler.setLoginService(loginService);
+             securityHandler.setAuthenticator(new BasicAuthenticator());
+             
+             context.setSecurityHandler(securityHandler);
+             
+             serverLog.debug("Basic Auth Configuration = " + serverConfig.authConfigFile);
+        }
+        
+        // Wire up context handler to server
         server.setHandler(context);
         
         // Constants. Add RDF types.

Modified: jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java?rev=1496676&r1=1496675&r2=1496676&view=diff
==============================================================================
--- jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java (original)
+++ jena/trunk/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java Tue Jun 25 23:56:41 2013
@@ -44,6 +44,10 @@ public class ServerConfig
     
     /** Enable additional logging */
     public boolean verboseLogging = false ;
+    /**
+     * Authentication config file used to setup Jetty Basic auth, if a Jetty config file was set this is ignored since Jetty config allows much more complex auth methods to be implemented
+     */
+    public String authConfigFile ;
 
 }
 

Modified: jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/ServerTest.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/ServerTest.java?rev=1496676&r1=1496675&r2=1496676&view=diff
==============================================================================
--- jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/ServerTest.java (original)
+++ jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/ServerTest.java Tue Jun 25 23:56:41 2013
@@ -86,7 +86,7 @@ public class ServerTest extends BaseServ
         server = new SPARQLServer(conf) ;
         server.start() ;
     }
-    
+        
     protected static void serverStart() {
         Log.logLevel(Fuseki.serverLog.getName(), org.apache.log4j.Level.WARN, java.util.logging.Level.WARNING) ;
         Log.logLevel(Fuseki.requestLog.getName(), org.apache.log4j.Level.WARN, java.util.logging.Level.WARNING) ;

Modified: jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java?rev=1496676&r1=1496675&r2=1496676&view=diff
==============================================================================
--- jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java (original)
+++ jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java Tue Jun 25 23:56:41 2013
@@ -36,6 +36,7 @@ import org.junit.runners.Suite ;
     , TestDatasetAccessorHTTP.class
     , TestUpdate.class
     , TestQuery.class
+    , TestAuth.class
 })
 public class TS_Fuseki extends BaseServerTest
 {

Added: jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TestAuth.java
URL: http://svn.apache.org/viewvc/jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TestAuth.java?rev=1496676&view=auto
==============================================================================
--- jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TestAuth.java (added)
+++ jena/trunk/jena-fuseki/src/test/java/org/apache/jena/fuseki/TestAuth.java Tue Jun 25 23:56:41 2013
@@ -0,0 +1,186 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import org.apache.jena.atlas.logging.Log;
+import org.apache.jena.atlas.web.HttpException;
+import org.apache.jena.fuseki.server.FusekiConfig;
+import org.apache.jena.fuseki.server.SPARQLServer;
+import org.apache.jena.fuseki.server.ServerConfig;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.hp.hpl.jena.query.QueryExecutionFactory;
+import com.hp.hpl.jena.sparql.core.DatasetGraph;
+import com.hp.hpl.jena.sparql.core.DatasetGraphFactory;
+import com.hp.hpl.jena.sparql.engine.http.QueryEngineHTTP;
+import com.hp.hpl.jena.sparql.engine.http.QueryExceptionHTTP;
+import com.hp.hpl.jena.sparql.modify.UpdateProcessRemoteBase;
+import com.hp.hpl.jena.update.UpdateExecutionFactory;
+import com.hp.hpl.jena.update.UpdateFactory;
+import com.hp.hpl.jena.update.UpdateRequest;
+
+/**
+ * Tests Fuseki operation with authentication enabled
+ * @author rvesse
+ *
+ */
+public class TestAuth extends BaseServerTest {
+    
+    private static File realmFile;
+    private static SPARQLServer server;
+
+    @BeforeClass
+    public static void setup() throws IOException {
+        realmFile = File.createTempFile("realm", ".properties");
+        
+        FileWriter writer = new FileWriter(realmFile);
+        writer.write("allowed: password, fuseki\n");
+        writer.write("forbidden: password, other");
+        writer.close();
+        
+        Log.logLevel(Fuseki.serverLog.getName(), org.apache.log4j.Level.WARN, java.util.logging.Level.WARNING) ;
+        Log.logLevel(Fuseki.requestLog.getName(), org.apache.log4j.Level.WARN, java.util.logging.Level.WARNING) ;
+        Log.logLevel("org.eclipse.jetty", org.apache.log4j.Level.WARN, java.util.logging.Level.WARNING) ;
+
+        DatasetGraph dsg = DatasetGraphFactory.createMem() ;
+        // This must agree with BaseServerTest
+        ServerConfig conf = FusekiConfig.defaultConfiguration(datasetPath, dsg, true) ;
+        conf.port = BaseServerTest.port ;
+        conf.pagesPort = BaseServerTest.port ;
+        conf.authConfigFile = realmFile.getAbsolutePath() ;
+
+        server = new SPARQLServer(conf) ;
+        server.start() ;
+    }
+    
+    @AfterClass
+    public static void teardown() {
+        server.stop();
+        
+        realmFile.delete();
+    }
+    
+    @Test(expected = QueryExceptionHTTP.class)
+    public void query_with_auth_01() {
+        QueryEngineHTTP qe = (QueryEngineHTTP)QueryExecutionFactory.sparqlService(serviceQuery, "ASK { }");
+        // No auth credentials should result in an error
+        qe.execAsk();
+    }
+    
+    @Test(expected = QueryExceptionHTTP.class)
+    public void query_with_auth_02() {
+        QueryEngineHTTP qe = (QueryEngineHTTP)QueryExecutionFactory.sparqlService(serviceQuery, "ASK { }");
+        // Auth credentials for valid user with bad password
+        qe.setBasicAuthentication("allowed", "incorrect".toCharArray());
+        qe.execAsk();
+    }
+    
+    @Test
+    public void query_with_auth_03() {
+        QueryEngineHTTP qe = (QueryEngineHTTP)QueryExecutionFactory.sparqlService(serviceQuery, "ASK { }");
+        // Auth credentials for valid user with correct password
+        qe.setBasicAuthentication("allowed", "password".toCharArray());
+        Assert.assertTrue(qe.execAsk());
+    }
+    
+    @Test(expected = QueryExceptionHTTP.class)
+    public void query_with_auth_04() {
+        QueryEngineHTTP qe = (QueryEngineHTTP)QueryExecutionFactory.sparqlService(serviceQuery, "ASK { }");
+        // Auth credentials for valid user with correct password BUT not in correct role
+        qe.setBasicAuthentication("forbidden", "password".toCharArray());
+        qe.execAsk();
+    }
+    
+    @Test(expected = HttpException.class)
+    public void update_with_auth_01() {
+        UpdateRequest updates = UpdateFactory.create("CREATE SILENT GRAPH <http://graph>");
+        UpdateProcessRemoteBase ue = (UpdateProcessRemoteBase)UpdateExecutionFactory.createRemote(updates, serviceUpdate);
+        // No auth credentials should result in an error
+        ue.execute();
+    }
+    
+    @Test(expected = HttpException.class)
+    public void update_with_auth_02() {
+        UpdateRequest updates = UpdateFactory.create("CREATE SILENT GRAPH <http://graph>");
+        UpdateProcessRemoteBase ue = (UpdateProcessRemoteBase)UpdateExecutionFactory.createRemote(updates, serviceUpdate);
+        // Auth credentials for valid user with bad password
+        ue.setAuthentication("allowed", "incorrect".toCharArray());
+        ue.execute();
+    }
+    
+    @Test
+    public void update_with_auth_03() {
+        UpdateRequest updates = UpdateFactory.create("CREATE SILENT GRAPH <http://graph>");
+        UpdateProcessRemoteBase ue = (UpdateProcessRemoteBase)UpdateExecutionFactory.createRemote(updates, serviceUpdate);
+        // Auth credentials for valid user with correct password
+        ue.setAuthentication("allowed", "password".toCharArray());
+        ue.execute();
+    }
+    
+    @Test(expected = HttpException.class)
+    public void update_with_auth_04() {
+        UpdateRequest updates = UpdateFactory.create("CREATE SILENT GRAPH <http://graph>");
+        UpdateProcessRemoteBase ue = (UpdateProcessRemoteBase)UpdateExecutionFactory.createRemote(updates, serviceUpdate);
+        // Auth credentials for valid user with correct password BUT not in correct role
+        ue.setAuthentication("forbidden", "password".toCharArray());
+        ue.execute();
+    }
+    
+    @Test(expected = HttpException.class)
+    public void update_with_auth_05() {
+        UpdateRequest updates = UpdateFactory.create("CREATE SILENT GRAPH <http://graph>");
+        UpdateProcessRemoteBase ue = (UpdateProcessRemoteBase)UpdateExecutionFactory.createRemoteForm(updates, serviceUpdate);
+        // No auth credentials should result in an error
+        ue.execute();
+    }
+    
+    @Test(expected = HttpException.class)
+    public void update_with_auth_06() {
+        UpdateRequest updates = UpdateFactory.create("CREATE SILENT GRAPH <http://graph>");
+        UpdateProcessRemoteBase ue = (UpdateProcessRemoteBase)UpdateExecutionFactory.createRemoteForm(updates, serviceUpdate);
+        // Auth credentials for valid user with bad password
+        ue.setAuthentication("allowed", "incorrect".toCharArray());
+        ue.execute();
+    }
+    
+    @Test
+    public void update_with_auth_07() {
+        UpdateRequest updates = UpdateFactory.create("CREATE SILENT GRAPH <http://graph>");
+        UpdateProcessRemoteBase ue = (UpdateProcessRemoteBase)UpdateExecutionFactory.createRemoteForm(updates, serviceUpdate);
+        // Auth credentials for valid user with correct password
+        ue.setAuthentication("allowed", "password".toCharArray());
+        ue.execute();
+    }
+    
+    @Test(expected = HttpException.class)
+    public void update_with_auth_08() {
+        UpdateRequest updates = UpdateFactory.create("CREATE SILENT GRAPH <http://graph>");
+        UpdateProcessRemoteBase ue = (UpdateProcessRemoteBase)UpdateExecutionFactory.createRemoteForm(updates, serviceUpdate);
+        // Auth credentials for valid user with correct password BUT not in correct role
+        ue.setAuthentication("forbidden", "password".toCharArray());
+        ue.execute();
+    }
+}