From f198157719a6b5fd841a93c8e34728ddcb4d4759 Mon Sep 17 00:00:00 2001 From: Jacek Date: Wed, 12 Feb 2020 16:34:45 +0100 Subject: [PATCH] SONAR-12922 add delete hotspot comment WS --- .../hotspot/ws/DeleteCommentAction.java | 94 +++++++++ .../server/hotspot/ws/HotspotWsSupport.java | 5 + .../server/hotspot/ws/HotspotsWsModule.java | 1 + .../hotspot/ws/DeleteCommentActionTest.java | 183 ++++++++++++++++++ .../hotspot/ws/HotspotsWsModuleTest.java | 2 +- .../client/hotspots/DeleteCommentRequest.java | 47 +++++ .../ws/client/hotspots/HotspotsService.java | 15 ++ 7 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/DeleteCommentAction.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/DeleteCommentActionTest.java create mode 100644 sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/DeleteCommentRequest.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 index 00000000000..4d74a212c09 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/DeleteCommentAction.java @@ -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.
" + + "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); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsSupport.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsSupport.java index 31c1f5ed430..29a280a2e64 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsSupport.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsSupport.java @@ -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()) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java index 73c05653e5f..1979b02e076 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotsWsModule.java @@ -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 index 00000000000..f2937d5347d --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/DeleteCommentActionTest.java @@ -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 getHotspotCommentByKey(String commentKey) { + return dbClient.issueChangeDao().selectCommentByKey(dbTester.getSession(), commentKey); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java index f0618b66128..099aecbd6a2 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/HotspotsWsModuleTest.java @@ -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 index 00000000000..a3426bd6b7f --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/DeleteCommentRequest.java @@ -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 Further information about this action online (including a response example) + * @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; + } +} diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java index be4ec96dc3e..4b37ba48a43 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/hotspots/HotspotsService.java @@ -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 Further information about this action online (including a response example) + * @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. -- 2.39.5