Merge "Throw specific exception caused by invalid email regex in config" into stable-3.3
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 288afcf..b69fa04 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/BlockedKeywordValidator.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/BlockedKeywordValidator.java
@@ -221,15 +221,18 @@
}
private static Optional<CommentValidationFailure> validateComment(
- ImmutableMap<String, Pattern> blockedKeywordPatterns, CommentForValidation comment) {
+ 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()));
+ 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))));
+ return Optional.of(
+ comment.failValidation(
+ String.format(
+ "banned words found in your comment (%s)", Iterables.toString(findings))));
}
private static void checkCommitMessageForBlockedKeywords(
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 e15ecb2..46ff9e1 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/Module.java
@@ -24,6 +24,7 @@
install(new PatternCacheModule());
install(ContentTypeUtil.module());
+ install(PluginConfigValidator.module());
install(FooterValidator.module());
install(MaxPathLengthValidator.module());
install(FileExtensionValidator.module());
diff --git a/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/PluginConfigValidator.java b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/PluginConfigValidator.java
new file mode 100644
index 0000000..d9cc872
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/uploadvalidator/PluginConfigValidator.java
@@ -0,0 +1,194 @@
+// Copyright (C) 2022 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.FluentLogger;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.events.CommitReceivedEvent;
+import com.google.gerrit.server.git.validators.CommitValidationException;
+import com.google.gerrit.server.git.validators.CommitValidationListener;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import com.google.gerrit.server.git.validators.ValidationMessage;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.server.project.ProjectLevelConfig;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+
+public class PluginConfigValidator implements CommitValidationListener {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private final String pluginName;
+
+ @Inject
+ PluginConfigValidator(@PluginName String pluginName) {
+ this.pluginName = pluginName;
+ }
+
+ public static AbstractModule module() {
+ return new AbstractModule() {
+ @Override
+ public void configure() {
+ DynamicSet.bind(binder(), CommitValidationListener.class).to(PluginConfigValidator.class);
+ }
+ };
+ }
+
+ @Override
+ public ImmutableList<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+ throws CommitValidationException {
+ String fileName = ProjectConfig.PROJECT_CONFIG;
+
+ try {
+ if (!receiveEvent.refName.equals(RefNames.REFS_CONFIG)) {
+ // the project.config file in refs/meta/config was not modified, so no need to
+ // modify
+ return ImmutableList.of();
+ }
+
+ ProjectLevelConfig.Bare cfg = loadConfig(receiveEvent, fileName);
+ // Project Level Config looks at what's in the refs/meta/config file.
+ ImmutableList<CommitValidationMessage> validationMessages =
+ validateConfig(fileName, cfg.getConfig());
+ if (!validationMessages.isEmpty()) {
+ throw new CommitValidationException(
+ exceptionMessage(fileName, cfg.getRevision()), validationMessages);
+ }
+ return ImmutableList.of();
+ } catch (IOException e) {
+ String errorMessage =
+ String.format(
+ "failed to validate file %s for revision %s in ref %s of project %s",
+ fileName,
+ receiveEvent.commit.getName(),
+ RefNames.REFS_CONFIG,
+ receiveEvent.project.getNameKey());
+ logger.atSevere().withCause(e).log("%s", errorMessage);
+ throw new CommitValidationException(errorMessage, e);
+ }
+ }
+
+ /**
+ * Loads the configuration from the file and revision.
+ *
+ * @param receiveEvent the receive event
+ * @param fileName the name of the config file
+ * @return the loaded configuration
+ * @throws CommitValidationException thrown if the configuration is invalid and cannot be parsed
+ */
+ private ProjectLevelConfig.Bare loadConfig(CommitReceivedEvent receiveEvent, String fileName)
+ throws CommitValidationException, IOException {
+ ProjectLevelConfig.Bare cfg = new ProjectLevelConfig.Bare(fileName);
+ try {
+ cfg.load(receiveEvent.project.getNameKey(), receiveEvent.revWalk, receiveEvent.commit);
+ } catch (ConfigInvalidException e) {
+ throw new CommitValidationException(
+ exceptionMessage(fileName, receiveEvent.commit),
+ new CommitValidationMessage(e.getMessage(), ValidationMessage.Type.ERROR));
+ }
+ return cfg;
+ }
+
+ /**
+ * Creates the message for {@link CommitValidationException}s that are thrown for validation
+ * errors in the project-level code-owners configuration.
+ *
+ * @param fileName the name of the config file
+ * @param revision the revision in which the configuration is invalid
+ * @return the created exception message
+ */
+ private static String exceptionMessage(String fileName, ObjectId revision) {
+ return String.format("invalid %s file in revision %s", fileName, revision.getName());
+ }
+
+ /**
+ * Validates the project.config for uploadvalidator
+ *
+ * @param fileName the name of the config file
+ * @param cfg the project-level code-owners configuration that should be validated
+ * @return list of messages with validation issues, empty list if there are no issues
+ */
+ public ImmutableList<CommitValidationMessage> validateConfig(String fileName, Config cfg) {
+ ImmutableList.Builder<CommitValidationMessage> validationMessages = ImmutableList.builder();
+ validationMessages.addAll(
+ validateRegex(fileName, cfg, ChangeEmailValidator.KEY_ALLOWED_AUTHOR_EMAIL_PATTERN));
+ validationMessages.addAll(
+ validateRegex(fileName, cfg, ChangeEmailValidator.KEY_ALLOWED_COMMITTER_EMAIL_PATTERN));
+ validationMessages.addAll(
+ validateInteger(fileName, cfg, MaxPathLengthValidator.KEY_MAX_PATH_LENGTH));
+ return validationMessages.build();
+ }
+
+ /**
+ * Validates the regex
+ *
+ * @param fileName the name of the config file
+ * @param cfg the project.config to validate
+ * @return list of messages with validation issues, empty list if there are no issues
+ */
+ @VisibleForTesting
+ public ImmutableList<CommitValidationMessage> validateRegex(
+ String fileName, Config cfg, String validatorKey) {
+
+ String pattern = cfg.getString("plugin", pluginName, validatorKey);
+
+ if (pattern != null) {
+ try {
+ Pattern.compile(pattern);
+ } catch (PatternSyntaxException e) {
+ return ImmutableList.of(
+ new CommitValidationMessage(
+ String.format(
+ "The value '%s' configured in %s (parameter %s.%s) is invalid.",
+ pattern, fileName, pluginName, validatorKey),
+ ValidationMessage.Type.ERROR));
+ }
+ }
+ return ImmutableList.of();
+ }
+
+ /**
+ * Validates an integer-only field
+ *
+ * @param fileName the name of the config file
+ * @param cfg the project.config to validate
+ * @return list of messages with validation issues, empty list if there are no issues
+ */
+ @VisibleForTesting
+ public ImmutableList<CommitValidationMessage> validateInteger(
+ String fileName, Config cfg, String validatorKey) {
+
+ String value = cfg.getString("plugin", pluginName, validatorKey);
+
+ if (Ints.tryParse(value) == null) {
+ return ImmutableList.of(
+ new CommitValidationMessage(
+ String.format(
+ "The value '%s' configured in %s (parameter %s.%s) is invalid.",
+ value, fileName, pluginName, validatorKey),
+ ValidationMessage.Type.ERROR));
+ }
+ return ImmutableList.of();
+ }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/PluginConfigValidatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/PluginConfigValidatorTest.java
new file mode 100644
index 0000000..685abb8
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/uploadvalidator/PluginConfigValidatorTest.java
@@ -0,0 +1,93 @@
+// Copyright (C) 2022 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.googlesource.gerrit.plugins.uploadvalidator.ChangeEmailValidator.KEY_ALLOWED_AUTHOR_EMAIL_PATTERN;
+import static com.googlesource.gerrit.plugins.uploadvalidator.ChangeEmailValidator.KEY_ALLOWED_COMMITTER_EMAIL_PATTERN;
+import static com.googlesource.gerrit.plugins.uploadvalidator.MaxPathLengthValidator.KEY_MAX_PATH_LENGTH;
+import static com.google.gerrit.server.project.ProjectConfig.PROJECT_CONFIG;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.server.git.validators.CommitValidationMessage;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PluginConfigValidatorTest {
+ private static final String PLUGIN_NAME = "uploadvalidator";
+ private static final String ILLEGAL_REGEX = "*";
+ private static final String LEGAL_REGEX = ".*";
+ private static final String LEGAL_PATH_LENGTH = "100";
+ private static final String ILLEGAL_PATH_LENGTH = "10xi";
+
+ private PluginConfigValidator configValidator;
+ private Config cfg;
+
+ @Before
+ public void setUp() throws Exception {
+ configValidator = new PluginConfigValidator(PLUGIN_NAME);
+ cfg = new Config();
+ }
+
+ @Test
+ public void hasLegalAuthorEmailPattern_noMessages() throws Exception {
+ cfg.setString("plugin", PLUGIN_NAME, KEY_ALLOWED_AUTHOR_EMAIL_PATTERN, LEGAL_REGEX);
+ ImmutableList<CommitValidationMessage> messages =
+ configValidator.validateRegex(PROJECT_CONFIG, cfg, KEY_ALLOWED_AUTHOR_EMAIL_PATTERN);
+ assertThat(messages).isEmpty();
+ }
+
+ @Test
+ public void hasIllegalAuthorEmailPattern_messages() throws Exception {
+ cfg.setString("plugin", PLUGIN_NAME, KEY_ALLOWED_AUTHOR_EMAIL_PATTERN, ILLEGAL_REGEX);
+ ImmutableList<CommitValidationMessage> messages =
+ configValidator.validateRegex(PROJECT_CONFIG, cfg, KEY_ALLOWED_AUTHOR_EMAIL_PATTERN);
+ assertThat(messages).isNotEmpty();
+ }
+
+ @Test
+ public void hasLegalCommitterEmailPattern_noMessages() throws Exception {
+ cfg.setString("plugin", PLUGIN_NAME, KEY_ALLOWED_COMMITTER_EMAIL_PATTERN, LEGAL_REGEX);
+ ImmutableList<CommitValidationMessage> messages =
+ configValidator.validateRegex(PROJECT_CONFIG, cfg, KEY_ALLOWED_COMMITTER_EMAIL_PATTERN);
+ assertThat(messages).isEmpty();
+ }
+
+ @Test
+ public void hasIllegalCommitterEmailPattern_messages() throws Exception {
+ cfg.setString("plugin", PLUGIN_NAME, KEY_ALLOWED_COMMITTER_EMAIL_PATTERN, ILLEGAL_REGEX);
+ ImmutableList<CommitValidationMessage> messages =
+ configValidator.validateRegex(PROJECT_CONFIG, cfg, KEY_ALLOWED_COMMITTER_EMAIL_PATTERN);
+ assertThat(messages).isNotEmpty();
+ }
+
+ @Test
+ public void hasLegalMaxPathLength_noMessages() throws Exception {
+ cfg.setString("plugin", PLUGIN_NAME, KEY_MAX_PATH_LENGTH, LEGAL_PATH_LENGTH);
+ ImmutableList<CommitValidationMessage> messages =
+ configValidator.validateInteger(PROJECT_CONFIG, cfg, KEY_MAX_PATH_LENGTH);
+ assertThat(messages).isEmpty();
+ }
+
+ @Test
+ public void hasIllegalMaxPathLength_messages() throws Exception {
+ cfg.setString("plugin", PLUGIN_NAME, KEY_MAX_PATH_LENGTH, ILLEGAL_PATH_LENGTH);
+
+ ImmutableList<CommitValidationMessage> messages =
+ configValidator.validateInteger(PROJECT_CONFIG, cfg, KEY_MAX_PATH_LENGTH);
+ assertThat(messages).isNotEmpty();
+ }
+}
\ No newline at end of file