From c273f192526360242eea101fe59d2edb56588bbf Mon Sep 17 00:00:00 2001 From: nikesh kumar Date: Fri, 15 Mar 2024 13:26:00 +0530 Subject: [PATCH] feat(rest): create new endpoint to delete ModerationRequests by id. Signed-off-by: Nikesh kumar --- .../src/docs/asciidoc/moderationRequests.adoc | 11 +++ .../ModerationRequestController.java | 79 +++++++++++++++++++ .../Sw360ModerationRequestService.java | 52 ++++++++++-- .../restdocs/ModerationRequestSpecTest.java | 34 +++++++- 4 files changed, 169 insertions(+), 7 deletions(-) diff --git a/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc b/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc index f8f8378a60..71fbf45405 100644 --- a/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc +++ b/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc @@ -163,3 +163,14 @@ include::{snippets}/should_document_check_user_message_moderationrequests/curl-r ===== Example response include::{snippets}/should_document_check_user_message_moderationrequests/http-response.adoc[] + +[[resources-moderationRequest-delete]] +==== Delete Moderation Requests + +A `DELETE` method will delete list of moderation request. + +===== Example request +include::{snippets}/should_document_delete_moderationrequests/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_delete_moderationrequests/http-response.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/ModerationRequestController.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/ModerationRequestController.java index b203c532b5..c22c4e91f5 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/ModerationRequestController.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/ModerationRequestController.java @@ -26,11 +26,14 @@ import org.apache.thrift.transport.TTransportException; import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.common.SW360Constants; +import org.eclipse.sw360.datahandler.resourcelists.PaginationParameterException; +import org.eclipse.sw360.datahandler.permissions.PermissionUtils; import org.eclipse.sw360.datahandler.resourcelists.PaginationResult; import org.eclipse.sw360.datahandler.resourcelists.ResourceClassNotFoundException; import org.eclipse.sw360.datahandler.thrift.ModerationState; import org.eclipse.sw360.datahandler.thrift.PaginationData; import org.eclipse.sw360.datahandler.thrift.SW360Exception; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.components.Component; import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.licenses.License; @@ -52,6 +55,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.rest.webmvc.BasePathAwareController; import org.springframework.data.rest.webmvc.RepositoryLinksResource; +import org.springframework.data.rest.webmvc.ResourceNotFoundException; import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; @@ -59,6 +63,7 @@ import org.springframework.hateoas.server.RepresentationModelProcessor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.HttpClientErrorException; @@ -70,6 +75,8 @@ import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; + import java.net.URISyntaxException; import java.util.*; import java.util.function.Function; @@ -383,6 +390,7 @@ private ResponseEntity> getModerationResponse return new ResponseEntity<>(resources, status); } + /** * Filter moderation request to remove duplicate additions and deletions data. * @param moderationRequest Moderation request to filter @@ -614,4 +622,75 @@ private Object getEntityByTypeAndId(String entityType, String entityId, User use throw new RuntimeException("Unable to connect to the service. Please check the server status.", e); } } + @Operation( + summary = "Delete moderation request.", + description = "Delete delete moderation request of the service.", + tags = {"ModerationRequest"} + ) + @PreAuthorize("hasAuthority('WRITE')") + @RequestMapping(value = MODERATION_REQUEST_URL + "/delete", method = RequestMethod.DELETE) + public ResponseEntity deleteModerationRequest( + HttpServletRequest request, + @Parameter(description = "List of moderation request IDs to delete") + @RequestBody List ids) throws TException { + User sw360User = restControllerHelper.getSw360UserFromAuthentication(); + List requestStatusList = new ArrayList<>(); + List incorrectIds = new ArrayList<>(); + List correctIds = new ArrayList<>(); + List deletedIds = new ArrayList<>(); + + for (String id : ids) { + try { + ModerationRequest moderationRequest = sw360ModerationRequestService.getModerationRequestById(id); + RequestStatus requestStatus = sw360ModerationRequestService.deleteModerationRequestInfo(sw360User, id, + moderationRequest); + requestStatusList.add(requestStatus); + if (requestStatus == RequestStatus.SUCCESS) { + deletedIds.add(id); + } else { + correctIds.add(id); + } + } catch (ResourceNotFoundException ex) { + incorrectIds.add(id); + } + } + + Map response = new HashMap<>(); + response.put("deleted", deletedIds); + response.put("incorrect", incorrectIds); + response.put("correct", correctIds); + + if (!requestStatusList.isEmpty()) { + if (requestStatusList.contains(RequestStatus.FAILURE)) { + response.put("message", "User doesn't have permission to delete."); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } else if (requestStatusList.contains(RequestStatus.SUCCESS) && requestStatusList.contains(null)) { + response.put("message", "Some requests were deleted, but some are in an open state."); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } else if (requestStatusList.contains(RequestStatus.SUCCESS) && incorrectIds.isEmpty() && correctIds.isEmpty()) { + response.put("message", "All specified moderation requests were successfully deleted."); + return ResponseEntity.status(HttpStatus.OK).body(response); + } else if (requestStatusList.contains(null)) { + response.put("message", "MR is in open state or user don't have permission."); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } + } + + if (!incorrectIds.isEmpty() && !correctIds.isEmpty()) { + response.put("message", "Some moderation requests are invalid or open."); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } else if (incorrectIds.isEmpty() && correctIds.isEmpty() && !deletedIds.isEmpty()) { + response.put("message", "All specified moderation requests were successfully deleted."); + return ResponseEntity.status(HttpStatus.OK).body(response); + } else if (!incorrectIds.isEmpty()) { + response.put("message", "Some moderation requests are invalid."); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } else if (!correctIds.isEmpty()) { + response.put("message", "Some moderation requests are in an open state."); + return ResponseEntity.status(HttpStatus.CONFLICT).body(response); + } else { + response.put("message", "No valid moderation requests found."); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + } } diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/Sw360ModerationRequestService.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/Sw360ModerationRequestService.java index a8309527e6..300a9746bd 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/Sw360ModerationRequestService.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/moderationrequest/Sw360ModerationRequestService.java @@ -15,12 +15,14 @@ import lombok.RequiredArgsConstructor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.thrift.TApplicationException; import org.apache.thrift.TException; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.TTransportException; import org.eclipse.sw360.datahandler.common.SW360Utils; +import org.eclipse.sw360.datahandler.permissions.PermissionUtils; import org.eclipse.sw360.datahandler.thrift.ModerationState; import org.eclipse.sw360.datahandler.thrift.PaginationData; import org.eclipse.sw360.datahandler.thrift.RemoveModeratorRequestStatus; @@ -37,6 +39,7 @@ import org.eclipse.sw360.datahandler.thrift.spdx.spdxpackageinfo.PackageInformationService; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.users.UserService; +import org.eclipse.sw360.datahandler.thrift.users.UserGroup; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -51,6 +54,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; @Service @RequiredArgsConstructor(onConstructor = @__(@Autowired)) @@ -116,22 +120,35 @@ private UserService.Iface getThriftUserClient() throws TTransportException { * @return Moderation Request * @throws TException Appropriate exception if request does not exists or not accessible. */ - public ModerationRequest getModerationRequestById(String requestId) throws TException { + public ModerationRequest getModerationRequestById(String requestId) throws TException, TApplicationException { + ModerationRequest moderationRequest = null; try { - return getThriftModerationClient().getModerationRequestById(requestId); + moderationRequest = getThriftModerationClient().getModerationRequestById(requestId); + } catch (TApplicationException tAppExp) { + log.error("TApplicationException while fetching moderation request by ID: {}. Exception: {}", + requestId, tAppExp.getMessage(), tAppExp); + throw new ResourceNotFoundException("Requested ModerationRequest not found: " + requestId, tAppExp); } catch (SW360Exception sw360Exp) { if (sw360Exp.getErrorCode() == 404) { - throw new ResourceNotFoundException("Requested ModerationRequest not found"); + log.warn("ModerationRequest not found with ID: {}", requestId); + throw new ResourceNotFoundException("Requested ModerationRequest not found: " + requestId, sw360Exp); } else if (sw360Exp.getErrorCode() == 403) { - throw new AccessDeniedException( - "ModerationRequest or its Linked Project are restricted and / or not accessible"); + log.warn("Access denied for ModerationRequest or its linked project with ID: {}", requestId); + throw new AccessDeniedException("ModerationRequest or its Linked Project are restricted and/or not accessible", sw360Exp); } else { - log.error("Error fetching moderation request by id: " + sw360Exp.getMessage()); + log.error("Unhandled SW360Exception while fetching moderation request by ID: {}. Exception: {}", + requestId, sw360Exp.getMessage(), sw360Exp); throw sw360Exp; } + } catch (Exception ex) { + log.error("Unexpected exception while fetching moderation request by ID: {}. Exception: {}", + requestId, ex.getMessage(), ex); + throw new RuntimeException("An unexpected error occurred while fetching the ModerationRequest: " + requestId, ex); } + return moderationRequest; } + /** * Get paginated list of moderation requests where user is one of the * moderators. @@ -447,4 +464,27 @@ public Integer getOpenCriticalCrCountByGroup(String group) { return 0; } } + + public RequestStatus deleteModerationRequestInfo(@NotNull User sw360User, @NotNull String id, + @NotNull ModerationRequest moderationRequest) + throws TTransportException, TException { + RequestStatus requestStatus = null; + Set moderators = moderationRequest.getModerators(); + String requestingUser = moderationRequest.getRequestingUser(); + ModerationState moderationState = moderationRequest.getModerationState(); + + if (moderators.contains(sw360User.getEmail())) { + if (moderationState == ModerationState.REJECTED || moderationState == ModerationState.APPROVED) { + requestStatus = getThriftModerationClient().deleteModerationRequest(id, sw360User); + } + } else if (!sw360User.getEmail().equals(requestingUser)) { + if (moderationState == ModerationState.PENDING || moderationState == ModerationState.INPROGRESS) { + requestStatus = RequestStatus.FAILURE; + } + } else { + requestStatus = getThriftModerationClient().deleteModerationRequest(id, sw360User); + } + + return requestStatus; + } } diff --git a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ModerationRequestSpecTest.java b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ModerationRequestSpecTest.java index d5dc8c14d0..2c8be4e489 100644 --- a/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ModerationRequestSpecTest.java +++ b/rest/resource-server/src/test/java/org/eclipse/sw360/rest/resourceserver/restdocs/ModerationRequestSpecTest.java @@ -13,6 +13,7 @@ import org.apache.thrift.TException; import org.eclipse.sw360.datahandler.thrift.ModerationState; import org.eclipse.sw360.datahandler.thrift.PaginationData; +import org.eclipse.sw360.datahandler.thrift.RequestStatus; import org.eclipse.sw360.datahandler.thrift.Visibility; import org.eclipse.sw360.datahandler.thrift.components.ComponentType; import org.eclipse.sw360.datahandler.thrift.components.ECCStatus; @@ -66,6 +67,7 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -90,6 +92,7 @@ public class ModerationRequestSpecTest extends TestRestDocsSpecBase { @MockBean private Project project; + private ModerationRequest moderationRequest; @Before public void before() throws TException, IOException { @@ -136,7 +139,7 @@ public void before() throws TException, IOException { project2Deletions.setProjectType(ProjectType.CUSTOMER); project2Deletions.setVisbility(Visibility.BUISNESSUNIT_AND_MODERATORS); - ModerationRequest moderationRequest = new ModerationRequest(); + moderationRequest = new ModerationRequest(); moderationRequest.setId("MR-101"); moderationRequest.setTimestamp(System.currentTimeMillis() / 1000L - 172800); moderationRequest.setDocumentId("R-101"); @@ -514,4 +517,33 @@ public void should_document_check_user_message_moderationrequests() throws Excep .andExpect(status().isOk()) .andReturn(); } + + @Test + public void should_document_delete_moderationrequests() throws Exception { + String accessToken = TestHelper.generateAuthHeader(testUserId, testUserPassword); + ModerationRequest mr3 = new ModerationRequest(); + mr3.setId("MR-20443"); + mr3.setRevision("1"); + mr3.setTimestamp(System.currentTimeMillis() / 1000L - 172800); + mr3.setTimestampOfDecision(System.currentTimeMillis() / 1000L - 155000); + mr3.setDocumentId("P-102"); + mr3.setDocumentType(DocumentType.PROJECT); + mr3.setRequestingUser("test.admin@sw360.org"); + mr3.setDocumentName("Project 2"); + mr3.setModerationState(ModerationState.REJECTED); + mr3.setReviewer("admin@sw360.org"); + mr3.setRequestingUserDepartment("DEPT"); + mr3.setComponentType(ComponentType.OSS); + mr3.setCommentRequestingUser("Update project version"); + + given(this.moderationRequestServiceMock.deleteModerationRequestInfo(any(), any(), any())) + .willReturn(RequestStatus.SUCCESS); + + mockMvc.perform(delete("/api/moderationrequest/delete") + .content("[\"" + mr3.getId() + "\"]") + .header("Authorization", accessToken) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()); + } }