From bc882348df81eb4510f0e7d39d2f931902660ab5 Mon Sep 17 00:00:00 2001 From: taskain7 Date: Thu, 16 Mar 2023 12:27:25 +0100 Subject: [PATCH] [FINERACT-1678] Set a limit to the request body list size on Inline Loan COB API --- .../InlineLoanCOBExecutorServiceImpl.java | 10 ++++ .../core/config/FineractProperties.java | 15 ++++++ .../core/data/ApiGlobalErrorResponse.java | 5 ++ ...questBodyItemLimitValidationException.java | 26 +++++++++++ ...odyItemLimitValidationExceptionMapper.java | 46 +++++++++++++++++++ ...esource.java => InlineJobApiResource.java} | 5 +- .../src/main/resources/application.properties | 2 + .../InlineLoanCOBExecutorServiceImplTest.java | 10 ++++ .../inlinecob/InlineLoanCOBHelper.java | 8 ++++ .../inlinecob/InlineLoanCOBTest.java | 11 +++++ 10 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformRequestBodyItemLimitValidationException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformRequestBodyItemLimitValidationExceptionMapper.java rename fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/{InlineJobResource.java => InlineJobApiResource.java} (94%) diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java index 838f06cb3..bd5336c98 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImpl.java @@ -45,6 +45,7 @@ import org.apache.fineract.infrastructure.core.config.FineractProperties; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformInternalServerException; +import org.apache.fineract.infrastructure.core.exception.PlatformRequestBodyItemLimitValidationException; import org.apache.fineract.infrastructure.core.serialization.GoogleGsonSerializerHelper; import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.infrastructure.jobs.domain.CustomJobParameter; @@ -95,6 +96,7 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService loanIds = dataParser.parseExecution(command); + validateLoanIdsListSize(loanIds); execute(loanIds, jobName); return new CommandProcessingResultBuilder().withCommandId(command.commandId()).build(); } @@ -231,4 +233,12 @@ public class InlineLoanCOBExecutorServiceImpl implements InlineExecutorService loanIds) { + int inlineLoanCobRequestItemLimit = fineractProperties.getApi().getBodyItemSizeLimit().getInlineLoanCob(); + if (loanIds.size() > inlineLoanCobRequestItemLimit) { + String userMessage = "Size of the loan IDs list cannot be over " + inlineLoanCobRequestItemLimit; + throw new PlatformRequestBodyItemLimitValidationException(userMessage); + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java index a28aa4ee8..bcf601ab8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/config/FineractProperties.java @@ -56,6 +56,7 @@ public class FineractProperties { private FineractJpaProperties jpa; private FineractQueryProperties query; + private FineractApiProperties api; @Getter @Setter @@ -265,4 +266,18 @@ public class FineractProperties { private int inClauseParameterSizeLimit; } + @Getter + @Setter + public static class FineractApiProperties { + + private FineractBodyItemSizeLimitProperties bodyItemSizeLimit; + } + + @Getter + @Setter + public static class FineractBodyItemSizeLimitProperties { + + private int inlineLoanCob; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java index 8bf5e4664..fe2105751 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/data/ApiGlobalErrorResponse.java @@ -21,6 +21,7 @@ package org.apache.fineract.infrastructure.core.data; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.Gson; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.ws.rs.core.Response.Status; @@ -176,6 +177,10 @@ public class ApiGlobalErrorResponse { return globalErrorResponse; } + public static ApiGlobalErrorResponse badClientRequest(final String globalisationMessageCode, final String defaultUserMessage) { + return badClientRequest(globalisationMessageCode, defaultUserMessage, Collections.emptyList()); + } + public static ApiGlobalErrorResponse serverSideError(final String globalisationMessageCode, final String defaultUserMessage, final Object... defaultUserMessageArgs) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformRequestBodyItemLimitValidationException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformRequestBodyItemLimitValidationException.java new file mode 100644 index 000000000..a0ee617dd --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exception/PlatformRequestBodyItemLimitValidationException.java @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.exception; + +public class PlatformRequestBodyItemLimitValidationException extends RuntimeException { + + public PlatformRequestBodyItemLimitValidationException(String message) { + super(message); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformRequestBodyItemLimitValidationExceptionMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformRequestBodyItemLimitValidationExceptionMapper.java new file mode 100644 index 000000000..59a7224d9 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/exceptionmapper/PlatformRequestBodyItemLimitValidationExceptionMapper.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.core.exceptionmapper; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.infrastructure.core.data.ApiGlobalErrorResponse; +import org.apache.fineract.infrastructure.core.exception.PlatformRequestBodyItemLimitValidationException; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Provider +@Component +@Scope("singleton") +@Slf4j +public class PlatformRequestBodyItemLimitValidationExceptionMapper + implements ExceptionMapper { + + @Override + public Response toResponse(PlatformRequestBodyItemLimitValidationException exception) { + String globalisationMessage = "error.msg.validation.request.body.item.limit.validation"; + log.warn("Exception: {}, Message: {}", exception.getClass().getName(), exception.getMessage()); + final ApiGlobalErrorResponse badRequestErrorResponse = ApiGlobalErrorResponse.badClientRequest(globalisationMessage, + exception.getMessage()); + return Response.status(Response.Status.BAD_REQUEST).entity(badRequestErrorResponse).type(MediaType.APPLICATION_JSON).build(); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResource.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobApiResource.java similarity index 94% rename from fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResource.java rename to fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobApiResource.java index 2c7a270e3..620b66df8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/api/InlineJobApiResource.java @@ -45,7 +45,7 @@ import org.springframework.stereotype.Component; @Component @Tag(name = "Inline Job", description = "") @RequiredArgsConstructor -public class InlineJobResource { +public class InlineJobApiResource { private final PortfolioCommandSourceWritePlatformService commandWritePlatformService; private final DefaultToApiJsonSerializer serializer; @@ -57,7 +57,8 @@ public class InlineJobResource { @Operation(summary = "Starts an inline Job", description = "Starts an inline Job") @RequestBody(content = @Content(schema = @Schema(implementation = InlineJobResourceSwagger.InlineJobRequest.class))) @ApiResponses({ - @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = InlineJobResourceSwagger.InlineJobResponse.class))) }) + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = InlineJobResourceSwagger.InlineJobResponse.class))), + @ApiResponse(responseCode = "400", description = "Request body item size validation error") }) public String executeInlineJob(@PathParam("jobName") @Parameter(description = "jobName") final String jobName, @Parameter(hidden = true) final String jsonRequestBody) { diff --git a/fineract-provider/src/main/resources/application.properties b/fineract-provider/src/main/resources/application.properties index ec67e4051..714e20715 100644 --- a/fineract-provider/src/main/resources/application.properties +++ b/fineract-provider/src/main/resources/application.properties @@ -44,6 +44,8 @@ fineract.mode.batch-manager-enabled=${FINERACT_MODE_BATCH_MANAGER_ENABLED:true} fineract.query.in-clause-parameter-size-limit=${FINERACT_QUERY_PARAMETER_SIZE:1000} +fineract.api.body-item-size-limit.inline-loan-cob=${FINERACT_API_REQUEST_BODY_SIZE_LIMIT_INLINE_COB:1000} + fineract.correlation.enabled=${FINERACT_LOGGING_HTTP_CORRELATION_ID_ENABLED:false} fineract.correlation.header-name=${FINERACT_LOGGING_HTTP_CORRELATION_ID_HEADER_NAME:X-Correlation-ID} diff --git a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java index 791b561c4..eae21ac21 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/cob/service/InlineLoanCOBExecutorServiceImplTest.java @@ -66,6 +66,10 @@ class InlineLoanCOBExecutorServiceImplTest { private FineractProperties fineractProperties; @Mock private FineractProperties.FineractQueryProperties fineractQueryProperties; + @Mock + private FineractProperties.FineractApiProperties fineractApiProperties; + @Mock + private FineractProperties.FineractBodyItemSizeLimitProperties fineractBodyItemSizeLimitProperties; @Test void shouldExceptionThrownIfLoanIsAlreadyLocked() { @@ -80,8 +84,11 @@ class InlineLoanCOBExecutorServiceImplTest { when(transactionTemplate.execute(any())).thenThrow(new LoanAccountLockCannotBeOverruledException("")); when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); + when(fineractProperties.getApi()).thenReturn(fineractApiProperties); when(dataParser.parseExecution(any())).thenReturn(List.of(1L)); when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(65000); + when(fineractApiProperties.getBodyItemSizeLimit()).thenReturn(fineractBodyItemSizeLimitProperties); + when(fineractBodyItemSizeLimitProperties.getInlineLoanCob()).thenReturn(1000); when(loanRepository.findAllNonClosedLoansBehindOrNullByLoanIds(any(), anyList())).thenReturn(List.of(loan)); assertThrows(LoanAccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB")); } @@ -101,8 +108,11 @@ class InlineLoanCOBExecutorServiceImplTest { when(transactionTemplate.execute(any())).thenThrow(new LoanAccountLockCannotBeOverruledException("")); when(fineractProperties.getQuery()).thenReturn(fineractQueryProperties); + when(fineractProperties.getApi()).thenReturn(fineractApiProperties); when(dataParser.parseExecution(any())).thenReturn(List.of(1L, 2L, 3L)); when(fineractQueryProperties.getInClauseParameterSizeLimit()).thenReturn(2); + when(fineractApiProperties.getBodyItemSizeLimit()).thenReturn(fineractBodyItemSizeLimitProperties); + when(fineractBodyItemSizeLimitProperties.getInlineLoanCob()).thenReturn(1000); when(loanRepository.findAllNonClosedLoansBehindOrNullByLoanIds(any(), anyList())).thenReturn(List.of(loan1, loan2, loan3)); assertThrows(LoanAccountLockCannotBeOverruledException.class, () -> testObj.executeInlineJob(command, "INLINE_LOAN_COB")); verify(loanRepository, times(2)).findAllNonClosedLoansBehindOrNullByLoanIds(any(), anyList()); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBHelper.java index 000e5bd4e..a9f092d81 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBHelper.java @@ -45,6 +45,14 @@ public class InlineLoanCOBHelper extends IntegrationTest { return Utils.performServerPost(requestSpec, responseSpec, EXECUTE_INLINE_COB_API, buildInlineCOBRequest(loanIds)); } + public String executeInlineCOB(List loanIds, String responseAttribute) { + final String EXECUTE_INLINE_COB_API = "/fineract-provider/api/v1/jobs/LOAN_COB/inline"; + log.info("------------------EXECUTE INLINE COB----------------------"); + log.info("------------------Loan IDs: {}----------------------", loanIds); + return Utils.performServerPost(requestSpec, responseSpec, EXECUTE_INLINE_COB_API, buildInlineCOBRequest(loanIds), + responseAttribute); + } + private static String buildInlineCOBRequest(List loanIds) { final HashMap> map = new HashMap<>(); map.put("loanIds", loanIds); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java index fc003432c..f9a3b5365 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/inlinecob/InlineLoanCOBTest.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests.inlinecob; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import io.restassured.builder.RequestSpecBuilder; @@ -31,6 +32,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.stream.LongStream; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.batch.domain.BatchRequest; import org.apache.fineract.batch.domain.BatchResponse; @@ -390,6 +392,15 @@ public class InlineLoanCOBTest { Assertions.assertEquals(LocalDate.of(2020, 3, 9), loan.getLastClosedBusinessDate()); } + @Test + public void testInlineCOBRequestBodyItemLimitValidation() { + responseSpec = new ResponseSpecBuilder().expectStatusCode(400).build(); + inlineLoanCOBHelper = new InlineLoanCOBHelper(requestSpec, responseSpec); + List loanIds = LongStream.rangeClosed(1, 1001).boxed().toList(); + String responseUserMessage = inlineLoanCOBHelper.executeInlineCOB(loanIds, "defaultUserMessage"); + assertEquals("Size of the loan IDs list cannot be over 1000", responseUserMessage); + } + private void createNewSimpleUserWithoutBypassPermission() { GetOfficesResponse headOffice = OfficeHelper.getHeadOffice(requestSpec, responseSpec); String username = Utils.uniqueRandomStringGenerator("NotificationUser", 4);