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:38 UTC
[12/34] jena git commit: JENA-1627: Native https support
JENA-1627: Native https support
Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/2f948641
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/2f948641
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/2f948641
Branch: refs/heads/master
Commit: 2f9486416061986ed58004cb9d75dfd215830c72
Parents: 0e9c55a
Author: Andy Seaborne <an...@apache.org>
Authored: Fri Nov 9 20:46:15 2018 +0000
Committer: Andy Seaborne <an...@apache.org>
Committed: Tue Nov 13 15:39:14 2018 +0000
----------------------------------------------------------------------
.../org/apache/jena/fuseki/jetty/AuthMode.java | 15 +-
.../apache/jena/fuseki/jetty/JettyHttps.java | 125 ++++++++++++
.../org/apache/jena/fuseki/jetty/JettyLib.java | 20 +-
.../apache/jena/fuseki/server/FusekiVocab.java | 6 +-
.../apache/jena/fuseki/main/FusekiServer.java | 192 ++++++++++++++++---
.../jena/fuseki/main/cmds/FusekiMain.java | 86 +++++++--
.../jena/fuseki/main/cmds/FusekiMainCmd.java | 7 +-
.../jena/fuseki/main/cmds/ServerConfig.java | 11 +-
8 files changed, 405 insertions(+), 57 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/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
index 2c6e30e..47db5b2 100644
--- 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
@@ -18,4 +18,17 @@
package org.apache.jena.fuseki.jetty;
-public enum AuthMode { BASIC, DIGEST }
+/** Authorization scheme */
+public enum AuthMode { BASIC, DIGEST ;
+ public static AuthMode scheme(String name) {
+ if ( name == null )
+ return null;
+ name = name.toLowerCase();
+ switch(name) {
+ case "basic": return BASIC;
+ case "digest": return DIGEST;
+ default:
+ throw new IllegalArgumentException("no recognized as an authorization scheme: "+name);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
new file mode 100644
index 0000000..5ca3a91
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyHttps.java
@@ -0,0 +1,125 @@
+/*
+ * 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;
+
+import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.*;
+import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+/** Library of functions to help with setting Jetty up with HTTPS.
+ * This code is not supposed to be fully general.
+ * It sets up "http" to redirect to "https".
+ */
+public class JettyHttps {
+
+ /*
+ * Useful documentation:
+ * http://www.eclipse.org/jetty/documentation/current/configuring-ssl.html
+ * https://medium.com/vividcode/enable-https-support-with-self-signed-certificate-for-embedded-jetty-9-d3a86f83e9d9
+ *
+ * Generate a self-signed certificate
+ * keytool -keystore mykey.jks -alias mykey -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -genkey -validity 3650
+ *
+ * Access with curl
+ * curl -v -k https://localhost:8443/
+ * curl -v -k -L http://localhost:8080/
+ */
+
+ /**
+ * Create a HTTPS Jetty server for the {@link ServletContextHandler}
+ * <p>
+ * If httpPort is -1, don't add http otherwise make http redirect to https.
+ */
+ public static Server jettyServerHttps(ServletContextHandler handler, String keystore, String certPassword, int httpPort, int httpsPort) {
+ // Server handling http and https.
+ Server jettyServer = server(keystore, certPassword, httpPort, httpsPort);
+ if ( httpPort > 0 ) {
+ // Redirect http to https.
+ // Order matters. Check https and bounce if http as first choice.
+ SecuredRedirectHandler srh = new SecuredRedirectHandler();
+ JettyLib.addHandler(jettyServer, srh);
+ }
+ JettyLib.addHandler(jettyServer, handler);
+ return jettyServer;
+ }
+
+ /** Build the server - http and https connectors.
+ * If httpPort is -1, don't add http.
+ */
+ private static Server server(String keystore, String certPassword, int httpPort, int httpsPort) {
+ Server server = new Server();
+ if ( httpPort > 0 ) {
+ ServerConnector plainConnector = httpConnector(server, httpPort, httpsPort);
+ server.addConnector(plainConnector);
+ }
+ ServerConnector httpsConnector = httpsConnector(server, httpsPort, keystore, certPassword);
+ server.addConnector(httpsConnector);
+ return server;
+ }
+
+ /** Add HTTP to a {@link Server}, setting the secure redirection port. */
+ private static ServerConnector httpConnector(Server server, int httpPort, int httpsPort) {
+ HttpConfiguration http_config = httpConfiguration();
+ http_config.setSendServerVersion(false);
+ if ( httpPort > 0 ) {
+ http_config.setSecureScheme(HttpScheme.HTTPS.asString());
+ http_config.setSecurePort(httpsPort);
+ }
+ ServerConnector plainConnector = new ServerConnector(server, new HttpConnectionFactory(http_config));
+ plainConnector.setPort(httpPort);
+ return plainConnector;
+ }
+
+ /** Add HTTPS to a {@link Server}. */
+ private static ServerConnector httpsConnector(Server server, int httpsPort, String keystore, String certPassword) {
+ SslContextFactory sslContextFactory = new SslContextFactory();
+ sslContextFactory.setKeyStorePath(keystore);
+ sslContextFactory.setKeyStorePassword(certPassword);
+
+ SecureRequestCustomizer src = new SecureRequestCustomizer();
+ src.setStsMaxAge(2000);
+ src.setStsIncludeSubDomains(true);
+
+ HttpConfiguration https_config = httpConfiguration();
+ https_config.setSecureScheme(HttpScheme.HTTPS.asString());
+ https_config.setSecurePort(httpsPort);
+ https_config.addCustomizer(src);
+
+ // HTTPS Connector
+ ServerConnector sslConnector = new ServerConnector(server,
+ new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
+ new HttpConnectionFactory(https_config));
+ sslConnector.setPort(httpsPort);
+ return sslConnector;
+ }
+
+ /** HTTP configuration with setting for Fuseki workload. No "secure" settings. */
+ private static HttpConfiguration httpConfiguration() {
+ HttpConfiguration http_config = new HttpConfiguration();
+ // Some people do try very large operations ... really, should use POST.
+ http_config.setRequestHeaderSize(512 * 1024);
+ http_config.setOutputBufferSize(1024 * 1024);
+// http_config.setResponseHeaderSize(8192);
+ http_config.setSendServerVersion(false);
+ return http_config;
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/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 616fbb7..30d061a 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
@@ -25,8 +25,6 @@ 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;
@@ -66,21 +64,28 @@ public class JettyLib {
* for adding the {@code pathspec} to apply it to.
*/
public static ConstraintSecurityHandler makeSecurityHandler(String realm, UserStore userStore) {
- return makeSecurityHandler(realm, userStore, "**");
+ return makeSecurityHandler(realm, userStore, "**", dftAuthMode);
}
- /**
+ /** 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, AuthMode authMode) {
+ return makeSecurityHandler(realm, userStore, "**", authMode);
+ }
+
+ /**
* Digest requires an extra round trip so it is unfriendly to API
* or scripts that stream.
*/
- // [AuthScheme] Default
- public static AuthMode authMode = AuthMode.DIGEST;
+ public static AuthMode dftAuthMode = 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) {
+ public static ConstraintSecurityHandler makeSecurityHandler(String realm, UserStore userStore, String role, AuthMode authMode) {
// role can be "**" for any authenticated user.
Objects.requireNonNull(userStore);
Objects.requireNonNull(role);
@@ -95,7 +100,6 @@ public class JettyLib {
loginService.setUserStore(userStore);
loginService.setIdentityService(identService);
- // [AuthScheme]
securityHandler.setLoginService(loginService);
securityHandler.setAuthenticator( authMode == AuthMode.BASIC ? new BasicAuthenticator() : new DigestAuthenticator() );
if ( realm != null )
http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
index f6ebca3..371d317 100644
--- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java
@@ -34,8 +34,12 @@ public class FusekiVocab
public static final Property pServices = property("services");
public static final Property pServiceName = property("name");
- public static final Property pAllowedUsers = property("allowedUsers");
+ public static final Property pAllowedUsers = property("allowedUsers");
+ public static final Property pPasswordFile = property("passwd");
+ public static final Property pRealm = property("realm");
+ public static final Property pAuth = property("auth");
+
public static final Property pServiceQueryEP = property("serviceQuery");
public static final Property pServiceUpdateEP = property("serviceUpdate");
public static final Property pServiceUploadEP = property("serviceUpload");
http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/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 23469b1..30819eb 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
@@ -20,6 +20,7 @@ package org.apache.jena.fuseki.main;
import static java.util.Objects.requireNonNull;
+import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
@@ -36,12 +37,11 @@ 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.AuthMode;
import org.apache.jena.fuseki.jetty.FusekiErrorHandler1;
+import org.apache.jena.fuseki.jetty.JettyHttps;
import org.apache.jena.fuseki.jetty.JettyLib;
-import org.apache.jena.fuseki.server.DataAccessPoint;
-import org.apache.jena.fuseki.server.DataAccessPointRegistry;
-import org.apache.jena.fuseki.server.DataService;
-import org.apache.jena.fuseki.server.Operation;
+import org.apache.jena.fuseki.server.*;
import org.apache.jena.fuseki.servlets.ActionService;
import org.apache.jena.fuseki.servlets.AuthFilter;
import org.apache.jena.fuseki.servlets.FusekiFilter;
@@ -51,8 +51,11 @@ 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.apache.jena.sparql.util.graph.GraphUtils;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.security.UserStore;
+import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
@@ -115,22 +118,31 @@ public class FusekiServer {
}
public final Server server;
- private int port;
+ private final int httpPort;
+ private final int httpsPort;
+ private final ServletContext servletContext;
+
+ private FusekiServer(int httpPort, Server server) {
+ this(httpPort, -1, server,
+ ((ServletContextHandler)server.getHandler()).getServletContext()
+ );
+ }
- private FusekiServer(int port, Server server) {
+ private FusekiServer(int httpPort, int httpsPort, Server server, ServletContext fusekiServletContext) {
this.server = server;
- // This should be the same.
- //this.port = ((ServerConnector)server.getConnectors()[0]).getPort();
- this.port = port;
+ this.httpPort = httpPort;
+ this.httpsPort = httpsPort;
+ this.servletContext = fusekiServletContext;
}
/**
- * Return the port begin used.
+ * Return the port being used.
* This will be the give port, which defaults to 3330, or
* the one actually allocated if the port was 0 ("choose a free port").
+ * If https in use, this is the HTTPS port.
*/
public int getPort() {
- return port;
+ return httpsPort > 0 ? httpsPort : httpPort;
}
/** Get the underlying Jetty server which has also been set up. */
@@ -138,11 +150,11 @@ public class FusekiServer {
return server;
}
- /** Get the {@link ServletContext}.
+ /** Get the {@link ServletContext} used for Fuseki processing.
* Adding new servlets is possible with care.
*/
public ServletContext getServletContext() {
- return ((ServletContextHandler)server.getHandler()).getServletContext();
+ return servletContext;
}
/** Get the {@link DataAccessPointRegistry}.
@@ -164,16 +176,29 @@ public class FusekiServer {
*/
public FusekiServer start() {
try { server.start(); }
- catch (Exception e) { throw new FusekiException(e); }
- if ( port == 0 )
- port = ((ServerConnector)server.getConnectors()[0]).getLocalPort();
- Fuseki.serverLog.info("Start Fuseki (port="+port+")");
+
+ catch (IOException ex) {
+ if ( ex.getCause() instanceof java.security.UnrecoverableKeyException )
+ // Unbundle for clearer message.
+ throw new FusekiException(ex.getMessage());
+ throw new FusekiException(ex);
+ }
+ catch (IllegalStateException ex) {
+ throw new FusekiException(ex.getMessage());
+ }
+ catch (Exception ex) {
+ throw new FusekiException(ex);
+ }
+ if ( httpsPort > 0 )
+ Fuseki.serverLog.info("Start Fuseki (port="+httpPort+"/"+httpsPort+")");
+ else
+ Fuseki.serverLog.info("Start Fuseki (port="+getPort()+")");
return this;
}
/** Stop the server. */
public void stop() {
- Fuseki.serverLog.info("Stop Fuseki (port="+port+")");
+ Fuseki.serverLog.info("Stop Fuseki (port="+getPort()+")");
try { server.stop(); }
catch (Exception e) { throw new FusekiException(e); }
}
@@ -190,11 +215,17 @@ public class FusekiServer {
private final ServiceDispatchRegistry serviceDispatch;
// Default values.
private int serverPort = 3330;
+ private int serverHttpsPort = -1;
private boolean networkLoopback = false;
private boolean verbose = false;
private boolean withStats = false;
private boolean withPing = false;
private RequestAuthorization serverAllowedUsers = null;
+ private String passwordFile = null;
+ private String realm = null;
+ private AuthMode authMode = null;
+ private String httpsKeystore = null;
+ private String httpsKeystorePasswd = null;
// Other servlets to add.
private List<Pair<String, HttpServlet>> servlets = new ArrayList<>();
private List<Pair<String, Filter>> filters = new ArrayList<>();
@@ -412,7 +443,7 @@ public class FusekiServer {
Model model = AssemblerUtils.readAssemblerFile(filename);
Resource server = FusekiConfig.findServer(model);
- serverAllowedUsers = FusekiBuilder.allowedUsers(server);
+ processServerLevel(server);
// Process server and services, whether via server ja:services or, if absent, by finding by type.
// Side effect - sets global context.
@@ -422,6 +453,58 @@ public class FusekiServer {
}
/**
+ * Server level setting specific to Fuseki main.
+ * General settings done by {@link FusekiConfig#processServerConfiguration}.
+ */
+ private void processServerLevel(Resource server) {
+ if ( server == null )
+ return;
+ // Extract settings - the server building is done in buildSecurityHandler,
+ // buildAccessControl. Dataset and graph level happen in assemblers.
+ passwordFile = GraphUtils.getAsStringValue(server, FusekiVocab.pPasswordFile);
+ if ( passwordFile != null )
+ passwordFile(passwordFile);
+ realm = GraphUtils.getAsStringValue(server, FusekiVocab.pRealm);
+ if ( realm == null )
+ realm = "TripleStore";
+ realm(realm);
+
+ String authStr = GraphUtils.getAsStringValue(server, FusekiVocab.pAuth);
+ authMode = AuthMode.scheme(authStr);
+
+ serverAllowedUsers = FusekiBuilder.allowedUsers(server);
+ }
+
+ /**
+ * Set the realm used for HTTP digest authentication.
+ * The default is "TripleStore".
+ */
+ public Builder realm(String realm) {
+ this.realm = realm;
+ return this;
+ }
+
+ /**
+ * Set the password file. This will be used to build a {@link #securityHandler
+ * security handler} if one is not supplied. Setting null clears any previous entry.
+ * The file should be
+ * <a href="https://www.eclipse.org/jetty/documentation/current/configuring-security.html#hash-login-service">Eclipse jetty password file</a>.
+ */
+ public Builder passwordFile(String passwordFile) {
+ this.passwordFile = passwordFile;
+ return this;
+ }
+
+ public Builder https(int httpsPort, String certStore, String certStorePasswd) {
+ requireNonNull(certStore, "certStore");
+ requireNonNull(certStorePasswd, "certStorePasswd");
+ this.httpsKeystore = certStore;
+ this.httpsKeystorePasswd = certStorePasswd;
+ this.serverHttpsPort = httpsPort;
+ return this;
+ }
+
+ /**
* Add the given servlet with the {@code pathSpec}. These servlets are added so
* that they are checked after the Fuseki filter for datasets and before the
* static content handler (which is the last servlet) used for
@@ -510,29 +593,68 @@ public class FusekiServer {
* Build a server according to the current description.
*/
public FusekiServer build() {
+ if ( securityHandler == null && passwordFile != null )
+ securityHandler = buildSecurityHandler();
ServletContextHandler handler = buildFusekiContext();
- // Use HandlerCollection for several ServletContextHandlers and thus several ServletContext.
- Server server = jettyServer(serverPort, networkLoopback);
- server.setHandler(handler);
- return new FusekiServer(serverPort, server);
+
+ Server server;
+ if ( serverHttpsPort == -1 ) {
+ // HTTP
+ server = jettyServer(handler, serverPort);
+ } else {
+ // HTTPS, no http redirection.
+ server = jettyServerHttps(handler, serverPort, serverHttpsPort, httpsKeystore, httpsKeystorePasswd);
+ }
+ if ( networkLoopback )
+ applyLocalhost(server);
+ return new FusekiServer(serverPort, serverHttpsPort, server, handler.getServletContext());
}
- /** Build one configured Fuseki in one unit - same ServletContext, same dispatch ContextPath */
+ /** Build one configured Fuseki processor (ServletContext), same dispatch ContextPath */
private ServletContextHandler buildFusekiContext() {
ServletContextHandler handler = buildServletContext(contextPath);
ServletContext cxt = handler.getServletContext();
Fuseki.setVerbose(cxt, verbose);
servletAttr.forEach((n,v)->cxt.setAttribute(n, v));
- // Clone to isolate from any future changes.
+
+ // Clone to isolate from any future changes (reusing the builder).
ServiceDispatchRegistry.set(cxt, new ServiceDispatchRegistry(serviceDispatch));
DataAccessPointRegistry.set(cxt, new DataAccessPointRegistry(dataAccessPoints));
JettyLib.setMimeTypes(handler);
servletsAndFilters(handler);
+ buildAccessControl(cxt);
+
// Start services.
DataAccessPointRegistry.get(cxt).forEach((name, dap)->dap.getDataService().goActive());
return handler;
}
+ private ConstraintSecurityHandler buildSecurityHandler() {
+ if ( passwordFile == null )
+ return null;
+ UserStore userStore = JettyLib.makeUserStore(passwordFile);
+ return JettyLib.makeSecurityHandler(realm, userStore, authMode);
+ }
+
+ private void buildAccessControl(ServletContext cxt) {
+ // -- Access control
+ if ( securityHandler != null && securityHandler instanceof ConstraintSecurityHandler ) {
+ ConstraintSecurityHandler csh = (ConstraintSecurityHandler)securityHandler;
+ if ( serverAllowedUsers != null )
+ JettyLib.addPathConstraint(csh, "/*");
+ else {
+ DataAccessPointRegistry.get(cxt).forEach((name, dap)-> {
+ DatasetGraph dsg = dap.getDataService().getDataset();
+ if ( dap.getDataService().allowedUsers() != null ) {
+ // Not need if whole server ACL'ed.
+ JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name));
+ JettyLib.addPathConstraint(csh, DataAccessPoint.canonical(name)+"/*");
+ }
+ });
+ }
+ }
+ }
+
/** Build a ServletContextHandler */
private ServletContextHandler buildServletContext(String contextPath) {
if ( contextPath == null || contextPath.isEmpty() )
@@ -595,7 +717,7 @@ public class FusekiServer {
}
/** Jetty server with one connector/port. */
- private static Server jettyServer(int port, boolean loopback) {
+ private static Server jettyServer(ServletContextHandler handler, int port) {
Server server = new Server();
HttpConnectionFactory f1 = new HttpConnectionFactory();
// Some people do try very large operations ... really, should use POST.
@@ -607,9 +729,23 @@ public class FusekiServer {
ServerConnector connector = new ServerConnector(server, f1);
connector.setPort(port);
server.addConnector(connector);
- if ( loopback )
- connector.setHost("localhost");
+ server.setHandler(handler);
return server;
}
+
+ /** Jetty server with https. */
+ private static Server jettyServerHttps(ServletContextHandler handler, int httpPort, int httpsPort, String keystore, String certPassword) {
+ return JettyHttps.jettyServerHttps(handler, keystore, certPassword, httpPort, httpsPort);
+ }
+
+ /** Restrict connectors to localhost */
+ private static void applyLocalhost(Server server) {
+ Connector[] connectors = server.getConnectors();
+ for ( int i = 0 ; i < connectors.length ; i++ ) {
+ if ( connectors[i] instanceof ServerConnector ) {
+ ((ServerConnector)connectors[i]).setHost("localhost");
+ }
+ }
+ }
}
}
http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
index 09edf8b..8fc596e 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
@@ -33,6 +33,8 @@ import arq.cmdline.ModDatasetAssembler;
import jena.cmd.ArgDecl;
import jena.cmd.CmdException;
import org.apache.jena.assembler.exceptions.AssemblerException;
+import org.apache.jena.atlas.json.JSON;
+import org.apache.jena.atlas.json.JsonObject;
import org.apache.jena.atlas.lib.DateTimeUtils;
import org.apache.jena.atlas.lib.FileOps;
import org.apache.jena.atlas.logging.FmtLog;
@@ -55,14 +57,14 @@ import org.apache.jena.riot.RDFLanguages;
import org.apache.jena.sparql.core.DatasetGraphFactory;
import org.apache.jena.sys.JenaSystem;
import org.apache.jena.system.Txn;
-import org.apache.jena.tdb.TDB;
import org.apache.jena.tdb.TDBFactory;
import org.apache.jena.tdb.transaction.TransactionManager;
import org.apache.jena.tdb2.DatabaseMgr;
import org.slf4j.Logger;
public class FusekiMain extends CmdARQ {
- private static int defaultPort = 3030;
+ private static int defaultPort = 3030;
+ private static int defaultHttpsPort = 3043;
private static ArgDecl argMem = new ArgDecl(ArgDecl.NoValue, "mem");
private static ArgDecl argUpdate = new ArgDecl(ArgDecl.NoValue, "update", "allowUpdate");
@@ -74,13 +76,21 @@ public class FusekiMain extends CmdARQ {
// No SPARQL dataset or services
private static ArgDecl argEmpty = new ArgDecl(ArgDecl.NoValue, "empty", "no-dataset");
+ private static ArgDecl argGeneralQuerySvc = new ArgDecl(ArgDecl.HasValue, "general");
+
private static ArgDecl argPort = new ArgDecl(ArgDecl.HasValue, "port");
private static ArgDecl argLocalhost = new ArgDecl(ArgDecl.NoValue, "localhost", "local");
private static ArgDecl argTimeout = new ArgDecl(ArgDecl.HasValue, "timeout");
private static ArgDecl argConfig = new ArgDecl(ArgDecl.HasValue, "config", "conf");
private static ArgDecl argGZip = new ArgDecl(ArgDecl.HasValue, "gzip");
private static ArgDecl argBase = new ArgDecl(ArgDecl.HasValue, "base", "files");
- private static ArgDecl argGeneralQuerySvc = new ArgDecl(ArgDecl.HasValue, "general");
+
+ private static ArgDecl argHttps = new ArgDecl(ArgDecl.HasValue, "https");
+ private static ArgDecl argHttpsPort = new ArgDecl(ArgDecl.HasValue, "httpsPort", "httpsport", "sport");
+
+ private static ArgDecl argPasswdFile = new ArgDecl(ArgDecl.HasValue, "passwd");
+ private static ArgDecl argRealm = new ArgDecl(ArgDecl.HasValue, "realm");
+
// Same as --empty --validators --general=/sparql, --files=ARG
@@ -100,7 +110,7 @@ public class FusekiMain extends CmdARQ {
return inner.buildServer();
}
- static void innerMain(String... argv) {
+ static void run(String... argv) {
JenaSystem.init();
new FusekiMain(argv).mainRun();
}
@@ -144,8 +154,14 @@ public class FusekiMain extends CmdARQ {
add(argSparqler, "--sparqler=DIR",
"Run with SPARQLer services Directory for static content");
add(argValidators, "--validators", "Install validators");
+
+ add(argHttps, "--https=CONF", "https certificate access details. JSON file { \"cert\":FILE , \"passwd\"; SECRET } ");
+ add(argHttpsPort, "--httpsPort=NUM", "https port (default port is 3043)");
+
+ // Disable - put in the configuration file.
+// add(argPasswdFile, "--passwd=FILE", "Password file");
+// add(argRealm, "--realm=REALM", "Realm name");
- super.modVersion.addClass(TDB.class);
super.modVersion.addClass(Fuseki.class);
}
@@ -208,15 +224,9 @@ public class FusekiMain extends CmdARQ {
// ---- Port
serverConfig.port = defaultPort;
- if ( contains(argPort) ) {
- String portStr = getValue(argPort);
- try {
- int port = Integer.parseInt(portStr);
- serverConfig.port = port;
- } catch (NumberFormatException ex) {
- throw new CmdException(argPort.getKeyName() + " : bad port number: " + portStr);
- }
- }
+ if ( contains(argPort) )
+ serverConfig.port = portNumber(argPort);
+
if ( contains(argLocalhost) )
serverConfig.loopback = true;
@@ -337,6 +347,30 @@ public class FusekiMain extends CmdARQ {
serverConfig.contentDirectory = filebase;
}
+ if ( contains(argPasswdFile) )
+ serverConfig.passwdFile = getValue(argPasswdFile);
+ if ( contains(argRealm) )
+ serverConfig.realm = getValue(argRealm);
+
+ if ( contains(argHttpsPort) && ! contains(argHttps) )
+ throw new CmdException("https port given but not certificate dtails via --"+argHttps.getKeyName());
+
+ if ( contains(argHttps) ) {
+ serverConfig.httpsPort = defaultHttpsPort;
+ if ( contains(argHttpsPort) )
+ serverConfig.httpsPort = portNumber(argHttpsPort);
+ String httpsSetup = getValue(argHttps);
+ // The details go in a separate file that can be secured.
+ JsonObject httpsConf = JSON.read(httpsSetup);
+ Path path = Paths.get(httpsSetup).toAbsolutePath();
+ String keystore = httpsConf.get("keystore").getAsString().value();
+ // Resolve relative to the https setup file.
+ serverConfig.httpsKeystore = path.getParent().resolve(keystore).toString();
+
+ serverConfig.httpsKeystorePasswd = httpsConf.get("passwd").getAsString().value();
+
+ }
+
// if ( contains(argGZip) ) {
// if ( !hasValueOfTrue(argGZip) && !hasValueOfFalse(argGZip) )
// throw new CmdException(argGZip.getNames().get(0) + ": Not understood: " + getValue(argGZip));
@@ -344,11 +378,22 @@ public class FusekiMain extends CmdARQ {
// }
}
+ private int portNumber(ArgDecl arg) {
+ String portStr = getValue(arg);
+ try {
+ int port = Integer.parseInt(portStr);
+ return port;
+ } catch (NumberFormatException ex) {
+ throw new CmdException(argPort.getKeyName() + " : bad port number: " + portStr);
+ }
+
+ }
+
@Override
protected void exec() {
try {
FusekiServer server = buildServer(serverConfig);
- info(server, serverConfig);
+ info(server);
try {
server.start();
} catch (FusekiException ex) {
@@ -413,10 +458,19 @@ public class FusekiMain extends CmdARQ {
if ( serverConfig.contentDirectory != null )
builder.staticFileBase(serverConfig.contentDirectory) ;
+ if ( serverConfig.passwdFile != null )
+ builder.passwordFile(serverConfig.passwdFile);
+
+ if ( serverConfig.realm != null )
+ builder.realm(serverConfig.realm);
+
+ if ( serverConfig.httpsKeystore != null )
+ builder.https(serverConfig.httpsPort, serverConfig.httpsKeystore, serverConfig.httpsKeystorePasswd);
+
return builder.build();
}
- private void info(FusekiServer server, ServerConfig serverConfig) {
+ private void info(FusekiServer server) {
if ( super.isQuiet() )
return;
http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
index a7ae54b..c19cf6b 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
@@ -29,7 +29,10 @@ import org.apache.jena.fuseki.system.FusekiLogging;
public class FusekiMainCmd {
// This class wraps FusekiBasicMain so that it can take control of logging setup.
- // It does not depend via inheritance on any Jena code - FusekiBasicMain does.
+ // This class does not depend via inheritance on any Jena code
+ // and does not trigger Jena initialization.
+ // FusekiLogging runs before any Jena code can trigger logging setup.
+ //
// Inheritance causes initialization in the super class first, before class
// initialization code in this class.
@@ -41,6 +44,6 @@ public class FusekiMainCmd {
* syntax but not start it.
*/
static public void main(String... argv) {
- FusekiMain.innerMain(argv);
+ FusekiMain.run(argv);
}
}
http://git-wip-us.apache.org/repos/asf/jena/blob/2f948641/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
index 2376a50..89f991c 100644
--- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
@@ -25,7 +25,7 @@ import org.apache.jena.sparql.core.DatasetGraph;
* This is processed by {@link FusekiMain#buildServer}.
*/
class ServerConfig {
- /** Server port */
+ /** Server port. This is the http port when both http and https are active. */
public int port;
/** Loopback */
public boolean loopback = false;
@@ -48,4 +48,13 @@ class ServerConfig {
/** An informative label */
public String datasetDescription;
public String contentDirectory = null;
+
+ // Server authentication
+ public String passwdFile = null;
+ public String realm = null;
+
+ // https
+ public int httpsPort = -1;
+ public String httpsKeystore = null;
+ public String httpsKeystorePasswd = null;
}
\ No newline at end of file