From: Guillaume Jambet Date: Thu, 8 Feb 2018 14:50:43 +0000 (+0100) Subject: SONAR-10345 Add Webhooks delete ws X-Git-Tag: 7.5~1616 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a5a65a2c81c6f44b1eb37d5630bd37ffb46c085a;p=sonarqube.git SONAR-10345 Add Webhooks delete ws --- diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java index b972edbe665..0711adecf5c 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java @@ -63,8 +63,11 @@ public class WebhookDao implements Dao { mapper(dbSession).update(dto.setUpdatedAt(system2.now())); } + public void delete(DbSession dbSession, String uuid) { + mapper(dbSession).delete(uuid); + } + private static WebhookMapper mapper(DbSession dbSession) { return dbSession.getMapper(WebhookMapper.class); } - } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java index 3b5706c62bc..354f0d2de5d 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java @@ -36,4 +36,6 @@ public interface WebhookMapper { void update(WebhookDto dto); + void delete(@Param("uuid") String uuid); + } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml index 0eb69994650..33171e72276 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml @@ -63,4 +63,10 @@ where uuid=#{uuid, jdbcType=VARCHAR} + + delete from webhooks + where + uuid = #{uuid,jdbcType=VARCHAR} + + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java index 01e8ef62225..2dafd3b90a9 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java @@ -116,6 +116,17 @@ public class WebhookDaoTest { assertThat(new Date(reloaded.getUpdatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); } + @Test + public void delete() { + + OrganizationDto organization = organizationDbTester.insert(); + WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid()); + + underTest.delete(dbSession, dto.getUuid()); + + Optional reloaded = underTest.selectByUuid(dbSession, dto.uuid); + assertThat(reloaded).isEmpty(); + } @Test public void fail_if_webhook_does_not_have_an_organization_nor_a_project() { 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 new file mode 100644 index 00000000000..3888bb5747f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/DeleteAction.java @@ -0,0 +1,109 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.webhook.ws; + +import java.util.Optional; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.webhook.WebhookDto; +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.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001; +import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; +import static org.sonar.server.ws.WsUtils.checkStateWithOptional; + +public class DeleteAction implements WebhooksWsAction { + + private final DbClient dbClient; + private final UserSession userSession; + private final WebhookSupport webhookSupport; + + public DeleteAction(DbClient dbClient, UserSession userSession, WebhookSupport webhookSupport) { + this.dbClient = dbClient; + this.userSession = userSession; + this.webhookSupport = webhookSupport; + } + + @Override + public void define(WebService.NewController controller) { + + WebService.NewAction action = controller.createAction(DELETE_ACTION) + .setPost(true) + .setDescription("Delete a Webhook.
" + + "Requires the global, organization or project permission.") + .setSince("7.1") + .setHandler(this); + + action.createParam(KEY_PARAM) + .setRequired(true) + .setMaximumLength(KEY_PARAM_MAXIMUN_LENGTH) + .setDescription("The key of the webhook to be deleted") + .setExampleValue(KEY_PROJECT_EXAMPLE_001); + + } + + @Override + public void handle(Request request, Response response) throws Exception { + + userSession.checkLoggedIn(); + + String webhookKey = request.param(KEY_PARAM); + + try (DbSession dbSession = dbClient.openSession(false)) { + + Optional dtoOptional = dbClient.webhookDao().selectByUuid(dbSession, webhookKey); + WebhookDto webhookDto = checkFoundWithOptional(dtoOptional, "No webhook with key '%s'", webhookKey); + + String organizationUuid = webhookDto.getOrganizationUuid(); + if (organizationUuid != null) { + Optional optionalDto = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid); + OrganizationDto organizationDto = checkStateWithOptional(optionalDto, "the requested organization '%s' was not found", organizationUuid); + webhookSupport.checkUserPermissionOn(organizationDto); + deleteWebhook(dbSession, webhookDto); + } + + String projectUuid = webhookDto.getProjectUuid(); + if (projectUuid != null) { + Optional optionalDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull()); + ComponentDto componentDto = checkStateWithOptional(optionalDto, "the requested project '%s' was not found", projectUuid); + webhookSupport.checkUserPermissionOn(componentDto); + deleteWebhook(dbSession, webhookDto); + } + + dbSession.commit(); + } + + response.noContent(); + } + + private void deleteWebhook(DbSession dbSession, WebhookDto webhookDto) { + dbClient.webhookDao().delete(dbSession, webhookDto.getUuid()); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java index 3927c5da41b..12625ea3634 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java @@ -30,6 +30,7 @@ public class WebhooksWsModule extends Module { SearchAction.class, CreateAction.class, UpdateAction.class, + DeleteAction.class, WebhookDeliveryAction.class, WebhookDeliveriesAction.class); } 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 10b2c7b0498..bf7c97c2ca6 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 @@ -24,9 +24,10 @@ class WebhooksWsParameters { static final String WEBHOOKS_CONTROLLER = "api/webhooks"; + static final String SEARCH_ACTION = "search"; static final String ACTION_CREATE = "create"; static final String UPDATE_ACTION = "update"; - static final String SEARCH_ACTION = "search"; + static final String DELETE_ACTION = "delete"; static final String ORGANIZATION_KEY_PARAM = "organization"; diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java new file mode 100644 index 00000000000..f54d55a6bd1 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java @@ -0,0 +1,185 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.webhook.ws; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.organization.OrganizationDbTester; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.webhook.WebhookDbTester; +import org.sonar.db.webhook.WebhookDto; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.organization.DefaultOrganizationProvider; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestResponse; +import org.sonar.server.ws.WsActionTester; + +import static java.net.HttpURLConnection.HTTP_NO_CONTENT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.tuple; +import static org.junit.rules.ExpectedException.none; +import static org.sonar.api.web.UserRole.ADMIN; +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; + +public class DeleteActionTest { + + @Rule + public ExpectedException expectedException = none(); + + @Rule + public UserSessionRule userSession = standalone(); + + @Rule + public DbTester db = create(); + private DbClient dbClient = db.getDbClient(); + private WebhookDbTester webhookDbTester = db.webhooks(); + private OrganizationDbTester organizationDbTester = db.organizations(); + private ComponentDbTester componentDbTester = db.components(); + + private DefaultOrganizationProvider defaultOrganizationProvider = from(db); + + private WebhookSupport webhookSupport = new WebhookSupport(userSession); + private DeleteAction underTest = new DeleteAction(dbClient, userSession, webhookSupport); + private WsActionTester wsActionTester = new WsActionTester(underTest); + + @Test + public void test_ws_definition() { + + WebService.Action action = wsActionTester.getDef(); + assertThat(action).isNotNull(); + assertThat(action.isInternal()).isFalse(); + assertThat(action.isPost()).isTrue(); + + assertThat(action.params()) + .extracting(WebService.Param::key, WebService.Param::isRequired) + .containsExactlyInAnyOrder(tuple("key", true)); + + } + + @Test + public void delete_a_project_webhook() { + + ComponentDto project = componentDbTester.insertPrivateProject(); + WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid()); + userSession.logIn().addProjectPermission(ADMIN, project); + + TestResponse response = wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .execute(); + + assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT); + Optional reloaded = webhookDbTester.selectWebhook(dto.getUuid()); + assertThat(reloaded).isEmpty(); + + } + + @Test + public void delete_an_organization_webhook() { + + OrganizationDto organization = organizationDbTester.insert(); + WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid()); + userSession.logIn().addPermission(ADMINISTER, organization.getUuid()); + + TestResponse response = wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .execute(); + + assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT); + Optional reloaded = webhookDbTester.selectWebhook(dto.getUuid()); + assertThat(reloaded).isEmpty(); + + } + + @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") + .execute(); + } + + @Test + public void fail_if_not_logged_in() throws Exception { + + OrganizationDto organization = organizationDbTester.insert(); + WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid()); + userSession.anonymous(); + + expectedException.expect(UnauthorizedException.class); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .execute(); + + } + + @Test + public void fail_if_no_permission_on_webhook_scope_project() { + + ComponentDto project = componentDbTester.insertPrivateProject(); + WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid()); + + userSession.logIn(); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .execute(); + + } + + @Test + public void fail_if_no_permission_on_webhook_scope_organization() { + + OrganizationDto organization = organizationDbTester.insert(); + WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid()); + + userSession.logIn(); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .execute(); + + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java index 7afb02511bd..3ef537af8ab 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java @@ -32,7 +32,7 @@ public class WebhooksWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); underTest.configure(container); - assertThat(container.size()).isEqualTo(3 + 6); + assertThat(container.size()).isEqualTo(3 + 7); } }