Added the emailReviewers as a global capability

This adds functionality to deny the emailing of reviewers to certain
groups.  This will replace the emailOnlyAuthors flag on the
AccountGroup.

Change-Id: If3697e88df50e0b0256b5b6a1ea810343124b96f
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index 2e46791..d3d2a4d 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -39,6 +39,16 @@
   /** Can create any project on the server. */
   public static final String CREATE_PROJECT = "createProject";
 
+  /**
+   * Denotes who may email change reviewers.
+   * <p>
+   * This can be used to deny build bots from emailing reviewers and people who
+   * have starred the changed. Instead, only the authors of the change will be
+   * emailed. The allow rules are evaluated before deny rules, however the
+   * default is to allow emailing, if no explicit rule is matched.
+   */
+  public static final String EMAIL_REVIEWERS = "emailReviewers";
+
   /** Can flush any cache except the active web_sessions cache. */
   public static final String FLUSH_CACHES = "flushCaches";
 
@@ -71,6 +81,7 @@
     NAMES_LC.add(CREATE_ACCOUNT.toLowerCase());
     NAMES_LC.add(CREATE_GROUP.toLowerCase());
     NAMES_LC.add(CREATE_PROJECT.toLowerCase());
+    NAMES_LC.add(EMAIL_REVIEWERS.toLowerCase());
     NAMES_LC.add(FLUSH_CACHES.toLowerCase());
     NAMES_LC.add(KILL_TASK.toLowerCase());
     NAMES_LC.add(PRIORITY.toLowerCase());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index f2a8ad6..14d25d2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -128,6 +128,7 @@
   createAccount, \
   createGroup, \
   createProject, \
+  emailReviewers, \
   flushCaches, \
   killTask, \
   priority, \
@@ -140,6 +141,7 @@
 createAccount = Create Account
 createGroup = Create Group
 createProject = Create Project
+emailReviewers = Email Reviewers
 flushCaches = Flush Caches
 killTask = Kill Task
 priority = Priority
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
index 6cf4457..796b44e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityCollection.java
@@ -34,6 +34,7 @@
   private final Map<String, List<PermissionRule>> permissions;
 
   public final List<PermissionRule> administrateServer;
+  public final List<PermissionRule> emailReviewers;
   public final List<PermissionRule> priority;
   public final List<PermissionRule> queryLimit;
 
@@ -46,14 +47,17 @@
         new HashMap<String, List<PermissionRule>>();
     for (Permission permission : section.getPermissions()) {
       for (PermissionRule rule : permission.getRules()) {
-        if (rule.getAction() != PermissionRule.Action.DENY) {
-          List<PermissionRule> r = tmp.get(permission.getName());
-          if (r == null) {
-            r = new ArrayList<PermissionRule>(2);
-            tmp.put(permission.getName(), r);
-          }
-          r.add(rule);
+        if (!permission.getName().equals(GlobalCapability.EMAIL_REVIEWERS)
+            && rule.getAction() == PermissionRule.Action.DENY) {
+          continue;
         }
+
+        List<PermissionRule> r = tmp.get(permission.getName());
+        if (r == null) {
+          r = new ArrayList<PermissionRule>(2);
+          tmp.put(permission.getName(), r);
+        }
+        r.add(rule);
       }
     }
     configureDefaults(tmp, section);
@@ -72,6 +76,7 @@
     permissions = Collections.unmodifiableMap(res);
 
     administrateServer = getPermission(GlobalCapability.ADMINISTRATE_SERVER);
+    emailReviewers = getPermission(GlobalCapability.EMAIL_REVIEWERS);
     priority = getPermission(GlobalCapability.PRIORITY);
     queryLimit = getPermission(GlobalCapability.QUERY_LIMIT);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index 9f17671..eb42921 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -15,11 +15,14 @@
 package com.google.gerrit.server.account;
 
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.PermissionRule.Action;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.PeerDaemonUser;
@@ -45,6 +48,7 @@
   private final Map<String, List<PermissionRule>> effective;
 
   private Boolean canAdministrateServer;
+  private Boolean canEmailReviewers;
 
   @Inject
   CapabilityControl(ProjectCache projectCache, @Assisted CurrentUser currentUser) {
@@ -62,7 +66,7 @@
   public boolean canAdministrateServer() {
     if (canAdministrateServer == null) {
       canAdministrateServer = user instanceof PeerDaemonUser
-          || matchAny(capabilities.administrateServer);
+          || matchAny(capabilities.administrateServer, ALLOWED_RULE);
     }
     return canAdministrateServer;
   }
@@ -85,6 +89,17 @@
       || canAdministrateServer();
   }
 
+  /** @return true if the user can email reviewers. */
+  public boolean canEmailReviewers() {
+    if (canEmailReviewers == null) {
+      canEmailReviewers =
+          matchAny(capabilities.emailReviewers, ALLOWED_RULE)
+          || !matchAny(capabilities.emailReviewers, Predicates.not(ALLOWED_RULE));
+
+    }
+    return canEmailReviewers;
+  }
+
   /** @return true if the user can kill any running task. */
   public boolean canKillTask() {
     return canPerform(GlobalCapability.KILL_TASK)
@@ -222,8 +237,16 @@
     return mine;
   }
 
-  private boolean matchAny(List<PermissionRule> rules) {
-    Iterable<AccountGroup.UUID> ids = Iterables.transform(rules,
+  private static final Predicate<PermissionRule> ALLOWED_RULE = new Predicate<PermissionRule>() {
+    @Override
+    public boolean apply(PermissionRule rule) {
+      return rule.getAction() == Action.ALLOW;
+    }
+  };
+
+  private boolean matchAny(Iterable<PermissionRule> rules, Predicate<PermissionRule> predicate) {
+    Iterable<AccountGroup.UUID> ids = Iterables.transform(
+        Iterables.filter(rules, predicate),
         new Function<PermissionRule, AccountGroup.UUID>() {
           @Override
           public AccountGroup.UUID apply(PermissionRule rule) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index a2bc483..faf586a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -70,12 +70,17 @@
 
     /** Is the from user in an email squelching group? */
     final IdentifiedUser user =  args.identifiedUserFactory.create(id);
-    final Set<AccountGroup.UUID> gids = user.getEffectiveGroups().getKnownGroups();
-    for (final AccountGroup.UUID gid : gids) {
-      AccountGroup group = args.groupCache.get(gid);
-      if (group != null && group.isEmailOnlyAuthors()) {
-        emailOnlyAuthors = true;
-        break;
+    if (!user.getCapabilities().canEmailReviewers()) {
+      emailOnlyAuthors = true;
+    } else {
+      // TODO(cranger): remove once the schema is migrated in the next patch.
+      final Set<AccountGroup.UUID> gids = user.getEffectiveGroups().getKnownGroups();
+      for (final AccountGroup.UUID gid : gids) {
+        AccountGroup group = args.groupCache.get(gid);
+        if (group != null && group.isEmailOnlyAuthors()) {
+          emailOnlyAuthors = true;
+          break;
+        }
       }
     }
   }