import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.onap.cps.ncmp.api.data.models.OperationType;
import org.onap.cps.ncmp.api.exceptions.ProvMnSException;
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
import org.onap.cps.ncmp.impl.provmns.RequestPathParameters;
import org.onap.cps.ncmp.impl.provmns.model.PatchItem;
import org.onap.cps.utils.JsonObjectMapper;
private static final String ATTRIBUTE_NAME_SEPARATOR = "/";
private static final String REGEX_FOR_LEADING_AND_TRAILING_SEPARATORS = "(^/)|(/$)";
+ private static final String NO_AUTHORIZATION = null;
+ private static final String UNSUPPORTED_OPERATION = "UNSUPPORTED_OP";
private final JsonObjectMapper jsonObjectMapper;
private final ObjectMapper objectMapper;
+ private final PolicyExecutor policyExecutor;
/**
- * Build a PatchOperationDetails object from ProvMnS request details.
+ * Build an operation details object from ProvMnS request details and send it to Policy Executor.
*
* @param requestPathParameters request parameters including uri-ldn-first-part, className and id
* @param patchItems provided request list of patch Items
- * @return CreateOperationDetails object
+ * @param yangModelCmHandle representation of the cm handle to check
*/
- public PatchOperationsDetails buildPatchOperationDetails(final RequestPathParameters requestPathParameters,
- final List<PatchItem> patchItems) {
- final List<Object> operations = new ArrayList<>(patchItems.size());
+ public void checkPermissionForEachPatchItem(final RequestPathParameters requestPathParameters,
+ final List<PatchItem> patchItems,
+ final YangModelCmHandle yangModelCmHandle) {
+ OperationDetails operationDetails;
for (final PatchItem patchItem : patchItems) {
switch (patchItem.getOp()) {
- case ADD -> operations.add(buildCreateOperationDetails(OperationType.CREATE, requestPathParameters,
- patchItem.getValue()));
- case REPLACE -> operations.add(buildCreateOperationDetailsForUpdate(OperationType.UPDATE,
- requestPathParameters,
- patchItem));
- case REMOVE -> operations.add(buildDeleteOperationDetails(requestPathParameters.toAlternateId()));
- default -> throw new ProvMnSException("UNSUPPORTED_OP",
- "Unsupported Patch Operation Type: " + patchItem.getOp().getValue());
+ case ADD -> operationDetails = buildCreateOperationDetails(OperationType.CREATE, requestPathParameters,
+ patchItem.getValue());
+ case REPLACE -> operationDetails = buildCreateOperationDetailsForUpdate(OperationType.UPDATE,
+ requestPathParameters,
+ patchItem);
+ case REMOVE -> operationDetails = buildDeleteOperationDetails(requestPathParameters.toAlternateId());
+ default -> throw new ProvMnSException(UNSUPPORTED_OPERATION,
+ "Unsupported Patch Operation Type: " + patchItem.getOp().getValue());
}
+ policyExecutor.checkPermission(yangModelCmHandle,
+ OperationType.fromOperationName(operationDetails.operation()),
+ NO_AUTHORIZATION,
+ requestPathParameters.toAlternateId(),
+ jsonObjectMapper.asJsonString(operationDetails)
+ );
}
- return new PatchOperationsDetails("Some Permission Id", "cm-legacy", operations);
}
/**
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.ncmp.api.exceptions.NcmpException
-import org.onap.cps.ncmp.api.exceptions.ProvMnSException;
+import org.onap.cps.ncmp.api.exceptions.ProvMnSException
+import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle;
import org.onap.cps.ncmp.impl.provmns.RequestPathParameters;
import org.onap.cps.ncmp.impl.provmns.model.PatchItem;
import org.onap.cps.ncmp.impl.provmns.model.ResourceOneOf
import spock.lang.Specification;
import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE;
+import static org.onap.cps.ncmp.api.data.models.OperationType.UPDATE;
+import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE;
class OperationDetailsFactorySpec extends Specification {
def spiedObjectMapper = Spy(ObjectMapper)
def jsonObjectMapper = new JsonObjectMapper(spiedObjectMapper)
+ def policyExecutor = Mock(PolicyExecutor)
- OperationDetailsFactory objectUnderTest = new OperationDetailsFactory(jsonObjectMapper, spiedObjectMapper)
+ OperationDetailsFactory objectUnderTest = new OperationDetailsFactory(jsonObjectMapper, spiedObjectMapper, policyExecutor)
static def complexValueAsResource = new ResourceOneOf(id: 'myId', attributes: ['myAttribute1:myValue1', 'myAttribute2:myValue2'], objectClass: 'myClassName')
static def simpleValueAsResource = new ResourceOneOf(id: 'myId', attributes: ['simpleAttribute:1'], objectClass: 'myClassName')
+ static def yangModelCmHandle = new YangModelCmHandle(id: 'someId')
+ def 'Build create operation details with all properties.'() {
+ given: 'request parameters and resource'
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'my uri', className: 'class in uri', id: 'my id')
+ def resource = new ResourceOneOf(id: 'some resource id', objectClass: 'class in resource')
+ when: 'create operation details are built'
+ def result = objectUnderTest.buildCreateOperationDetails(CREATE, requestPathParameters, resource)
+ then: 'all details are correct'
+ assert result.targetIdentifier == 'my uri'
+ assert result.changeRequest.keySet()[0] == 'class in resource'
+ assert result.changeRequest['class in resource'][0].id == 'my id'
+ }
- def 'Build policy executor patch operation details from ProvMnS request parameters where #scenario.'() {
- given: 'a provMnsRequestParameter and a patchItem list'
- def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'classNameInUri', id: 'myId')
- def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'], objectClass: classNameInBody)
- def patchItemsList = [new PatchItem(op: 'ADD', 'path':'myUriLdnFirstPart', value: resource), new PatchItem(op: 'REPLACE', 'path':'myUriLdnFirstPart', value: resource), new PatchItem(op: 'REMOVE', 'path':'myUriLdnFirstPart'),]
- when: 'patch operation details are created'
- def result = objectUnderTest.buildPatchOperationDetails(path, patchItemsList)
- then: 'the result contain 3 operations of the correct types in the correct order'
- result.operations.size() == 3
- and: 'note that Add and Replace both are defined using Create Operation Details'
- assert result.operations[0] instanceof CreateOperationDetails
- assert result.operations[1] instanceof CreateOperationDetails
- assert result.operations[2] instanceof DeleteOperationDetails
- and: 'the add operation target identifier is just the uri first part'
- assert result.operations[0]['targetIdentifier'] == 'myUriLdnFirstPart'
- and: 'the replace operation target identifier is just the uri first part'
- assert result.operations[1]['targetIdentifier'] == 'myUriLdnFirstPart'
- and: 'the replace change request has the correct class name'
- assert result.operations[1].changeRequest.keySet()[0] == expectedChangeRequestKey
- and: 'the delete operation target identifier includes the target class and id'
- assert result.operations[2]['targetIdentifier'] == 'myUriLdnFirstPart/classNameInUri=myId'
- where: 'the following class names are used in the body'
- scenario | classNameInBody || expectedChangeRequestKey
- 'class name in body is populated' | 'myClass' || 'myClass'
- 'class name in body is empty' | '' || 'classNameInUri'
- 'class name in body is null' | null || 'classNameInUri'
+ def 'Build replace operation details with all properties where class name in body is #scenario.'() {
+ given: 'request parameters and resource'
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'my uri', className: 'class in uri', id: 'some id')
+ def resource = new ResourceOneOf(id: 'some resource id', objectClass: classNameInBody)
+ when: 'replace operation details are built'
+ def result = objectUnderTest.buildCreateOperationDetails(CREATE, requestPathParameters, resource)
+ then: 'all details are correct'
+ assert result.targetIdentifier == 'my uri'
+ assert result.changeRequest.keySet()[0] == expectedChangeRequestKey
+ where:
+ scenario | classNameInBody || expectedChangeRequestKey
+ 'populated' | 'class in body' || 'class in body'
+ 'empty' | '' || 'class in uri'
+ 'null' | null || 'class in uri'
+ }
+
+ def 'Build delete operation details with all properties'() {
+ given: 'request parameters'
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'my uri', className: 'classNameInUri', id: 'myId')
+ when: 'delete operation details are built'
+ def result = objectUnderTest.buildDeleteOperationDetails(requestPathParameters.toAlternateId())
+ then: 'all details are correct'
+ assert result.targetIdentifier == 'my uri/classNameInUri=myId'
+ }
+
+ def 'Single patch operation with #patchOperationType checks correct operation type.'() {
+ given: 'request parameters and single patch item'
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'some class')
+ def resource = new ResourceOneOf(id: 'some resource id')
+ def patchItem = new PatchItem(op: patchOperationType, 'path':'some uri', value: resource)
+ when: 'patch operation is processed'
+ objectUnderTest.checkPermissionForEachPatchItem(requestPathParameters, [patchItem], yangModelCmHandle)
+ then: 'policy executor is called with correct operation type'
+ 1 * policyExecutor.checkPermission(yangModelCmHandle, expectedPolicyExecutorOperationType, _, _, _)
+ where: 'following operations are used'
+ patchOperationType | expectedPolicyExecutorOperationType
+ 'ADD' | CREATE
+ 'REPLACE' | UPDATE
+ }
+
+ def 'Single patch operation with REMOVE checks correct operation type.'() {
+ given: 'request parameters and single remove patch item'
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri')
+ def patchItem = new PatchItem(op: 'REMOVE')
+ when: 'patch operation is processed'
+ objectUnderTest.checkPermissionForEachPatchItem(requestPathParameters, [patchItem], yangModelCmHandle)
+ then: 'policy executor is called with DELETE operation type'
+ 1 * policyExecutor.checkPermission(yangModelCmHandle, DELETE, _, _, _)
+ }
+
+ def 'Multiple patch operations invoke policy executor correct number of times in order.'() {
+ given: 'request parameters and multiple patch items'
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri')
+ def resource = new ResourceOneOf(id: 'some resource id', objectClass: 'some class')
+ def patchItemsList = [
+ new PatchItem(op: 'ADD', 'path':'some uri', value: resource),
+ new PatchItem(op: 'REPLACE', 'path':'some uri', value: resource),
+ new PatchItem(op: 'REMOVE', 'path':'some uri')
+ ]
+ when: 'patch operations are processed'
+ objectUnderTest.checkPermissionForEachPatchItem(requestPathParameters, patchItemsList, yangModelCmHandle)
+ then: 'policy executor is checked for create first'
+ 1 * policyExecutor.checkPermission(yangModelCmHandle, CREATE, _, _, _)
+ then: 'update is next'
+ 1 * policyExecutor.checkPermission(yangModelCmHandle, UPDATE, _, _, _)
+ then: 'and finally delete'
+ 1 * policyExecutor.checkPermission(yangModelCmHandle, DELETE, _, _, _)
}
def 'Build policy executor patch operation details with single replace operation and #scenario.'() {
given: 'a requestParameter and a patchItem list'
- def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'myClassName', id: 'myId')
- def pathItems = [new PatchItem(op: 'REPLACE', 'path':"myUriLdnFirstPart${suffix}", value: value)]
- when: 'patch operation details are created'
- def result = objectUnderTest.buildPatchOperationDetails(path, pathItems)
- then: 'the result has the correct type'
- assert result instanceof PatchOperationsDetails
- and: 'the change request contains the correct attributes value'
- assert result.operations[0]['changeRequest']['myClassName'][0]['attributes'].toString() == attributesValueInOperation
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'some class')
+ def pathItems = [new PatchItem(op: 'REPLACE', 'path':"some uri${suffix}", value: value)]
+ when: 'patch operation details are checked'
+ objectUnderTest.checkPermissionForEachPatchItem(requestPathParameters, pathItems, yangModelCmHandle)
+ then: 'policyExecutor is called with correct payload'
+ 1 * policyExecutor.checkPermission(
+ yangModelCmHandle,
+ UPDATE,
+ null,
+ requestPathParameters.toAlternateId(),
+ { json -> assert json.contains(attributesValueInOperation) } // check for more details eg. verify type
+ )
where: 'attributes are set using # or resource'
scenario | suffix | value || attributesValueInOperation
- 'set simple value using #' | '#/attributes/simpleAttribute' | 1 || '[simpleAttribute:1]'
- 'set simple value using resource' | '' | simpleValueAsResource || '[simpleAttribute:1]'
- 'set complex value using resource' | '' | complexValueAsResource || '[myAttribute1:myValue1, myAttribute2:myValue2]'
+ 'set simple value using #' | '#/attributes/simpleAttribute' | 1 || '{"simpleAttribute":1}'
+ 'set simple value using resource' | '' | simpleValueAsResource || '["simpleAttribute:1"]'
+ 'set complex value using resource' | '' | complexValueAsResource || '["myAttribute1:myValue1","myAttribute2:myValue2"]'
}
def 'Build an attribute map with different depths of hierarchy with #scenario.'() {
def 'Build policy executor patch operation details from ProvMnS request parameters with invalid op.'() {
given: 'a provMnsRequestParameter and a patchItem list'
- def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId')
- def patchItemsList = [new PatchItem(op: 'TEST', 'path':'myUriLdnFirstPart')]
+ def path = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'some class')
+ def patchItemsList = [new PatchItem(op: 'TEST', 'path':'some uri')]
when: 'a build is attempted with an invalid op'
- objectUnderTest.buildPatchOperationDetails(path, patchItemsList)
+ objectUnderTest.checkPermissionForEachPatchItem(path, patchItemsList, yangModelCmHandle)
then: 'the result is as expected (exception thrown)'
thrown(ProvMnSException)
}
- def 'Build policy executor create operation details from ProvMnS request parameters where #scenario.'() {
+ def 'Build policy executor create operation details from ProvMnS request parameters where objectClass in resource #scenario.'() {
given: 'a provMnsRequestParameter and a resource'
- def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId')
- def resource = new ResourceOneOf(id: 'someResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'], objectClass: objectClass)
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'class in uri', id:'my id')
+ def resource = new ResourceOneOf(id: 'some resource id', objectClass: objectInResouce)
when: 'a configurationManagementOperation is created and converted to JSON'
- def result = objectUnderTest.buildCreateOperationDetails(CREATE, path, resource)
+ def result = objectUnderTest.buildCreateOperationDetails(CREATE, requestPathParameters, resource)
then: 'the result is as expected (using json to compare)'
- String expectedJsonString = '{"operation":"CREATE","targetIdentifier":"myUriLdnFirstPart","changeRequest":{"' + changeRequestClassReference + '":[{"id":"someId","attributes":["someAttribute1:someValue1","someAttribute2:someValue2"]}]}}'
+ String expectedJsonString = '{"operation":"CREATE","targetIdentifier":"some uri","changeRequest":{"' + changeRequestClassReference + '":[{"id":"my id","attributes":null}]}}'
assert jsonObjectMapper.asJsonString(result) == expectedJsonString
where:
- scenario | objectClass || changeRequestClassReference
- 'objectClass is populated' | 'someObjectClass' || 'someObjectClass'
- 'objectClass is empty' | '' || 'someClassName'
- 'objectClass is null' | null || 'someClassName'
+ scenario | objectInResouce || changeRequestClassReference
+ 'populated' | 'class in resource' || 'class in resource'
+ 'empty' | '' || 'class in uri'
+ 'null' | null || 'class in uri'
}
- def 'Build Policy Executor Operation Details with a exception during conversion'() {
+ def 'Build Policy Executor Operation Details with a exception during conversion.'() {
given: 'a provMnsRequestParameter and a resource'
- def path = new RequestPathParameters(uriLdnFirstPart: 'myUriLdnFirstPart', className: 'someClassName', id: 'someId')
- def resource = new ResourceOneOf(id: 'myResourceId', attributes: ['someAttribute1:someValue1', 'someAttribute2:someValue2'])
+ def requestPathParameters = new RequestPathParameters(uriLdnFirstPart: 'some uri', className: 'some class')
+ def resource = new ResourceOneOf(id: 'some resource id')
and: 'json object mapper throws an exception'
- def originalException = new JsonProcessingException('some-exception')
- spiedObjectMapper.readValue(*_) >> {throw originalException}
+ spiedObjectMapper.readValue(*_) >> { throw new JsonProcessingException('original exception message') }
when: 'a configurationManagementOperation is created and converted to JSON'
- objectUnderTest.buildCreateOperationDetails(CREATE, path, resource)
- then: 'the expected exception is throw and matches the original'
+ objectUnderTest.buildCreateOperationDetails(CREATE, requestPathParameters, resource)
+ then: 'the expected exception is throw and contains the original message'
def thrown = thrown(NcmpException)
assert thrown.message.contains('Cannot convert Resource Object')
- assert thrown.details.contains('some-exception')
+ assert thrown.details.contains('original exception message')
}
}