2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * SPDX-License-Identifier: Apache-2.0
20 * ============LICENSE_END=========================================================
23 package org.onap.policy.pdp.xacml.application.common.std;
25 import com.att.research.xacml.api.Identifier;
26 import com.att.research.xacml.api.Request;
27 import com.att.research.xacml.api.XACML3;
28 import com.att.research.xacml.std.IdentifierImpl;
29 import com.att.research.xacml.util.XACMLPolicyWriter;
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.List;
43 import java.util.Map.Entry;
45 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
51 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
52 import org.onap.policy.common.utils.coder.CoderException;
53 import org.onap.policy.common.utils.coder.StandardCoder;
54 import org.onap.policy.common.utils.coder.StandardYamlCoder;
55 import org.onap.policy.models.decisions.concepts.DecisionRequest;
56 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
57 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
58 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
59 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
60 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
61 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
62 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
63 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
64 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
65 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
66 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
67 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationException;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
72 * This standard matchable translator uses Policy Types that contain "matchable" field in order
73 * to translate policies.
75 * @author pameladragosh
78 public class StdMatchableTranslator extends StdBaseTranslator {
80 private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
81 private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
83 private final Map<ToscaPolicyTypeIdentifier, ToscaPolicyType> matchablePolicyTypes = new HashMap<>();
85 private RestServerParameters apiRestParameters;
87 private Path pathForData;
89 public StdMatchableTranslator() {
94 public Request convertRequest(DecisionRequest request) {
95 LOGGER.info("Converting Request {}", request);
97 return StdMatchablePolicyRequest.createInstance(request);
98 } catch (XacmlApplicationException e) {
99 LOGGER.error("Failed to convert DecisionRequest: {}", e);
102 // TODO throw exception
108 public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
110 // Get the TOSCA Policy Type for this policy
112 Collection<ToscaPolicyType> toscaPolicyTypes = this.getPolicyTypes(toscaPolicy.getTypeIdentifier());
114 // If we don't have any TOSCA policy types, then we cannot know
115 // which properties are matchable.
117 if (toscaPolicyTypes.isEmpty()) {
118 throw new ToscaPolicyConversionException(
119 "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
122 // Policy name should be at the root
124 String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
126 // Set it as the policy ID
128 PolicyType newPolicyType = new PolicyType();
129 newPolicyType.setPolicyId(policyName);
131 // Optional description
133 newPolicyType.setDescription(toscaPolicy.getDescription());
135 // There should be a metadata section
137 fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
139 // Set the combining rule
141 newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
143 // Generate the TargetType - the policy should not be evaluated
144 // unless all the matchable properties it cares about are matched.
146 newPolicyType.setTarget(generateTargetType(toscaPolicy.getProperties(), toscaPolicyTypes));
148 // Now represent the policy as Json
150 StandardCoder coder = new StandardCoder();
153 jsonPolicy = coder.encode(toscaPolicy);
154 } catch (CoderException e) {
155 throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
158 // Add it as an obligation
160 addObligation(newPolicyType, jsonPolicy);
162 // Now create the Permit Rule.
164 RuleType rule = new RuleType();
165 rule.setDescription("Default is to PERMIT if the policy matches.");
166 rule.setRuleId(policyName + ":rule");
167 rule.setEffect(EffectType.PERMIT);
168 rule.setTarget(new TargetType());
170 // The rule contains the Condition which adds logic for
171 // optional policy-type filtering.
173 rule.setCondition(generateConditionForPolicyType(toscaPolicy.getType()));
175 // Add the rule to the policy
177 newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
179 // Log output of the policy
181 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
182 XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
183 LOGGER.info("{}", os);
184 } catch (IOException e) {
185 LOGGER.error("Failed to create byte array stream", e);
190 return newPolicyType;
194 * For generating target type, we scan for matchable properties
195 * and use those to build the policy.
197 * @param properties Properties section of policy
198 * @param policyTypes Collection of policy Type to find matchable metadata
199 * @return TargetType object
201 protected TargetType generateTargetType(Map<String, Object> properties, Collection<ToscaPolicyType> policyTypes) {
202 TargetType targetType = new TargetType();
204 // Iterate the properties
206 for (Entry<String, Object> entrySet : properties.entrySet()) {
208 // Find matchable properties
210 if (isMatchable(entrySet.getKey(), policyTypes)) {
211 LOGGER.info("Found matchable property {}", entrySet.getKey());
212 generateMatchable(targetType, entrySet.getKey(), entrySet.getValue());
220 * isMatchable - Iterates through available TOSCA Policy Types to determine if a property
221 * should be treated as matchable.
223 * @param propertyName Name of property
224 * @param policyTypes Collection of TOSCA Policy Types to scan
225 * @return true if matchable
227 protected boolean isMatchable(String propertyName, Collection<ToscaPolicyType> policyTypes) {
228 for (ToscaPolicyType policyType : policyTypes) {
229 for (Entry<String, ToscaProperty> propertiesEntry : policyType.getProperties().entrySet()) {
230 if (checkIsMatchableProperty(propertyName, propertiesEntry)) {
239 * checkIsMatchableProperty - checks the policy contents for matchable field. If the metadata doesn't exist,
240 * then definitely not. If the property doesn't exist, then definitely not. Otherwise need to have a metadata
241 * section with the matchable property set to true.
243 * @param propertyName String value of property
244 * @param propertiesEntry Section of the TOSCA Policy Type where properties and metadata sections are held
245 * @return true if matchable
247 protected boolean checkIsMatchableProperty(String propertyName, Entry<String, ToscaProperty> propertiesEntry) {
248 if (! propertiesEntry.getKey().equals(propertyName)
249 || propertiesEntry.getValue().getMetadata() == null) {
252 for (Entry<String, String> entrySet : propertiesEntry.getValue().getMetadata().entrySet()) {
253 if ("matchable".equals(entrySet.getKey()) && "true".equals(entrySet.getValue())) {
261 * generateMatchable - Given the object, generates list of MatchType objects and add them
262 * to the TargetType object.
264 * @param targetType TargetType object to add matches to
265 * @param key Property key
266 * @param value Object is the value - which can be a Collection or single Object
267 * @return TargetType incoming TargetType returned as a convenience
269 @SuppressWarnings("unchecked")
270 protected TargetType generateMatchable(TargetType targetType, String key, Object value) {
271 if (value instanceof Collection) {
272 AnyOfType anyOf = generateMatches((Collection<Object>) value,
273 new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
274 if (! anyOf.getAllOf().isEmpty()) {
275 targetType.getAnyOf().add(anyOf);
278 AnyOfType anyOf = generateMatches(Arrays.asList(value),
279 new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
280 if (! anyOf.getAllOf().isEmpty()) {
281 targetType.getAnyOf().add(anyOf);
288 * generateMatches - Goes through the collection of objects, creates a MatchType object
289 * for each object and associates it with the given attribute Id. Returns the AnyOfType
290 * object that contains all the generated MatchType objects.
292 * @param matchables Collection of object to generate MatchType from
293 * @param attributeId Given attribute Id for each MatchType
294 * @return AnyOfType object
296 protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
298 // This is our outer AnyOf - which is an OR
300 AnyOfType anyOf = new AnyOfType();
301 for (Object matchable : matchables) {
305 Identifier idFunction = XACML3.ID_FUNCTION_STRING_EQUAL;
306 Identifier idDatatype = XACML3.ID_DATATYPE_STRING;
308 // See if we are another datatype
310 // We should add datetime support. But to do that we need
311 // probably more metadata to describe how that would be translated.
313 if (matchable instanceof Integer) {
314 idFunction = XACML3.ID_FUNCTION_INTEGER_EQUAL;
315 idDatatype = XACML3.ID_DATATYPE_INTEGER;
316 } else if (matchable instanceof Double) {
317 idFunction = XACML3.ID_FUNCTION_DOUBLE_EQUAL;
318 idDatatype = XACML3.ID_DATATYPE_DOUBLE;
319 } else if (matchable instanceof Boolean) {
320 idFunction = XACML3.ID_FUNCTION_BOOLEAN_EQUAL;
321 idDatatype = XACML3.ID_DATATYPE_BOOLEAN;
324 // Create a match for this
326 MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
328 matchable.toString(),
331 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
333 // Now create an anyOf (OR)
335 anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
341 * Get Policy Type definitions. This could be previously loaded, or could be
342 * stored in application path, or may need to be pulled from the API.
345 * @param policyTypeId Policy Type Id
346 * @return A list of PolicyTypes
348 private List<ToscaPolicyType> getPolicyTypes(ToscaPolicyTypeIdentifier policyTypeId) {
350 // Create identifier from the policy
352 ToscaPolicyTypeIdentifier typeId = new ToscaPolicyTypeIdentifier(policyTypeId);
354 // Find the Policy Type
356 ToscaPolicyType policyType = findPolicyType(typeId);
357 if (policyType == null) {
358 return Collections.emptyList();
361 // Create our return object
363 List<ToscaPolicyType> listTypes = new ArrayList<>();
364 listTypes.add(policyType);
366 // Look for parent policy types that could also contain matchable properties
368 ToscaPolicyType childPolicyType = policyType;
369 while (! childPolicyType.getDerivedFrom().startsWith("tosca.policies.Root")) {
371 // Create parent policy type id.
373 // We will have to assume the same version between child and the
374 // parent policy type it derives from.
376 // Or do we assume 1.0.0?
378 ToscaPolicyTypeIdentifier parentId = new ToscaPolicyTypeIdentifier(childPolicyType.getDerivedFrom(),
381 // Find the policy type
383 ToscaPolicyType parentPolicyType = findPolicyType(parentId);
384 if (parentPolicyType == null) {
386 // Probably would be best to throw an exception and
387 // return nothing back.
389 // But instead we will log a warning
391 LOGGER.warn("Missing parent policy type - proceeding anyway {}", parentId);
400 listTypes.add(parentPolicyType);
402 // Move to the next parent
404 childPolicyType = parentPolicyType;
410 * findPolicyType - given the ToscaPolicyTypeIdentifier, finds it in memory, or
411 * then tries to find it either locally on disk or pull it from the Policy
412 * Lifecycle API the given TOSCA Policy Type.
414 * @param policyTypeId ToscaPolicyTypeIdentifier to find
415 * @return ToscaPolicyType object. Can be null if failure.
417 private ToscaPolicyType findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
419 // Is it loaded in memory?
421 ToscaPolicyType policyType = this.matchablePolicyTypes.get(policyTypeId);
422 if (policyType == null) {
426 policyType = this.loadPolicyType(policyTypeId);
435 * loadPolicyType - Tries to load the given ToscaPolicyTypeIdentifier from local
436 * storage. If it does not exist, will then attempt to pull from Policy Lifecycle
439 * @param policyTypeId ToscaPolicyTypeIdentifier input
440 * @return ToscaPolicyType object. Null if failure.
442 private ToscaPolicyType loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
444 // Construct what the file name should be
446 Path policyTypePath = this.constructLocalFilePath(policyTypeId);
453 // If it exists locally, read the bytes in
455 bytes = Files.readAllBytes(policyTypePath);
456 } catch (IOException e) {
458 // Does not exist locally, so let's GET it from the policy api
460 LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
462 // So let's pull it from API REST call and save it locally
464 return this.pullPolicyType(policyTypeId, policyTypePath);
467 // Success - we have read locally the policy type. Now bring it into our
470 LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
472 ToscaServiceTemplate serviceTemplate = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
473 ToscaServiceTemplate.class);
474 JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
475 jtst.fromAuthorative(serviceTemplate);
476 ToscaServiceTemplate completedJtst = jtst.toAuthorative();
478 // Search for our Policy Type, there really only should be one but
479 // this is returned as a map.
481 for ( Entry<String, ToscaPolicyType> entrySet : completedJtst.getPolicyTypes().entrySet()) {
482 ToscaPolicyType entryPolicyType = entrySet.getValue();
483 if (policyTypeId.getName().equals(entryPolicyType.getName())
484 && policyTypeId.getVersion().equals(entryPolicyType.getVersion())) {
485 LOGGER.info("Found existing local policy type {} {}", entryPolicyType.getName(),
486 entryPolicyType.getVersion());
488 // Just simply return the policy type right here
490 return entryPolicyType;
492 LOGGER.warn("local policy type contains different name version {} {}", entryPolicyType.getName(),
493 entryPolicyType.getVersion());
497 // This would be an error, if the file stored does not match what its supposed to be
499 LOGGER.error("Existing policy type file does not contain right name and version");
500 } catch (CoderException e) {
501 LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
504 // Hopefully we never get here
506 LOGGER.error("Failed to find/load policy type {}", policyTypeId);
511 * pullPolicyType - pulls the given ToscaPolicyTypeIdentifier from the Policy Lifecycle API.
512 * If successful, will store it locally given the policyTypePath.
514 * @param policyTypeId ToscaPolicyTypeIdentifier
515 * @param policyTypePath Path object to store locally
516 * @return ToscaPolicyType object. Null if failure.
518 private synchronized ToscaPolicyType pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId, Path policyTypePath) {
520 // This is what we return
522 ToscaPolicyType policyType = null;
524 PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
526 policyType = api.getPolicyType(policyTypeId);
527 } catch (PolicyApiException e) {
528 LOGGER.error("Failed to make API call", e);
529 LOGGER.error("parameters: {} ", this.apiRestParameters);
536 standardYamlCoder.encode(policyTypePath.toFile(), policyType);
537 } catch (CoderException e) {
538 LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
541 // Done return the policy type
547 * constructLocalFilePath - common method to ensure the name of the local file for the
548 * policy type is the same.
550 * @param policyTypeId ToscaPolicyTypeIdentifier
551 * @return Path object
553 private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
554 return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
555 + policyTypeId.getVersion() + ".yaml");