From 3b755ec2e3d9776980236db6ba754ae6b7cc2402 Mon Sep 17 00:00:00 2001 From: Pamela Dragosh Date: Wed, 18 Mar 2020 07:50:22 -0400 Subject: Re-factor matchable to reduce complexity This solution is much cleaner than what is in StdMatchableTranslator. Over 90% code coverage on it - utilizes a callback to retrieve DataType and PolicyTypes. Support for missing timestamp TOSCA type. Also can do a better job differentiating between a property contained in the policy vs a schema. Changed StdMatchableTranslator to utilize these classes. And removed the old spaghetti. Added some JUnit coverage for ToscaPolicyTranslatorUtils. Removed duplicate code in the XACML Native Exception classes. Issue-ID: POLICY-2242 Change-Id: I18f898d9e65f6da28e3b27517d40f8d389de18a0 Signed-off-by: Pamela Dragosh --- .../common/ToscaPolicyTranslatorUtilsTest.java | 25 +- .../common/matchable/MatchablePolicyTypeTest.java | 300 +++++++++++++++++++++ .../common/std/StdMatchableTranslatorTest.java | 2 +- .../matchable/onap.policies.Test-1.0.0.yaml | 96 ++++++- .../matchable/test.policies.input.tosca.yaml | 38 ++- 5 files changed, 434 insertions(+), 27 deletions(-) create mode 100644 applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/matchable/MatchablePolicyTypeTest.java (limited to 'applications/common/src/test') diff --git a/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/ToscaPolicyTranslatorUtilsTest.java b/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/ToscaPolicyTranslatorUtilsTest.java index 8b85a0df..5d451e2c 100644 --- a/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/ToscaPolicyTranslatorUtilsTest.java +++ b/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/ToscaPolicyTranslatorUtilsTest.java @@ -27,7 +27,10 @@ import static org.junit.Assert.assertTrue; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; - +import oasis.names.tc.xacml._3_0.core.schema.wd_17.AllOfType; +import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType; +import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType; +import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType; import org.junit.Test; public class ToscaPolicyTranslatorUtilsTest { @@ -45,4 +48,24 @@ public class ToscaPolicyTranslatorUtilsTest { assertThat(ToscaPolicyTranslatorUtils.generateTimeInRange("T00:00:00Z", "T08:00:00Z")).isNotNull(); } + @Test + public void testBuildAndAppend() { + assertThat(ToscaPolicyTranslatorUtils.buildAndAppendAllof(null, new MatchType())).isInstanceOf(AnyOfType.class); + assertThat(ToscaPolicyTranslatorUtils.buildAndAppendAllof(null, new AllOfType())).isInstanceOf(AnyOfType.class); + assertThat(ToscaPolicyTranslatorUtils.buildAndAppendAllof(null, new String())).isNull(); + + assertThat(ToscaPolicyTranslatorUtils.buildAndAppendTarget(new TargetType(), + new AnyOfType()).getAnyOf()).hasSize(1); + assertThat(ToscaPolicyTranslatorUtils.buildAndAppendTarget(new TargetType(), + new MatchType()).getAnyOf()).hasSize(1); + assertThat(ToscaPolicyTranslatorUtils.buildAndAppendTarget(new TargetType(), + new String()).getAnyOf()).isEmpty(); + } + + @Test + public void testInteger() { + assertThat(ToscaPolicyTranslatorUtils.parseInteger("foo")).isNull(); + assertThat(ToscaPolicyTranslatorUtils.parseInteger("1")).isEqualTo(1); + assertThat(ToscaPolicyTranslatorUtils.parseInteger("1.0")).isEqualTo(1); + } } diff --git a/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/matchable/MatchablePolicyTypeTest.java b/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/matchable/MatchablePolicyTypeTest.java new file mode 100644 index 00000000..c23f7028 --- /dev/null +++ b/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/matchable/MatchablePolicyTypeTest.java @@ -0,0 +1,300 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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.policy.pdp.xacml.application.common.matchable; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.att.research.xacml.api.Identifier; +import com.att.research.xacml.api.XACML3; +import com.att.research.xacml.std.IdentifierImpl; +import com.att.research.xacml.util.XACMLPolicyWriter; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import oasis.names.tc.xacml._3_0.core.schema.wd_17.AllOfType; +import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType; +import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType; +import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType; +import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardYamlCoder; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaEntrySchema; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType; +import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate; +import org.onap.policy.pdp.xacml.application.common.ToscaDictionary; +import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MatchablePolicyTypeTest implements MatchableCallback { + private static final Logger LOGGER = LoggerFactory.getLogger(MatchablePolicyTypeTest.class); + private static final StandardYamlCoder yamlCoder = new StandardYamlCoder(); + private static final String TEST_POLICYTYPE_FILE = "src/test/resources/matchable/onap.policies.Test-1.0.0.yaml"; + private static final String TEST_POLICY_FILE = "src/test/resources/matchable/test.policies.input.tosca.yaml"; + private static final String TEST_POLICYTYPE = "onap.policies.base.middle.Test"; + private static ToscaServiceTemplate testTemplate; + private static ToscaPolicy testPolicy; + + /** + * Loads our resources. + * + * @throws CoderException object + */ + @BeforeClass + public static void setupLoadPolicy() throws CoderException { + // + // Load our test policy type + // + String policyType = ResourceUtils.getResourceAsString(TEST_POLICYTYPE_FILE); + // + // Serialize it into a class + // + ToscaServiceTemplate serviceTemplate = yamlCoder.decode(policyType, ToscaServiceTemplate.class); + // + // Make sure all the fields are setup properly + // + JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate(); + jtst.fromAuthorative(serviceTemplate); + testTemplate = jtst.toAuthorative(); + // + // Make sure the Policy Types are there + // + assertEquals(3, testTemplate.getPolicyTypes().size()); + assertNotNull(testTemplate.getPolicyTypes().get("onap.policies.Base")); + assertNotNull(testTemplate.getPolicyTypes().get("onap.policies.base.Middle")); + assertNotNull(testTemplate.getPolicyTypes().get(TEST_POLICYTYPE)); + // + // Load our test policy + // + String policy = ResourceUtils.getResourceAsString(TEST_POLICY_FILE); + // + // Serialize it into a class + // + serviceTemplate = yamlCoder.decode(policy, ToscaServiceTemplate.class); + // + // Make sure all the fields are setup properly + // + jtst = new JpaToscaServiceTemplate(); + jtst.fromAuthorative(serviceTemplate); + ToscaServiceTemplate policyTemplate = jtst.toAuthorative(); + assertEquals(1, policyTemplate.getToscaTopologyTemplate().getPolicies().size()); + testPolicy = policyTemplate.getToscaTopologyTemplate().getPolicies().get(0).get("Test.policy"); + assertNotNull(testPolicy); + } + + @Test + public void testAllCodeCoverage() { + assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> + new MatchablePolicyType(null, null)); + + assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> + new MatchablePropertyTypeMap(null)); + assertThatExceptionOfType(NullPointerException.class).isThrownBy(() -> + MatchablePolicyType.isMatchable(null)); + assertThat(MatchablePolicyType.isMatchable(new ToscaProperty())).isFalse(); + // + // Unlikely these would be called - just get code coverage on them + // + ToscaEntrySchema schema = new ToscaEntrySchema(); + schema.setType("integer"); + assertThat(MatchablePolicyType.handlePrimitive("foo", schema)).isNotNull(); + schema.setType("float"); + assertThat(MatchablePolicyType.handlePrimitive("foo", schema)).isNotNull(); + schema.setType("boolean"); + assertThat(MatchablePolicyType.handlePrimitive("foo", schema)).isNotNull(); + schema.setType("timestamp"); + assertThat(MatchablePolicyType.handlePrimitive("foo", schema)).isNotNull(); + schema.setType("footype"); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> + MatchablePolicyType.handlePrimitive("foo", schema) + ); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> + MatchablePolicyType.handlePrimitive("foo", schema) + ); + ToscaProperty toscaProperty = new ToscaProperty(); + Map metadata = new HashMap<>(); + metadata.put("matchable", "true"); + toscaProperty.setMetadata(metadata); + toscaProperty.setType("garbage"); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> + MatchablePolicyType.handlePrimitive("foo", toscaProperty) + ); + Map matchables = null; + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> + MatchablePolicyType.handleList("foo", toscaProperty, matchables, this) + ); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> + MatchablePolicyType.handleMap("foo", toscaProperty, matchables, this) + ); + } + + @Test + public void testPrimitiveValidation() throws Exception { + ToscaProperty property = new ToscaProperty(); + MatchablePropertyTypeBoolean booleanValue = new MatchablePropertyTypeBoolean(property); + assertThat(booleanValue.validate(Boolean.TRUE)).isEqualTo(Boolean.TRUE); + assertThat(booleanValue.validate("no")).isEqualTo(Boolean.FALSE); + assertThat(booleanValue.validate("foo")).isEqualTo(Boolean.FALSE); + + MatchablePropertyTypeInteger integerValue = new MatchablePropertyTypeInteger(property); + assertThat(integerValue.validate("5")).isEqualTo(5); + assertThatExceptionOfType(ToscaPolicyConversionException.class).isThrownBy(() -> integerValue.validate("foo")); + + MatchablePropertyTypeFloat floatValue = new MatchablePropertyTypeFloat(property); + assertThat(floatValue.validate("5")).isEqualTo(5); + assertThat(floatValue.validate(Float.MIN_NORMAL)).isEqualTo(Float.MIN_NORMAL); + assertThatExceptionOfType(ToscaPolicyConversionException.class).isThrownBy(() -> floatValue.validate("foo")); + + MatchablePropertyTypeTimestamp timestampValue = new MatchablePropertyTypeTimestamp(property); + assertThat(timestampValue.validate("2018-10-11T22:12:44").getHour()).isEqualTo(22); + assertThatExceptionOfType(ToscaPolicyConversionException.class).isThrownBy(() -> + timestampValue.validate("foo")); + + ToscaEntrySchema schema = new ToscaEntrySchema(); + schema.setType("string"); + property.setEntrySchema(schema); + MatchablePropertyTypeMap mapValue = new MatchablePropertyTypeMap(property); + assertThat(mapValue.validate(new String("foo"))).hasSize(0); + + MatchablePropertyTypeList listValue = new MatchablePropertyTypeList(property); + assertThat(listValue.validate(new String("foo"))).hasSize(0); + } + + @Test + public void testMatchables() throws ToscaPolicyConversionException { + // + // Step 1: Create matchables from the PolicyType + // + MatchablePolicyType matchablePolicyType = new MatchablePolicyType(testTemplate.getPolicyTypes() + .get(TEST_POLICYTYPE), this); + assertThat(matchablePolicyType).isNotNull(); + assertThat(matchablePolicyType.getPolicyId()).isNotNull(); + assertThat(matchablePolicyType.getPolicyId().getName()).isEqualTo(TEST_POLICYTYPE); + // + // Dump them out to see what we have + // + matchablePolicyType.getMatchables().forEach((matchable, property) -> { + LOGGER.info("matchable: {}: {}", matchable, property); + }); + // + // Sanity check - these are the total possible match types available + // + assertThat(matchablePolicyType.getMatchables()).hasSize(19); + // + // Step 2) Go through example policy and generate data for our Target + // + final TargetType target = new TargetType(); + target.getAnyOf().add(new AnyOfType()); + generateTargetType(target, matchablePolicyType, testPolicy.getProperties()); + // + // Stuff results in a simple Policy + // + final PolicyType policy = new PolicyType(); + policy.setTarget(target); + policy.setPolicyId("foo"); + policy.setVersion("1"); + policy.setRuleCombiningAlgId(XACML3.DENY_UNLESS_PERMIT); + // + // Dump it out so we can see what was created + // + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + XACMLPolicyWriter.writePolicyFile(os, policy); + LOGGER.info("{}", os); + } catch (IOException e) { + LOGGER.error("Failed to create byte array stream", e); + } + // + // Sanity check - the example policy should have each possible match type plus + // an extra one for the list and an extra one for the map. + // + assertThat(policy.getTarget().getAnyOf()).hasSize(20); + } + + @Override + public ToscaPolicyType retrievePolicyType(String derivedFrom) { + for (Entry entrySet : testTemplate.getPolicyTypes().entrySet()) { + if (entrySet.getValue().getName().equals(derivedFrom)) { + return entrySet.getValue(); + } + } + return null; + } + + @Override + public ToscaDataType retrieveDataType(String datatype) { + return testTemplate.getDataTypes().get(datatype); + } + + @SuppressWarnings("unchecked") + private void generateTargetType(TargetType target, MatchablePolicyType matchablePolicyType, + Map properties) throws ToscaPolicyConversionException { + for (Entry entrySet : properties.entrySet()) { + String propertyName = entrySet.getKey(); + Object propertyValue = entrySet.getValue(); + MatchableProperty matchable = matchablePolicyType.get(propertyName); + if (matchable != null) { + Identifier id = new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + propertyName); + Object object = matchable.getType().generate(propertyValue, id); + // + // Depending on what type it is, add it into the target + // + if (object instanceof AnyOfType) { + target.getAnyOf().add((AnyOfType) object); + } else if (object instanceof MatchType) { + AllOfType allOf = new AllOfType(); + allOf.getMatch().add((MatchType) object); + AnyOfType anyOf = new AnyOfType(); + anyOf.getAllOf().add(allOf); + target.getAnyOf().add(anyOf); + } + } else { + // + // Here is the special case where we look for a Collection of values that may + // contain potential matchables + // + if (propertyValue instanceof List) { + for (Object listValue : ((List)propertyValue)) { + if (listValue instanceof Map) { + generateTargetType(target, matchablePolicyType, (Map) listValue); + } + } + } else if (propertyValue instanceof Map) { + generateTargetType(target, matchablePolicyType, (Map) propertyValue); + } + } + } + } +} diff --git a/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/std/StdMatchableTranslatorTest.java b/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/std/StdMatchableTranslatorTest.java index 584390cd..1de1d79d 100644 --- a/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/std/StdMatchableTranslatorTest.java +++ b/applications/common/src/test/java/org/onap/policy/pdp/xacml/application/common/std/StdMatchableTranslatorTest.java @@ -160,7 +160,7 @@ public class StdMatchableTranslatorTest { } @Test - public void test() throws CoderException, ToscaPolicyConversionException, ParseException { + public void testMatchableTranslator() throws CoderException, ToscaPolicyConversionException, ParseException { // // Create our translator // diff --git a/applications/common/src/test/resources/matchable/onap.policies.Test-1.0.0.yaml b/applications/common/src/test/resources/matchable/onap.policies.Test-1.0.0.yaml index 7179c310..f0737b56 100644 --- a/applications/common/src/test/resources/matchable/onap.policies.Test-1.0.0.yaml +++ b/applications/common/src/test/resources/matchable/onap.policies.Test-1.0.0.yaml @@ -1,4 +1,4 @@ -tosca_definitions_version: tosca_simple_yaml_1_0_0 +tosca_definitions_version: tosca_simple_yaml_1_1_0 policy_types: onap.policies.Base: derived_from: tosca.policies.Root @@ -12,6 +12,7 @@ policy_types: matchable: true onap.policies.base.Middle: derived_from: onap.policies.Base + type_version: 1.0.0 version: 1.0.0 properties: middleNoMatch: @@ -25,47 +26,110 @@ policy_types: type_version: 1.0.0 version: 1.0.0 properties: - nonmatachableString: + nonmatchableString: type: string matchableString: type: string metadata: matchable: true - nonmatachableInteger: + nonmatchableInteger: type: integer metadata: matchable: false - matachableInteger: + matchableInteger: type: integer metadata: matchable: true - nonmatachableFloat: + nonmatchableFloat: type: float matchableFloat: type: float metadata: matchable: true - nonmatachableBoolean: + nonmatchableBoolean: type: boolean - matachableBoolean: + matchableBoolean: type: boolean metadata: matchable: true + nonmatchableTimestamp: + type: timestamp + matchableTimestamp: + type: timestamp + metadata: + matchable: true nonmatchableListInteger: type: list entry_schema: type: integer matchableListString: type: list + description: | + Every entry in the list is matchable, the attribute id will be the same for all value. eg. the + property name. metadata: matchable: true entry_schema: type: string - propertyOneMap: + matchableMapString: + type: map + description: | + Every entry in the map is matchable, however the attribute id will be set by the key. + metadata: + matchable: true + entry_schema: + type: string + nonmatchableMapString: type: map + description: | + Nothing gets matched - however you have no control over the LHS key. Someone could + easily set that value to a matchable property name defined elsewhere. entry_schema: - type: onap.datatype.one + type: string + badDataType: + type: i.do.not.exist + description: we can only ignore this - should get caught in the API + matchableDataType: + type: onap.datatype.zero + description: | + The matchable field in a datatype must be IGNORED, because this will result in too many assumptions + as we may go down many levels of datatypes, lists of datatypes, maps of datatypes, etc. Does every + field in the datatype become matchable? That does not make sense right now to write a Policy Type + like that. + metadata: + matchable: true data_types: + onap.datatype.zero: + derived_from: tosca.datatypes.Root + description: Note that we do not have to declare matchable for each property. + properties: + zeroStringMatchable: + type: string + metadata: + matchable: true + zeroBooleanMatchable: + type: boolean + metadata: + matchable: true + zeroFloatMatchable: + type: float + metadata: + matchable: true + zeroIntegerMatchable: + type: integer + metadata: + matchable: true + zeroTimestampMatchable: + type: timestamp + metadata: + matchable: true + zeroDatatypeOne: + type: onap.datatype.one + zeroBadDatatype: + type: list + description: we can only ignore this - should get caught in the API + entry_schema: + type: another.missing.datatype onap.datatype.one: derived_from: tosca.datatypes.Root properties: @@ -75,7 +139,7 @@ data_types: type: string metadata: matchable: true - propertyTwoList: + propertyTwoListOfDatatype: type: list entry_schema: type: onap.datatype.two @@ -88,16 +152,22 @@ data_types: type: string metadata: matchable: true - propertyThreeMap: + twoIntegerMatchable: + type: integer + metadata: + matchable: true + propertyThreeDatatype: type: map entry_schema: type: onap.datatype.three onap.datatype.three: derived_from: tosca.datatypes.Root properties: - threeString: - type: string threeStringMatchable: type: string + metadata: + matchable: true + threeIntegerMatchable: + type: integer metadata: matchable: true \ No newline at end of file diff --git a/applications/common/src/test/resources/matchable/test.policies.input.tosca.yaml b/applications/common/src/test/resources/matchable/test.policies.input.tosca.yaml index 80f72b2f..daffc2cb 100644 --- a/applications/common/src/test/resources/matchable/test.policies.input.tosca.yaml +++ b/applications/common/src/test/resources/matchable/test.policies.input.tosca.yaml @@ -1,4 +1,4 @@ -tosca_definitions_version: tosca_simple_yaml_1_0_0 +tosca_definitions_version: tosca_simple_yaml_1_1_0 topology_template: policies: - Test.policy: @@ -13,24 +13,38 @@ topology_template: baseMatch: base Match middleNoMatch: Do not match the middle middleMatch: middle Match - nonmatachableString: I am NON matchable + nonmatchableString: I am NON matchable matchableString: I should be matched - nonmatachableInteger: 0 - matachableInteger: 1000 - nonmatachableFloat: 0.0 + nonmatchableInteger: 0 + matchableInteger: 1000 + nonmatchableFloat: 0.0 matchableFloat: 1.1 - nonmatachableBoolean: false - matachableBoolean: true + nonmatchableBoolean: false + matchableBoolean: true + nonmatchableTimestamp: 2019-01-01T00:00:00Z + matchableTimestamp: 2020-01-01T00:00:00Z nonmatchableListInteger: {0, 1, 2} matchableListString: - match A - match B - propertyOneMap: + matchableMapString: + test1: matchableMap1 + test2: matchableMap2 + nonmatchableMapString: + risk: potential risk of matching + matchableDataType: + zeroStringMatchable: zero Match + zeroBooleanMatchable: true + zeroFloatMatchable: 9.9 + zeroIntegerMatchable: 1000 + zeroTimestampMatchable: 2020-01-01T23:59:59Z oneString: One is NOT matchable oneStringMatchable: One should be matched - propertyTwoList: + propertyTwoListOfDatatype: - twoString: Two is NOT matchable twoStringMatchable: Two should be matched - propertyThreeMap: - threeString: Three is NOT matchable - threeStringMatchable: Three should be matched \ No newline at end of file + twoIntegerMatchable: 55 + propertyThreeDatatype: + myThree: + threeStringMatchable: Three should match + threeIntegerMatchable: 66 \ No newline at end of file -- cgit 1.2.3-korg