From: Simon Brandhof Date: Fri, 19 Apr 2019 11:18:24 +0000 (+0200) Subject: SONAR-12000 add secret to WS api/webhooks/create and api/webhooks/update X-Git-Tag: 7.8~322 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d79cd20db14afdc65967ec478c9ea5869d40a9d4;p=sonarqube.git SONAR-12000 add secret to WS api/webhooks/create and api/webhooks/update --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java index 19c2fea77eb..9f485ee9e7d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java @@ -32,11 +32,9 @@ import org.sonar.db.organization.OrganizationDto; import org.sonar.db.webhook.WebhookDto; import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.user.UserSession; -import org.sonarqube.ws.Webhooks; import static com.google.common.base.Preconditions.checkState; import static java.lang.String.format; -import static java.util.Optional.ofNullable; import static org.apache.commons.lang.StringUtils.isNotBlank; import static org.sonar.server.webhook.ws.WebhooksWsParameters.ACTION_CREATE; import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM; @@ -44,7 +42,9 @@ import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM_MAXIMU import static org.sonar.server.webhook.ws.WebhooksWsParameters.ORGANIZATION_KEY_PARAM; import static org.sonar.server.webhook.ws.WebhooksWsParameters.ORGANIZATION_KEY_PARAM_MAXIMUM_LENGTH; import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM; -import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM_MAXIMUN_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM_MAXIMUM_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.SECRET_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.SECRET_PARAM_MAXIMUM_LENGTH; import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM; import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM_MAXIMUM_LENGTH; import static org.sonar.server.ws.KeyExamples.KEY_ORG_EXAMPLE_001; @@ -68,7 +68,7 @@ public class CreateAction implements WebhooksWsAction { private final WebhookSupport webhookSupport; public CreateAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, - UuidFactory uuidFactory, WebhookSupport webhookSupport) { + UuidFactory uuidFactory, WebhookSupport webhookSupport) { this.dbClient = dbClient; this.userSession = userSession; this.defaultOrganizationProvider = defaultOrganizationProvider; @@ -78,7 +78,6 @@ public class CreateAction implements WebhooksWsAction { @Override public void define(WebService.NewController controller) { - WebService.NewAction action = controller.createAction(ACTION_CREATE) .setPost(true) .setDescription("Create a Webhook.
" + @@ -103,7 +102,7 @@ public class CreateAction implements WebhooksWsAction { action.createParam(PROJECT_KEY_PARAM) .setRequired(false) - .setMaximumLength(PROJECT_KEY_PARAM_MAXIMUN_LENGTH) + .setMaximumLength(PROJECT_KEY_PARAM_MAXIMUM_LENGTH) .setDescription("The key of the project that will own the webhook") .setExampleValue(KEY_PROJECT_EXAMPLE_001); @@ -114,20 +113,26 @@ public class CreateAction implements WebhooksWsAction { .setDescription("The key of the organization that will own the webhook") .setExampleValue(KEY_ORG_EXAMPLE_001); + action.createParam(SECRET_PARAM) + .setRequired(false) + .setMinimumLength(1) + .setMaximumLength(SECRET_PARAM_MAXIMUM_LENGTH) + .setDescription("If provided, secret will be used as the key to generate the HMAC hex (lowercase) digest value in the 'X-Sonar-Webhook-HMAC-SHA256' header") + .setExampleValue("your_secret") + .setSince("7.8"); } @Override public void handle(Request request, Response response) throws Exception { - userSession.checkLoggedIn(); String name = request.mandatoryParam(NAME_PARAM); String url = request.mandatoryParam(URL_PARAM); String projectKey = request.param(PROJECT_KEY_PARAM); String organizationKey = request.param(ORGANIZATION_KEY_PARAM); + String secret = request.param(SECRET_PARAM); try (DbSession dbSession = dbClient.openSession(false)) { - OrganizationDto organizationDto; if (isNotBlank(organizationKey)) { Optional dtoOptional = dbClient.organizationDao().selectByKey(dbSession, organizationKey); @@ -138,7 +143,7 @@ public class CreateAction implements WebhooksWsAction { ComponentDto projectDto = null; if (isNotBlank(projectKey)) { - Optional dtoOptional = ofNullable(dbClient.componentDao().selectByKey(dbSession, projectKey).orElse(null)); + Optional dtoOptional = dbClient.componentDao().selectByKey(dbSession, projectKey); ComponentDto componentDto = checkFoundWithOptional(dtoOptional, "No project with key '%s'", projectKey); webhookSupport.checkThatProjectBelongsToOrganization(componentDto, organizationDto, "Project '%s' does not belong to organisation '%s'", projectKey, organizationKey); webhookSupport.checkPermission(componentDto); @@ -149,18 +154,18 @@ public class CreateAction implements WebhooksWsAction { webhookSupport.checkUrlPattern(url, "Url parameter with value '%s' is not a valid url", url); - WebhookDto webhookDto = doHandle(dbSession, organizationDto, projectDto, name, url); - - dbClient.webhookDao().insert(dbSession, webhookDto); + WebhookDto dto = doHandle(dbSession, organizationDto, projectDto, name, url, secret); + dbClient.webhookDao().insert(dbSession, dto); dbSession.commit(); - writeResponse(request, response, webhookDto); + writeResponse(request, response, dto); } } - private WebhookDto doHandle(DbSession dbSession, @Nullable OrganizationDto organization, @Nullable ComponentDto project, String name, String url) { + private WebhookDto doHandle(DbSession dbSession, @Nullable OrganizationDto organization, + @Nullable ComponentDto project, String name, String url, @Nullable String secret) { checkState(organization != null || project != null, "A webhook can not be created if not linked to an organization or a project."); @@ -168,7 +173,8 @@ public class CreateAction implements WebhooksWsAction { WebhookDto dto = new WebhookDto() .setUuid(uuidFactory.create()) .setName(name) - .setUrl(url); + .setUrl(url) + .setSecret(secret); if (project != null) { checkNumberOfWebhook(numberOfWebhookOf(dbSession, project), "Maximum number of webhook reached for project '%s'", project.getKey()); @@ -181,18 +187,20 @@ public class CreateAction implements WebhooksWsAction { return dto; } - private static void writeResponse(Request request, Response response, WebhookDto element) { - Webhooks.CreateWsResponse.Builder responseBuilder = newBuilder(); - responseBuilder.setWebhook(Webhook.newBuilder() - .setKey(element.getUuid()) - .setName(element.getName()) - .setUrl(element.getUrl())); - - writeProtobuf(responseBuilder.build(), request, response); + private static void writeResponse(Request request, Response response, WebhookDto dto) { + Webhook.Builder webhookBuilder = Webhook.newBuilder(); + webhookBuilder + .setKey(dto.getUuid()) + .setName(dto.getName()) + .setUrl(dto.getUrl()); + if (dto.getSecret() != null) { + webhookBuilder.setSecret(dto.getSecret()); + } + writeProtobuf(newBuilder().setWebhook(webhookBuilder).build(), request, response); } private static void checkNumberOfWebhook(int nbOfWebhooks, String message, Object... messageArguments) { - if (nbOfWebhooks >= MAX_NUMBER_OF_WEBHOOKS){ + if (nbOfWebhooks >= MAX_NUMBER_OF_WEBHOOKS) { throw new IllegalArgumentException(format(message, messageArguments)); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/DeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/DeleteAction.java index 0ee82cf613a..d88edc6149d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/DeleteAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/DeleteAction.java @@ -33,7 +33,7 @@ import org.sonar.server.user.UserSession; import static java.util.Optional.ofNullable; import static org.sonar.server.webhook.ws.WebhooksWsParameters.DELETE_ACTION; import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM; -import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM_MAXIMUN_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM_MAXIMUM_LENGTH; import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; import static org.sonar.server.ws.WsUtils.checkStateWithOptional; @@ -61,7 +61,7 @@ public class DeleteAction implements WebhooksWsAction { action.createParam(KEY_PARAM) .setRequired(true) - .setMaximumLength(KEY_PARAM_MAXIMUN_LENGTH) + .setMaximumLength(KEY_PARAM_MAXIMUM_LENGTH) .setDescription("The key of the webhook to be deleted, "+ "auto-generated value can be obtained through api/webhooks/create or api/webhooks/list") .setExampleValue(KEY_PROJECT_EXAMPLE_001); diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java index 21ab7e79578..8ca9ac95c35 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java @@ -20,6 +20,7 @@ package org.sonar.server.webhook.ws; import java.util.Optional; +import javax.annotation.Nullable; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; @@ -32,9 +33,11 @@ import org.sonar.server.user.UserSession; import static java.util.Optional.ofNullable; import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM; -import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM_MAXIMUN_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM_MAXIMUM_LENGTH; import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM; import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM_MAXIMUM_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.SECRET_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.SECRET_PARAM_MAXIMUM_LENGTH; import static org.sonar.server.webhook.ws.WebhooksWsParameters.UPDATE_ACTION; import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM; import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM_MAXIMUM_LENGTH; @@ -58,7 +61,6 @@ public class UpdateAction implements WebhooksWsAction { @Override public void define(WebService.NewController controller) { - WebService.NewAction action = controller.createAction(UPDATE_ACTION) .setPost(true) .setDescription("Update a Webhook.
" + @@ -68,8 +70,8 @@ public class UpdateAction implements WebhooksWsAction { action.createParam(KEY_PARAM) .setRequired(true) - .setMaximumLength(KEY_PARAM_MAXIMUN_LENGTH) - .setDescription("The key of the webhook to be updated, "+ + .setMaximumLength(KEY_PARAM_MAXIMUM_LENGTH) + .setDescription("The key of the webhook to be updated, " + "auto-generated value can be obtained through api/webhooks/create or api/webhooks/list") .setExampleValue(KEY_PROJECT_EXAMPLE_001); @@ -85,6 +87,13 @@ public class UpdateAction implements WebhooksWsAction { .setDescription("new url to be called by the webhook") .setExampleValue(URL_WEBHOOK_EXAMPLE_001); + action.createParam(SECRET_PARAM) + .setRequired(false) + .setMinimumLength(1) + .setMaximumLength(SECRET_PARAM_MAXIMUM_LENGTH) + .setDescription("If provided, secret will be used as the key to generate the HMAC hex (lowercase) digest value in the 'X-Sonar-Webhook-HMAC-SHA256' header") + .setExampleValue("your_secret") + .setSince("7.8"); } @Override @@ -94,6 +103,7 @@ public class UpdateAction implements WebhooksWsAction { String webhookKey = request.param(KEY_PARAM); String name = request.mandatoryParam(NAME_PARAM); String url = request.mandatoryParam(URL_PARAM); + String secret = request.param(SECRET_PARAM); webhookSupport.checkUrlPattern(url, "Url parameter with value '%s' is not a valid url", url); @@ -107,7 +117,7 @@ public class UpdateAction implements WebhooksWsAction { Optional optionalDto = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid); OrganizationDto organizationDto = checkStateWithOptional(optionalDto, "the requested organization '%s' was not found", organizationUuid); webhookSupport.checkPermission(organizationDto); - updateWebhook(dbSession, webhookDto, name, url); + updateWebhook(dbSession, webhookDto, name, url, secret); } String projectUuid = webhookDto.getProjectUuid(); @@ -115,7 +125,7 @@ public class UpdateAction implements WebhooksWsAction { Optional optionalDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orElse(null)); ComponentDto componentDto = checkStateWithOptional(optionalDto, "the requested project '%s' was not found", projectUuid); webhookSupport.checkPermission(componentDto); - updateWebhook(dbSession, webhookDto, name, url); + updateWebhook(dbSession, webhookDto, name, url, secret); } dbSession.commit(); @@ -124,8 +134,12 @@ public class UpdateAction implements WebhooksWsAction { response.noContent(); } - private void updateWebhook(DbSession dbSession, WebhookDto webhookDto, String name, String url) { - dbClient.webhookDao().update(dbSession, webhookDto.setName(name).setUrl(url)); + private void updateWebhook(DbSession dbSession, WebhookDto dto, String name, String url, @Nullable String secret) { + dto + .setName(name) + .setUrl(url) + .setSecret(secret); + dbClient.webhookDao().update(dbSession, dto); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java index 4861bc43f3e..ec847a6d3e3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java @@ -33,13 +33,15 @@ class WebhooksWsParameters { static final String ORGANIZATION_KEY_PARAM = "organization"; static final int ORGANIZATION_KEY_PARAM_MAXIMUM_LENGTH = 255; static final String PROJECT_KEY_PARAM = "project"; - static final int PROJECT_KEY_PARAM_MAXIMUN_LENGTH = 100; + static final int PROJECT_KEY_PARAM_MAXIMUM_LENGTH = 100; static final String NAME_PARAM = "name"; static final int NAME_PARAM_MAXIMUM_LENGTH = 100; static final String URL_PARAM = "url"; static final int URL_PARAM_MAXIMUM_LENGTH = 512; static final String KEY_PARAM = "webhook"; - static final int KEY_PARAM_MAXIMUN_LENGTH = 40; + static final int KEY_PARAM_MAXIMUM_LENGTH = 40; + static final String SECRET_PARAM = "secret"; + static final int SECRET_PARAM_MAXIMUM_LENGTH = 200; private WebhooksWsParameters() { // prevent instantiation diff --git a/server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json b/server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json index 28bb248cf7c..29b4ee4762e 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json +++ b/server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json @@ -2,6 +2,7 @@ "webhook": { "key": "uuid", "name": "My webhook", - "url": "https://www.my-webhook-listener.com/sonar" + "url": "https://www.my-webhook-listener.com/sonar", + "secret": "your_secret" } -} \ No newline at end of file +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java index e3bfc36862e..06fe6016c0f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java @@ -80,7 +80,6 @@ public class CreateActionTest { @Test public void test_ws_definition() { - WebService.Action action = wsActionTester.getDef(); assertThat(action).isNotNull(); assertThat(action.isInternal()).isFalse(); @@ -93,92 +92,104 @@ public class CreateActionTest { tuple("organization", false), tuple("project", false), tuple("name", true), - tuple("url", true)); + tuple("url", true), + tuple("secret", false)); } @Test - public void create_a_webhook_on_default_organization() { - + public void create_a_webhook_with_secret() { userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid()); CreateWsResponse response = wsActionTester.newRequest() - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) + .setParam("secret", "a_secret") .executeProtobuf(CreateWsResponse.class); assertThat(response.getWebhook()).isNotNull(); assertThat(response.getWebhook().getKey()).isNotNull(); assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001); assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); + assertThat(response.getWebhook().getSecret()).isEqualTo("a_secret"); + } + @Test + public void create_a_webhook_on_default_organization() { + userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid()); + + CreateWsResponse response = wsActionTester.newRequest() + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) + .executeProtobuf(CreateWsResponse.class); + + assertThat(response.getWebhook()).isNotNull(); + assertThat(response.getWebhook().getKey()).isNotNull(); + assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001); + assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); + assertThat(response.getWebhook().hasSecret()).isFalse(); } @Test public void create_a_webhook_on_specific_organization() { - OrganizationDto organization = organizationDbTester.insert(); - userSession.logIn().addPermission(ADMINISTER, organization.getUuid()); CreateWsResponse response = wsActionTester.newRequest() - .setParam(ORGANIZATION_KEY_PARAM, organization.getKey()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("organization", organization.getKey()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .executeProtobuf(CreateWsResponse.class); assertThat(response.getWebhook()).isNotNull(); assertThat(response.getWebhook().getKey()).isNotNull(); assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001); assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); - + assertThat(response.getWebhook().hasSecret()).isFalse(); } @Test public void create_a_webhook_on_project() { - ComponentDto project = componentDbTester.insertPrivateProject(); userSession.logIn().addProjectPermission(ADMIN, project); CreateWsResponse response = wsActionTester.newRequest() - .setParam(PROJECT_KEY_PARAM, project.getKey()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("project", project.getKey()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .executeProtobuf(CreateWsResponse.class); assertThat(response.getWebhook()).isNotNull(); assertThat(response.getWebhook().getKey()).isNotNull(); assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001); assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); - + assertThat(response.getWebhook().hasSecret()).isFalse(); } @Test public void create_a_webhook_on_a_project_belonging_to_an_organization() { - OrganizationDto organization = organizationDbTester.insert(); ComponentDto project = componentDbTester.insertPrivateProject(organization); userSession.logIn().addProjectPermission(ADMIN, project); CreateWsResponse response = wsActionTester.newRequest() - .setParam(ORGANIZATION_KEY_PARAM, organization.getKey()) - .setParam(PROJECT_KEY_PARAM, project.getKey()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("organization", organization.getKey()) + .setParam("project", project.getKey()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .executeProtobuf(CreateWsResponse.class); assertThat(response.getWebhook()).isNotNull(); assertThat(response.getWebhook().getKey()).isNotNull(); assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001); assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); - + assertThat(response.getWebhook().hasSecret()).isFalse(); } @Test public void fail_if_project_does_not_belong_to_requested_organization() { - OrganizationDto organization = organizationDbTester.insert(); ComponentDto project = componentDbTester.insertPrivateProject(); @@ -188,17 +199,15 @@ public class CreateActionTest { userSession.logIn().addProjectPermission(ADMIN, project); wsActionTester.newRequest() - .setParam(ORGANIZATION_KEY_PARAM, organization.getKey()) - .setParam(PROJECT_KEY_PARAM, project.getKey()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("organization", organization.getKey()) + .setParam("project", project.getKey()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .execute(); - } @Test - public void fail_if_project_does_not_exists() { - + public void fail_if_project_does_not_exist() { expectedException.expect(NotFoundException.class); expectedException.expectMessage("No project with key 'inexistent-project-uuid'"); @@ -209,12 +218,10 @@ public class CreateActionTest { .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) .execute(); - } @Test public void fail_if_crossing_maximum_quantity_of_webhooks_on_this_project() { - ComponentDto project = componentDbTester.insertPrivateProject(); expectedException.expect(IllegalArgumentException.class); @@ -230,12 +237,10 @@ public class CreateActionTest { .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) .execute(); - } @Test public void fail_if_crossing_maximum_quantity_of_webhooks_on_this_organization() { - OrganizationDto organization = organizationDbTester.insert(); expectedException.expect(IllegalArgumentException.class); @@ -254,8 +259,7 @@ public class CreateActionTest { } @Test - public void fail_if_organization_does_not_exists() { - + public void fail_if_organization_does_not_exist() { expectedException.expect(NotFoundException.class); expectedException.expectMessage("No organization with key 'inexistent-organization-uuid'"); @@ -266,12 +270,10 @@ public class CreateActionTest { .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) .execute(); - } @Test - public void fail_if_url_is_not_valid() throws Exception { - + public void fail_if_url_is_not_valid() { userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid()); expectedException.expect(IllegalArgumentException.class); @@ -280,12 +282,10 @@ public class CreateActionTest { .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) .setParam(URL_PARAM, "htp://www.wrong-protocol.com/") .execute(); - } @Test - public void fail_if_credential_in_url_is_have_a_wrong_format() throws Exception { - + public void fail_if_credential_in_url_is_have_a_wrong_format() { userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid()); expectedException.expect(IllegalArgumentException.class); @@ -294,12 +294,10 @@ public class CreateActionTest { .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) .setParam(URL_PARAM, "http://:www.wrong-protocol.com/") .execute(); - } @Test - public void return_UnauthorizedException_if_not_logged_in() throws Exception { - + public void return_UnauthorizedException_if_not_logged_in() { userSession.anonymous(); expectedException.expect(UnauthorizedException.class); @@ -307,12 +305,10 @@ public class CreateActionTest { .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) .execute(); - } @Test public void throw_ForbiddenException_if_no_organization_provided_and_user_is_not_system_administrator() { - userSession.logIn(); expectedException.expect(ForbiddenException.class); @@ -326,7 +322,6 @@ public class CreateActionTest { @Test public void throw_ForbiddenException_if_organization_provided_but_user_is_not_organization_administrator() { - OrganizationDto organization = organizationDbTester.insert(); userSession.logIn(); @@ -339,14 +334,11 @@ public class CreateActionTest { .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) .setParam(ORGANIZATION_KEY_PARAM, organization.getKey()) .execute(); - } @Test public void throw_ForbiddenException_if_not_project_administrator() { - ComponentDto project = componentDbTester.insertPrivateProject(); - userSession.logIn(); expectedException.expect(ForbiddenException.class); @@ -357,7 +349,6 @@ public class CreateActionTest { .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) .setParam(PROJECT_KEY_PARAM, project.getKey()) .execute(); - } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java index 9fb54f8cde2..9dd2f5e9d11 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java @@ -49,9 +49,6 @@ import static org.sonar.db.DbTester.create; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.server.organization.TestDefaultOrganizationProvider.from; import static org.sonar.server.tester.UserSessionRule.standalone; -import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM; -import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM; -import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM; import static org.sonar.server.ws.KeyExamples.NAME_WEBHOOK_EXAMPLE_001; import static org.sonar.server.ws.KeyExamples.URL_WEBHOOK_EXAMPLE_001; @@ -78,7 +75,6 @@ public class UpdateActionTest { @Test public void test_ws_definition() { - WebService.Action action = wsActionTester.getDef(); assertThat(action).isNotNull(); assertThat(action.isInternal()).isFalse(); @@ -89,21 +85,20 @@ public class UpdateActionTest { .containsExactlyInAnyOrder( tuple("webhook", true), tuple("name", true), - tuple("url", true)); - + tuple("url", true), + tuple("secret", false)); } @Test - public void update_a_project_webhook() { - + public void update_a_project_webhook_with_required_fields() { ComponentDto project = componentDbTester.insertPrivateProject(); WebhookDto dto = webhookDbTester.insertWebhook(project); userSession.logIn().addProjectPermission(ADMIN, project); TestResponse response = wsActionTester.newRequest() - .setParam(KEY_PARAM, dto.getUuid()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("webhook", dto.getUuid()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .execute(); assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT); @@ -113,20 +108,43 @@ public class UpdateActionTest { assertThat(reloaded.get().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); assertThat(reloaded.get().getOrganizationUuid()).isNull(); assertThat(reloaded.get().getProjectUuid()).isEqualTo(dto.getProjectUuid()); + assertThat(reloaded.get().getSecret()).isNull(); + } + + @Test + public void update_a_project_webhook_with_all_fields() { + ComponentDto project = componentDbTester.insertPrivateProject(); + WebhookDto dto = webhookDbTester.insertWebhook(project); + userSession.logIn().addProjectPermission(ADMIN, project); + + TestResponse response = wsActionTester.newRequest() + .setParam("webhook", dto.getUuid()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) + .setParam("secret", "a_new_secret") + .execute(); + assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT); + Optional reloaded = webhookDbTester.selectWebhook(dto.getUuid()); + assertThat(reloaded.get()).isNotNull(); + assertThat(reloaded.get().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001); + assertThat(reloaded.get().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); + assertThat(reloaded.get().getOrganizationUuid()).isNull(); + assertThat(reloaded.get().getProjectUuid()).isEqualTo(dto.getProjectUuid()); + assertThat(reloaded.get().getSecret()).isEqualTo("a_new_secret"); } @Test public void update_an_organization_webhook() { - OrganizationDto organization = organizationDbTester.insert(); WebhookDto dto = webhookDbTester.insertWebhook(organization); userSession.logIn().addPermission(ADMINISTER, organization.getUuid()); TestResponse response = wsActionTester.newRequest() - .setParam(KEY_PARAM, dto.getUuid()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("webhook", dto.getUuid()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) + .setParam("secret", "a_new_secret") .execute(); assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT); @@ -136,27 +154,25 @@ public class UpdateActionTest { assertThat(reloaded.get().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); assertThat(reloaded.get().getOrganizationUuid()).isEqualTo(dto.getOrganizationUuid()); assertThat(reloaded.get().getProjectUuid()).isNull(); - + assertThat(reloaded.get().getSecret()).isEqualTo("a_new_secret"); } @Test public void fail_if_webhook_does_not_exist() { - userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid()); expectedException.expect(NotFoundException.class); expectedException.expectMessage("No webhook with key 'inexistent-webhook-uuid'"); wsActionTester.newRequest() - .setParam(KEY_PARAM, "inexistent-webhook-uuid") - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("webhook", "inexistent-webhook-uuid") + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .execute(); } @Test - public void fail_if_not_logged_in() throws Exception { - + public void fail_if_not_logged_in() { OrganizationDto organization = organizationDbTester.insert(); WebhookDto dto = webhookDbTester.insertWebhook(organization); userSession.anonymous(); @@ -164,16 +180,14 @@ public class UpdateActionTest { expectedException.expect(UnauthorizedException.class); wsActionTester.newRequest() - .setParam(KEY_PARAM, dto.getUuid()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("webhook", dto.getUuid()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .execute(); - } @Test public void fail_if_no_permission_on_webhook_scope_project() { - ComponentDto project = componentDbTester.insertPrivateProject(); WebhookDto dto = webhookDbTester.insertWebhook(project); @@ -183,16 +197,14 @@ public class UpdateActionTest { expectedException.expectMessage("Insufficient privileges"); wsActionTester.newRequest() - .setParam(KEY_PARAM, dto.getUuid()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("webhook", dto.getUuid()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .execute(); - } @Test public void fail_if_no_permission_on_webhook_scope_organization() { - OrganizationDto organization = organizationDbTester.insert(); WebhookDto dto = webhookDbTester.insertWebhook(organization); @@ -202,16 +214,14 @@ public class UpdateActionTest { expectedException.expectMessage("Insufficient privileges"); wsActionTester.newRequest() - .setParam(KEY_PARAM, dto.getUuid()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .setParam("webhook", dto.getUuid()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", URL_WEBHOOK_EXAMPLE_001) .execute(); - } @Test - public void fail_if_url_is_not_valid() throws Exception { - + public void fail_if_url_is_not_valid() { ComponentDto project = componentDbTester.insertPrivateProject(); WebhookDto dto = webhookDbTester.insertWebhook(project); userSession.logIn().addProjectPermission(ADMIN, project); @@ -219,16 +229,14 @@ public class UpdateActionTest { expectedException.expect(IllegalArgumentException.class); wsActionTester.newRequest() - .setParam(KEY_PARAM, dto.getUuid()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, "htp://www.wrong-protocol.com/") + .setParam("webhook", dto.getUuid()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", "htp://www.wrong-protocol.com/") .execute(); - } @Test - public void fail_if_credential_in_url_is_have_a_wrong_format() throws Exception { - + public void fail_if_credential_in_url_is_have_a_wrong_format() { ComponentDto project = componentDbTester.insertPrivateProject(); WebhookDto dto = webhookDbTester.insertWebhook(project); userSession.logIn().addProjectPermission(ADMIN, project); @@ -236,11 +244,10 @@ public class UpdateActionTest { expectedException.expect(IllegalArgumentException.class); wsActionTester.newRequest() - .setParam(KEY_PARAM, dto.getUuid()) - .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) - .setParam(URL_PARAM, "http://:www.wrong-protocol.com/") + .setParam("webhook", dto.getUuid()) + .setParam("name", NAME_WEBHOOK_EXAMPLE_001) + .setParam("url", "http://:www.wrong-protocol.com/") .execute(); - } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/CreateRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/CreateRequest.java index e9e974e96bf..737638bd945 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/CreateRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/CreateRequest.java @@ -19,7 +19,6 @@ */ package org.sonarqube.ws.client.webhooks; -import java.util.List; import javax.annotation.Generated; /** @@ -34,6 +33,7 @@ public class CreateRequest { private String name; private String organization; private String project; + private String secret; private String url; /** @@ -74,6 +74,18 @@ public class CreateRequest { return project; } + /** + * Example value: "your_secret" + */ + public CreateRequest setSecret(String secret) { + this.secret = secret; + return this; + } + + public String getSecret() { + return secret; + } + /** * This is a mandatory parameter. * Example value: "https://www.my-webhook-listener.com/sonar" diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/UpdateRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/UpdateRequest.java index 289062ce9d4..11615e3e6b0 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/UpdateRequest.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/UpdateRequest.java @@ -19,7 +19,6 @@ */ package org.sonarqube.ws.client.webhooks; -import java.util.List; import javax.annotation.Generated; /** @@ -32,6 +31,7 @@ import javax.annotation.Generated; public class UpdateRequest { private String name; + private String secret; private String url; private String webhook; @@ -48,6 +48,18 @@ public class UpdateRequest { return name; } + /** + * Example value: "your_secret" + */ + public UpdateRequest setSecret(String secret) { + this.secret = secret; + return this; + } + + public String getSecret() { + return secret; + } + /** * This is a mandatory parameter. * Example value: "https://www.my-webhook-listener.com/sonar" diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/WebhooksService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/WebhooksService.java index e55e891b0a3..f61621a7516 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/WebhooksService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/webhooks/WebhooksService.java @@ -53,6 +53,7 @@ public class WebhooksService extends BaseService { .setParam("name", request.getName()) .setParam("organization", request.getOrganization()) .setParam("project", request.getProject()) + .setParam("secret", request.getSecret()) .setParam("url", request.getUrl()), CreateWsResponse.parser()); } @@ -68,8 +69,7 @@ public class WebhooksService extends BaseService { call( new PostRequest(path("delete")) .setParam("webhook", request.getWebhook()) - .setMediaType(MediaTypes.JSON) - ).content(); + .setMediaType(MediaTypes.JSON)).content(); } /** @@ -130,9 +130,9 @@ public class WebhooksService extends BaseService { call( new PostRequest(path("update")) .setParam("name", request.getName()) + .setParam("secret", request.getSecret()) .setParam("url", request.getUrl()) .setParam("webhook", request.getWebhook()) - .setMediaType(MediaTypes.JSON) - ).content(); + .setMediaType(MediaTypes.JSON)).content(); } } diff --git a/sonar-ws/src/main/protobuf/ws-webhooks.proto b/sonar-ws/src/main/protobuf/ws-webhooks.proto index 81e45aa24fd..f8642e7c5ef 100644 --- a/sonar-ws/src/main/protobuf/ws-webhooks.proto +++ b/sonar-ws/src/main/protobuf/ws-webhooks.proto @@ -54,6 +54,7 @@ message CreateWsResponse { optional string key = 1; optional string name = 2; optional string url = 3; + optional string secret = 4; } }