diff options
8 files changed, 178 insertions, 16 deletions
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index bb2332d615..a482cf5a3c 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -121,7 +121,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { /** * Query resource data from datastore. * - * @param datastoreName name of the datastore + * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:operational") * @param cmHandle cm handle identifier * @param cpsPath CPS Path * @param optionsParamInQuery options query parameter @@ -144,9 +144,9 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } /** - * Patch resource data from passthrough-running. + * Patch resource data. * - * @param datastoreName name of the datastore + * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:passthrough-running") * @param cmHandle cm handle identifier * @param resourceIdentifier resource identifier * @param requestBody the request body @@ -173,9 +173,9 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } /** - * Create resource data in datastore pass-through running for given cm-handle. + * Create resource data for given cm-handle. * - * @param datastoreName name of the datastore + * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:passthrough-running") * @param cmHandle cm handle identifier * @param resourceIdentifier resource identifier * @param requestBody the request body @@ -198,9 +198,9 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } /** - * Update resource data in datastore pass-through running for given cm-handle. + * Update resource data for given cm-handle. * - * @param datastoreName name of the datastore + * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:passthrough-running") * @param cmHandle cm handle identifier * @param resourceIdentifier resource identifier * @param requestBody the request body @@ -224,9 +224,9 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } /** - * Delete resource data in datastore pass-through running for a given cm-handle. + * Delete resource data for a given cm-handle. * - * @param datastoreName name of the datastore + * @param datastoreName name of the datastore (currently only supports "ncmp-datastore:passthrough-running") * @param cmHandle cm handle identifier * @param resourceIdentifier resource identifier * @param contentType content type of the body diff --git a/cps-ncmp-rest/src/test/resources/application.yml b/cps-ncmp-rest/src/test/resources/application.yml index 9df1e580f6..aa5716716b 100644 --- a/cps-ncmp-rest/src/test/resources/application.yml +++ b/cps-ncmp-rest/src/test/resources/application.yml @@ -26,4 +26,4 @@ notification: enabled: true async: executor: - time-out-value-in-ms: 2000
\ No newline at end of file + time-out-value-in-ms: 2000 diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java index b902fe2767..4cbf9d4b3b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/DmiDataOperations.java @@ -71,6 +71,7 @@ public class DmiDataOperations { private final JsonObjectMapper jsonObjectMapper; private final DmiProperties dmiProperties; private final DmiRestClient dmiRestClient; + private final PolicyExecutor policyExecutor; /** * This method fetches the resource data from the operational data store for a given CM handle @@ -170,6 +171,9 @@ public class DmiDataOperations { final String dataType, final String authorization) { final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId); + + policyExecutor.checkPermission(yangModelCmHandle, operationType, authorization, resourceId, requestData); + final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState(); validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/PolicyExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/PolicyExecutor.java new file mode 100644 index 0000000000..2b5eb9e792 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/PolicyExecutor.java @@ -0,0 +1,74 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.data; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.ncmp.api.data.models.OperationType; +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class PolicyExecutor { + + @Value("${ncmp.policy-executor.enabled:false}") + private boolean enabled; + + @Value("${ncmp.policy-executor.server.address:http://policy-executor}") + private String serverAddress; + + @Value("${ncmp.policy-executor.server.port:8080}") + private String serverPort; + + private static final String PAYLOAD_TYPE_PREFIX = "cm_"; + + /** + * Use the Policy Executor to check permission for a cm write operation. + * Wil throw an exception when the operation is not permitted (work in progress) + * + * @param yangModelCmHandle the cm handle involved + * @param operationType the write operation + * @param authorization the original rest authorization token (can be used to determine the client) + * @param resourceIdentifier the resource identifier (can be blank) + * @param changeRequestAsJson the change details from the original rest request in json format + */ + public void checkPermission(final YangModelCmHandle yangModelCmHandle, + final OperationType operationType, + final String authorization, + final String resourceIdentifier, + final String changeRequestAsJson) { + if (enabled) { + final String payloadType = PAYLOAD_TYPE_PREFIX + operationType.getOperationName(); + log.info("Policy Executor Enabled"); + log.info("Address : {}", serverAddress); + log.info("Port : {}", serverPort); + log.info("Authorization : {}", authorization); + log.info("Payload Type : {}", payloadType); + log.info("Target FDN : {}", yangModelCmHandle.getAlternateId()); + log.info("CM Handle Id : {}", yangModelCmHandle.getId()); + log.info("Resource Identifier : {}", resourceIdentifier); + log.info("Change Request (json) : {}", changeRequestAsJson); + } + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/HttpClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/HttpClientConfigurationSpec.groovy index 91aeb88a54..1d3b22fb73 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/HttpClientConfigurationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/HttpClientConfigurationSpec.groovy @@ -25,23 +25,21 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.TestPropertySource import spock.lang.Specification @SpringBootTest @ContextConfiguration(classes = [HttpClientConfiguration]) -@EnableConfigurationProperties(HttpClientConfiguration.class) -@TestPropertySource(properties = ["ncmp.dmi.httpclient.data-services.readTimeoutInSeconds=789", "ncmp.dmi.httpclient.model-services.maximumConnectionsTotal=111"]) +@EnableConfigurationProperties(HttpClientConfiguration) class HttpClientConfigurationSpec extends Specification { @Autowired - private HttpClientConfiguration httpClientConfiguration + HttpClientConfiguration httpClientConfiguration def 'Test http client configuration properties of data with custom and default values'() { expect: 'properties are populated correctly for data' with(httpClientConfiguration.dataServices) { assert connectionTimeoutInSeconds == 123 - assert readTimeoutInSeconds == 789 + assert readTimeoutInSeconds == 33 assert writeTimeoutInSeconds == 30 assert maximumConnectionsTotal == 100 assert pendingAcquireMaxCount == 22 diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy index c2bbfc2edc..970444f643 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/DmiDataOperationsSpec.groovy @@ -36,6 +36,7 @@ import org.onap.cps.ncmp.utils.TestUtils import org.onap.cps.utils.JsonObjectMapper import org.spockframework.spring.SpringBean import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootContextLoader import org.springframework.boot.test.context.SpringBootTest import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -53,7 +54,7 @@ import static org.onap.cps.ncmp.impl.models.RequiredDmiService.DATA import static org.onap.cps.ncmp.utils.events.CloudEventMapper.toTargetEvent @SpringBootTest -@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations]) +@ContextConfiguration(classes = [EventsPublisher, CpsApplicationContext, DmiProperties, DmiDataOperations, PolicyExecutor]) class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def NO_TOPIC = null @@ -72,6 +73,9 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { @SpringBean EventsPublisher eventsPublisher = Stub() + @SpringBean + PolicyExecutor policyExecutor = Mock() + def 'call get resource data for #expectedDataStore from DMI without topic #scenario.'() { given: 'a cm handle for #cmHandleId' mockYangModelCmHandleRetrieval(dmiProperties) @@ -161,6 +165,8 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec { def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type', NO_AUTH_HEADER) then: 'the result is the response from the DMI service' assert result == responseFromDmi + and: 'the permission was checked with the policy executor' + 1 * policyExecutor.checkPermission(_, operation, NO_AUTH_HEADER, resourceIdentifier, 'requestData' ) where: 'the following operation is performed' operation || expectedOperationInUrl CREATE || 'create' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy new file mode 100644 index 0000000000..654206776b --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/PolicyExecutorSpec.groovy @@ -0,0 +1,73 @@ +package org.onap.cps.ncmp.impl.data + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender +import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import spock.lang.Specification + +import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH + +@SpringBootTest +@ContextConfiguration(classes = [PolicyExecutor]) +class PolicyExecutorSpec extends Specification { + + @Autowired + PolicyExecutor objectUnderTest + + def logAppender = Spy(ListAppender<ILoggingEvent>) + + def setup() { + setupLogger() + } + + def cleanup() { + ((Logger) LoggerFactory.getLogger(PolicyExecutor)).detachAndStopAllAppenders() + } + + def 'Configuration properties.'() { + expect: 'properties used from application.yml' + assert objectUnderTest.enabled + assert objectUnderTest.serverAddress == 'http://localhost' + assert objectUnderTest.serverPort == '8785' + } + + def 'Permission check logging.'() { + when: 'permission is checked for an operation' + def yangModelCmHandle = new YangModelCmHandle(id:'ch-1', alternateId:'fdn1') + objectUnderTest.checkPermission(yangModelCmHandle, PATCH, 'my credentials','my resource','my change') + then: 'correct details are logged ' + assert getLogEntry(0) == 'Policy Executor Enabled' + assert getLogEntry(3).contains('my credentials') + assert getLogEntry(4).contains('cm_patch') + assert getLogEntry(5).contains('fdn1') + assert getLogEntry(6).contains('ch-1') + assert getLogEntry(7).contains('my resource') + assert getLogEntry(8).contains('my change') + } + + def 'Permission check with feature disabled.'() { + given: 'feature is disabled' + objectUnderTest.enabled = false + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource','my change') + then: 'nothing is logged' + assert logAppender.list.isEmpty() + } + + def setupLogger() { + def logger = LoggerFactory.getLogger(PolicyExecutor) + logger.setLevel(Level.DEBUG) + logger.addAppender(logAppender) + logAppender.start() + } + + def getLogEntry(index) { + logAppender.list[index].formattedMessage + } +} diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index 5b10e7376b..f0790dda4b 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -41,10 +41,12 @@ ncmp: pendingAcquireMaxCount: 22 connectionTimeoutInSeconds: 123 maximumInMemorySizeInMegabytes: 7 + readTimeoutInSeconds: 33 model-services: pendingAcquireMaxCount: 44 connectionTimeoutInSeconds: 456 maximumInMemorySizeInMegabytes: 8 + maximumConnectionsTotal: 111 auth: username: some-user password: some-password @@ -59,6 +61,11 @@ ncmp: async-executor: parallelism-level: 3 + policy-executor: + enabled: true + server: + address: "http://localhost" + port: "8785" # Custom Hazelcast Config. hazelcast: |