Merge "Configurable front-end Servlet Filter as proxy surrogate."
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 8d134ff..ac2821b 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1648,6 +1648,38 @@
 +
 By default, 5 minutes.
 
+[[httpd.filterClass]]httpd.filterClass::
++
+Class that implements the javax.servlet.Filter interface
+for filtering any HTTP related traffic going through the Gerrit
+HTTP protocol.
+Class is loaded and configured in the Gerrit Jetty container
+and run in front of all Gerrit URL handlers, allowing the filter
+to inspect, modify, allow or reject each request.
+It needs to be provided as JAR library
+under $GERRIT_SITE/lib as it is resolved using the default Gerrit class
+loader and cannot be dynamically loaded by a plugin.
++
+Failing to load the Filter class would result in a Gerrit start-up
+failure, as this class is supposed to provide mandatory filtering
+in front of Gerrit HTTP protocol.
++
+Typical usage is in conjunction with the auth.type=HTTP as replacement
+of an Apache HTTP proxy layer as security enforcement on top of Gerrit
+by returning a trusted username as HTTP Header.
++
+Example of using a security library secure.jar under $GERRIT_SITE/lib
+that provides a org.anyorg.MySecureFilter Servlet Filter that enforces
+a trusted username in the `TRUSTED_USER` HTTP Header:
+
+----
+[auth]
+	type = HTTP
+	httpHeader = TRUSTED_USER
+
+[http]
+	filterClass = org.anyorg.MySecureFilter
+----
 
 [[ldap]]Section ldap
 ~~~~~~~~~~~~~~~~~~~~
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 37637d5..ddc12c8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -76,6 +76,7 @@
 import java.util.zip.ZipFile;
 
 import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
 
 @Singleton
 public class JettyServer {
@@ -305,7 +306,7 @@
 
     final List<ContextHandler> all = new ArrayList<ContextHandler>();
     for (String path : paths) {
-      all.add(makeContext(path, env));
+      all.add(makeContext(path, env, cfg));
     }
 
     if (all.size() == 1) {
@@ -325,7 +326,7 @@
   }
 
   private ContextHandler makeContext(final String contextPath,
-      final JettyEnv env) throws MalformedURLException, IOException {
+      final JettyEnv env, final Config cfg) throws MalformedURLException, IOException {
     final ServletContextHandler app = new ServletContextHandler();
 
     // This enables the use of sessions in Jetty, feature available
@@ -344,6 +345,27 @@
     //
     app.setBaseResource(getBaseResource());
 
+    // HTTP front-end filter to be used as surrogate of Apache HTTP
+    // reverse-proxy filtering.
+    // It is meant to be used as simpler tiny deployment of custom-made
+    // security enforcement (Security tokens, IP-based security filtering, others)
+    String filterClassName = cfg.getString("httpd", null, "filterClass");
+    if (filterClassName != null) {
+      try {
+        @SuppressWarnings("unchecked")
+        Class<? extends Filter> filterClass =
+            (Class<? extends Filter>) Class.forName(filterClassName);
+        Filter filter = env.webInjector.getInstance(filterClass);
+        app.addFilter(new FilterHolder(filter), "/*",
+            EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
+      } catch (Throwable e) {
+        String errorMessage =
+            "Unable to instantiate front-end HTTP Filter " + filterClassName;
+        log.error(errorMessage, e);
+        throw new IllegalArgumentException(errorMessage, e);
+      }
+    }
+
     // Perform the same binding as our web.xml would do, but instead
     // of using the listener to create the injector pass the one we
     // already have built.