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.AttributeAssignment;
26 import com.att.research.xacml.api.DataTypeException;
27 import com.att.research.xacml.api.Decision;
28 import com.att.research.xacml.api.Identifier;
29 import com.att.research.xacml.api.Obligation;
30 import com.att.research.xacml.api.Request;
31 import com.att.research.xacml.api.Response;
32 import com.att.research.xacml.api.Result;
33 import com.att.research.xacml.api.XACML3;
34 import com.att.research.xacml.std.IdentifierImpl;
35 import com.att.research.xacml.std.annotations.RequestParser;
36 import com.att.research.xacml.util.XACMLPolicyWriter;
37 import com.google.gson.Gson;
38 import java.io.ByteArrayOutputStream;
39 import java.io.IOException;
40 import java.nio.charset.StandardCharsets;
41 import java.nio.file.Files;
42 import java.nio.file.Path;
43 import java.nio.file.Paths;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.HashMap;
49 import java.util.List;
51 import java.util.Map.Entry;
53 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
54 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeAssignmentExpressionType;
55 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
56 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
57 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
58 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
59 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionType;
60 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionsType;
61 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
62 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
63 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
64 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
65 import org.onap.policy.common.utils.coder.CoderException;
66 import org.onap.policy.common.utils.coder.StandardCoder;
67 import org.onap.policy.models.decisions.concepts.DecisionRequest;
68 import org.onap.policy.models.decisions.concepts.DecisionResponse;
69 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
70 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
71 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
72 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
73 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
74 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
75 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
76 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
77 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
78 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
79 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslator;
80 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
85 * This standard matchable translator uses Policy Types that contain "matchable" field in order
86 * to translate policies.
88 * @author pameladragosh
91 public class StdMatchableTranslator implements ToscaPolicyTranslator {
93 private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
94 private static final String POLICY_ID = "policy-id";
95 private static final StandardCoder standardCoder = new StandardCoder();
97 private final Map<ToscaPolicyTypeIdentifier, ToscaPolicyType> matchablePolicyTypes = new HashMap<>();
99 private RestServerParameters apiRestParameters;
101 private Path pathForData;
103 public StdMatchableTranslator() {
108 public Request convertRequest(DecisionRequest request) {
109 LOGGER.info("Converting Request {}", request);
111 return StdMatchablePolicyRequest.createInstance(request);
112 } catch (IllegalArgumentException | IllegalAccessException | DataTypeException e) {
113 LOGGER.error("Failed to convert DecisionRequest: {}", e);
116 // TODO throw exception
122 public DecisionResponse convertResponse(Response xacmlResponse) {
123 LOGGER.info("Converting Response {}", xacmlResponse);
124 DecisionResponse decisionResponse = new DecisionResponse();
128 decisionResponse.setPolicies(new HashMap<>());
130 // Iterate through all the results
132 for (Result xacmlResult : xacmlResponse.getResults()) {
136 if (xacmlResult.getDecision() == Decision.PERMIT) {
138 // Go through obligations
140 scanObligations(xacmlResult.getObligations(), decisionResponse);
142 if (xacmlResult.getDecision() == Decision.DENY
143 || xacmlResult.getDecision() == Decision.INDETERMINATE) {
145 // TODO we have to return an ErrorResponse object instead
147 decisionResponse.setStatus("A better error message");
151 return decisionResponse;
154 protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
155 for (Obligation obligation : obligations) {
156 LOGGER.info("Obligation: {}", obligation);
157 for (AttributeAssignment assignment : obligation.getAttributeAssignments()) {
158 LOGGER.info("Attribute Assignment: {}", assignment);
160 // We care about the content attribute
162 if (ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS
163 .equals(assignment.getAttributeId())) {
165 // The contents are in Json form
167 Object stringContents = assignment.getAttributeValue().getValue();
168 if (LOGGER.isInfoEnabled()) {
169 LOGGER.info("Policy contents: {}{}", System.lineSeparator(), stringContents);
172 // Let's parse it into a map using Gson
174 Gson gson = new Gson();
175 @SuppressWarnings("unchecked")
176 Map<String, Object> result = gson.fromJson(stringContents.toString() ,Map.class);
178 // Find the metadata section
180 @SuppressWarnings("unchecked")
181 Map<String, Object> metadata = (Map<String, Object>) result.get("metadata");
182 if (metadata != null) {
183 decisionResponse.getPolicies().put(metadata.get(POLICY_ID).toString(), result);
185 LOGGER.error("Missing metadata section in policy contained in obligation.");
194 public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
196 // Get the TOSCA Policy Type for this policy
198 Collection<ToscaPolicyType> policyTypes = this.getPolicyTypes(toscaPolicy.getTypeIdentifier());
200 // If we don't have any policy types, then we cannot know
201 // which properties are matchable.
203 if (policyTypes.isEmpty()) {
204 throw new ToscaPolicyConversionException(
205 "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
208 // Policy name should be at the root
210 String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
212 // Set it as the policy ID
214 PolicyType newPolicyType = new PolicyType();
215 newPolicyType.setPolicyId(policyName);
217 // Optional description
219 newPolicyType.setDescription(toscaPolicy.getDescription());
221 // There should be a metadata section
223 this.fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
225 // Set the combining rule
227 newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
229 // Generate the TargetType
231 newPolicyType.setTarget(generateTargetType(toscaPolicy.getProperties(), policyTypes));
233 // Now create the Permit Rule
234 // No target since the policy has a target
237 RuleType rule = new RuleType();
238 rule.setDescription("Default is to PERMIT if the policy matches.");
239 rule.setRuleId(policyName + ":rule");
240 rule.setEffect(EffectType.PERMIT);
241 rule.setTarget(new TargetType());
243 // Now represent the policy as Json
245 StandardCoder coder = new StandardCoder();
248 jsonPolicy = coder.encode(toscaPolicy);
249 } catch (CoderException e) {
250 throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
252 addObligation(rule, jsonPolicy);
254 // Add the rule to the policy
256 newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
258 // Return our new policy
260 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
261 XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
262 LOGGER.info("{}", os);
263 } catch (IOException e) {
264 LOGGER.error("Failed to create byte array stream", e);
266 return newPolicyType;
270 * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
272 * @param policy Policy Object to store the metadata
273 * @param map The Metadata TOSCA Map
274 * @return Same Policy Object
275 * @throws ToscaPolicyConversionException If there is something missing from the metadata
277 protected PolicyType fillMetadataSection(PolicyType policy,
278 Map<String, String> map) throws ToscaPolicyConversionException {
279 if (! map.containsKey(POLICY_ID)) {
280 throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-id");
283 // Do nothing here - the XACML PolicyId is used from TOSCA Policy Name field
286 if (! map.containsKey("policy-version")) {
287 throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-version");
290 // Add in the Policy Version
292 policy.setVersion(map.get("policy-version"));
298 * For generating target type, we are scan for matchable properties
299 * and use those to build the policy.
301 * @param properties Properties section of policy
302 * @param policyTypes Collection of policy Type to find matchable metadata
303 * @return TargetType object
305 @SuppressWarnings("unchecked")
306 protected TargetType generateTargetType(Map<String, Object> properties, Collection<ToscaPolicyType> policyTypes) {
307 TargetType targetType = new TargetType();
309 // Iterate the properties
311 for (Entry<String, Object> entrySet : properties.entrySet()) {
313 // Find matchable properties
315 if (isMatchable(entrySet.getKey(), policyTypes)) {
316 LOGGER.info("Found matchable property {}", entrySet.getValue());
317 if (entrySet.getValue() instanceof Collection) {
318 AnyOfType anyOf = generateMatches((Collection<Object>) entrySet.getValue(),
319 new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + entrySet.getKey()));
320 if (! anyOf.getAllOf().isEmpty()) {
321 targetType.getAnyOf().add(anyOf);
324 AnyOfType anyOf = generateMatches(Arrays.asList(entrySet.getValue()),
325 new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + entrySet.getKey()));
326 if (! anyOf.getAllOf().isEmpty()) {
327 targetType.getAnyOf().add(anyOf);
336 protected boolean isMatchable(String propertyName, Collection<ToscaPolicyType> policyTypes) {
337 for (ToscaPolicyType policyType : policyTypes) {
338 for (Entry<String, ToscaProperty> propertiesEntry : policyType.getProperties().entrySet()) {
339 if (! propertiesEntry.getKey().equals(propertyName)
340 || propertiesEntry.getValue().getMetadata() == null) {
343 for (Entry<String, String> entrySet : propertiesEntry.getValue().getMetadata().entrySet()) {
344 if (entrySet.getKey().equals("matchable") && entrySet.getValue().equals("true")) {
353 protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
355 // This is our outer AnyOf - which is an OR
357 AnyOfType anyOf = new AnyOfType();
358 for (Object matchable : matchables) {
362 Identifier idFunction = XACML3.ID_FUNCTION_STRING_EQUAL;
363 Identifier idDatatype = XACML3.ID_DATATYPE_STRING;
365 // See if we are another datatype
367 // TODO We should add datetime support. But to do that we need
368 // probably more metadata to describe how that would be translated.
370 if (matchable instanceof Integer) {
371 idFunction = XACML3.ID_FUNCTION_INTEGER_EQUAL;
372 idDatatype = XACML3.ID_DATATYPE_INTEGER;
373 } else if (matchable instanceof Double) {
374 idFunction = XACML3.ID_FUNCTION_DOUBLE_EQUAL;
375 idDatatype = XACML3.ID_DATATYPE_DOUBLE;
376 } else if (matchable instanceof Boolean) {
377 idFunction = XACML3.ID_FUNCTION_BOOLEAN_EQUAL;
378 idDatatype = XACML3.ID_DATATYPE_BOOLEAN;
381 // Create a match for this
383 MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
385 matchable.toString(),
388 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
390 // Now create an anyOf (OR)
392 anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
397 protected RuleType addObligation(RuleType rule, String jsonPolicy) {
399 // Convert the YAML Policy to JSON Object
401 if (LOGGER.isInfoEnabled()) {
402 LOGGER.info("JSON Optimization Policy {}{}", System.lineSeparator(), jsonPolicy);
405 // Create an AttributeValue for it
407 AttributeValueType value = new AttributeValueType();
408 value.setDataType(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_DATATYPE.stringValue());
409 value.getContent().add(jsonPolicy);
411 // Create our AttributeAssignmentExpression where we will
412 // store the contents of the policy in JSON format.
414 AttributeAssignmentExpressionType expressionType = new AttributeAssignmentExpressionType();
415 expressionType.setAttributeId(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS.stringValue());
416 ObjectFactory factory = new ObjectFactory();
417 expressionType.setExpression(factory.createAttributeValue(value));
419 // Create an ObligationExpression for it
421 ObligationExpressionType obligation = new ObligationExpressionType();
422 obligation.setFulfillOn(EffectType.PERMIT);
423 obligation.setObligationId(ToscaDictionary.ID_OBLIGATION_REST_BODY.stringValue());
424 obligation.getAttributeAssignmentExpression().add(expressionType);
426 // Now we can add it into the rule
428 ObligationExpressionsType obligations = new ObligationExpressionsType();
429 obligations.getObligationExpression().add(obligation);
430 rule.setObligationExpressions(obligations);
436 * Get Policy Type definitions. This could be previously loaded, or could be
437 * stored in application path, or may need to be pulled from the API.
440 * @param policyTypeId Policy Type Id
441 * @return A list of PolicyTypes
443 private List<ToscaPolicyType> getPolicyTypes(ToscaPolicyTypeIdentifier policyTypeId) {
445 // Create identifier from the policy
447 ToscaPolicyTypeIdentifier typeId = new ToscaPolicyTypeIdentifier(policyTypeId);
449 // Find the Policy Type
451 ToscaPolicyType policyType = findPolicyType(typeId);
452 if (policyType == null) {
453 return Collections.emptyList();
456 // Create our return object
458 List<ToscaPolicyType> listTypes = new ArrayList<>();
459 listTypes.add(policyType);
461 // Look for parent policy types that could also contain matchable properties
463 ToscaPolicyType childPolicyType = policyType;
464 while (! childPolicyType.getDerivedFrom().startsWith("tosca.policies.Root")) {
466 // Create parent policy type id.
468 // We will have to assume the same version between child and the
469 // parent policy type it derives from.
471 // Or do we assume 1.0.0?
473 String strDerivedFrom = childPolicyType.getDerivedFrom();
475 // Hack that fixes policy/models appending 0.0.0 to the derivedFrom name
477 if (strDerivedFrom.endsWith("0.0.0")) {
478 strDerivedFrom = strDerivedFrom.substring(0, strDerivedFrom.length() - "0.0.0".length() - 1);
480 ToscaPolicyTypeIdentifier parentId = new ToscaPolicyTypeIdentifier(strDerivedFrom, "1.0.0");
482 // Find the policy type
484 ToscaPolicyType parentPolicyType = findPolicyType(parentId);
485 if (parentPolicyType == null) {
487 // Probably would be best to throw an exception and
488 // return nothing back.
490 // But instead we will log a warning
492 LOGGER.warn("Missing parent policy type - proceeding anyway {}", parentId);
501 listTypes.add(parentPolicyType);
503 // Move to the next parent
505 childPolicyType = parentPolicyType;
512 private ToscaPolicyType findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
514 // Is it loaded in memory?
516 ToscaPolicyType policyType = this.matchablePolicyTypes.get(policyTypeId);
517 if (policyType == null) {
521 policyType = this.loadPolicyType(policyTypeId);
529 private ToscaPolicyType loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
531 // Construct what the file name should be
533 Path policyTypePath = this.constructLocalFilePath(policyTypeId);
540 // If it exists locally, read the bytes in
542 bytes = Files.readAllBytes(policyTypePath);
543 } catch (IOException e) {
545 // Does not exist locally, so let's GET it from the policy api
547 LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
549 // So let's pull it from API REST call and save it locally
551 return this.pullPolicyType(policyTypeId, policyTypePath);
553 LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
555 ToscaServiceTemplate serviceTemplate = standardCoder.decode(new String(bytes, StandardCharsets.UTF_8),
556 ToscaServiceTemplate.class);
557 JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
558 jtst.fromAuthorative(serviceTemplate);
559 ToscaServiceTemplate completedJtst = jtst.toAuthorative();
561 // Search for our Policy Type, there really only should be one but
562 // this is returned as a map.
564 for ( Entry<String, ToscaPolicyType> entrySet : completedJtst.getPolicyTypes().entrySet()) {
565 ToscaPolicyType entryPolicyType = entrySet.getValue();
566 if (policyTypeId.getName().equals(entryPolicyType.getName())
567 && policyTypeId.getVersion().equals(entryPolicyType.getVersion())) {
568 LOGGER.info("Found existing local policy type {} {}", entryPolicyType.getName(),
569 entryPolicyType.getVersion());
571 // Just simply return the policy type right here
573 return entryPolicyType;
575 LOGGER.warn("local policy type contains different name version {} {}", entryPolicyType.getName(),
576 entryPolicyType.getVersion());
580 // This would be an error, if the file stored does not match what its supposed to be
582 LOGGER.error("Existing policy type file does not contain right name and version");
583 } catch (CoderException e) {
584 LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
587 // Hopefully we never get here
589 LOGGER.error("Failed to find/load policy type {}", policyTypeId);
593 private synchronized ToscaPolicyType pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId, Path policyTypePath) {
595 // This is what we return
597 ToscaPolicyType policyType = null;
599 PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
601 policyType = api.getPolicyType(policyTypeId);
602 } catch (PolicyApiException e) {
603 LOGGER.error("Failed to make API call", e);
604 LOGGER.error("parameters: {} ", this.apiRestParameters);
611 standardCoder.encode(policyTypePath.toFile(), policyType);
612 } catch (CoderException e) {
613 LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
616 // Done return the policy type
621 private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
622 return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
623 + policyTypeId.getVersion() + ".json");