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);
+  }
 }