]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10345 Add Webhooks delete ws
authorGuillaume Jambet <guillaume.jambet@sonarsource.com>
Thu, 8 Feb 2018 14:50:43 +0000 (15:50 +0100)
committerGuillaume Jambet <guillaume.jambet@gmail.com>
Thu, 1 Mar 2018 14:21:05 +0000 (15:21 +0100)
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java
server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/DeleteAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java

index b972edbe6653be7064b1c3f8d65c1babf82a362c..0711adecf5c135a870d9c211c3948c55b57fd14c 100644 (file)
@@ -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);
   }
-
 }
index 3b5706c62bc08e6af42de3997e46b8088819d5bf..354f0d2de5df0b267c6b431bb3e358ec673101a8 100644 (file)
@@ -36,4 +36,6 @@ public interface WebhookMapper {
 
   void update(WebhookDto dto);
 
+  void delete(@Param("uuid") String uuid);
+
 }
index 0eb69994650ddadd3f297b1eea1a25891ccaf5a9..33171e72276ed1ea5eecbe40d55a21693d59f14e 100644 (file)
     where uuid=#{uuid, jdbcType=VARCHAR}
   </update>
 
+  <delete id="delete" parameterType="String">
+    delete from webhooks
+    where
+    uuid = #{uuid,jdbcType=VARCHAR}
+  </delete>
+
 </mapper>
index 01e8ef62225163baaf464d00d69ee30454d25f05..2dafd3b90a910ea2b7ff684c7afb416d411097df 100644 (file)
@@ -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<WebhookDto> 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 (file)
index 0000000..3888bb5
--- /dev/null
@@ -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.<br>" +
+        "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<WebhookDto> dtoOptional = dbClient.webhookDao().selectByUuid(dbSession, webhookKey);
+      WebhookDto webhookDto = checkFoundWithOptional(dtoOptional, "No webhook with key '%s'", webhookKey);
+
+      String organizationUuid = webhookDto.getOrganizationUuid();
+      if (organizationUuid != null) {
+        Optional<OrganizationDto> 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<ComponentDto> 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());
+  }
+
+}
index 3927c5da41b3535a4340ce10b038c623b3b719fe..12625ea3634b460503c6f53c84dc06cec707f3d7 100644 (file)
@@ -30,6 +30,7 @@ public class WebhooksWsModule extends Module {
       SearchAction.class,
       CreateAction.class,
       UpdateAction.class,
+      DeleteAction.class,
       WebhookDeliveryAction.class,
       WebhookDeliveriesAction.class);
   }
index 10b2c7b04980131072924eb9d0da4bc7630dca5e..bf7c97c2ca680c15ec725db2548319d8259bae2f 100644 (file)
@@ -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 (file)
index 0000000..f54d55a
--- /dev/null
@@ -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<WebhookDto> 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<WebhookDto> 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();
+
+  }
+
+}
index 7afb02511bdcc15ad9457b0dfb8740cafb96898e..3ef537af8ab61fc6c43c295d81da7b11ed92e8c7 100644 (file)
@@ -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);
   }
 
 }