Add allowedUrlPattern global config Provide admins ability to allow only desired remote urls that match configured patterns. Change-Id: I7856e03829978b75bd7e2461bf498a27b5169480
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/Configuration.java index eb54ec2..3c3f67b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/webhooks/Configuration.java +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/Configuration.java
@@ -14,15 +14,23 @@ package com.googlesource.gerrit.plugins.webhooks; +import com.google.common.flogger.FluentLogger; import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.server.config.PluginConfig; import com.google.gerrit.server.config.PluginConfigFactory; import com.google.inject.Inject; import com.google.inject.Singleton; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; @Singleton public class Configuration { + private static final FluentLogger log = FluentLogger.forEnclosingClass(); + public static final String THREAD_POOL_SIZE = "threadPoolSize"; + public static final String ALLOWED_URL_PATTERN = "allowedUrlPattern"; public static final String ALLOWED_EVENT = "allowedEvent"; public static final String MAX_ALLOWED_CONNECTION_TIMEOUT = "maxAllowedConnectionTimeout"; public static final String MAX_ALLOWED_SOCKET_TIMEOUT = "maxAllowedSocketTimeout"; @@ -42,6 +50,7 @@ private final int threadPoolSize; private final boolean sslVerify; private final String[] allowedEvents; + private final String[] allowedUrlPatterns; private final int maxAllowedConnectionTimeout; private final int maxAllowedSocketTimeout; private final int maxAllowedTries; @@ -57,6 +66,7 @@ threadPoolSize = cfg.getInt(THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE); sslVerify = cfg.getBoolean(RemoteConfig.SSL_VERIFY, DEFAULT_SSL_VERIFY); allowedEvents = cfg.getStringList(ALLOWED_EVENT); + allowedUrlPatterns = cfg.getStringList(ALLOWED_URL_PATTERN); maxAllowedConnectionTimeout = cfg.getInt(MAX_ALLOWED_CONNECTION_TIMEOUT, 0); maxAllowedSocketTimeout = cfg.getInt(MAX_ALLOWED_SOCKET_TIMEOUT, 0); maxAllowedTries = cfg.getInt(MAX_ALLOWED_TRIES, 0); @@ -91,6 +101,19 @@ return allowedEvents; } + public List<Pattern> getAllowedUrlPatterns() { + List<Pattern> patterns = new ArrayList<>(); + for (String regex : allowedUrlPatterns) { + try { + patterns.add(Pattern.compile(regex)); + } catch (PatternSyntaxException e) { + log.atWarning().withCause(e).log( + "Invalid webhook allowed URL pattern '%s' configured", regex); + } + } + return patterns; + } + public int getMaxAllowedConnectionTimeout() { return maxAllowedConnectionTimeout; }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/webhooks/EventHandler.java b/src/main/java/com/googlesource/gerrit/plugins/webhooks/EventHandler.java index 986c84e..3cf1f9c 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/webhooks/EventHandler.java +++ b/src/main/java/com/googlesource/gerrit/plugins/webhooks/EventHandler.java
@@ -30,6 +30,7 @@ class EventHandler implements EventListener { private static final FluentLogger log = FluentLogger.forEnclosingClass(); + private final Configuration global; private final PluginConfigFactory configFactory; private final String pluginName; private final RemoteConfig.Factory remoteFactory; @@ -37,10 +38,12 @@ @Inject EventHandler( + Configuration global, PluginConfigFactory configFactory, @PluginName String pluginName, RemoteConfig.Factory remoteFactory, PostTask.Factory taskFactory) { + this.global = global; this.configFactory = configFactory; this.pluginName = pluginName; this.remoteFactory = remoteFactory; @@ -72,8 +75,18 @@ log.atWarning().log("remote.%s.url not defined, skipping this remote", name); continue; } - + if (!isUrlAllowed(remote.getUrl())) { + log.atWarning().log( + "remote.%s.url does not match any allowed URL patterns, skipping this remote", name); + continue; + } taskFactory.create(projectEvent, remote).schedule(); } } + + private boolean isUrlAllowed(String url) { + return global.getAllowedUrlPatterns().isEmpty() + || global.getAllowedUrlPatterns().stream() + .anyMatch(pattern -> pattern.matcher(url).matches()); + } }
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index 280b8e7..f4b3250 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -71,6 +71,11 @@ Multiple event types can be provided. If not specified, then all event types are allowed. +@PLUGIN@.allowedUrlPattern +: A regex that remote URLs in the plugin config must match. If multiple + patterns are provided, the remote URL must match at least one of them. + If not specified, then all remote URLs are allowed. + @PLUGIN@.maxAllowedConnectionTimeout : Maximum allowed value for the connection timeout. If a value greater than this is configured in the @PLUGIN@.config, this value will be chosen
diff --git a/src/test/java/com/googlesource/gerrit/plugins/webhooks/EventHandlerTest.java b/src/test/java/com/googlesource/gerrit/plugins/webhooks/EventHandlerTest.java index 8baeb5b..2aae9af 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/webhooks/EventHandlerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/webhooks/EventHandlerTest.java
@@ -20,12 +20,14 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.gerrit.entities.Project; import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.ProjectCreatedEvent; import com.google.gerrit.server.project.NoSuchProjectException; +import java.util.regex.Pattern; import org.eclipse.jgit.lib.Config; import org.junit.Before; import org.junit.Test; @@ -41,6 +43,8 @@ private static final String FOO = "foo"; private static final String FOO_URL = "foo-url"; + @Mock private Configuration global; + @Mock private ProjectCreatedEvent projectCreated; @Mock private PluginConfigFactory configFactory; @@ -64,7 +68,7 @@ .thenReturn(config); when(remoteFactory.create(eq(config), eq(FOO))).thenReturn(remote); when(taskFactory.create(eq(projectCreated), eq(remote))).thenReturn(postTask); - eventHandler = new EventHandler(configFactory, PLUGIN, remoteFactory, taskFactory); + eventHandler = new EventHandler(global, configFactory, PLUGIN, remoteFactory, taskFactory); } @Test @@ -93,4 +97,27 @@ verifyNoInteractions(taskFactory); verifyNoInteractions(postTask); } + + @Test + public void allowedUrlTaskScheduled() { + when(config.getSubsections(eq(REMOTE))).thenReturn(ImmutableSet.of(FOO)); + when(global.getAllowedUrlPatterns()).thenReturn(ImmutableList.of(Pattern.compile(FOO_URL))); + when(remote.getUrl()).thenReturn(FOO_URL); + + eventHandler.onEvent(projectCreated); + verify(taskFactory, times(1)).create(eq(projectCreated), eq(remote)); + verify(postTask, times(1)).schedule(); + } + + @Test + public void allowedUrlTaskNotScheduled() { + when(config.getSubsections(eq(REMOTE))).thenReturn(ImmutableSet.of(FOO)); + when(global.getAllowedUrlPatterns()) + .thenReturn(ImmutableList.of(Pattern.compile("does-not-match"))); + when(remote.getUrl()).thenReturn(FOO_URL); + + eventHandler.onEvent(projectCreated); + verifyNoInteractions(taskFactory); + verifyNoInteractions(postTask); + } }