From af1cfc2bcaff2dac0540bf7f91ae68da6eaa696c Mon Sep 17 00:00:00 2001 From: nikesh kumar Date: Thu, 5 Dec 2024 13:18:21 +0530 Subject: [PATCH] fix(rest): Validate comment message while create a moderation request. Signed-off-by: Nikesh kumar --- .../src/docs/asciidoc/moderationRequests.adoc | 17 +++ .../core/RestControllerHelper.java | 6 + .../ModerationRequestController.java | 124 ++++++++++++++++++ .../restdocs/ModerationRequestSpecTest.java | 31 +++++ 4 files changed, 178 insertions(+) diff --git a/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc b/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc index 49ac1ede58..f8f8378a60 100644 --- a/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc +++ b/rest/resource-server/src/docs/asciidoc/moderationRequests.adoc @@ -146,3 +146,20 @@ include::{snippets}/should_document_get_moderationrequests_submission/curl-reque ===== Example response include::{snippets}/should_document_get_moderationrequests_submission/http-response.adoc[] + +[[resources-moderationRequest-validate]] +==== Validate User And Message Moderation Requests + +A `POST` will validate the user and will check the comment message is present or not. + +===== Request parameter +include::{snippets}/should_document_check_user_message_moderationrequests/query-parameters.adoc[] + +===== Response structure +include::{snippets}/should_document_check_user_message_moderationrequests/response-fields.adoc[] + +===== Example request +include::{snippets}/should_document_check_user_message_moderationrequests/curl-request.adoc[] + +===== Example response +include::{snippets}/should_document_check_user_message_moderationrequests/http-response.adoc[] diff --git a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java index d4d700f4ed..bc70c0611a 100644 --- a/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java +++ b/rest/resource-server/src/main/java/org/eclipse/sw360/rest/resourceserver/core/RestControllerHelper.java @@ -54,6 +54,7 @@ import org.eclipse.sw360.datahandler.thrift.spdx.documentcreationinformation.*; import org.eclipse.sw360.datahandler.thrift.spdx.spdxdocument.SPDXDocument; import org.eclipse.sw360.datahandler.thrift.spdx.spdxpackageinfo.PackageInformation; +import org.eclipse.sw360.datahandler.thrift.users.RequestedAction; import org.eclipse.sw360.datahandler.thrift.users.User; import org.eclipse.sw360.datahandler.thrift.vendors.Vendor; import org.eclipse.sw360.datahandler.thrift.vulnerabilities.*; @@ -102,6 +103,7 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder; +import static org.eclipse.sw360.datahandler.permissions.PermissionUtils.makePermission; import jakarta.servlet.http.HttpServletRequest; import java.io.UnsupportedEncodingException; @@ -1682,4 +1684,8 @@ public ClearingRequest updateCRSize(ClearingRequest clearingRequest, Project pro } return clearingRequestService.getClearingRequestById(clearingRequest.getId(), sw360User); } + + public boolean isWriteActionAllowed(Object object, User user) { + return makePermission(object, user).isActionAllowed(RequestedAction.WRITE); + } } 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 a3571db74c..96306b5711 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 @@ -18,10 +18,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.SchemaProperty; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransportException; +import org.bouncycastle.util.test.TestFailedException; import org.eclipse.sw360.datahandler.common.CommonUtils; import org.eclipse.sw360.datahandler.common.SW360Constants; import org.eclipse.sw360.datahandler.resourcelists.PaginationParameterException; @@ -29,6 +32,7 @@ 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.components.Component; import org.eclipse.sw360.datahandler.thrift.components.Release; import org.eclipse.sw360.datahandler.thrift.licenses.License; @@ -50,6 +54,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; @@ -57,8 +62,10 @@ import org.springframework.hateoas.server.RepresentationModelProcessor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.client.HttpClientErrorException; +import io.swagger.v3.oas.annotations.media.ExampleObject; import jakarta.servlet.http.HttpServletRequest; import java.net.URISyntaxException; @@ -478,4 +485,121 @@ private , F extends org.apache.thrift.TF setAddition.apply(addition); setDeletion.apply(deletion); } + + @PreAuthorize("hasAuthority('WRITE')") + @Operation( + summary = "Validate Moderation Request", + description = "This endpoint validates a user for a moderation request.", + tags = {"Moderation Requests"} + ) + @ApiResponses(value = { + @ApiResponse( + responseCode = "200", description = "Moderation request validated successfully.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"Moderation request validated successfully.\"}" + )) + } + ), + @ApiResponse( + responseCode = "202", + description = "Accepted - Moderation request is pending review.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"Moderation request is pending review.\"}" + )) + } + ), + @ApiResponse( + responseCode = "400", description = "Bad Request - Invalid input or missing parameters.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"Invalid input or missing required parameters.\"}" + )) + } + ), + @ApiResponse( + responseCode = "401", description = "Unauthorized - User does not have the required permissions.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"User is not authorized to perform this action.\"}" + )) + } + ), + @ApiResponse( + responseCode = "403", description = "Forbidden - Access denied.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"Access is denied due to insufficient permissions.\"}" + )) + } + ), + @ApiResponse( + responseCode = "500", description = "Internal Server Error.", + content = { + @Content(mediaType = "application/json", + examples = @ExampleObject( + value = "{\"message\": \"An unexpected error occurred while validating the moderation request.\"}" + )) + } + ) + }) + @RequestMapping(value = MODERATION_REQUEST_URL + "/validate", method = RequestMethod.POST) + public ResponseEntity validateModerationRequest( + @Parameter(description = "Project id.") + @RequestParam String entityType, + @Parameter(description = "Entity type", example = "Pass entity type like PROJECT/COMPONENT/RELEASE") + @RequestParam String entityId, + HttpServletRequest request) throws TException{ + + try { + User user = restControllerHelper.getSw360UserFromAuthentication(); + Object entity = getEntityByTypeAndId(entityType, entityId, user); + if (entity == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body("Entity not found for the given ID: " + entityId); + } + boolean isWriteActionAllowed = restControllerHelper.isWriteActionAllowed(entity, user); + if (!isWriteActionAllowed) { + return ResponseEntity.status(HttpStatus.ACCEPTED).body( + "User allowed to perform write on entity.MR is required."); + } + return ResponseEntity.ok("User can write to the entity. MR is not required."); + } catch (SW360Exception ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Entity not found for the given ID: " + entityId); + } catch (IllegalArgumentException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body("Invalid entity type provided: " + ex.getMessage()); + } catch (TException ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("An error occurred while processing the request: " + ex.getMessage()); + } + } + + /** + * Helper method to fetch entity by type and ID. + */ + private Object getEntityByTypeAndId(String entityType, String entityId, User user) throws TException { + try { + switch (entityType.toLowerCase()) { + case "project": + return projectService.getProjectForUserById(entityId, user); + case "component": + return componentService.getComponentForUserById(entityId, user); + case "release": + return releaseService.getReleaseForUserById(entityId, user); + default: + throw new IllegalArgumentException("Unsupported entity type: " + entityType); + } + } catch (TTransportException e) { + throw new RuntimeException("Unable to connect to the service. Please check the server status.", e); + } catch (TException e) { + throw e; + } + } } 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 acbbad4a9f..95c1bce82a 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 @@ -29,6 +29,7 @@ import org.eclipse.sw360.rest.resourceserver.TestHelper; import org.eclipse.sw360.rest.resourceserver.moderationrequest.ModerationPatch; import org.eclipse.sw360.rest.resourceserver.moderationrequest.Sw360ModerationRequestService; +import org.eclipse.sw360.rest.resourceserver.project.Sw360ProjectService; import org.eclipse.sw360.rest.resourceserver.release.Sw360ReleaseService; import org.eclipse.sw360.rest.resourceserver.user.Sw360UserService; import org.junit.Before; @@ -40,6 +41,7 @@ import org.springframework.http.MediaType; import org.springframework.restdocs.payload.JsonFieldType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.MvcResult; import java.io.IOException; import java.util.ArrayList; @@ -50,12 +52,14 @@ import java.util.Map; import java.util.Set; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; @@ -81,6 +85,12 @@ public class ModerationRequestSpecTest extends TestRestDocsSpecBase { @MockBean private Sw360ModerationRequestService moderationRequestServiceMock; + @MockBean + private Sw360ProjectService projectServiceMock; + + @MockBean + private Project project; + @Before public void before() throws TException, IOException { Set moderatorList = new HashSet<>(); @@ -190,6 +200,7 @@ public void before() throws TException, IOException { user.setId("123456789"); user.setEmail(testUserId); user.setFullname("John Doe"); + user.setDepartment("xyz"); given(this.releaseServiceMock.getReleaseForUserById(eq(moderationRequest.getDocumentId()), any())).willReturn(releaseAdditions); given(this.userServiceMock.getUserByEmail(moderationRequest.getRequestingUser())).willReturn(new User("test.admin@sw360.org", "DEPT").setId("12345")); @@ -482,4 +493,24 @@ public void should_document_get_moderationrequests_submission() throws Exception subsectionWithPath("_links").description("<> to other resources") ))); } + + @Test + public void should_document_check_user_message_moderationrequests() throws Exception { + project = new Project(); + project.setId("98745"); + project.setName("Test Project"); + project.setProjectType(ProjectType.PRODUCT); + project.setVersion("1"); + project.setCreatedOn("2021-04-27"); + project.setCreatedBy("admin@sw360.org"); + given(this.projectServiceMock.getProjectForUserById(eq(project.getId()), any())).willReturn(project); + + this.mockMvc.perform(post("/api/moderationrequest/validate") + .param("entityType", "PROJECT") + .param("entityId", project.getId()) + .header("Authorization", TestHelper.generateAuthHeader(testUserId, testUserPassword)) + .accept(MediaTypes.HAL_JSON)) + .andExpect(status().isOk()) + .andReturn(); + } }