Add project-specific scoping

Also add project and ref scoping to the UI.

Change-Id: If89e95fea8d36b287fe0d1e174885f7f6f7f6311
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java
index b1cba3c..9717779 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java
@@ -34,6 +34,7 @@
     install(InvalidLineEndingValidator.module());
     install(ContentTypeValidator.module());
     install(DuplicatePathnameValidator.module());
+    install(ValidatorConfig.module());
 
     bind(ConfigFactory.class).to(PluginConfigWithInheritanceFactory.class).in(
         Scopes.SINGLETON);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ValidatorConfig.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ValidatorConfig.java
index cf80a6c..30227fc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ValidatorConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ValidatorConfig.java
@@ -15,12 +15,16 @@
 package com.googlesource.gerrit.plugins.uploadvalidator;
 
 import com.google.gerrit.common.data.RefConfigSection;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.project.RefPatternMatcher;
+import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 
 import org.slf4j.Logger;
@@ -33,9 +37,29 @@
 public class ValidatorConfig {
   private static final Logger log = LoggerFactory
       .getLogger(ValidatorConfig.class);
+  private static final String KEY_PROJECT = "project";
+  private static final String KEY_REF = "ref";
   private final ConfigFactory configFactory;
   private final GroupCache groupCache;
 
+  public static AbstractModule module() {
+    return new AbstractModule() {
+      @Override
+      protected void configure() {
+        bind(ProjectConfigEntry.class)
+            .annotatedWith(Exports.named(KEY_PROJECT))
+            .toInstance(new ProjectConfigEntry("Projects", null,
+                ProjectConfigEntryType.ARRAY, null, false,
+                "Only projects that match this regex will be validated."));
+        bind(ProjectConfigEntry.class)
+            .annotatedWith(Exports.named(KEY_REF))
+            .toInstance(new ProjectConfigEntry("Refs", null,
+                ProjectConfigEntryType.ARRAY, null, false,
+                "Only refs that match this regex will be validated."));
+      }
+    };
+  }
+
   @Inject
   public ValidatorConfig(ConfigFactory configFactory,
       GroupCache groupCache) {
@@ -51,6 +75,7 @@
         && isValidConfig(conf, projectName)
         && (activeForRef(conf, refName))
         && (activeForEmail(conf, user.getAccount().getPreferredEmail()))
+        && (activeForProject(conf, projectName.get()))
         && (!hasCriteria(conf, "skipGroup")
             || !canSkipValidation(conf, validatorOp)
             || !canSkipRef(conf, refName)
@@ -80,6 +105,10 @@
     return config.getStringList(criteria).length > 0;
   }
 
+  private boolean activeForProject(PluginConfig config, String project) {
+    return matchCriteria(config, "project", project, true, false);
+  }
+
   private boolean activeForRef(PluginConfig config, String ref) {
     return matchCriteria(config, "ref", ref, true, true);
   }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index a4151a0..04c04a1 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -241,6 +241,22 @@
     email = .*@example.com$
 ```
 
+Project-specific validations
+---------------------------
+
+By default, the validation will be enabled for all projects. However, it can
+be limited to particular projects by setting `plugin.@PLUGIN@.project`. The
+projects may be configured using specific project names, project patterns, or
+regular expressions. Multiple projects may be specified.
+
+E.g. to limit the validation to all projects that match `^platform/.*` the
+following could be configured:
+
+```
+  [plugin "@PLUGIN@"]
+    project = ^platform/.*
+```
+
 Permission to skip the rules
 ----------------------------
 
@@ -324,4 +340,4 @@
     skipGroup = ldap/ReleaseManagers
     skipGroup = ldap/GerritAdmins
     skipRef = refs/heads/master
-```
\ No newline at end of file
+```
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ProjectAwareValidatorConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ProjectAwareValidatorConfigTest.java
new file mode 100644
index 0000000..1ddcdc3
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ProjectAwareValidatorConfigTest.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2017 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.uploadvalidator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.IdentifiedUser;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.junit.Test;
+
+public class ProjectAwareValidatorConfigTest {
+  private Project.NameKey projectName = new Project.NameKey("testProject");
+  private IdentifiedUser anyUser = new FakeUserProvider().get();
+
+  @Test
+  public void isEnabledForAllProjectsByDefault() throws Exception {
+    ValidatorConfig config =
+        getConfig("[plugin \"uploadvalidator\"]\n"
+            + "blockedFileExtension = jar", projectName);
+
+    assertThat(
+        config.isEnabledForRef(anyUser, projectName, "anyRef",
+            "blockedFileExtension")).isTrue();
+  }
+
+  @Test
+  public void isEnabledForSingleProject() throws Exception {
+    ValidatorConfig config =
+        getConfig("[plugin \"uploadvalidator\"]\n"
+            + "   project = testProject\n"
+            + "   blockedFileExtension = jar", projectName);
+
+    assertThat(
+        config.isEnabledForRef(anyUser, projectName, "anyRef",
+            "blockedFileExtension")).isTrue();
+  }
+
+  @Test
+  public void isDisabledForInvalidProject() throws Exception {
+    ValidatorConfig config =
+        getConfig("[plugin \"uploadvalidator\"]\n"
+            + "   project = someOtherProject\n"
+            + "   blockedFileExtension = jar", projectName);
+
+    assertThat(
+        config.isEnabledForRef(anyUser, projectName, "anyRef",
+            "blockedFileExtension")).isFalse();
+  }
+
+  @Test
+  public void isEnabledForRegexProject() throws Exception {
+    String configString = "[plugin \"uploadvalidator\"]\n"
+        + "   project = test.*\n"
+        + "   blockedFileExtension = jar";
+    Project.NameKey otherNameKey = new Project.NameKey("someOtherProject");
+    ValidatorConfig config = getConfig(configString, projectName);
+    ValidatorConfig config2 = getConfig(configString, otherNameKey);
+
+    assertThat(
+        config.isEnabledForRef(anyUser, projectName, "anyRef",
+            "blockedFileExtension")).isTrue();
+    assertThat(
+        config2.isEnabledForRef(anyUser, otherNameKey,
+            "anyRef", "blockedFileExtension")).isFalse();
+  }
+
+  @Test
+  public void isEnabledForMultipleProjects() throws Exception {
+    String configString = "[plugin \"uploadvalidator\"]\n"
+            + "   project = testProject\n"
+            + "   project = another.*\n"
+            + "   blockedFileExtension = jar";
+    Project.NameKey anotherNameKey = new Project.NameKey("anotherProject");
+    Project.NameKey someOtherNameKey = new Project.NameKey("someOtherProject");
+    ValidatorConfig config = getConfig(configString, projectName);
+    ValidatorConfig config2 = getConfig(configString, anotherNameKey);
+    ValidatorConfig config3 = getConfig(configString, someOtherNameKey);
+
+    assertThat(
+        config.isEnabledForRef(anyUser, projectName, "anyRef",
+            "blockedFileExtension")).isTrue();
+    assertThat(
+        config2.isEnabledForRef(anyUser, anotherNameKey, "anyRef",
+            "blockedFileExtension")).isTrue();
+    assertThat(
+        config3.isEnabledForRef(anyUser, someOtherNameKey, "anyRef",
+            "blockedFileExtension")).isFalse();
+  }
+
+  private ValidatorConfig getConfig(
+      String defaultConfig, Project.NameKey projName)
+      throws ConfigInvalidException {
+    ValidatorConfig config =
+        new ValidatorConfig(new FakeConfigFactory(projName, defaultConfig),
+            new FakeGroupCacheUUIDByName());
+    return config;
+  }
+}