2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
6 * Modifications Copyright (C) 2021 Nordix Foundation.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
20 * SPDX-License-Identifier: Apache-2.0
21 * ============LICENSE_END=========================================================
24 package org.onap.policy.pdp.xacml.application.common.std;
26 import com.att.research.xacml.api.Advice;
27 import com.att.research.xacml.api.Identifier;
28 import com.att.research.xacml.api.Obligation;
29 import com.att.research.xacml.api.Request;
30 import com.att.research.xacml.api.XACML3;
31 import com.att.research.xacml.std.IdentifierImpl;
32 import com.att.research.xacml.util.XACMLPolicyWriter;
33 import java.io.ByteArrayOutputStream;
34 import java.io.IOException;
35 import java.nio.charset.StandardCharsets;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.util.Collection;
40 import java.util.HashMap;
41 import java.util.LinkedHashMap;
42 import java.util.LinkedList;
43 import java.util.List;
45 import java.util.Map.Entry;
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AllOfType;
48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
51 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
52 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
53 import org.apache.commons.lang3.tuple.Pair;
54 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
55 import org.onap.policy.common.utils.coder.CoderException;
56 import org.onap.policy.common.utils.coder.StandardCoder;
57 import org.onap.policy.common.utils.coder.StandardYamlCoder;
58 import org.onap.policy.models.decisions.concepts.DecisionRequest;
59 import org.onap.policy.models.decisions.concepts.DecisionResponse;
60 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
61 import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
62 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
63 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
64 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
65 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
66 import org.onap.policy.pdp.xacml.application.common.OnapObligation;
67 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
68 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
69 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
70 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
71 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
72 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationException;
73 import org.onap.policy.pdp.xacml.application.common.matchable.MatchableCallback;
74 import org.onap.policy.pdp.xacml.application.common.matchable.MatchablePolicyType;
75 import org.onap.policy.pdp.xacml.application.common.matchable.MatchableProperty;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
80 * This standard matchable translator uses Policy Types that contain "matchable" field in order
81 * to translate policies.
83 * @author pameladragosh
86 public class StdMatchableTranslator extends StdBaseTranslator implements MatchableCallback {
88 private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
89 private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
91 private final Map<ToscaConceptIdentifier, ToscaServiceTemplate> matchablePolicyTypes = new HashMap<>();
92 private final Map<ToscaConceptIdentifier, MatchablePolicyType> matchableCache = new HashMap<>();
95 private RestServerParameters apiRestParameters;
97 private Path pathForData;
99 public StdMatchableTranslator() {
104 public Request convertRequest(DecisionRequest request) throws ToscaPolicyConversionException {
105 LOGGER.info("Converting Request {}", request);
107 return StdMatchablePolicyRequest.createInstance(request);
108 } catch (XacmlApplicationException e) {
109 throw new ToscaPolicyConversionException("Failed to convert DecisionRequest", e);
114 * scanObligations - scans the list of obligations and make appropriate method calls to process
117 * @param obligations Collection of obligation objects
118 * @param decisionResponse DecisionResponse object used to store any results from obligations.
121 protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
123 // Implementing a crude "closest match" on the results, which means we will strip out
124 // any policies that has the lower weight than any of the others.
126 // Most likely these are "default" policies with a weight of zero, but not always.
128 // It is possible to have multiple policies with an equal weight, that is desired.
130 // So we need to track each policy type separately and the weights for each policy.
132 // policy-type -> weight -> List({policy-id, policy-content}, {policy-id, policy-content})
134 Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches = new LinkedHashMap<>();
136 // Now scan the list of obligations
138 for (Obligation obligation : obligations) {
139 Identifier obligationId = obligation.getId();
140 LOGGER.info("Obligation: {}", obligationId);
141 if (ToscaDictionary.ID_OBLIGATION_REST_BODY.equals(obligationId)) {
142 scanClosestMatchObligation(closestMatches, obligation);
144 LOGGER.warn("Unsupported Obligation Id {}", obligation.getId());
148 // Now add all the policies to the DecisionResponse
150 closestMatches.forEach((thePolicyType, weightMap) ->
151 weightMap.forEach((weight, policies) ->
152 policies.forEach(policy -> {
153 LOGGER.info("Policy {}", policy);
154 decisionResponse.getPolicies().put(policy.getLeft(), policy.getRight());
161 protected void scanAdvice(Collection<Advice> advice, DecisionResponse decisionResponse) {
162 LOGGER.warn("scanAdvice not supported by {}", this.getClass());
166 * scanClosestMatchObligation - scans for the obligation specifically holding policy
167 * contents and their details.
169 * @param closestMatches Map holding the current set of highest weight policy types
170 * @param Obligation Obligation object
172 protected void scanClosestMatchObligation(
173 Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches, Obligation obligation) {
175 // Create our OnapObligation object
177 OnapObligation onapObligation = new OnapObligation(obligation);
179 // All 4 *should* be there
181 if (onapObligation.getPolicyId() == null || onapObligation.getPolicyContent() == null
182 || onapObligation.getPolicyType() == null || onapObligation.getWeight() == null) {
183 LOGGER.error("Missing an expected attribute in obligation.");
189 String policyId = onapObligation.getPolicyId();
190 String policyType = onapObligation.getPolicyType();
191 Map<String, Object> policyContent = onapObligation.getPolicyContentAsMap();
192 int policyWeight = onapObligation.getWeight();
194 // If the Policy Type exists, get the weight map.
196 Map<Integer, List<Pair<String, Map<String, Object>>>> weightMap = closestMatches.get(policyType);
197 if (weightMap != null) {
199 // Only need to check first one - as we will ensure there is only one weight
201 Entry<Integer, List<Pair<String, Map<String, Object>>>> firstEntry =
202 weightMap.entrySet().iterator().next();
203 if (policyWeight < firstEntry.getKey()) {
205 // Existing policies have a greater weight, so we will not add it
207 LOGGER.info("{} is lesser weight {} than current policies, will not return it", policyWeight,
208 firstEntry.getKey());
209 } else if (firstEntry.getKey().equals(policyWeight)) {
211 // Same weight - we will add it
213 LOGGER.info("Same weight {}, adding policy", policyWeight);
214 firstEntry.getValue().add(Pair.of(policyId, policyContent));
217 // The weight is greater, so we need to remove the other policies
218 // and point to this one.
220 LOGGER.info("New policy has greater weight {}, replacing {}", policyWeight, firstEntry.getKey());
221 List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
222 listPolicies.add(Pair.of(policyId, policyContent));
224 weightMap.put(policyWeight, listPolicies);
228 // Create a new entry
230 LOGGER.info("New entry {} weight {}", policyType, policyWeight);
231 List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
232 listPolicies.add(Pair.of(policyId, policyContent));
233 Map<Integer, List<Pair<String, Map<String, Object>>>> newWeightMap = new LinkedHashMap<>();
234 newWeightMap.put(policyWeight, listPolicies);
235 closestMatches.put(policyType, newWeightMap);
240 public Object convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
242 // Get the TOSCA Policy Type for this policy
244 ToscaServiceTemplate toscaPolicyTypeTemplate = this.findPolicyType(toscaPolicy.getTypeIdentifier());
246 // If we don't have any TOSCA policy types, then we cannot know
247 // which properties are matchable.
249 if (toscaPolicyTypeTemplate == null) {
250 throw new ToscaPolicyConversionException(
251 "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
254 // Policy name should be at the root
256 String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
258 // Set it as the policy ID
260 PolicyType newPolicyType = new PolicyType();
261 newPolicyType.setPolicyId(policyName);
263 // Optional description
265 newPolicyType.setDescription(toscaPolicy.getDescription());
267 // There should be a metadata section
269 fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
271 // Set the combining rule
273 newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
275 // Generate the TargetType - the policy should not be evaluated
276 // unless all the matchable properties it cares about are matched.
278 Pair<TargetType, Integer> pairGenerated = generateTargetType(toscaPolicy, toscaPolicyTypeTemplate);
279 newPolicyType.setTarget(pairGenerated.getLeft());
281 // Now represent the policy as Json
283 StandardCoder coder = new StandardCoder();
286 jsonPolicy = coder.encode(toscaPolicy);
287 } catch (CoderException e) {
288 throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
291 // Add it as an obligation
293 addObligation(newPolicyType, policyName, jsonPolicy, pairGenerated.getRight(), toscaPolicy.getType());
295 // Now create the Permit Rule.
297 RuleType rule = new RuleType();
298 rule.setDescription("Default is to PERMIT if the policy matches.");
299 rule.setRuleId(policyName + ":rule");
300 rule.setEffect(EffectType.PERMIT);
301 rule.setTarget(new TargetType());
303 // The rule contains the Condition which adds logic for
304 // optional policy-type filtering.
306 rule.setCondition(generateConditionForPolicyType(toscaPolicy.getType()));
308 // Add the rule to the policy
310 newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
312 // Log output of the policy
314 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
315 XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
316 LOGGER.info("{}", os);
317 } catch (IOException e) {
318 LOGGER.error("Failed to create byte array stream", e);
323 return newPolicyType;
327 public ToscaPolicyType retrievePolicyType(String derivedFrom) {
328 ToscaServiceTemplate template = this.findPolicyType(new ToscaConceptIdentifier(derivedFrom, "1.0.0"));
329 if (template == null) {
330 LOGGER.error("Could not retrieve Policy Type {}", derivedFrom);
333 return template.getPolicyTypes().get(derivedFrom);
337 public ToscaDataType retrieveDataType(String datatype) {
339 // Our outer class is not storing the current template being scanned
341 LOGGER.error("this retrieveDataType should not be called.");
345 private class MyMatchableCallback implements MatchableCallback {
346 private StdMatchableTranslator translator;
347 private ToscaServiceTemplate template;
349 public MyMatchableCallback(StdMatchableTranslator translator, ToscaServiceTemplate template) {
350 this.translator = translator;
351 this.template = template;
355 public ToscaPolicyType retrievePolicyType(String derivedFrom) {
356 ToscaPolicyType policyType = this.template.getPolicyTypes().get(derivedFrom);
357 if (policyType != null) {
360 return translator.retrievePolicyType(derivedFrom);
364 public ToscaDataType retrieveDataType(String datatype) {
365 return this.template.getDataTypes().get(datatype);
371 * For generating target type, we scan for matchable properties
372 * and use those to build the policy.
374 * @param properties Properties section of policy
375 * @param policyTypes Collection of policy Type to find matchable metadata
376 * @return {@code Pair<TargetType, Integer>} Returns a TargetType and a Total Weight of matchables.
378 protected Pair<TargetType, Integer> generateTargetType(ToscaPolicy policy, ToscaServiceTemplate template) {
382 TargetType target = new TargetType();
384 // See if we have a matchable in the cache already
386 MatchablePolicyType matchablePolicyType = matchableCache.get(policy.getTypeIdentifier());
388 // If not found, create one
390 if (matchablePolicyType == null) {
394 MyMatchableCallback myCallback = new MyMatchableCallback(this, template);
396 // Create the matchable
398 matchablePolicyType = new MatchablePolicyType(
399 template.getPolicyTypes().get(policy.getType()), myCallback);
403 matchableCache.put(policy.getTypeIdentifier(), matchablePolicyType);
406 // Fill in the target type with potential matchables
409 fillTargetTypeWithMatchables(target, matchablePolicyType, policy.getProperties());
410 } catch (ToscaPolicyConversionException e) {
411 LOGGER.error("Could not generate target type", e);
414 // There may be a case for default policies there is no weight - need to clean
415 // up the target then else PDP will report bad policy missing AnyOf
417 int weight = calculateWeight(target);
418 LOGGER.debug("Weight is {} for policy {}", weight, policy.getName());
420 // Assume the number of AllOf's is the weight for now
422 return Pair.of(target, weight);
425 @SuppressWarnings("unchecked")
426 protected void fillTargetTypeWithMatchables(TargetType target, MatchablePolicyType matchablePolicyType,
427 Map<String, Object> properties) throws ToscaPolicyConversionException {
428 for (Entry<String, Object> entrySet : properties.entrySet()) {
429 String propertyName = entrySet.getKey();
430 Object propertyValue = entrySet.getValue();
431 MatchableProperty matchable = matchablePolicyType.get(propertyName);
432 if (matchable != null) {
434 // Construct attribute id
436 Identifier id = new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + propertyName);
438 // Depending on what type it is, add it into the target
440 ToscaPolicyTranslatorUtils.buildAndAppendTarget(target,
441 matchable.getType().generate(propertyValue, id));
446 // Here is the special case where we look for a Collection of values that may
447 // contain potential matchables
449 if (propertyValue instanceof List) {
450 for (Object listValue : ((List<?>) propertyValue)) {
451 if (listValue instanceof Map) {
452 fillTargetTypeWithMatchables(target, matchablePolicyType, (Map<String, Object>) listValue);
455 } else if (propertyValue instanceof Map) {
456 fillTargetTypeWithMatchables(target, matchablePolicyType, (Map<String, Object>) propertyValue);
461 protected int calculateWeight(TargetType target) {
463 for (AnyOfType anyOf : target.getAnyOf()) {
464 for (AllOfType allOf : anyOf.getAllOf()) {
465 weight += allOf.getMatch().size();
473 * findPolicyType - given the ToscaConceptIdentifier, finds it in memory, or
474 * then tries to find it either locally on disk or pull it from the Policy
475 * Lifecycle API the given TOSCA Policy Type.
477 * @param policyTypeId ToscaConceptIdentifier to find
478 * @return ToscaPolicyType object. Can be null if failure.
480 private ToscaServiceTemplate findPolicyType(ToscaConceptIdentifier policyTypeId) {
482 // Is it loaded in memory?
484 ToscaServiceTemplate policyTemplate = this.matchablePolicyTypes.get(policyTypeId);
485 if (policyTemplate == null) {
489 policyTemplate = this.loadPolicyType(policyTypeId);
493 if (policyTemplate != null) {
494 this.matchablePolicyTypes.put(policyTypeId, policyTemplate);
500 return policyTemplate;
504 * loadPolicyType - Tries to load the given ToscaConceptIdentifier from local
505 * storage. If it does not exist, will then attempt to pull from Policy Lifecycle
508 * @param policyTypeId ToscaConceptIdentifier input
509 * @return ToscaPolicyType object. Null if failure.
511 private ToscaServiceTemplate loadPolicyType(ToscaConceptIdentifier policyTypeId) {
513 // Construct what the file name should be
515 Path policyTypePath = this.constructLocalFilePath(policyTypeId);
522 // If it exists locally, read the bytes in
524 bytes = Files.readAllBytes(policyTypePath);
525 } catch (IOException e) {
527 // Does not exist locally, so let's GET it from the policy api
529 LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
531 // So let's pull it from API REST call and save it locally
533 return this.pullPolicyType(policyTypeId, policyTypePath);
536 // Success - we have read locally the policy type. Now bring it into our
539 LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
542 // Decode the template
544 ToscaServiceTemplate template = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
545 ToscaServiceTemplate.class);
547 // Ensure all the fields are setup correctly
549 JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
550 jtst.fromAuthorative(template);
551 return jtst.toAuthorative();
552 } catch (CoderException e) {
553 LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
556 // Hopefully we never get here
558 LOGGER.error("Failed to find/load policy type {}", policyTypeId);
563 * pullPolicyType - pulls the given ToscaConceptIdentifier from the Policy Lifecycle API.
564 * If successful, will store it locally given the policyTypePath.
566 * @param policyTypeId ToscaConceptIdentifier
567 * @param policyTypePath Path object to store locally
568 * @return ToscaPolicyType object. Null if failure.
570 private synchronized ToscaServiceTemplate pullPolicyType(ToscaConceptIdentifier policyTypeId,
571 Path policyTypePath) {
573 // This is what we return
575 ToscaServiceTemplate policyTemplate = null;
577 PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
579 policyTemplate = api.getPolicyType(policyTypeId);
580 } catch (PolicyApiException e) {
581 LOGGER.error("Failed to make API call", e);
582 LOGGER.error("parameters: {} ", this.apiRestParameters);
585 LOGGER.info("Successfully pulled {}", policyTypeId);
590 standardYamlCoder.encode(policyTypePath.toFile(), policyTemplate);
591 } catch (CoderException e) {
592 LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
595 // Done return the policy type
597 return policyTemplate;
601 * constructLocalFilePath - common method to ensure the name of the local file for the
602 * policy type is the same.
604 * @param policyTypeId ToscaConceptIdentifier
605 * @return Path object
607 private Path constructLocalFilePath(ToscaConceptIdentifier policyTypeId) {
608 return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
609 + policyTypeId.getVersion() + ".yaml");