From: seanbeirne Date: Thu, 29 Jan 2026 12:40:37 +0000 (+0000) Subject: Handle Root MO modify scenario in ProvMnS X-Git-Tag: 3.7.6~2 X-Git-Url: https://gerrit.onap.org/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F84%2F143084%2F1;p=cps.git Handle Root MO modify scenario in ProvMnS - Authentication fix for PUT request Issue-ID: CPS-3151 Change-Id: Ic235ff01488ba417da1378970c319c3650714bd5 Signed-off-by: seanbeirne --- diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnSController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnSController.java index b23436c236..2662e944e7 100644 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnSController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/ProvMnSController.java @@ -21,6 +21,7 @@ package org.onap.cps.ncmp.rest.controller; import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE; +import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE; import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA; import static org.onap.cps.ncmp.impl.provmns.ParameterHelper.NO_OP; import static org.springframework.http.HttpStatus.BAD_REQUEST; @@ -191,21 +192,23 @@ public class ProvMnSController implements ProvMnS { final Map> changeRequestAsMap = new HashMap<>(1); changeRequestAsMap.put(operationDetails.className(), operationDetails.ClassInstances()); final String changeRequestAsJson = jsonObjectMapper.asJsonString(changeRequestAsMap); - final String resourceIdentifier; - if (operationDetails.parentFdn().length() <= yangModelCmHandle.getAlternateId().length()) { - if (operationDetails.parentFdn().isEmpty()) { - resourceIdentifier = "/"; - } else { - resourceIdentifier = operationDetails.parentFdn(); - } - } else { - final int index = yangModelCmHandle.getAlternateId().length(); - resourceIdentifier = operationDetails.parentFdn().substring(index); + if (targetIsRootMo(yangModelCmHandle.getAlternateId(), operationDetails)) { + throw new DataValidationException("Data manipulation operations are not supported on " + + requestParameters.fdn(), ""); } + final int index = yangModelCmHandle.getAlternateId().length(); + final String resourceIdentifier = operationDetails.parentFdn().substring(index); policyExecutor.checkPermission(yangModelCmHandle, operationDetails.operationType(), requestParameters.authorization(), resourceIdentifier, changeRequestAsJson); } + private static boolean targetIsRootMo(final String alternateId, final OperationDetails operationDetails) { + if (DELETE.equals(operationDetails.operationType())) { + return operationDetails.parentFdn().length() <= alternateId.length(); + } + return operationDetails.parentFdn().length() < alternateId.length(); + } + private void checkPermissionForEachPatchItem(final String baseFdn, final List patchItems, final YangModelCmHandle yangModelCmHandle, diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnSControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnSControllerSpec.groovy index ba2bd5ec77..e0140176e0 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnSControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/ProvMnSControllerSpec.groovy @@ -253,9 +253,35 @@ class ProvMnSControllerSpec extends Specification { 'modify grandchild' | patchMediaType | '/subnetwork=1/managedElement=2' | '/subnetwork=1/managedElement=2/child=id1' | patchJsonBody || '/child=id1' | '{"otherChild":[{"id":"id2","attributes":{"attr1":"test"}}]}' '3gpp modify grandchild' | patchMediaType3gpp | '/subnetwork=1/managedElement=2' | '/subnetwork=1/managedElement=2/child=id1' | patchJsonBody3gpp || '/child=id1' | '{"otherChild":[{"id":"id2","attributes":{"attr1":"test"}}]}' 'no subnetwork' | patchMediaType | '/managedElement=2' | '/managedElement=2/child=id1' | patchJsonBody || '/child=id1' | '{"otherChild":[{"id":"id2","attributes":{"attr1":"test"}}]}' - 'modify first child' | patchMediaType | '/subnetwork=1/managedElement=2' | '/subnetwork=1/managedElement=2' | patchJsonBody || '/subnetwork=1/managedElement=2' | '{"otherChild":[{"id":"id2","attributes":{"attr1":"test"}}]}' - 'modify alternate id' | patchMediaType | '/subnetwork=1/managedElement=2' | '/subnetwork=1/managedElement=2' | patchWithoutChild || '/subnetwork=1' | '{"managedElement":[{"id":"2","attributes":{"attr2":"test2"}}]}' - 'modify root MO' | patchMediaType | '/managedElement=2' | '/managedElement=2' | patchWithoutChild || '/' | '{"managedElement":[{"id":"2","attributes":{"attr2":"test2"}}]}' + 'modify first child' | patchMediaType | '/subnetwork=1/managedElement=2' | '/subnetwork=1/managedElement=2' | patchJsonBody || '' | '{"otherChild":[{"id":"id2","attributes":{"attr1":"test"}}]}' + } + + def 'Attempt Patch request with root MO, #scenario.'() { + given: 'provmns url' + def provmnsUrl = "$provMnSBasePath/v1$fdn" + and: 'alternate Id can be matched' + mockedCmHandle.getAlternateId() >> alternateId + mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(fdn, "/") >> 'mock' + and: 'persistence service returns yangModelCmHandle' + mockInventoryPersistence.getYangModelCmHandle('mock') >> mockedCmHandle + when: 'patch request is performed' + def response = mvc.perform(patch(provmnsUrl) + .header('Authorization', 'my authorization') + .contentType(patchMediaType) + .content(jsonBody)) + .andReturn().response + then: 'policy executor is never invoked' + 0 * mockPolicyExecutor._ + and: 'the response status is BAD_REQUEST' + assert response.status == BAD_REQUEST.value() + and: 'the response content contains the required error details' + assert response.contentAsString.contains('VALIDATION_ERROR') + assert response.contentAsString.contains('not supported') + assert response.contentAsString.contains(fdn) + where: 'following scenarios are applied' + scenario | alternateId | fdn | jsonBody + 'modify alternate id' | '/subnetwork=1/managedElement=2' | '/subnetwork=1/managedElement=2' | patchWithoutChild + 'modify root MO' | '/managedElement=2' | '/managedElement=2' | patchWithoutChild } def 'Patch request with error from DMI.'() { @@ -487,4 +513,28 @@ class ProvMnSControllerSpec extends Specification { assert response.contentAsString.contains('"title":"/myClass=id1 not found"') } + def 'Attempt Delete root MO, #scenario.'() { + given: 'resource data url' + def deleteUrl = "$provMnSBasePath/v1$fdn" + and: 'alternate Id is mocked can be matched' + mockedCmHandle.getAlternateId() >> alternateId + mockAlternateIdMatcher.getCmHandleIdByLongestMatchingAlternateId(fdn, "/") >> 'mock' + and: 'persistence service returns yangModelCmHandle' + mockInventoryPersistence.getYangModelCmHandle('mock') >> mockedCmHandle + when: 'Delete data resource request is attempted' + def response = mvc.perform(delete(deleteUrl).header('Authorization', 'my authorization')).andReturn().response + then: 'policy executor is never invoked' + 0 * mockPolicyExecutor._ + and: 'the response status is BAD_REQUEST' + assert response.status == BAD_REQUEST.value() + and: 'the response content contains the required error details' + assert response.contentAsString.contains('VALIDATION_ERROR') + assert response.contentAsString.contains('not supported') + assert response.contentAsString.contains(fdn) + where: 'root MOs are targeted' + scenario | fdn | alternateId + 'with subnetwork' | '/Subnetwork=1/ManagedElement=1' | '/subnetwork=1/managedElement=1' + 'no subnetwork' | '/ManagedElement=1' | '/managedElement=1' + } + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java index 752ce85295..d1172ced15 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/dmi/DmiRestClient.java @@ -154,7 +154,7 @@ public class DmiRestClient { return getWebClient(requiredDmiService) .put() .uri(urlTemplateParameters.urlTemplate(), urlTemplateParameters.urlVariables()) - .headers(httpHeaders -> configureHttpHeaders(httpHeaders, NO_AUTHORIZATION)) + .headers(httpHeaders -> configureHttpHeaders(httpHeaders, authorization)) .bodyValue(body) .exchangeToMono(this::createIdenticalResponseForClient) .block();