Limit number of operations in a ProvMnS PATCH request 75/142575/2
authorseanbeirne <sean.beirne@est.tech>
Fri, 28 Nov 2025 11:22:09 +0000 (11:22 +0000)
committerseanbeirne <sean.beirne@est.tech>
Tue, 2 Dec 2025 09:36:15 +0000 (09:36 +0000)
-New configurable stting in application.yml

Issue-ID: CPS-3070
Change-Id: Ia41ec57cd72a19da989b85b3dbd88e36b2f2dc5b
Signed-off-by: seanbeirne <sean.beirne@est.tech>
cps-application/src/main/resources/application.yml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnsController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/provmns/ErrorResponseBuilder.java
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnsControllerSpec.groovy

index bfc5339..e3e4261 100644 (file)
@@ -107,6 +107,8 @@ app:
             cm-subscription-dmi-out: ${CM_SUBSCRIPTION_DMI_OUT_TOPIC:dmi-ncmp-cm-avc-subscription}
             cm-events-topic: ${NCMP_CM_EVENTS_TOPIC:cm-events}
             inventory-events-topic: ncmp-inventory-events
+        provmns:
+            max-patch-operations: 10
     lcm:
         events:
             topic: ${LCM_EVENTS_TOPIC:ncmp-events}
index 9ea71de..1bad56b 100644 (file)
@@ -44,6 +44,7 @@ import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher;
 import org.onap.cps.ncmp.impl.utils.http.UrlTemplateParameters;
 import org.onap.cps.ncmp.rest.provmns.ErrorResponseBuilder;
 import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -68,6 +69,9 @@ public class ProvMnsController implements ProvMnS {
     private final JsonObjectMapper jsonObjectMapper;
     private final OperationDetailsFactory operationDetailsFactory;
 
+    @Value("${app.ncmp.provmns.max-patch-operations:10}")
+    private Integer maxNumberOfPatchOperations;
+
     @Override
     public ResponseEntity<Object> getMoi(final HttpServletRequest httpServletRequest,
                                          final Scope scope,
@@ -98,6 +102,11 @@ public class ProvMnsController implements ProvMnS {
     @Override
     public ResponseEntity<Object> patchMoi(final HttpServletRequest httpServletRequest,
                                            final List<PatchItem> patchItems) {
+        if (patchItems.size() > maxNumberOfPatchOperations) {
+            return errorResponseBuilder.buildErrorResponsePatch(HttpStatus.PAYLOAD_TOO_LARGE,
+                patchItems.size() + " operations in request, this exceeds the maximum of "
+                    + maxNumberOfPatchOperations);
+        }
         final RequestPathParameters requestPathParameters =
             parameterMapper.extractRequestParameters(httpServletRequest);
         try {
index 50b99c2..ac0a9e4 100644 (file)
@@ -34,7 +34,8 @@ public class ErrorResponseBuilder {
     private static final Map<HttpStatus, String> ERROR_MAP = Map.of(
         HttpStatus.NOT_FOUND, "IE_NOT_FOUND",
         HttpStatus.NOT_ACCEPTABLE, "APPLICATION_LAYER_ERROR",
-        HttpStatus.UNPROCESSABLE_ENTITY, "SERVER_LIMITATION"
+        HttpStatus.UNPROCESSABLE_ENTITY, "SERVER_LIMITATION",
+        HttpStatus.PAYLOAD_TOO_LARGE, "SERVER_LIMITATION"
     );
 
     /**
index b06e407..9291855 100644 (file)
@@ -92,6 +92,9 @@ class ProvMnsControllerSpec extends Specification {
     @Value('${rest.api.provmns-base-path}')
     def provMnSBasePath
 
+    @Value('${app.ncmp.provmns.max-patch-operations:10}')
+    Integer maxNumberOfPatchOperations
+
     def 'Get resource data request where #scenario.'() {
         given: 'resource data url'
             def getUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
@@ -200,7 +203,7 @@ class ProvMnsControllerSpec extends Specification {
             inventoryPersistence.getYangModelCmHandle('cm-1') >> new YangModelCmHandle(id:'cm-1', dmiServiceName: 'someDmiService', dataProducerIdentifier: 'someDataProducerId', compositeState: new CompositeState(cmHandleState: READY))
         and: 'policy executor throws exception (denied)'
             policyExecutor.checkPermission(*_) >> {throw new RuntimeException()}
-        when: 'put data resource request is performed'
+        when: 'patch data resource request is performed'
             def response = mvc.perform(patch(url)
                     .contentType(new MediaType('application', 'json-patch+json'))
                     .content(patchJsonBody))
@@ -209,6 +212,24 @@ class ProvMnsControllerSpec extends Specification {
             assert response.status == HttpStatus.NOT_ACCEPTABLE.value()
     }
 
+    def 'Patch request with too many operations.'() {
+        given: 'resource data url'
+            def url = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"
+        and: 'a patch request with more operations than the max allowed'
+            def patchItems = []
+            for (def i = 0; i <= maxNumberOfPatchOperations; i++) {
+                patchItems.add(new PatchItem(op: 'REMOVE', path: 'someUriLdnFirstPart'))
+            }
+           def patchItemsJsonRequestBody = jsonObjectMapper.asJsonString(patchItems)
+        when: 'patch data resource request is performed'
+            def response = mvc.perform(patch(url)
+                    .contentType(new MediaType('application', 'json-patch+json'))
+                    .content(patchItemsJsonRequestBody))
+                    .andReturn().response
+        then: 'response status is PAYLOAD_TOO_LARGE (413)'
+            assert response.status == HttpStatus.PAYLOAD_TOO_LARGE.value()
+    }
+
     def 'Put resource data request where #scenario.'() {
         given: 'resource data url'
             def putUrl = "$provMnSBasePath/v1/someUriLdnFirstPart/someClassName=someId"