Merge branch 'stable-3.2' into stable-3.3

* stable-3.2:
  ContentTypeUtil: Add singleton scope
  Upgrade bazlets to latest stable-3.1 to build with 3.1.12 API
  Upgrade bazlets to latest stable-3.0 to build with 3.0.15 API
  Upgrade bazlets to latest stable-2.16 to build with 2.16.26 API
  Upgrade bazlets to latest stable-3.2 to build with 3.2.6 API
  Upgrade bazlets to latest stable-3.1 to build with 3.1.11 API
  Upgrade bazlets to latest stable-2.16 to build with 2.16.23 API

Change-Id: I6f872661664235fa58eee326262f1c9ad9737623
diff --git a/BUILD b/BUILD
index d74886f..e7b7df1 100644
--- a/BUILD
+++ b/BUILD
@@ -16,20 +16,23 @@
     ],
 )
 
-TEST_SRCS = "src/test/java/**/*Test.java"
-
 TEST_DEPS = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
     "@commons-io//jar",
     "@mime-types//jar",
     ":uploadvalidator__plugin",
 ]
 
+TEST_SRCS = [
+    "src/test/java/**/*Test.java",
+    "src/test/java/**/*IT.java",
+]
+
 java_library(
     name = "testutils",
     testonly = 1,
     srcs = glob(
         ["src/test/java/**/*.java"],
-        exclude = [TEST_SRCS],
+        exclude = TEST_SRCS,
     ),
     deps = TEST_DEPS,
 )
@@ -37,7 +40,19 @@
 junit_tests(
     name = "uploadvalidator_tests",
     testonly = 1,
-    srcs = glob([TEST_SRCS]),
+    srcs = glob(
+        ["src/test/java/**/*Test.java"]),
+    tags = ["uploadvalidator"],
+    deps = TEST_DEPS + [
+        ":testutils",
+    ],
+)
+
+junit_tests(
+    name = "uploadvalidator_integration_tests",
+    testonly = 1,
+    srcs = glob(
+        ["src/test/java/**/*IT.java"]),
     tags = ["uploadvalidator"],
     deps = TEST_DEPS + [
         ":testutils",
diff --git a/WORKSPACE b/WORKSPACE
index 872eb15..aa36a75 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,7 +3,7 @@
 load("//:bazlets.bzl", "load_bazlets")
 
 load_bazlets(
-    commit = "8dc0767541f16b35d2136eccebffd9ebe2b81133",
+    commit = "cf0bbc90e09a8a1d7c042d79f1555e3fa40984e1",
     #local_path = "/home/<user>/projects/bazlets",
 )
 
@@ -12,7 +12,7 @@
     "gerrit_api",
 )
 
-gerrit_api()
+gerrit_api(version = "3.4.0-SNAPSHOT")
 
 load("//:external_plugin_deps.bzl", "external_plugin_deps")
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/BlockedKeywordValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/BlockedKeywordValidator.java
index c52b588..288afcf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/BlockedKeywordValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/BlockedKeywordValidator.java
@@ -20,12 +20,21 @@
 import com.google.common.base.Joiner;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Project.NameKey;
 import com.google.gerrit.extensions.annotations.Exports;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.validators.CommentForValidation;
+import com.google.gerrit.extensions.validators.CommentValidationContext;
+import com.google.gerrit.extensions.validators.CommentValidationFailure;
+import com.google.gerrit.extensions.validators.CommentValidator;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.ProjectConfigEntry;
@@ -47,9 +56,12 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -60,8 +72,20 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 
-public class BlockedKeywordValidator implements CommitValidationListener {
+/**
+ * A validator for checking for use of blocked keywords in commit content and comments.
+ *
+ * <p>This class implements both {@link CommitValidationListener} and {@link CommentValidator}
+ * validator classes to run its list of blocked keywords against commit content and comments.
+ */
+public class BlockedKeywordValidator implements CommitValidationListener, CommentValidator {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  // These keys are used for turning on specific validation elements.
+  // i.e. enableSkipValidation = blockedKeyword will enabled skipRef and skipGroup checks
+  // i.e. disabledValidation = blockedKeywordComments will disable the comment blocked keyword check
   private static final String KEY_CHECK_BLOCKED_KEYWORD = "blockedKeyword";
+  private static final String KEY_CHECK_COMMENT_BLOCKED_KEYWORD = "blockedKeywordComments";
   private static final String KEY_CHECK_BLOCKED_KEYWORD_PATTERN =
       KEY_CHECK_BLOCKED_KEYWORD + "Pattern";
 
@@ -70,6 +94,7 @@
       @Override
       protected void configure() {
         DynamicSet.bind(binder(), CommitValidationListener.class).to(BlockedKeywordValidator.class);
+        DynamicSet.bind(binder(), CommentValidator.class).to(BlockedKeywordValidator.class);
         bind(ProjectConfigEntry.class)
             .annotatedWith(Exports.named(KEY_CHECK_BLOCKED_KEYWORD_PATTERN))
             .toInstance(
@@ -120,7 +145,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
@@ -148,29 +173,65 @@
     return Collections.emptyList();
   }
 
+  @Override
+  public ImmutableList<CommentValidationFailure> validateComments(
+      CommentValidationContext ctx, ImmutableList<CommentForValidation> comments) {
+    try {
+      NameKey projectNameKey = Project.nameKey(ctx.getProject());
+      PluginConfig cfg = cfgFactory.getFromProjectConfigWithInheritance(projectNameKey, pluginName);
+      if (isActive(cfg)
+          && validatorConfig.isEnabled(
+              null, projectNameKey, "", KEY_CHECK_COMMENT_BLOCKED_KEYWORD)) {
+        ImmutableMap<String, Pattern> blockedKeywordPatterns =
+            patternCache.getAll(
+                Arrays.asList(cfg.getStringList(KEY_CHECK_BLOCKED_KEYWORD_PATTERN)));
+        return comments.stream()
+            .map(comment -> validateComment(blockedKeywordPatterns, comment))
+            .filter(Optional::isPresent)
+            .map(Optional::get)
+            .collect(ImmutableList.toImmutableList());
+      }
+    } catch (NoSuchProjectException | ExecutionException e) {
+      throw new IllegalStateException("Exception validating comments in uploadvalidator", e);
+    }
+    return ImmutableList.of();
+  }
+
   @VisibleForTesting
   List<CommitValidationMessage> performValidation(
       Repository repo,
       RevCommit c,
       RevWalk revWalk,
-      ImmutableCollection<Pattern> blockedKeywordPartterns,
+      ImmutableCollection<Pattern> blockedKeywordPatterns,
       PluginConfig cfg)
       throws IOException, ExecutionException {
     List<CommitValidationMessage> messages = new LinkedList<>();
-    checkCommitMessageForBlockedKeywords(blockedKeywordPartterns, messages, c.getFullMessage());
+    checkCommitMessageForBlockedKeywords(blockedKeywordPatterns, messages, c.getFullMessage());
     Map<String, ObjectId> content = CommitUtils.getChangedContent(repo, c, revWalk);
     for (String path : content.keySet()) {
       ObjectLoader ol = revWalk.getObjectReader().open(content.get(path));
       try (InputStream in = ol.openStream()) {
-        if (RawText.isBinary(in) || contentTypeUtil.isBlacklistedBinaryContentType(ol, path, cfg)) {
+        if (RawText.isBinary(in) || contentTypeUtil.isForbiddenBinaryContentType(ol, path, cfg)) {
           continue;
         }
       }
-      checkFileForBlockedKeywords(blockedKeywordPartterns, messages, path, ol);
+      checkFileForBlockedKeywords(blockedKeywordPatterns, messages, path, ol);
     }
     return messages;
   }
 
+  private static Optional<CommentValidationFailure> validateComment(
+      ImmutableMap<String, Pattern>  blockedKeywordPatterns, CommentForValidation comment) {
+    // Uses HashSet data structure for de-duping found blocked keywords.
+    Set<String> findings = new LinkedHashSet<String>(
+        findBlockedKeywordsInString(blockedKeywordPatterns.values(), comment.getText()));
+    if (findings.isEmpty()) {
+      return Optional.empty();
+    }
+    return Optional.of(comment.failValidation(
+        String.format("banned words found in your comment (%s)", Iterables.toString(findings))));
+  }
+
   private static void checkCommitMessageForBlockedKeywords(
       ImmutableCollection<Pattern> blockedKeywordPatterns,
       List<CommitValidationMessage> messages,
@@ -183,7 +244,7 @@
   }
 
   private static void checkFileForBlockedKeywords(
-      ImmutableCollection<Pattern> blockedKeywordPartterns,
+      ImmutableCollection<Pattern> blockedKeywordPatterns,
       List<CommitValidationMessage> messages,
       String path,
       ObjectLoader ol)
@@ -193,24 +254,30 @@
       int line = 0;
       for (String l = br.readLine(); l != null; l = br.readLine()) {
         line++;
-        checkLineForBlockedKeywords(blockedKeywordPartterns, messages, path, line, l);
+        checkLineForBlockedKeywords(blockedKeywordPatterns, messages, path, line, l);
       }
     }
   }
 
-  private static void checkLineForBlockedKeywords(
-      ImmutableCollection<Pattern> blockedKeywordPartterns,
-      List<CommitValidationMessage> messages,
-      String path,
-      int lineNumber,
-      String line) {
+  private static List<String> findBlockedKeywordsInString(
+      ImmutableCollection<Pattern> blockedKeywordPatterns, String text) {
     List<String> found = new ArrayList<>();
-    for (Pattern p : blockedKeywordPartterns) {
-      Matcher matcher = p.matcher(line);
+    for (Pattern p : blockedKeywordPatterns) {
+      Matcher matcher = p.matcher(text);
       while (matcher.find()) {
         found.add(matcher.group());
       }
     }
+    return found;
+  }
+
+  private static void checkLineForBlockedKeywords(
+      ImmutableCollection<Pattern> blockedKeywordPatterns,
+      List<CommitValidationMessage> messages,
+      String path,
+      int lineNumber,
+      String line) {
+    List<String> found = findBlockedKeywordsInString(blockedKeywordPatterns, line);
     if (!found.isEmpty()) {
       messages.add(
           new CommitValidationMessage(
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ChangeEmailValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ChangeEmailValidator.java
index 7ee8f95..04525ec 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ChangeEmailValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ChangeEmailValidator.java
@@ -108,7 +108,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isAuthorActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
@@ -123,7 +123,7 @@
         }
       }
       if (isCommitterActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeUtil.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeUtil.java
index 517d245..bc207b5 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeUtil.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeUtil.java
@@ -74,11 +74,15 @@
     this.patternCache = patternCache;
   }
 
-  public boolean isBlacklistedBinaryContentType(ObjectLoader ol, String pathname, PluginConfig cfg)
+  public boolean isForbiddenBinaryContentType(ObjectLoader ol, String pathname, PluginConfig cfg)
       throws IOException, ExecutionException {
     try (InputStream is = ol.openStream()) {
-      return matchesAny(getContentType(is, pathname), getBinaryTypes(cfg));
+      String[] types = getBinaryTypes(cfg);
+      if (types.length > 0) {
+        return matchesAny(getContentType(is, pathname), types);
+      }
     }
+    return false;
   }
 
   public String getContentType(InputStream is, String pathname) throws IOException {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeValidator.java
index 0d8f2e4..df1b633 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeValidator.java
@@ -63,27 +63,27 @@
                     "Pushes of commits that contain files with blocked content "
                         + "types will be rejected."));
         bind(ProjectConfigEntry.class)
-            .annotatedWith(Exports.named(KEY_BLOCKED_CONTENT_TYPE_WHITELIST))
+            .annotatedWith(Exports.named(KEY_BLOCKED_CONTENT_TYPE_ALLOWLIST))
             .toInstance(
                 new ProjectConfigEntry(
-                    "Blocked Content Type Whitelist",
+                    "Blocked Content Type Allowlist",
                     "false",
                     ProjectConfigEntryType.BOOLEAN,
                     null,
                     false,
                     "If this option is checked, the entered content types are "
-                        + "interpreted as a whitelist. Otherwise commits that "
+                        + "interpreted as an allowlist. Otherwise commits that "
                         + "contain one of these content types will be rejected."));
       }
     };
   }
 
   public static final String KEY_BLOCKED_CONTENT_TYPE = "blockedContentType";
-  public static final String KEY_BLOCKED_CONTENT_TYPE_WHITELIST = "blockedContentTypeWhitelist";
+  public static final String KEY_BLOCKED_CONTENT_TYPE_ALLOWLIST = "blockedContentTypeWhitelist";
 
   @VisibleForTesting
-  static boolean isWhitelist(PluginConfig cfg) {
-    return cfg.getBoolean(KEY_BLOCKED_CONTENT_TYPE_WHITELIST, false);
+  static boolean isAllowList(PluginConfig cfg) {
+    return cfg.getBoolean(KEY_BLOCKED_CONTENT_TYPE_ALLOWLIST, false);
   }
 
   @VisibleForTesting
@@ -123,7 +123,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
@@ -135,7 +135,7 @@
                   receiveEvent.commit,
                   receiveEvent.revWalk,
                   getBlockedTypes(cfg),
-                  isWhitelist(cfg));
+                  isAllowList(cfg));
           if (!messages.isEmpty()) {
             throw new CommitValidationException("contains blocked content type", messages);
           }
@@ -149,7 +149,7 @@
 
   @VisibleForTesting
   List<CommitValidationMessage> performValidation(
-      Repository repo, RevCommit c, RevWalk revWalk, String[] blockedTypes, boolean whitelist)
+      Repository repo, RevCommit c, RevWalk revWalk, String[] blockedTypes, boolean allowList)
       throws IOException, ExecutionException {
     List<CommitValidationMessage> messages = new LinkedList<>();
     Map<String, ObjectId> content = CommitUtils.getChangedContent(repo, c, revWalk);
@@ -157,8 +157,8 @@
       ObjectLoader ol = repo.open(content.get(path));
       try (ObjectStream os = ol.openStream()) {
         String contentType = contentTypeUtil.getContentType(os, path);
-        if ((contentTypeUtil.matchesAny(contentType, blockedTypes) && !whitelist)
-            || (!contentTypeUtil.matchesAny(contentType, blockedTypes) && whitelist)) {
+        if ((contentTypeUtil.matchesAny(contentType, blockedTypes) && !allowList)
+            || (!contentTypeUtil.matchesAny(contentType, blockedTypes) && allowList)) {
           messages.add(
               new CommitValidationMessage(
                   "found blocked content type (" + contentType + ") in file: " + path, true));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidator.java
index 0b93e6d..8a9ea2b 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/DuplicatePathnameValidator.java
@@ -181,7 +181,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/FileExtensionValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/FileExtensionValidator.java
index 716f32e..4b29b1f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/FileExtensionValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/FileExtensionValidator.java
@@ -100,7 +100,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/FooterValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/FooterValidator.java
index 51d6a8e..b5b92c2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/FooterValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/FooterValidator.java
@@ -85,7 +85,7 @@
               receiveEvent.project.getNameKey(), pluginName);
       String[] requiredFooters = cfg.getStringList(KEY_REQUIRED_FOOTER);
       if (requiredFooters.length > 0
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/InvalidFilenameValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/InvalidFilenameValidator.java
index 846288f..c6371df 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/InvalidFilenameValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/InvalidFilenameValidator.java
@@ -95,7 +95,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/InvalidLineEndingValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/InvalidLineEndingValidator.java
index 2f602b0..aa72ee0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/InvalidLineEndingValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/InvalidLineEndingValidator.java
@@ -105,7 +105,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
@@ -134,7 +134,7 @@
     for (String path : content.keySet()) {
       ObjectLoader ol = revWalk.getObjectReader().open(content.get(path));
       try (InputStream in = ol.openStream()) {
-        if (RawText.isBinary(in) || contentTypeUtil.isBlacklistedBinaryContentType(ol, path, cfg)) {
+        if (RawText.isBinary(in) || contentTypeUtil.isForbiddenBinaryContentType(ol, path, cfg)) {
           continue;
         }
       }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/MaxPathLengthValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/MaxPathLengthValidator.java
index 2216451..a3db8f4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/MaxPathLengthValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/MaxPathLengthValidator.java
@@ -89,7 +89,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/SubmoduleValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/SubmoduleValidator.java
index 08d4d3f..f96d325 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/SubmoduleValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/SubmoduleValidator.java
@@ -92,7 +92,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/SymlinkValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/SymlinkValidator.java
index 7b7d893..85a8151 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/SymlinkValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/SymlinkValidator.java
@@ -93,7 +93,7 @@
           cfgFactory.getFromProjectConfigWithInheritance(
               receiveEvent.project.getNameKey(), pluginName);
       if (isActive(cfg)
-          && validatorConfig.isEnabledForRef(
+          && validatorConfig.isEnabled(
               receiveEvent.user,
               receiveEvent.getProjectNameKey(),
               receiveEvent.getRefName(),
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 15b64fe..d1c5f7e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ValidatorConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/ValidatorConfig.java
@@ -14,9 +14,13 @@
 
 package com.googlesource.gerrit.plugins.uploadvalidator;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.entities.AccessSection;
 import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroup.UUID;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.annotations.Exports;
@@ -33,7 +37,6 @@
 import java.util.Arrays;
 import java.util.Optional;
 import java.util.regex.Pattern;
-import java.util.stream.Stream;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -79,15 +82,29 @@
     this.groupByNameFinder = groupByNameFinder;
   }
 
-  public boolean isEnabledForRef(
-      IdentifiedUser user, Project.NameKey projectName, String refName, String validatorOp) {
+  /**
+   * Checks whether the provided params match with the plugin configuration to verify it is enabled.
+   *
+   * @param user A Nullable field identifying the user defined on the ref. Passing null will ignore
+   *     user checks.
+   * @param projectName Identifier for the project name on the ref.
+   * @param refName Identifier for the ref name.
+   * @param validatorOp The name of the validator operation. Can be used in skip validation config.
+   * @return boolean indicating if the ref is enabled for validation.
+   */
+  public boolean isEnabled(
+      @Nullable IdentifiedUser user,
+      Project.NameKey projectName,
+      String refName,
+      String validatorOp) {
     PluginConfig conf = configFactory.get(projectName);
 
     return conf != null
         && isValidConfig(conf, projectName)
         && (activeForRef(conf, refName))
-        && (activeForEmail(conf, user.getAccount().preferredEmail()))
+        && (user == null || activeForEmail(conf, user.getAccount().preferredEmail()))
         && (activeForProject(conf, projectName.get()))
+        && (!isDisabledValidatorOp(conf, validatorOp))
         && (!hasCriteria(conf, "skipGroup")
             || !canSkipValidation(conf, validatorOp)
             || !canSkipRef(conf, refName)
@@ -119,6 +136,11 @@
     return config.getStringList(criteria).length > 0;
   }
 
+  private boolean isDisabledValidatorOp(PluginConfig config, String validatorOp) {
+    String[] c = config.getStringList("disabledValidation");
+    return Arrays.asList(c).contains(validatorOp);
+  }
+
   private boolean activeForProject(PluginConfig config, String project) {
     return matchCriteria(config, "project", project, true, false);
   }
@@ -165,14 +187,16 @@
     return Pattern.matches(pattern, value);
   }
 
-  private boolean canSkipGroup(PluginConfig conf, IdentifiedUser user) {
-    if (!user.isIdentifiedUser()) {
+  private boolean canSkipGroup(PluginConfig conf, @Nullable IdentifiedUser user) {
+    if (user == null || !user.isIdentifiedUser()) {
       return false;
     }
 
-    Stream<AccountGroup.UUID> skipGroups =
-        Arrays.stream(conf.getStringList("skipGroup")).map(this::groupUUID);
-    return user.asIdentifiedUser().getEffectiveGroups().containsAnyOf(skipGroups::iterator);
+    ImmutableList<UUID> skipGroups =
+        Arrays.stream(conf.getStringList("skipGroup"))
+            .map(this::groupUUID)
+            .collect(toImmutableList());
+    return user.asIdentifiedUser().getEffectiveGroups().containsAnyOf(skipGroups);
   }
 
   private AccountGroup.UUID groupUUID(String groupNameOrUUID) {
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 86f2015..878b272 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -182,16 +182,15 @@
 plugin.@PLUGIN@.blockedContentTypeWhitelist
 :    Blocked content type whitelist.
 
-    If this option is checked, the entered content types are interpreted as
-    a whitelist. Otherwise the entered content types are interpreted as a
-    blacklist and commits that contains one of these content types will be
-    rejected.
+    If this option is checked, the entered content types are allowed.
+    Otherwise the entered content types are forbidden and commits that
+    contains one of these content types will be rejected.
 
     There must be specified at least one blocked content type pattern,
     otherwise this option will be ignored.
 
     The default value is false. This means the entered content types are
-    interpreted as a blacklist.
+    forbidden.
 
 plugin.@PLUGIN@.rejectDuplicatePathnames
 :    Reject duplicate pathnames.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeValidatorTest.java
index 6477189..5ef6227 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeValidatorTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ContentTypeValidatorTest.java
@@ -79,7 +79,7 @@
   }
 
   @Test
-  public void testWhitelist() throws Exception {
+  public void testAllowlist() throws Exception {
     String[] patterns = new String[] {"application/pdf", "application/xml"};
 
     try (RevWalk rw = new RevWalk(repo)) {
@@ -93,7 +93,7 @@
   @Test
   public void validatorBehaviorWhenConfigEmpty() {
     assertThat(ContentTypeValidator.isActive(EMPTY_PLUGIN_CONFIG)).isFalse();
-    assertThat(ContentTypeValidator.isWhitelist(EMPTY_PLUGIN_CONFIG)).isFalse();
+    assertThat(ContentTypeValidator.isAllowList(EMPTY_PLUGIN_CONFIG)).isFalse();
   }
 
   private RevCommit makeCommit(RevWalk rw) throws IOException, GitAPIException {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/EmailAwareValidatorConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/EmailAwareValidatorConfigTest.java
index 64184cc..8c00744 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/EmailAwareValidatorConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/EmailAwareValidatorConfigTest.java
@@ -30,7 +30,7 @@
     ValidatorConfig config =
         getConfig("[plugin \"uploadvalidator\"]\n" + "blockedFileExtension = jar");
 
-    assertThat(config.isEnabledForRef(anyUser, projectName, "anyRef", "blockedFileExtension"))
+    assertThat(config.isEnabled(anyUser, projectName, "anyRef", "blockedFileExtension"))
         .isTrue();
   }
 
@@ -40,7 +40,7 @@
     ValidatorConfig config =
         getConfig("[plugin \"uploadvalidator\"]\n" + "blockedFileExtension = jar");
 
-    assertThat(config.isEnabledForRef(missingEmail, projectName, "anyRef", "blockedFileExtension"))
+    assertThat(config.isEnabled(missingEmail, projectName, "anyRef", "blockedFileExtension"))
         .isTrue();
   }
 
@@ -55,7 +55,7 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isTrue();
   }
@@ -69,7 +69,7 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isFalse();
   }
@@ -84,11 +84,11 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isFalse();
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 exampleOrgUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isTrue();
   }
@@ -103,7 +103,7 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 missingEmail, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isFalse();
   }
@@ -120,14 +120,14 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 exampleOrgUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isTrue();
     assertThat(
-            config.isEnabledForRef(xUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
+            config.isEnabled(xUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isTrue();
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isFalse();
   }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/FakeConfigFactory.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/FakeConfigFactory.java
index 79f4cc5..0aaae0f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/FakeConfigFactory.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/FakeConfigFactory.java
@@ -34,9 +34,9 @@
   @Override
   public PluginConfig get(NameKey projectName) {
     if (this.projectName.equals(projectName)) {
-      return new PluginConfig("uploadvalidator", config);
+      return PluginConfig.create("uploadvalidator", config, null);
     }
 
-    return new PluginConfig("uploadvalidator", new Config());
+    return PluginConfig.create("uploadvalidator", new Config(), null);
   }
 }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ProjectAwareValidatorConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ProjectAwareValidatorConfigTest.java
index 4c5df11..c968ec5 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ProjectAwareValidatorConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/ProjectAwareValidatorConfigTest.java
@@ -30,7 +30,7 @@
     ValidatorConfig config =
         getConfig("[plugin \"uploadvalidator\"]\n" + "blockedFileExtension = jar", projectName);
 
-    assertThat(config.isEnabledForRef(anyUser, projectName, "anyRef", "blockedFileExtension"))
+    assertThat(config.isEnabled(anyUser, projectName, "anyRef", "blockedFileExtension"))
         .isTrue();
   }
 
@@ -43,7 +43,7 @@
                 + "   blockedFileExtension = jar",
             projectName);
 
-    assertThat(config.isEnabledForRef(anyUser, projectName, "anyRef", "blockedFileExtension"))
+    assertThat(config.isEnabled(anyUser, projectName, "anyRef", "blockedFileExtension"))
         .isTrue();
   }
 
@@ -56,7 +56,7 @@
                 + "   blockedFileExtension = jar",
             projectName);
 
-    assertThat(config.isEnabledForRef(anyUser, projectName, "anyRef", "blockedFileExtension"))
+    assertThat(config.isEnabled(anyUser, projectName, "anyRef", "blockedFileExtension"))
         .isFalse();
   }
 
@@ -70,9 +70,9 @@
     ValidatorConfig config = getConfig(configString, projectName);
     ValidatorConfig config2 = getConfig(configString, otherNameKey);
 
-    assertThat(config.isEnabledForRef(anyUser, projectName, "anyRef", "blockedFileExtension"))
+    assertThat(config.isEnabled(anyUser, projectName, "anyRef", "blockedFileExtension"))
         .isTrue();
-    assertThat(config2.isEnabledForRef(anyUser, otherNameKey, "anyRef", "blockedFileExtension"))
+    assertThat(config2.isEnabled(anyUser, otherNameKey, "anyRef", "blockedFileExtension"))
         .isFalse();
   }
 
@@ -89,11 +89,11 @@
     ValidatorConfig config2 = getConfig(configString, anotherNameKey);
     ValidatorConfig config3 = getConfig(configString, someOtherNameKey);
 
-    assertThat(config.isEnabledForRef(anyUser, projectName, "anyRef", "blockedFileExtension"))
+    assertThat(config.isEnabled(anyUser, projectName, "anyRef", "blockedFileExtension"))
         .isTrue();
-    assertThat(config2.isEnabledForRef(anyUser, anotherNameKey, "anyRef", "blockedFileExtension"))
+    assertThat(config2.isEnabled(anyUser, anotherNameKey, "anyRef", "blockedFileExtension"))
         .isTrue();
-    assertThat(config3.isEnabledForRef(anyUser, someOtherNameKey, "anyRef", "blockedFileExtension"))
+    assertThat(config3.isEnabled(anyUser, someOtherNameKey, "anyRef", "blockedFileExtension"))
         .isFalse();
   }
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/RefAwareValidatorConfigTest.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/RefAwareValidatorConfigTest.java
index 0ef4f4b..f74d346 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/RefAwareValidatorConfigTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/RefAwareValidatorConfigTest.java
@@ -30,7 +30,7 @@
     ValidatorConfig config =
         getConfig("[plugin \"uploadvalidator\"]\n" + "blockedFileExtension = jar");
 
-    assertThat(config.isEnabledForRef(anyUser, projectName, "anyRef", "blockedFileExtension"))
+    assertThat(config.isEnabled(anyUser, projectName, "anyRef", "blockedFileExtension"))
         .isTrue();
   }
 
@@ -43,7 +43,7 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isTrue();
   }
@@ -57,7 +57,7 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/anyref", "blockedFileExtension"))
         .isFalse();
   }
@@ -71,11 +71,11 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/anotherref", "blockedFileExtension"))
         .isFalse();
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/mybranch123", "blockedFileExtension"))
         .isTrue();
   }
@@ -90,15 +90,15 @@
                 + "   blockedFileExtension = jar");
 
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/branch1", "blockedFileExtension"))
         .isTrue();
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/branch2", "blockedFileExtension"))
         .isTrue();
     assertThat(
-            config.isEnabledForRef(
+            config.isEnabled(
                 anyUser, projectName, "refs/heads/branch3", "blockedFileExtension"))
         .isFalse();
   }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/SkipValidationTest.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/SkipValidationTest.java
index e928b34..b46ac86 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/SkipValidationTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/SkipValidationTest.java
@@ -31,7 +31,7 @@
     ValidatorConfig validatorConfig =
         new ValidatorConfig(new FakeConfigFactory(projectName, ""), new FakeGroupByNameFinder());
 
-    assertThat(validatorConfig.isEnabledForRef(anyUser, projectName, "anyRef", "anyOp")).isTrue();
+    assertThat(validatorConfig.isEnabled(anyUser, projectName, "anyRef", "anyOp")).isTrue();
   }
 
   @Test
@@ -47,7 +47,7 @@
             new FakeConfigFactory(projectName, config), new FakeGroupByNameFinder());
 
     assertThat(
-            validatorConfig.isEnabledForRef(
+            validatorConfig.isEnabled(
                 new FakeUserProvider("testGroup", "yetAnotherGroup").get(),
                 projectName,
                 "anyRef",
@@ -70,7 +70,7 @@
                 TimeUtil.nowTs()));
 
     assertThat(
-            validatorConfig.isEnabledForRef(
+            validatorConfig.isEnabled(
                 new FakeUserProvider("testGroupId").get(), projectName, "anyRef", "testOp"))
         .isFalse();
   }
@@ -88,7 +88,7 @@
             new FakeConfigFactory(projectName, config), new FakeGroupByNameFinder());
 
     assertThat(
-            validatorConfig.isEnabledForRef(
+            validatorConfig.isEnabled(
                 new FakeUserProvider("yetAnotherGroup").get(), projectName, "anyRef", "testOp"))
         .isTrue();
   }
@@ -105,7 +105,7 @@
         new ValidatorConfig(
             new FakeConfigFactory(projectName, config), new FakeGroupByNameFinder());
 
-    assertThat(validatorConfig.isEnabledForRef(anyUser, projectName, "anyRef", "anotherOp"))
+    assertThat(validatorConfig.isEnabled(anyUser, projectName, "anyRef", "anotherOp"))
         .isTrue();
   }
 
@@ -122,7 +122,7 @@
             new FakeConfigFactory(projectName, config), new FakeGroupByNameFinder());
 
     assertThat(
-            validatorConfig.isEnabledForRef(
+            validatorConfig.isEnabled(
                 new FakeUserProvider("testGroup").get(), projectName, "refs/heads/myref", "testOp"))
         .isFalse();
   }
@@ -140,7 +140,7 @@
             new FakeConfigFactory(projectName, config), new FakeGroupByNameFinder());
 
     assertThat(
-            validatorConfig.isEnabledForRef(
+            validatorConfig.isEnabled(
                 anyUser, projectName, "refs/heads/anotherRef", "testOp"))
         .isTrue();
   }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/TestUtils.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/TestUtils.java
index 22f95f0..f18295e 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/TestUtils.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/TestUtils.java
@@ -43,7 +43,8 @@
 import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
 
 public class TestUtils {
-  public static final PluginConfig EMPTY_PLUGIN_CONFIG = new PluginConfig("", new Config());
+  public static final PluginConfig EMPTY_PLUGIN_CONFIG =
+      PluginConfig.create("", new Config(), null);
 
   protected static final byte[] EMPTY_CONTENT = "".getBytes(Charsets.UTF_8);
 
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/UploadValidatorIT.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/UploadValidatorIT.java
new file mode 100644
index 0000000..a558336
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/UploadValidatorIT.java
@@ -0,0 +1,164 @@
+// Copyright (C) 2021 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 static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.entities.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.changes.DraftInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.inject.Inject;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.junit.Before;
+import org.junit.Test;
+
+@TestPlugin(
+    name = "uploadvalidator",
+    sysModule = "com.googlesource.gerrit.plugins.uploadvalidator.Module")
+public class UploadValidatorIT extends LightweightPluginDaemonTest {
+
+  @Inject ProjectOperations projectOperations;
+
+  TestRepository<InMemoryRepository> clone;
+
+  void pushConfig(String config) throws Exception {
+    TestRepository<InMemoryRepository> allProjectRepo = cloneProject(allProjects, admin);
+    GitUtil.fetch(allProjectRepo, RefNames.REFS_CONFIG + ":config");
+    allProjectRepo.reset("config");
+    PushOneCommit push =
+        pushFactory.create(admin.newIdent(), allProjectRepo, "Subject", "project.config", config);
+    PushOneCommit.Result res = push.to(RefNames.REFS_CONFIG);
+    res.assertOkStatus();
+  }
+
+  @Before
+  public void setup() throws Exception {
+
+    pushConfig(
+        Joiner.on("\n")
+            .join(
+                "[plugin \"uploadvalidator\"]",
+                "    blockedFileExtension = jar",
+                "    blockedFileExtension = .zip",
+                "    blockedKeywordPattern = secr3t",
+                "    invalidFilenamePattern = [%:@]",
+                "    rejectWindowsLineEndings = true",
+                "    maxPathLength = 20",
+                "    rejectDuplicatePathnames = true"));
+
+    projectOperations
+        .project(allProjects)
+        .forUpdate()
+        .add(allow(Permission.READ).ref("refs/*").group(SystemGroupBackend.REGISTERED_USERS))
+        .add(allow(Permission.CREATE).ref("refs/*").group(SystemGroupBackend.REGISTERED_USERS))
+        .add(allow(Permission.PUSH).ref("refs/*").group(SystemGroupBackend.REGISTERED_USERS))
+        .update();
+
+    clone = GitUtil.cloneProject(project, registerRepoConnection(project, admin));
+  }
+
+  @Test
+  public void testFileExtension() throws Exception {
+    pushFactory
+        .create(admin.newIdent(), clone, "Subject", "file.jar", "content")
+        .to("refs/heads/master")
+        .assertErrorStatus("blocked file extensions");
+
+    pushFactory
+        .create(admin.newIdent(), clone, "Subject", "file.zip", "content")
+        .to("refs/heads/master")
+        .assertErrorStatus("blocked file extensions");
+  }
+
+  @Test
+  public void testKeywordInComment() throws Exception {
+    PushOneCommit.Result r1 = createChange("Subject", "file.txt", "content");
+    DraftInput in = new DraftInput();
+    in.message = "the password is secr3t ! ";
+    in.path = "file.txt";
+    gApi.changes().id(r1.getChangeId()).revision("current").createDraft(in);
+
+    ReviewInput reviewIn = new ReviewInput();
+    reviewIn.drafts = DraftHandling.PUBLISH;
+    BadRequestException e =
+        assertThrows(
+            BadRequestException.class,
+            () -> gApi.changes().id(r1.getChangeId()).revision("current").review(reviewIn));
+    assertThat(e.getMessage()).contains("banned words");
+  }
+
+  @Test
+  public void testKeywordInFile() throws Exception {
+    pushFactory
+        .create(admin.newIdent(), clone, "Subject", "file.txt", "blah secr3t blah")
+        .to("refs/heads/master")
+        .assertErrorStatus("blocked keywords");
+  }
+
+  @Test
+  public void testFilenamePattern() throws Exception {
+    pushFactory
+        .create(admin.newIdent(), clone, "Subject", "f:le.txt", "content")
+        .to("refs/heads/master")
+        .assertErrorStatus("invalid filename");
+  }
+
+  @Test
+  public void testWindowsLineEndings() throws Exception {
+    pushFactory
+        .create(admin.newIdent(), clone, "Subject", "win.ini", "content\r\nline2\r\n")
+        .to("refs/heads/master")
+        .assertErrorStatus("Windows line ending");
+  }
+
+  @Test
+  public void testPathLength() throws Exception {
+    pushFactory
+        .create(
+            admin.newIdent(),
+            clone,
+            "Subject",
+            "123456789012345678901234567890.txt",
+            "content\nline2\n")
+        .to("refs/heads/master")
+        .assertErrorStatus("too long paths");
+  }
+
+  @Test
+  public void testUniqueName() throws Exception {
+    pushFactory
+        .create(
+            admin.newIdent(),
+            clone,
+            "Subject",
+            ImmutableMap.of("a.txt", "content\nline2\n", "A.TXT", "content"))
+        .to("refs/heads/master")
+        .assertErrorStatus("duplicate pathnames");
+  }
+}