8550b12eb2c3162c49f9543e6ed3d7078d5bfb30
[policy/xacml-pdp.git] / applications / common / src / main / java / org / onap / policy / pdp / xacml / application / common / std / StdMatchableTranslator.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
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
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.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.annotations.RequestParser;
35 import com.att.research.xacml.util.XACMLPolicyWriter;
36 import com.google.gson.Gson;
37
38 import java.io.ByteArrayOutputStream;
39 import java.io.IOException;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.Collection;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Map.Entry;
46
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeAssignmentExpressionType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
51 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
52 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
53 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionType;
54 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionsType;
55 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
56 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
57 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
58
59 import org.json.JSONObject;
60 import org.onap.policy.models.decisions.concepts.DecisionRequest;
61 import org.onap.policy.models.decisions.concepts.DecisionResponse;
62 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
63 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
64 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslator;
65 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
68
69 public class StdMatchableTranslator implements ToscaPolicyTranslator {
70
71     private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
72
73     public StdMatchableTranslator() {
74         super();
75     }
76
77     @SuppressWarnings("unchecked")
78     @Override
79     public List<PolicyType> scanAndConvertPolicies(Map<String, Object> toscaObject)
80             throws ToscaPolicyConversionException {
81         //
82         // Our return object
83         //
84         List<PolicyType> scannedPolicies = new ArrayList<>();
85         //
86         // Iterate each of the Policies
87         //
88         List<Object> policies = (List<Object>) toscaObject.get("policies");
89         for (Object policyObject : policies) {
90             //
91             // Get the contents
92             //
93             LOGGER.debug("Found policy {}", policyObject.getClass());
94             Map<String, Object> policyContents = (Map<String, Object>) policyObject;
95             for (Entry<String, Object> entrySet : policyContents.entrySet()) {
96                 LOGGER.debug("Entry set {}", entrySet);
97                 //
98                 // Convert this policy
99                 //
100                 PolicyType policy = this.convertPolicy(entrySet);
101                 try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
102                     XACMLPolicyWriter.writePolicyFile(os, policy);
103                     LOGGER.debug("{}", os);
104                 } catch (IOException e) {
105                     LOGGER.error("Failed to convert {}", e);
106                 }
107                 //
108                 // Convert and add in the new policy
109                 //
110                 scannedPolicies.add(policy);
111             }
112         }
113
114         return scannedPolicies;
115     }
116
117     @Override
118     public Request convertRequest(DecisionRequest request) {
119         LOGGER.debug("Converting Request {}", request);
120         try {
121             return RequestParser.parseRequest(StdMatchablePolicyRequest.createInstance(request));
122         } catch (IllegalArgumentException | IllegalAccessException | DataTypeException e) {
123             LOGGER.error("Failed to convert DecisionRequest: {}", e);
124         }
125         //
126         // TODO throw exception
127         //
128         return null;
129     }
130
131     @Override
132     public DecisionResponse convertResponse(Response xacmlResponse) {
133         LOGGER.debug("Converting Response {}", xacmlResponse);
134         DecisionResponse decisionResponse = new DecisionResponse();
135         //
136         // Iterate through all the results
137         //
138         for (Result xacmlResult : xacmlResponse.getResults()) {
139             //
140             // Check the result
141             //
142             if (xacmlResult.getDecision() == Decision.PERMIT) {
143                 //
144                 // Setup policies
145                 //
146                 decisionResponse.setPolicies(new ArrayList<>());
147                 //
148                 // Go through obligations
149                 //
150                 scanObligations(xacmlResult.getObligations(), decisionResponse);
151             }
152             if (xacmlResult.getDecision() == Decision.NOTAPPLICABLE) {
153                 //
154                 // There is no policy
155                 //
156                 decisionResponse.setPolicies(new ArrayList<>());
157             }
158             if (xacmlResult.getDecision() == Decision.DENY
159                     || xacmlResult.getDecision() == Decision.INDETERMINATE) {
160                 //
161                 // TODO we have to return an ErrorResponse object instead
162                 //
163                 decisionResponse.setStatus("A better error message");
164             }
165         }
166
167         return decisionResponse;
168     }
169
170     protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
171         for (Obligation obligation : obligations) {
172             LOGGER.debug("Obligation: {}", obligation);
173             for (AttributeAssignment assignment : obligation.getAttributeAssignments()) {
174                 LOGGER.debug("Attribute Assignment: {}", assignment);
175                 //
176                 // We care about the content attribute
177                 //
178                 if (ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS
179                         .equals(assignment.getAttributeId())) {
180                     //
181                     // The contents are in Json form
182                     //
183                     Object stringContents = assignment.getAttributeValue().getValue();
184                     if (LOGGER.isDebugEnabled()) {
185                         LOGGER.debug("DCAE contents: {}{}", System.lineSeparator(), stringContents);
186                     }
187                     //
188                     // Let's parse it into a map using Gson
189                     //
190                     Gson gson = new Gson();
191                     @SuppressWarnings("unchecked")
192                     Map<String, Object> result = gson.fromJson(stringContents.toString() ,Map.class);
193                     decisionResponse.getPolicies().add(result);
194                 }
195             }
196         }
197
198     }
199
200     @SuppressWarnings("unchecked")
201     protected PolicyType convertPolicy(Entry<String, Object> entrySet) throws ToscaPolicyConversionException {
202         //
203         // Policy name should be at the root
204         //
205         String policyName = entrySet.getKey();
206         Map<String, Object> policyDefinition = (Map<String, Object>) entrySet.getValue();
207         //
208         // Set it as the policy ID
209         //
210         PolicyType newPolicyType = new PolicyType();
211         newPolicyType.setPolicyId(policyName);
212         //
213         // Optional description
214         //
215         if (policyDefinition.containsKey("description")) {
216             newPolicyType.setDescription(policyDefinition.get("description").toString());
217         }
218         //
219         // There should be a metadata section
220         //
221         if (! policyDefinition.containsKey("metadata")) {
222             throw new ToscaPolicyConversionException(policyName + " missing metadata section");
223         }
224         this.fillMetadataSection(newPolicyType,
225                 (Map<String, Object>) policyDefinition.get("metadata"));
226         //
227         // Set the combining rule
228         //
229         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
230         //
231         // Generate the TargetType
232         //
233         if (! policyDefinition.containsKey("properties")) {
234             throw new ToscaPolicyConversionException(policyName + " missing properties section");
235         }
236         policyDefinition.get("properties");
237         newPolicyType.setTarget(generateTargetType((Map<String, Object>) policyDefinition.get("properties")));
238         //
239         // Now create the Permit Rule
240         // No target since the policy has a target
241         // With obligations.
242         //
243         RuleType rule = new RuleType();
244         rule.setDescription("Default is to PERMIT if the policy matches.");
245         rule.setRuleId(policyName + ":rule");
246         rule.setEffect(EffectType.PERMIT);
247         rule.setTarget(new TargetType());
248         //
249         // Now represent the policy as Json
250         //
251         JSONObject jsonObligation = new JSONObject();
252         jsonObligation.put(policyName, policyDefinition);
253         addObligation(rule, jsonObligation);
254         //
255         // Add the rule to the policy
256         //
257         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
258         //
259         // Return our new policy
260         //
261         return newPolicyType;
262     }
263
264     /**
265      * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
266      *
267      * @param policy Policy Object to store the metadata
268      * @param metadata The Metadata TOSCA Map
269      * @return Same Policy Object
270      * @throws ToscaPolicyConversionException If there is something missing from the metadata
271      */
272     protected PolicyType fillMetadataSection(PolicyType policy,
273             Map<String, Object> metadata) throws ToscaPolicyConversionException {
274         if (! metadata.containsKey("policy-id")) {
275             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-id");
276         } else {
277             //
278             // Do nothing here - the XACML PolicyId is used from TOSCA Policy Name field
279             //
280         }
281         if (! metadata.containsKey("policy-version")) {
282             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-version");
283         } else {
284             //
285             // Add in the Policy Version
286             //
287             policy.setVersion(metadata.get("policy-version").toString());
288         }
289         return policy;
290     }
291
292     /**
293      * For generating target type, we are making an assumption that the
294      * policyScope and policyType are the fields that OOF wants to match on.
295      *
296      * <P>In the future, we would need to receive the Policy Type specification
297      * from the PAP so we can dynamically see which fields are matchable.
298      *
299      * <P>Note: I am making an assumption that the matchable fields are what
300      * the OOF wants to query a policy on.
301      *
302      * @param properties Properties section of policy
303      * @return TargetType object
304      */
305     @SuppressWarnings("unchecked")
306     protected TargetType generateTargetType(Map<String, Object> properties) {
307         TargetType targetType = new TargetType();
308         //
309         // Iterate the properties
310         //
311         for (Entry<String, Object> entrySet : properties.entrySet()) {
312             //
313             // Find policyScope and policyType
314             //
315             if (entrySet.getKey().equals("policyScope")) {
316                 LOGGER.debug("Found policyScope: {}", entrySet.getValue());
317                 if (entrySet.getValue() instanceof Collection) {
318                     targetType.getAnyOf().add(generateMatches((Collection<Object>) entrySet.getValue(),
319                             ToscaDictionary.ID_RESOURCE_POLICY_SCOPE_PROPERTY));
320                 } else if (entrySet.getValue() instanceof String) {
321                     targetType.getAnyOf().add(generateMatches(Arrays.asList(entrySet.getValue()),
322                             ToscaDictionary.ID_RESOURCE_POLICY_SCOPE_PROPERTY));
323                 }
324             }
325             if (entrySet.getKey().equals("policyType")) {
326                 LOGGER.debug("Found policyType: {}", entrySet.getValue());
327                 if (entrySet.getValue() instanceof Collection) {
328                     targetType.getAnyOf().add(generateMatches((Collection<Object>) entrySet.getValue(),
329                             ToscaDictionary.ID_RESOURCE_POLICY_TYPE_PROPERTY));
330                 } else if (entrySet.getValue() instanceof String) {
331                     targetType.getAnyOf().add(generateMatches(Arrays.asList(entrySet.getValue()),
332                             ToscaDictionary.ID_RESOURCE_POLICY_TYPE_PROPERTY));
333                 }
334             }
335         }
336
337         return targetType;
338     }
339
340     protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
341         //
342         // This is our outer AnyOf - which is an OR
343         //
344         AnyOfType anyOf = new AnyOfType();
345         for (Object matchable : matchables) {
346             //
347             // Create a match for this
348             //
349             MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
350                     XACML3.ID_FUNCTION_STRING_EQUAL,
351                     matchable.toString(),
352                     XACML3.ID_DATATYPE_STRING,
353                     attributeId,
354                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
355             //
356             // Now create an anyOf (OR)
357             //
358             anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
359         }
360         return anyOf;
361     }
362
363     protected RuleType addObligation(RuleType rule, JSONObject jsonPolicy) {
364         //
365         // Convert the YAML Policy to JSON Object
366         //
367         if (LOGGER.isDebugEnabled()) {
368             LOGGER.debug("JSON Optimization Policy {}{}", System.lineSeparator(), jsonPolicy);
369         }
370         //
371         // Create an AttributeValue for it
372         //
373         AttributeValueType value = new AttributeValueType();
374         value.setDataType(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_DATATYPE.stringValue());
375         value.getContent().add(jsonPolicy.toString());
376         //
377         // Create our AttributeAssignmentExpression where we will
378         // store the contents of the policy in JSON format.
379         //
380         AttributeAssignmentExpressionType expressionType = new AttributeAssignmentExpressionType();
381         expressionType.setAttributeId(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS.stringValue());
382         ObjectFactory factory = new ObjectFactory();
383         expressionType.setExpression(factory.createAttributeValue(value));
384         //
385         // Create an ObligationExpression for it
386         //
387         ObligationExpressionType obligation = new ObligationExpressionType();
388         obligation.setFulfillOn(EffectType.PERMIT);
389         obligation.setObligationId(ToscaDictionary.ID_OBLIGATION_REST_BODY.stringValue());
390         obligation.getAttributeAssignmentExpression().add(expressionType);
391         //
392         // Now we can add it into the rule
393         //
394         ObligationExpressionsType obligations = new ObligationExpressionsType();
395         obligations.getObligationExpression().add(obligation);
396         rule.setObligationExpressions(obligations);
397         return rule;
398     }
399
400 }