import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder; | import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder; | ||||
import static java.lang.String.format; | import static java.lang.String.format; | ||||
import static org.sonar.core.config.WebhookProperties.MAX_WEBHOOKS_PER_TYPE; | |||||
public class WebhookPostTask implements PostProjectAnalysisTask { | public class WebhookPostTask implements PostProjectAnalysisTask { | ||||
String[] webhookIds = settings.getStringArray(propertyKey); | String[] webhookIds = settings.getStringArray(propertyKey); | ||||
return Arrays.stream(webhookIds) | return Arrays.stream(webhookIds) | ||||
.map(webhookId -> format("%s.%s", propertyKey, webhookId)) | .map(webhookId -> format("%s.%s", propertyKey, webhookId)) | ||||
.limit(MAX_WEBHOOKS_PER_TYPE) | |||||
.collect(Collectors.toList(webhookIds.length)); | .collect(Collectors.toList(webhookIds.length)); | ||||
} | } | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.Date; | import java.util.Date; | ||||
import java.util.stream.Collectors; | |||||
import java.util.stream.IntStream; | |||||
import org.junit.Rule; | import org.junit.Rule; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.ce.posttask.CeTask; | import org.sonar.api.ce.posttask.CeTask; | ||||
verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class)); | verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class)); | ||||
verify(deliveryStorage).purge(PROJECT_UUID); | verify(deliveryStorage).purge(PROJECT_UUID); | ||||
} | } | ||||
@Test | @Test | ||||
public void send_project_webhooks() { | public void send_project_webhooks() { | ||||
settings.setProperty("sonar.webhooks.project", "1"); | settings.setProperty("sonar.webhooks.project", "1"); | ||||
verify(deliveryStorage).purge(PROJECT_UUID); | 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() { | private void execute() { | ||||
SettingsRepository settingsRepository = new TestSettingsRepository(settings); | SettingsRepository settingsRepository = new TestSettingsRepository(settings); | ||||
WebhookPostTask task = new WebhookPostTask(rootHolder, settingsRepository, caller, deliveryStorage); | WebhookPostTask task = new WebhookPostTask(rootHolder, settingsRepository, caller, deliveryStorage); |
public static final String NAME_FIELD = "name"; | public static final String NAME_FIELD = "name"; | ||||
public static final String URL_FIELD = "url"; | 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 | * Prefix of the properties to be automatically exported from scanner to payload | ||||
*/ | */ | ||||
private static final String CATEGORY = "webhooks"; | private static final String CATEGORY = "webhooks"; | ||||
private static final String DESCRIPTION = "Webhooks are used to notify external services when a project analysis is done. " + | 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>."; | "Learn more in the <a href=\"#\">Webhooks documentation</a>."; | ||||
private WebhookProperties() { | private WebhookProperties() { |