Include returned attributes in Decision
[policy/xacml-pdp.git] / applications / common / src / main / java / org / onap / policy / pdp / xacml / application / common / std / StdBaseTranslator.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2021 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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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.
18  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.pdp.xacml.application.common.std;
24
25 import com.att.research.xacml.api.Advice;
26 import com.att.research.xacml.api.Attribute;
27 import com.att.research.xacml.api.AttributeCategory;
28 import com.att.research.xacml.api.Decision;
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 java.util.Collection;
35 import java.util.HashMap;
36 import java.util.Map;
37 import lombok.Getter;
38 import lombok.Setter;
39 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
40 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ApplyType;
41 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeDesignatorType;
42 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
43 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ConditionType;
44 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
45 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionType;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionsType;
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySetType;
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 org.onap.policy.models.decisions.concepts.DecisionRequest;
51 import org.onap.policy.models.decisions.concepts.DecisionResponse;
52 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
53 import org.onap.policy.pdp.xacml.application.common.OnapObligation;
54 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
55 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
56 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslator;
57 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
58 import org.onap.policy.pdp.xacml.application.common.XacmlPolicyUtils;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 public abstract class StdBaseTranslator implements ToscaPolicyTranslator {
63     private static final Logger LOGGER = LoggerFactory.getLogger(StdBaseTranslator.class);
64     private static final ObjectFactory factory = new ObjectFactory();
65
66     @Getter
67     @Setter
68     protected boolean booleanReturnAttributes = false;
69
70     @Getter
71     @Setter
72     protected boolean booleanReturnSingleValueAttributesAsCollection = false;
73
74     public static final String POLICY_ID = "policy-id";
75     public static final String POLICY_VERSION = "policy-version";
76
77     @Override
78     public Object convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
79         throw new ToscaPolicyConversionException("Please override convertPolicy");
80     }
81
82     @Override
83     public Request convertRequest(DecisionRequest request) throws ToscaPolicyConversionException {
84         return null;
85     }
86
87     @Override
88     public DecisionResponse convertResponse(Response xacmlResponse) {
89         LOGGER.info("Converting Response {}", xacmlResponse);
90         var decisionResponse = new DecisionResponse();
91         //
92         // Setup policies
93         //
94         decisionResponse.setPolicies(new HashMap<>());
95         //
96         // Iterate through all the results
97         //
98         for (Result xacmlResult : xacmlResponse.getResults()) {
99             //
100             // Check the result
101             //
102             if (xacmlResult.getDecision() == Decision.PERMIT) {
103                 //
104                 // Go through obligations
105                 //
106                 scanObligations(xacmlResult.getObligations(), decisionResponse);
107                 //
108                 // Go through advice
109                 //
110                 scanAdvice(xacmlResult.getAssociatedAdvice(), decisionResponse);
111             } else {
112                 //
113                 // Return error information back
114                 //
115                 decisionResponse.setStatus("error");
116                 decisionResponse.setMessage(xacmlResult.getStatus().getStatusMessage());
117             }
118             //
119             // Add attributes
120             //
121             if (booleanReturnAttributes) {
122                 scanAttributes(xacmlResult.getAttributes(), decisionResponse);
123             }
124         }
125
126         return decisionResponse;
127     }
128
129     /**
130      * scanObligations - scans the list of obligations and make appropriate method calls to process
131      * obligations. This method must be overridden and be implemented for the specific application as
132      * obligations may have different expected attributes per application.
133      *
134      * @param obligations Collection of obligation objects
135      * @param decisionResponse DecisionResponse object used to store any results from obligations.
136      */
137     protected abstract void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse);
138
139     /**
140      * scanAdvice - scans the list of advice and make appropriate call to process the advice. This method
141      * can be overridden for each specific application as advice may have different expected attributes per
142      * application.
143      *
144      * @param advice Collection of Advice objects
145      * @param decisionResponse DecisionResponse object used to store any results from advice.
146      */
147     protected abstract void scanAdvice(Collection<Advice> advice, DecisionResponse decisionResponse);
148
149     /**
150      * scanAttributes - scans the attributes that are returned in the XACML Decision and puts them into the
151      * DecisionResponse object.
152      *
153      * @param attributeCategories Collection of AttributeCategory objects
154      * @param decisionResponse DecisionResponse object used to store any attributes
155      */
156     protected void scanAttributes(Collection<AttributeCategory> attributeCategories,
157             DecisionResponse decisionResponse) {
158         var returnedAttributes = new HashMap<String, Object>();
159         for (AttributeCategory attributeCategory : attributeCategories) {
160             var mapCategory = new HashMap<String, Object>();
161             for (Attribute attribute : attributeCategory.getAttributes()) {
162                 //
163                 // Most attributes have a single value, thus the collection is not necessary to
164                 // return. However, we will allow this to be configurable.
165                 //
166                 if (! booleanReturnSingleValueAttributesAsCollection && attribute.getValues().size() == 1) {
167                     var iterator = attribute.getValues().iterator();
168                     var value = iterator.next();
169                     mapCategory.put(attribute.getAttributeId().stringValue(), value.getValue().toString());
170                 } else {
171                     mapCategory.put(attribute.getAttributeId().stringValue(), attribute.getValues());
172                 }
173             }
174             returnedAttributes.put(attributeCategory.getCategory().stringValue(), mapCategory);
175         }
176         if (! returnedAttributes.isEmpty()) {
177             decisionResponse.setAttributes(returnedAttributes);
178         }
179     }
180
181     /**
182      * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
183      *
184      * @param policy Policy Object to store the metadata
185      * @param map The Metadata TOSCA Map
186      * @return Same Policy Object
187      * @throws ToscaPolicyConversionException If there is something missing from the metadata
188      */
189     protected PolicyType fillMetadataSection(PolicyType policy,
190             Map<String, String> map) throws ToscaPolicyConversionException {
191         //
192         // Ensure the policy-id exists - we don't use it here. It
193         // is saved in the TOSCA Policy Name field.
194         //
195         if (! map.containsKey(POLICY_ID)) {
196             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata " + POLICY_ID);
197         }
198         //
199         // Ensure the policy-version exists
200         //
201         if (! map.containsKey(POLICY_VERSION)) {
202             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata "
203                     + POLICY_VERSION);
204         }
205         //
206         // Add in the Policy Version
207         //
208         policy.setVersion(map.get(POLICY_VERSION));
209         return policy;
210     }
211
212     /**
213      * addObligation - general code to add a json policy as an obligation. Probably could just
214      * return the obligation only instead of adding it directly to a rule/policy/policyset.
215      * But this is fine for now.
216      *
217      * @param <T> RuleType, PolicyType, PolicySetType object
218      * @Param policyId The policy-id
219      * @param ruleOrPolicy Incoming RuleType, PolicyType, PolicySetType object
220      * @param jsonPolicy JSON String representation of policy.
221      * @param weight Weighting for the policy (optional)
222      * @return Return the Incoming RuleType, PolicyType, PolicySetType object for convenience.
223      */
224     protected <T> T addObligation(T ruleOrPolicy, String policyId, String jsonPolicy, Integer weight,
225             String policyType) {
226         //
227         // Creating obligation for returning policy
228         //
229         LOGGER.info("Obligation Policy id: {} type: {} weight: {} policy:{}{}", policyId, policyType, weight,
230                 XacmlPolicyUtils.LINE_SEPARATOR, jsonPolicy);
231         //
232         // Create our OnapObligation
233         //
234         var onapObligation = new OnapObligation(policyId, jsonPolicy, policyType, weight);
235         //
236         // Generate the obligation
237         //
238         ObligationExpressionType obligation = onapObligation.generateObligation();
239         //
240         // Now we can add it into the rule/policy/policyset
241         //
242         var obligations = new ObligationExpressionsType();
243         obligations.getObligationExpression().add(obligation);
244         if (ruleOrPolicy instanceof RuleType) {
245             ((RuleType) ruleOrPolicy).setObligationExpressions(obligations);
246         } else if (ruleOrPolicy instanceof PolicyType) {
247             ((PolicyType) ruleOrPolicy).setObligationExpressions(obligations);
248         } else if (ruleOrPolicy instanceof PolicySetType) {
249             ((PolicySetType) ruleOrPolicy).setObligationExpressions(obligations);
250         } else {
251             LOGGER.error("Unsupported class for adding obligation {}", ruleOrPolicy.getClass());
252         }
253         //
254         // Return as a convenience
255         //
256         return ruleOrPolicy;
257     }
258
259     /**
260      * generateAnyOfForPolicyType - Creates a specific AnyOfType that includes the check
261      * to match on a specific TOSCA Policy Type.
262      *
263      * @param type String represenatation of TOSCA Policy Type (eg. "onap.policies.Foo")
264      * @return AnyOfType object
265      */
266     protected AnyOfType generateAnyOfForPolicyType(String type) {
267         //
268         // Create the match for the policy type
269         //
270         var match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
271                 XACML3.ID_FUNCTION_STRING_EQUAL,
272                 type,
273                 XACML3.ID_DATATYPE_STRING,
274                 ToscaDictionary.ID_RESOURCE_POLICY_TYPE,
275                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
276         //
277         // Add it to an AnyOfType object
278         //
279         var anyOf = new AnyOfType();
280         anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
281         //
282         // Return new AnyOfType
283         //
284         return anyOf;
285     }
286
287     /**
288      * generateConditionForPolicyType - create a ConditionType XACML object
289      * that is able to determine if a request specifies a specific policy type
290      * that the policy is created from, only then is the rule applied. If the
291      * request doesn't even care about the policy type (eg it is missing) then
292      * return the rule should not apply.
293      *
294      * @param type PolicyType (eg. onap.policies.Foo
295      * @return ConditionType object
296      */
297     protected ConditionType generateConditionForPolicyType(String type) {
298         //
299         // Create an ApplyType that checks if the request contains the
300         // policy-type attribute
301         //
302         var designator = new AttributeDesignatorType();
303         designator.setAttributeId(ToscaDictionary.ID_RESOURCE_POLICY_TYPE.stringValue());
304         designator.setCategory(XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE.stringValue());
305         designator.setDataType(XACML3.ID_DATATYPE_STRING.stringValue());
306
307         var applyBagSize = new ApplyType();
308         applyBagSize.setDescription("Get the size of policy-type attributes");
309         applyBagSize.setFunctionId(XACML3.ID_FUNCTION_STRING_BAG_SIZE.stringValue());
310
311         var valueZero = new AttributeValueType();
312         valueZero.setDataType(XACML3.ID_DATATYPE_INTEGER.stringValue());
313         valueZero.getContent().add("0");    // Yes really - represent as a string
314
315         applyBagSize.getExpression().add(factory.createAttributeDesignator(designator));
316
317         var applyGreaterThan = new ApplyType();
318         applyGreaterThan.setDescription("Does the policy-type attribute exist?");
319         applyGreaterThan.setFunctionId(XACML3.ID_FUNCTION_INTEGER_EQUAL.stringValue());
320
321         applyGreaterThan.getExpression().add(factory.createApply(applyBagSize));
322         applyGreaterThan.getExpression().add(factory.createAttributeValue(valueZero));
323
324         //
325         // Create an apply type that checks the actual value
326         //
327         var value = new AttributeValueType();
328         value.setDataType(XACML3.ID_DATATYPE_STRING.stringValue());
329         value.getContent().add(type);
330
331         //
332         // Create string-is-in apply - which determines if the policy-type
333         // is in the request bag of resources for policy-type
334         //
335         var applyIsIn = new ApplyType();
336         applyIsIn.setDescription("Is this policy-type in the list?");
337         applyIsIn.setFunctionId(XACML3.ID_FUNCTION_STRING_IS_IN.stringValue());
338         applyIsIn.getExpression().add(factory.createAttributeValue(value));
339         applyIsIn.getExpression().add(factory.createAttributeDesignator(designator));
340
341         //
342         // Create our outer apply
343         //
344         var applyOr = new ApplyType();
345         applyOr.setDescription("IF exists and is equal");
346         applyOr.setFunctionId(XACML3.ID_FUNCTION_OR.stringValue());
347
348         applyOr.getExpression().add(factory.createApply(applyGreaterThan));
349         applyOr.getExpression().add(factory.createApply(applyIsIn));
350
351         //
352         // Finally create the condition
353         //
354         var condition = new ConditionType();
355
356         condition.setExpression(factory.createApply(applyOr));
357
358         return condition;
359     }
360
361 }