]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12922 add delete hotspot comment WS
authorJacek <jacek.poreda@sonarsource.com>
Wed, 12 Feb 2020 15:34:45 +0000 (16:34 +0100)
committerSonarTech <sonartech@sonarsource.com>
Fri, 21 Feb 2020 19:46:18 +0000 (20:46 +0100)
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/DeleteCommentAction.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsSupport.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/DeleteCommentActionTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/DeleteCommentRequest.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java

diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/DeleteCommentAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/DeleteCommentAction.java
new file mode 100644 (file)
index 0000000..4d74a21
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.hotspot.ws;
+
+import java.util.Objects;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
+
+public class DeleteCommentAction implements HotspotsWsAction {
+  private static final String PARAM_COMMENT = "comment";
+
+  private final UserSession userSession;
+  private final DbClient dbClient;
+  private final HotspotWsSupport hotspotWsSupport;
+
+  public DeleteCommentAction(DbClient dbClient, HotspotWsSupport hotspotWsSupport, UserSession userSession) {
+    this.dbClient = dbClient;
+    this.hotspotWsSupport = hotspotWsSupport;
+    this.userSession = userSession;
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+    WebService.NewAction action = controller
+      .createAction("delete_comment")
+      .setHandler(this)
+      .setPost(true)
+      .setDescription("Delete comment from Security Hotpot.<br/>" +
+        "Requires authentication and the following permission: 'Browse' on the project of the specified Security Hotspot.")
+      .setSince("8.2")
+      .setInternal(true);
+
+    action.createParam(PARAM_COMMENT)
+      .setDescription("Comment key.")
+      .setRequired(true)
+      .setExampleValue(UUID_EXAMPLE_01);
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    hotspotWsSupport.checkLoggedIn();
+
+    String commentKey = request.mandatoryParam(PARAM_COMMENT);
+
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      IssueChangeDto hotspotComment = getHotspotComment(commentKey, dbSession);
+      validate(dbSession, hotspotComment);
+      deleteComment(dbSession, hotspotComment.getKey());
+      response.noContent();
+    }
+  }
+
+  private IssueChangeDto getHotspotComment(String commentKey, DbSession dbSession) {
+    return dbClient.issueChangeDao().selectCommentByKey(dbSession, commentKey)
+      .orElseThrow(() -> new NotFoundException(format("Comment with key '%s' does not exist", commentKey)));
+  }
+
+  private void validate(DbSession dbSession, IssueChangeDto issueChangeDto) {
+    hotspotWsSupport.loadAndCheckProject(dbSession, issueChangeDto.getIssueKey());
+    checkArgument(Objects.equals(issueChangeDto.getUserUuid(), userSession.getUuid()), "You can only delete your own comments");
+  }
+
+  private void deleteComment(DbSession dbSession, String commentKey) {
+    dbClient.issueChangeDao().delete(dbSession, commentKey);
+  }
+}
index 31c1f5ed430fd51aa54a7c5de217fb973cfce800..29a280a2e6426a80e981b227c02a024f78995a54 100644 (file)
@@ -50,6 +50,11 @@ public class HotspotWsSupport {
     return userSession.checkLoggedIn().getUuid();
   }
 
+  ComponentDto loadAndCheckProject(DbSession dbSession, String hotspotKey) {
+    IssueDto hotspot = loadHotspot(dbSession, hotspotKey);
+    return loadAndCheckProject(dbSession, hotspot, UserRole.USER);
+  }
+
   IssueDto loadHotspot(DbSession dbSession, String hotspotKey) {
     return dbClient.issueDao().selectByKey(dbSession, hotspotKey)
       .filter(t -> t.getType() == RuleType.SECURITY_HOTSPOT.getDbConstant())
index 73c05653e5f8f110c97edd8f76fb17de99a9f937..1979b02e076fcd41b57f6e31d231e93512d10a48 100644 (file)
@@ -32,6 +32,7 @@ public class HotspotsWsModule extends Module {
       ShowAction.class,
       ChangeStatusAction.class,
       AddCommentAction.class,
+      DeleteCommentAction.class,
       HotspotsWs.class);
   }
 }
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/DeleteCommentActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/DeleteCommentActionTest.java
new file mode 100644 (file)
index 0000000..f2937d5
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.hotspot.ws;
+
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService.Param;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.TestResponse;
+import org.sonar.server.ws.WsActionTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+public class DeleteCommentActionTest {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private System2 system2 = mock(System2.class);
+  private HotspotWsSupport hotspotWsSupport = new HotspotWsSupport(dbClient, userSessionRule, system2);
+
+  private DeleteCommentAction underTest = new DeleteCommentAction(dbClient, hotspotWsSupport, userSessionRule);
+  private WsActionTester actionTester = new WsActionTester(underTest);
+
+  @Test
+  public void verify_ws_def() {
+    assertThat(actionTester.getDef().isInternal()).isTrue();
+    assertThat(actionTester.getDef().isPost()).isTrue();
+
+    Param commentKeyParam = actionTester.getDef().param("comment");
+    assertThat(commentKeyParam).isNotNull();
+    assertThat(commentKeyParam.isRequired()).isTrue();
+  }
+
+  @Test
+  public void delete_comment_from_hotspot_private_project() {
+    UserDto userDeletingOwnComment = dbTester.users().insertUser();
+
+    ComponentDto project = dbTester.components().insertPrivateProject();
+
+    IssueDto hotspot = dbTester.issues().insertHotspot(h -> h.setProject(project));
+    IssueChangeDto comment = dbTester.issues().insertComment(hotspot, userDeletingOwnComment, "Some comment");
+
+    userSessionRule.logIn(userDeletingOwnComment);
+    userSessionRule.addProjectPermission(UserRole.USER, project);
+
+    TestRequest request = newRequest(comment.getKey());
+
+    TestResponse response = request.execute();
+    assertThat(response.getStatus()).isEqualTo(204);
+    assertThat(getHotspotCommentByKey(comment.getKey()))
+      .isEmpty();
+  }
+
+  @Test
+  public void delete_comment_from_hotspot_public_project() {
+    UserDto userAddingComment = dbTester.users().insertUser();
+
+    ComponentDto project = dbTester.components().insertPublicProject();
+
+    IssueDto hotspot = dbTester.issues().insertHotspot(h -> h.setProject(project));
+    IssueChangeDto comment = dbTester.issues().insertComment(hotspot, userAddingComment, "Some comment");
+
+    assertThat(getHotspotCommentByKey(comment.getKey()))
+      .isNotEmpty();
+
+    userSessionRule.logIn(userAddingComment);
+    userSessionRule.registerComponents(project);
+
+    TestRequest request = newRequest(comment.getKey());
+
+    TestResponse response = request.execute();
+    assertThat(response.getStatus()).isEqualTo(204);
+    assertThat(getHotspotCommentByKey(comment.getKey()))
+      .isEmpty();
+  }
+
+  @Test
+  public void fails_with_UnauthorizedException_if_user_is_anonymous() {
+    userSessionRule.anonymous();
+
+    TestRequest request = actionTester.newRequest();
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(UnauthorizedException.class)
+      .hasMessage("Authentication is required");
+  }
+
+  @Test
+  public void fails_if_comment_with_provided_key_does_not_exist() {
+    userSessionRule.logIn();
+
+    TestRequest request = newRequest("not-existing-comment-key");
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(NotFoundException.class)
+      .hasMessage("Comment with key 'not-existing-comment-key' does not exist");
+  }
+
+  @Test
+  public void fails_if_trying_to_delete_comment_of_another_user_in_private_project() {
+    UserDto userTryingToDelete = dbTester.users().insertUser();
+    UserDto userWithHotspotComment = dbTester.users().insertUser();
+
+    ComponentDto project = dbTester.components().insertPrivateProject();
+
+    IssueDto hotspot = dbTester.issues().insertHotspot(h -> h.setProject(project));
+    IssueChangeDto comment = dbTester.issues().insertComment(hotspot, userWithHotspotComment, "Some comment");
+
+    userSessionRule.logIn(userTryingToDelete);
+    userSessionRule.addProjectPermission(UserRole.USER, project);
+
+    TestRequest request = newRequest(comment.getKey());
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("You can only delete your own comments");
+  }
+
+  @Test
+  public void fails_if_trying_to_delete_comment_of_another_user_in_public_project() {
+    UserDto userTryingToDelete = dbTester.users().insertUser();
+    UserDto userWithHotspotComment = dbTester.users().insertUser();
+
+    ComponentDto project = dbTester.components().insertPublicProject();
+
+    IssueDto hotspot = dbTester.issues().insertHotspot(h -> h.setProject(project));
+    IssueChangeDto comment = dbTester.issues().insertComment(hotspot, userWithHotspotComment, "Some comment");
+
+    userSessionRule.logIn(userTryingToDelete)
+      .registerComponents(project);
+
+    TestRequest request = newRequest(comment.getKey());
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("You can only delete your own comments");
+  }
+
+  private TestRequest newRequest(String commentKey) {
+    return actionTester.newRequest()
+      .setParam("comment", commentKey);
+  }
+
+  private Optional<IssueChangeDto> getHotspotCommentByKey(String commentKey) {
+    return dbClient.issueChangeDao().selectCommentByKey(dbTester.getSession(), commentKey);
+  }
+}
index f0618b661288e68d4901e95e83660336e74f3b8e..099aecbd6a21cfeff59f2d8685709b0172be47f7 100644 (file)
@@ -30,7 +30,7 @@ public class HotspotsWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new HotspotsWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 8);
+    assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 9);
   }
 
 }
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/DeleteCommentRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/DeleteCommentRequest.java
new file mode 100644 (file)
index 0000000..a3426bd
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.sonarqube.ws.client.hotspots;
+
+import javax.annotation.Generated;
+
+/**
+ * This is part of the internal API.
+ * This is a POST request.
+ * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/hotspots/delete_comment">Further information about this action online (including a response example)</a>
+ * @since 8.2
+ */
+@Generated("sonar-ws-generator")
+public class DeleteCommentRequest {
+
+  private String comment;
+
+  /**
+   * This is a mandatory parameter.
+   * Example value: "AU-Tpxb--iU5OvuD2FLy"
+   */
+  public DeleteCommentRequest setComment(String comment) {
+    this.comment = comment;
+    return this;
+  }
+
+  public String getComment() {
+    return comment;
+  }
+}
index be4ec96dc3ee29a2b38e3773d778d1c3bcaec984..4b37ba48a4328f1f2c68fabf8a99e7e4f9a67c2e 100644 (file)
@@ -86,6 +86,21 @@ public class HotspotsService extends BaseService {
         .setMediaType(MediaTypes.JSON)).content();
   }
 
+  /**
+   *
+   * This is part of the internal API.
+   * This is a POST request.
+   * @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/hotspots/delete_comment">Further information about this action online (including a response example)</a>
+   * @since 8.2
+   */
+  public void deleteComment(DeleteCommentRequest request) {
+    call(
+      new PostRequest(path("delete_comment"))
+        .setParam("comment", request.getComment())
+        .setMediaType(MediaTypes.JSON)
+      ).content();
+  }
+
   /**
    *
    * This is part of the internal API.