Add read only HTTP filter to prevent PUT/POST/DELETE

The filter intercepts all HTTP requests and rejects any PUT, POST
or DELETE with a "503 Service Unavailable" status code [1].

This prevents:

- Creating a new project from the UI
- Changing a project's configuration from the UI
- Changing user preferences from the UI
- and any other operation performed by a POST/PUT/DELETE REST API call

[1] https://httpstatuses.com/503

Change-Id: I4fa7d335d8392270e004f55d29581eeffa3346a2
diff --git a/BUILD b/BUILD
index cf9a4be..b3ce4cd 100644
--- a/BUILD
+++ b/BUILD
@@ -6,6 +6,7 @@
     manifest_entries = [
         "Gerrit-PluginName: readonly",
         "Gerrit-Module: com.googlesource.gerrit.plugins.readonly.Module",
+        "Gerrit-HttpModule: com.googlesource.gerrit.plugins.readonly.HttpModule",
     ],
     resources = glob(["src/main/**/*"]),
 )
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/HttpModule.java
new file mode 100644
index 0000000..be76f40
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/HttpModule.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed 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 com.googlesource.gerrit.plugins.readonly;
+
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.httpd.AllRequestFilter;
+import com.google.gerrit.httpd.plugins.HttpPluginModule;
+import com.google.inject.Scopes;
+
+public class HttpModule extends HttpPluginModule {
+  @Override
+  protected void configureServlets() {
+    DynamicSet.bind(binder(), AllRequestFilter.class).to(ReadOnly.class).in(Scopes.SINGLETON);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnly.java b/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnly.java
index d933e78..1a5a6fc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnly.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/readonly/ReadOnly.java
@@ -14,15 +14,25 @@
 
 package com.googlesource.gerrit.plugins.readonly;
 
+import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
+
+import com.google.gerrit.httpd.AllRequestFilter;
 import com.google.gerrit.server.events.CommitReceivedEvent;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidationListener;
 import com.google.gerrit.server.git.validators.CommitValidationMessage;
 import com.google.inject.Singleton;
+import java.io.IOException;
 import java.util.List;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 @Singleton
-class ReadOnly implements CommitValidationListener {
+class ReadOnly extends AllRequestFilter implements CommitValidationListener {
   private static final String READ_ONLY_MSG = "Gerrit is under maintenance - all data is READ ONLY";
 
   @Override
@@ -30,4 +40,17 @@
       throws CommitValidationException {
     throw new CommitValidationException(READ_ONLY_MSG);
   }
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+      throws IOException, ServletException {
+    if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
+      String method = ((HttpServletRequest) request).getMethod();
+      if (method == "POST" || method == "PUT" || method == "DELETE") {
+        ((HttpServletResponse) response).sendError(SC_SERVICE_UNAVAILABLE, READ_ONLY_MSG);
+        return;
+      }
+    }
+    chain.doFilter(request, response);
+  }
 }