Allow magic branch pushes with certs from untrusted keys

The traditional way to establish that a GPG key offered by a user
actually belongs to that user in real life is to do some sort of
offline identity verification (checking paper ID, etc.). If a site
requires that level of verification for _any_ push, that may be an
unreasonably high bar for someone to meet before their first
contribution to a project. (But some organizations may still want it,
so it may be worth controlling with an option in the future.)

Allow untrusted keys if such a user is only pushing code for review,
i.e. to a magic branch name like refs/for/*. We now store the push
certificate itself in any PatchSets that are created, so it can be
verified later and/or exposed in the UI.

Change-Id: I0b1e2a13a6228b2ca00a391f005d9baddf08817f
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushPreReceiveHook.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushPreReceiveHook.java
index 50f0642..1911381 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushPreReceiveHook.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/SignedPushPreReceiveHook.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.MagicBranch;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -76,7 +77,7 @@
       }
     };
     CheckResult result = checker.check(cert);
-    if (!result.isOk()) {
+    if (!isAllowed(result, commands)) {
       for (String problem : result.getProblems()) {
         rp.sendMessage(problem);
       }
@@ -84,6 +85,28 @@
     }
   }
 
+  private static boolean isAllowed(CheckResult result,
+      Collection<ReceiveCommand> commands) {
+    if (onlyMagicBranches(commands)) {
+      // Only pushing magic branches: allow a valid push certificate even if the
+      // key is not ultimately trusted. Assume anyone with Submit permission to
+      // the branch is able to verify during review that the code is legitimate.
+      return result.isOk();
+    } else {
+      // Directly updating one or more refs: require a trusted key.
+      return result.isTrusted();
+    }
+  }
+
+  private static boolean onlyMagicBranches(Iterable<ReceiveCommand> commands) {
+    for (ReceiveCommand c : commands) {
+      if (!MagicBranch.isMagicBranch(c.getRefName())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   private static void reject(Collection<ReceiveCommand> commands,
       String reason) {
     for (ReceiveCommand cmd : commands) {