@@ -33,6 +33,7 @@ import org.sonar.server.computation.task.projectanalysis.component.SettingsRepos | |||
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder; | |||
import static java.lang.String.format; | |||
import static org.sonar.core.config.WebhookProperties.MAX_WEBHOOKS_PER_TYPE; | |||
public class WebhookPostTask implements PostProjectAnalysisTask { | |||
@@ -68,6 +69,7 @@ public class WebhookPostTask implements PostProjectAnalysisTask { | |||
String[] webhookIds = settings.getStringArray(propertyKey); | |||
return Arrays.stream(webhookIds) | |||
.map(webhookId -> format("%s.%s", propertyKey, webhookId)) | |||
.limit(MAX_WEBHOOKS_PER_TYPE) | |||
.collect(Collectors.toList(webhookIds.length)); | |||
} | |||
@@ -21,6 +21,8 @@ package org.sonar.server.computation.task.projectanalysis.webhook; | |||
import java.io.IOException; | |||
import java.util.Date; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.IntStream; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.ce.posttask.CeTask; | |||
@@ -85,6 +87,7 @@ public class WebhookPostTaskTest { | |||
verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class)); | |||
verify(deliveryStorage).purge(PROJECT_UUID); | |||
} | |||
@Test | |||
public void send_project_webhooks() { | |||
settings.setProperty("sonar.webhooks.project", "1"); | |||
@@ -100,6 +103,31 @@ public class WebhookPostTaskTest { | |||
verify(deliveryStorage).purge(PROJECT_UUID); | |||
} | |||
@Test | |||
public void process_only_the_10_first_global_webhooks() { | |||
testMaxWebhooks("sonar.webhooks.global"); | |||
} | |||
@Test | |||
public void process_only_the_10_first_project_webhooks() { | |||
testMaxWebhooks("sonar.webhooks.project"); | |||
} | |||
private void testMaxWebhooks(String property) { | |||
IntStream.range(1, 15) | |||
.forEach(i -> { | |||
settings.setProperty(property + "." + i + ".name", "First"); | |||
settings.setProperty(property + "." + i + ".url", "http://url"); | |||
caller.enqueueSuccess(NOW, 200, 1_234); | |||
}); | |||
settings.setProperty(property, IntStream.range(1, 15).mapToObj(String::valueOf).collect(Collectors.joining(","))); | |||
execute(); | |||
assertThat(caller.countSent()).isEqualTo(10); | |||
assertThat(logTester.logs(LoggerLevel.DEBUG).stream().filter(log -> log.contains("Sent"))).hasSize(10); | |||
} | |||
private void execute() { | |||
SettingsRepository settingsRepository = new TestSettingsRepository(settings); | |||
WebhookPostTask task = new WebhookPostTask(rootHolder, settingsRepository, caller, deliveryStorage); |
@@ -33,6 +33,13 @@ public class WebhookProperties { | |||
public static final String NAME_FIELD = "name"; | |||
public static final String URL_FIELD = "url"; | |||
/** | |||
* Maximum allowed number of webhooks per type (globally or per project). | |||
* That is required to not become a DoS attacker, for instance | |||
* if thousands of webhooks are configured. | |||
*/ | |||
public static final long MAX_WEBHOOKS_PER_TYPE = 10; | |||
/** | |||
* Prefix of the properties to be automatically exported from scanner to payload | |||
*/ | |||
@@ -40,7 +47,8 @@ public class WebhookProperties { | |||
private static final String CATEGORY = "webhooks"; | |||
private static final String DESCRIPTION = "Webhooks are used to notify external services when a project analysis is done. " + | |||
"A HTTP POST request including a JSON payload is sent to each of the provided URLs. " + | |||
"A HTTP POST request including a JSON payload is sent to each of the provided URLs. <br/>" + | |||
"Maximum " + MAX_WEBHOOKS_PER_TYPE + " webhooks are allowed. <br/>" + | |||
"Learn more in the <a href=\"#\">Webhooks documentation</a>."; | |||
private WebhookProperties() { |