Merge "Add a 'banner' extension point" into stable-2.16
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index 6fe24815..27a39f4 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -15,9 +15,7 @@
   [--abandon | --restore]
   [--rebase]
   [--move <BRANCH>]
-  [--publish]
   [--json | -j]
-  [--delete]
   [--verified <N>] [--code-review <N>]
   [--label Label-Name=<N>]
   [--tag TAG]
@@ -66,7 +64,7 @@
 	Read review input json from stdin. See
 	link:rest-api-changes.html#review-input[ReviewInput] entity for the
 	format.
-	(option is mutually exclusive with --submit, --restore, --publish, --delete,
+	(option is mutually exclusive with --submit, --restore,
 	--abandon, --message, --rebase and --move)
 
 --notify::
@@ -88,7 +86,7 @@
 
 --abandon::
 	Abandon the specified change(s).
-	(option is mutually exclusive with --submit, --restore, --publish, --delete,
+	(option is mutually exclusive with --submit, --restore,
 	--rebase, --move and --json)
 
 --restore::
@@ -97,7 +95,7 @@
 
 --rebase::
 	Rebase the specified change(s).
-	(option is mutually exclusive with --abandon, --submit, --delete and --json)
+	(option is mutually exclusive with --abandon, --submit and --json)
 
 --move::
 	Move the specified change(s).
@@ -106,7 +104,7 @@
 --submit::
 -s::
 	Submit the specified patch set(s) for merging.
-	(option is mutually exclusive with --abandon, --publish --delete, --rebase
+	(option is mutually exclusive with --abandon, --rebase
 	and --json)
 
 --code-review::
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index c65acb7..9a63503 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2786,8 +2786,8 @@
 }
 ----
 
-[[ssh-command-interception]]
-== SSH Command Interception
+[[ssh-command-creation-interception]]
+== SSH Command Creation Interception
 
 Gerrit provides an extension point that allows a plugin to intercept
 creation of SSH commands and override the functionality with its own
@@ -2910,6 +2910,40 @@
 }
 ----
 
+[[ssh-command-execution-interception]]
+== SSH Command Execution Interception
+Gerrit provides an extension point that enables plugins to check and
+prevent an SSH command from being run.
+
+[source, java]
+----
+import com.google.gerrit.sshd.SshExecuteCommandInterceptor;
+
+@Singleton
+public class SshExecuteCommandInterceptorImpl implements SshExecuteCommandInterceptor {
+  private final Provider<SshSession> sessionProvider;
+
+  @Inject
+  SshExecuteCommandInterceptorImpl(Provider<SshSession> sessionProvider) {
+    this.sessionProvider = sessionProvider;
+  }
+
+  @Override
+  public boolean accept(String command, List<String> arguments) {
+    if (command.startsWith("gerrit") && !"10.1.2.3".equals(sessionProvider.get().getRemoteAddressAsString())) {
+      return false;
+    }
+    return true;
+  }
+}
+----
+
+And then declare it in your SSH module:
+[source, java]
+----
+  DynamicSet.bind(binder(), SshExecuteCommandInterceptor.class).to(SshExecuteCommandInterceptorImpl.class);
+----
+
 Plugin authors should also consider binding their SubmitRule using a `Gerrit-BatchModule`.
 See link:dev-plugins.html[Batch runtime] for more informations.
 
diff --git a/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index bc562cc..9d09461 100644
--- a/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -107,6 +107,7 @@
     extractMailExample("Abandoned.soy");
     extractMailExample("AbandonedHtml.soy");
     extractMailExample("AddKey.soy");
+    extractMailExample("AddKeyHtml.soy");
     extractMailExample("ChangeFooter.soy");
     extractMailExample("ChangeFooterHtml.soy");
     extractMailExample("ChangeSubject.soy");
diff --git a/java/com/google/gerrit/sshd/DispatchCommand.java b/java/com/google/gerrit/sshd/DispatchCommand.java
index 4c9ca91..68962db 100644
--- a/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -18,6 +18,7 @@
 import com.google.common.base.Throwables;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Atomics;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.server.args4j.SubcommandHandler;
 import com.google.gerrit.server.permissions.GlobalPermission;
@@ -44,6 +45,7 @@
   private final PermissionBackend permissionBackend;
   private final Map<String, CommandProvider> commands;
   private final AtomicReference<Command> atomicCmd;
+  private final DynamicSet<SshExecuteCommandInterceptor> commandInterceptors;
 
   @Argument(index = 0, required = false, metaVar = "COMMAND", handler = SubcommandHandler.class)
   private String commandName;
@@ -52,10 +54,14 @@
   private List<String> args = new ArrayList<>();
 
   @Inject
-  DispatchCommand(PermissionBackend permissionBackend, @Assisted Map<String, CommandProvider> all) {
+  DispatchCommand(
+      PermissionBackend permissionBackend,
+      DynamicSet<SshExecuteCommandInterceptor> commandInterceptors,
+      @Assisted Map<String, CommandProvider> all) {
     this.permissionBackend = permissionBackend;
     commands = all;
     atomicCmd = Atomics.newReference();
+    this.commandInterceptors = commandInterceptors;
   }
 
   Map<String, CommandProvider> getMap() {
@@ -84,19 +90,29 @@
 
       final Command cmd = p.getProvider().get();
       checkRequiresCapability(cmd);
+      String actualCommandName = commandName;
       if (cmd instanceof BaseCommand) {
         final BaseCommand bc = (BaseCommand) cmd;
-        if (getName().isEmpty()) {
-          bc.setName(commandName);
-        } else {
-          bc.setName(getName() + " " + commandName);
+        if (!getName().isEmpty()) {
+          actualCommandName = getName() + " " + commandName;
         }
+        bc.setName(actualCommandName);
         bc.setArguments(args.toArray(new String[args.size()]));
 
       } else if (!args.isEmpty()) {
         throw die(commandName + " does not take arguments");
       }
 
+      for (SshExecuteCommandInterceptor commandInterceptor : commandInterceptors) {
+        if (!commandInterceptor.accept(actualCommandName, args)) {
+          throw new UnloggedFailure(
+              126,
+              String.format(
+                  "blocked by %s, contact gerrit administrators for more details",
+                  commandInterceptor.name()));
+        }
+      }
+
       provideStateTo(cmd);
       atomicCmd.set(cmd);
       cmd.start(env);
diff --git a/java/com/google/gerrit/sshd/SshExecuteCommandInterceptor.java b/java/com/google/gerrit/sshd/SshExecuteCommandInterceptor.java
new file mode 100644
index 0000000..ee60670
--- /dev/null
+++ b/java/com/google/gerrit/sshd/SshExecuteCommandInterceptor.java
@@ -0,0 +1,35 @@
+// Copyright (C) 2019 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.google.gerrit.sshd;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import java.util.List;
+
+@ExtensionPoint
+public interface SshExecuteCommandInterceptor {
+
+  /**
+   * Check the command and return false if this command must not be run.
+   *
+   * @param command the command
+   * @param arguments the list of arguments
+   * @return whether or not this command with these arguments can be executed
+   */
+  boolean accept(String command, List<String> arguments);
+
+  default String name() {
+    return this.getClass().getSimpleName();
+  }
+}
diff --git a/java/com/google/gerrit/sshd/SshModule.java b/java/com/google/gerrit/sshd/SshModule.java
index acdc958..df1f633 100644
--- a/java/com/google/gerrit/sshd/SshModule.java
+++ b/java/com/google/gerrit/sshd/SshModule.java
@@ -106,6 +106,7 @@
 
     DynamicMap.mapOf(binder(), DynamicOptions.DynamicBean.class);
     DynamicItem.itemOf(binder(), SshCreateCommandInterceptor.class);
+    DynamicSet.setOf(binder(), SshExecuteCommandInterceptor.class);
 
     listener().toInstance(registerInParentInjectors());
     listener().to(SshLog.class);
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index 7120c21..8fe8b14 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -1,10 +1,10 @@
 load("//tools/bzl:maven_jar.bzl", "MAVEN_CENTRAL", "maven_jar")
 
-_JGIT_VERS = "5.1.6.201903130242-r"
+_JGIT_VERS = "5.1.7.201904200442-r"
 
 _DOC_VERS = _JGIT_VERS  # Set to _JGIT_VERS unless using a snapshot
 
-JGIT_DOC_URL = "http://download.eclipse.org/jgit/site/" + _DOC_VERS + "/apidocs"
+JGIT_DOC_URL = "https://download.eclipse.org/jgit/site/" + _DOC_VERS + "/apidocs"
 
 _JGIT_REPO = MAVEN_CENTRAL  # Leave here even if set to MAVEN_CENTRAL.
 
@@ -40,28 +40,28 @@
         name = "jgit-lib",
         artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "c33db190745a8a40524d3076f033ba2ee4616a64",
-        src_sha1 = "f0e33171d3d413c5a94f07794aef7b71a35772f6",
+        sha1 = "ef1f744a1117e3d8916f3770486502a56d241b16",
+        src_sha1 = "7bf5b5298f8936e959a92f62b5151d58feb9c00a",
         unsign = True,
     )
     maven_jar(
         name = "jgit-servlet",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "86564c102e81a76f67630d4fde862d137e24c63a",
+        sha1 = "8306e221fb50985247138adb8ffb0f4cd74c5e9a",
         unsign = True,
     )
     maven_jar(
         name = "jgit-archive",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "f4ebfa91f4c988f37648b3087830a83973a975be",
+        sha1 = "fefb13ab8331cc8b13a15b653b1e057de7d3e514",
     )
     maven_jar(
         name = "jgit-junit",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "b2e512e920afbac405a678efe92e3b4bacb9a1df",
+        sha1 = "f73f1eacc38f8329d9453f1d0353e82404379a89",
         unsign = True,
     )
 
diff --git a/tools/bzl/javadoc.bzl b/tools/bzl/javadoc.bzl
index fcf9f33..6387210 100644
--- a/tools/bzl/javadoc.bzl
+++ b/tools/bzl/javadoc.bzl
@@ -23,7 +23,7 @@
     transitive_jar_paths = [j.path for j in transitive_jars.to_list()]
     dir = ctx.outputs.zip.path + ".dir"
     source = ctx.outputs.zip.path + ".source"
-    external_docs = ["http://docs.oracle.com/javase/8/docs/api"] + ctx.attr.external_docs
+    external_docs = ["https://docs.oracle.com/javase/8/docs/api"] + ctx.attr.external_docs
     cmd = [
         "TZ=UTC",
         "export TZ",
diff --git a/tools/js/npm_pack.py b/tools/js/npm_pack.py
index f14262a..57f3166 100755
--- a/tools/js/npm_pack.py
+++ b/tools/js/npm_pack.py
@@ -54,7 +54,7 @@
 
     name, version = args
     filename = '%s-%s.tgz' % (name, version)
-    url = 'http://registry.npmjs.org/%s/-/%s' % (name, filename)
+    url = 'https://registry.npmjs.org/%s/-/%s' % (name, filename)
 
     tmpdir = tempfile.mkdtemp()
     tgz = os.path.join(tmpdir, filename)
diff --git a/tools/util.py b/tools/util.py
index 172ecfe..947e2c0 100644
--- a/tools/util.py
+++ b/tools/util.py
@@ -16,9 +16,9 @@
 
 REPO_ROOTS = {
   'ECLIPSE': 'https://repo.eclipse.org/content/groups/releases',
-  'GERRIT': 'http://gerrit-maven.storage.googleapis.com',
+  'GERRIT': 'https://gerrit-maven.storage.googleapis.com',
   'GERRIT_API': 'https://gerrit-api.commondatastorage.googleapis.com/release',
-  'MAVEN_CENTRAL': 'http://repo1.maven.org/maven2',
+  'MAVEN_CENTRAL': 'https://repo1.maven.org/maven2',
   'MAVEN_LOCAL': 'file://' + path.expanduser('~/.m2/repository'),
   'MAVEN_SNAPSHOT': 'https://oss.sonatype.org/content/repositories/snapshots',
 }
diff --git a/tools/util_test.py b/tools/util_test.py
index fa67696..1a389f5 100644
--- a/tools/util_test.py
+++ b/tools/util_test.py
@@ -29,18 +29,18 @@
 
     def testKnownRedirect(self):
         url = resolve_url('MAVEN_CENTRAL:foo.jar',
-                          {'MAVEN_CENTRAL': 'http://my.company.mirror/maven2'})
-        self.assertEqual(url, 'http://my.company.mirror/maven2/foo.jar')
+                          {'MAVEN_CENTRAL': 'https://my.company.mirror/maven2'})
+        self.assertEqual(url, 'https://my.company.mirror/maven2/foo.jar')
 
     def testCustom(self):
-        url = resolve_url('http://maven.example.com/release/foo.jar', {})
-        self.assertEqual(url, 'http://maven.example.com/release/foo.jar')
+        url = resolve_url('https://maven.example.com/release/foo.jar', {})
+        self.assertEqual(url, 'https://maven.example.com/release/foo.jar')
 
     def testCustomRedirect(self):
         url = resolve_url('MAVEN_EXAMPLE:foo.jar',
                           {'MAVEN_EXAMPLE':
-                           'http://maven.example.com/release'})
-        self.assertEqual(url, 'http://maven.example.com/release/foo.jar')
+                           'https://maven.example.com/release'})
+        self.assertEqual(url, 'https://maven.example.com/release/foo.jar')
 
 
 if __name__ == '__main__':