1c69c7a6fc6b189cb624291faf14d3b0347f363a
[policy/xacml-pdp.git] /
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.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;
50 import java.util.Map;
51 import java.util.Map.Entry;
52 import lombok.Setter;
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;
83
84 /**
85  * This standard matchable translator uses Policy Types that contain "matchable" field in order
86  * to translate policies.
87  *
88  * @author pameladragosh
89  *
90  */
91 public class StdMatchableTranslator implements ToscaPolicyTranslator {
92
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();
96
97     private final Map<ToscaPolicyTypeIdentifier, ToscaPolicyType> matchablePolicyTypes = new HashMap<>();
98     @Setter
99     private RestServerParameters apiRestParameters;
100     @Setter
101     private Path pathForData;
102
103     public StdMatchableTranslator() {
104         super();
105     }
106
107     @Override
108     public Request convertRequest(DecisionRequest request) {
109         LOGGER.info("Converting Request {}", request);
110         try {
111             return StdMatchablePolicyRequest.createInstance(request);
112         } catch (IllegalArgumentException | IllegalAccessException | DataTypeException e) {
113             LOGGER.error("Failed to convert DecisionRequest: {}", e);
114         }
115         //
116         // TODO throw exception
117         //
118         return null;
119     }
120
121     @Override
122     public DecisionResponse convertResponse(Response xacmlResponse) {
123         LOGGER.info("Converting Response {}", xacmlResponse);
124         DecisionResponse decisionResponse = new DecisionResponse();
125         //
126         // Setup policies
127         //
128         decisionResponse.setPolicies(new HashMap<>());
129         //
130         // Iterate through all the results
131         //
132         for (Result xacmlResult : xacmlResponse.getResults()) {
133             //
134             // Check the result
135             //
136             if (xacmlResult.getDecision() == Decision.PERMIT) {
137                 //
138                 // Go through obligations
139                 //
140                 scanObligations(xacmlResult.getObligations(), decisionResponse);
141             }
142             if (xacmlResult.getDecision() == Decision.DENY
143                     || xacmlResult.getDecision() == Decision.INDETERMINATE) {
144                 //
145                 // TODO we have to return an ErrorResponse object instead
146                 //
147                 decisionResponse.setStatus("A better error message");
148             }
149         }
150
151         return decisionResponse;
152     }
153
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);
159                 //
160                 // We care about the content attribute
161                 //
162                 if (ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS
163                         .equals(assignment.getAttributeId())) {
164                     //
165                     // The contents are in Json form
166                     //
167                     Object stringContents = assignment.getAttributeValue().getValue();
168                     if (LOGGER.isInfoEnabled()) {
169                         LOGGER.info("Policy contents: {}{}", System.lineSeparator(), stringContents);
170                     }
171                     //
172                     // Let's parse it into a map using Gson
173                     //
174                     Gson gson = new Gson();
175                     @SuppressWarnings("unchecked")
176                     Map<String, Object> result = gson.fromJson(stringContents.toString() ,Map.class);
177                     //
178                     // Find the metadata section
179                     //
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);
184                     } else {
185                         LOGGER.error("Missing metadata section in policy contained in obligation.");
186                     }
187                 }
188             }
189         }
190
191     }
192
193     @Override
194     public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
195         //
196         // Get the TOSCA Policy Type for this policy
197         //
198         Collection<ToscaPolicyType> policyTypes = this.getPolicyTypes(toscaPolicy.getTypeIdentifier());
199         //
200         // If we don't have any policy types, then we cannot know
201         // which properties are matchable.
202         //
203         if (policyTypes.isEmpty()) {
204             throw new ToscaPolicyConversionException(
205                     "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
206         }
207         //
208         // Policy name should be at the root
209         //
210         String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
211         //
212         // Set it as the policy ID
213         //
214         PolicyType newPolicyType = new PolicyType();
215         newPolicyType.setPolicyId(policyName);
216         //
217         // Optional description
218         //
219         newPolicyType.setDescription(toscaPolicy.getDescription());
220         //
221         // There should be a metadata section
222         //
223         this.fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
224         //
225         // Set the combining rule
226         //
227         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
228         //
229         // Generate the TargetType
230         //
231         newPolicyType.setTarget(generateTargetType(toscaPolicy.getProperties(), policyTypes));
232         //
233         // Now create the Permit Rule
234         // No target since the policy has a target
235         // With obligations.
236         //
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());
242         //
243         // Now represent the policy as Json
244         //
245         StandardCoder coder = new StandardCoder();
246         String jsonPolicy;
247         try {
248             jsonPolicy = coder.encode(toscaPolicy);
249         } catch (CoderException e) {
250             throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
251         }
252         addObligation(rule, jsonPolicy);
253         //
254         // Add the rule to the policy
255         //
256         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
257         //
258         // Return our new policy
259         //
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);
265         }
266         return newPolicyType;
267     }
268
269     /**
270      * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
271      *
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
276      */
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");
281         } else {
282             //
283             // Do nothing here - the XACML PolicyId is used from TOSCA Policy Name field
284             //
285         }
286         if (! map.containsKey("policy-version")) {
287             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-version");
288         } else {
289             //
290             // Add in the Policy Version
291             //
292             policy.setVersion(map.get("policy-version"));
293         }
294         return policy;
295     }
296
297     /**
298      * For generating target type, we are scan for matchable properties
299      * and use those to build the policy.
300      *
301      * @param properties Properties section of policy
302      * @param policyTypes Collection of policy Type to find matchable metadata
303      * @return TargetType object
304      */
305     @SuppressWarnings("unchecked")
306     protected TargetType generateTargetType(Map<String, Object> properties, Collection<ToscaPolicyType> policyTypes) {
307         TargetType targetType = new TargetType();
308         //
309         // Iterate the properties
310         //
311         for (Entry<String, Object> entrySet : properties.entrySet()) {
312             //
313             // Find matchable properties
314             //
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);
322                     }
323                 } else {
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);
328                     }
329                 }
330             }
331         }
332
333         return targetType;
334     }
335
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) {
341                     continue;
342                 }
343                 for (Entry<String, String> entrySet : propertiesEntry.getValue().getMetadata().entrySet()) {
344                     if (entrySet.getKey().equals("matchable") && entrySet.getValue().equals("true")) {
345                         return true;
346                     }
347                 }
348             }
349         }
350         return false;
351     }
352
353     protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
354         //
355         // This is our outer AnyOf - which is an OR
356         //
357         AnyOfType anyOf = new AnyOfType();
358         for (Object matchable : matchables) {
359             //
360             // Default to string
361             //
362             Identifier idFunction = XACML3.ID_FUNCTION_STRING_EQUAL;
363             Identifier idDatatype = XACML3.ID_DATATYPE_STRING;
364             //
365             // See if we are another datatype
366             //
367             // TODO We should add datetime support. But to do that we need
368             // probably more metadata to describe how that would be translated.
369             //
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;
379             }
380             //
381             // Create a match for this
382             //
383             MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
384                     idFunction,
385                     matchable.toString(),
386                     idDatatype,
387                     attributeId,
388                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
389             //
390             // Now create an anyOf (OR)
391             //
392             anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
393         }
394         return anyOf;
395     }
396
397     protected RuleType addObligation(RuleType rule, String jsonPolicy) {
398         //
399         // Convert the YAML Policy to JSON Object
400         //
401         if (LOGGER.isInfoEnabled()) {
402             LOGGER.info("JSON Optimization Policy {}{}", System.lineSeparator(), jsonPolicy);
403         }
404         //
405         // Create an AttributeValue for it
406         //
407         AttributeValueType value = new AttributeValueType();
408         value.setDataType(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_DATATYPE.stringValue());
409         value.getContent().add(jsonPolicy);
410         //
411         // Create our AttributeAssignmentExpression where we will
412         // store the contents of the policy in JSON format.
413         //
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));
418         //
419         // Create an ObligationExpression for it
420         //
421         ObligationExpressionType obligation = new ObligationExpressionType();
422         obligation.setFulfillOn(EffectType.PERMIT);
423         obligation.setObligationId(ToscaDictionary.ID_OBLIGATION_REST_BODY.stringValue());
424         obligation.getAttributeAssignmentExpression().add(expressionType);
425         //
426         // Now we can add it into the rule
427         //
428         ObligationExpressionsType obligations = new ObligationExpressionsType();
429         obligations.getObligationExpression().add(obligation);
430         rule.setObligationExpressions(obligations);
431         return rule;
432     }
433
434
435     /**
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.
438      *
439      *
440      * @param policyTypeId Policy Type Id
441      * @return A list of PolicyTypes
442      */
443     private List<ToscaPolicyType> getPolicyTypes(ToscaPolicyTypeIdentifier policyTypeId) {
444         //
445         // Create identifier from the policy
446         //
447         ToscaPolicyTypeIdentifier typeId = new ToscaPolicyTypeIdentifier(policyTypeId);
448         //
449         // Find the Policy Type
450         //
451         ToscaPolicyType policyType = findPolicyType(typeId);
452         if (policyType == null)  {
453             return Collections.emptyList();
454         }
455         //
456         // Create our return object
457         //
458         List<ToscaPolicyType> listTypes = new ArrayList<>();
459         listTypes.add(policyType);
460         //
461         // Look for parent policy types that could also contain matchable properties
462         //
463         ToscaPolicyType childPolicyType = policyType;
464         while (! childPolicyType.getDerivedFrom().startsWith("tosca.policies.Root")) {
465             //
466             // Create parent policy type id.
467             //
468             // We will have to assume the same version between child and the
469             // parent policy type it derives from.
470             //
471             // Or do we assume 1.0.0?
472             //
473             String strDerivedFrom = childPolicyType.getDerivedFrom();
474             //
475             // Hack that fixes policy/models appending 0.0.0 to the derivedFrom name
476             //
477             if (strDerivedFrom.endsWith("0.0.0")) {
478                 strDerivedFrom = strDerivedFrom.substring(0, strDerivedFrom.length() - "0.0.0".length() - 1);
479             }
480             ToscaPolicyTypeIdentifier parentId = new ToscaPolicyTypeIdentifier(strDerivedFrom, "1.0.0");
481             //
482             // Find the policy type
483             //
484             ToscaPolicyType parentPolicyType = findPolicyType(parentId);
485             if (parentPolicyType == null) {
486                 //
487                 // Probably would be best to throw an exception and
488                 // return nothing back.
489                 //
490                 // But instead we will log a warning
491                 //
492                 LOGGER.warn("Missing parent policy type - proceeding anyway {}", parentId);
493                 //
494                 // Break the loop
495                 //
496                 break;
497             }
498             //
499             // Great save it
500             //
501             listTypes.add(parentPolicyType);
502             //
503             // Move to the next parent
504             //
505             childPolicyType = parentPolicyType;
506         }
507
508
509         return listTypes;
510     }
511
512     private ToscaPolicyType findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
513         //
514         // Is it loaded in memory?
515         //
516         ToscaPolicyType policyType = this.matchablePolicyTypes.get(policyTypeId);
517         if (policyType == null)  {
518             //
519             // Load the policy
520             //
521             policyType = this.loadPolicyType(policyTypeId);
522         }
523         //
524         // Yep return it
525         //
526         return policyType;
527     }
528
529     private ToscaPolicyType loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
530         //
531         // Construct what the file name should be
532         //
533         Path policyTypePath = this.constructLocalFilePath(policyTypeId);
534         //
535         // See if it exists
536         //
537         byte[] bytes;
538         try {
539             //
540             // If it exists locally, read the bytes in
541             //
542             bytes = Files.readAllBytes(policyTypePath);
543         } catch (IOException e) {
544             //
545             // Does not exist locally, so let's GET it from the policy api
546             //
547             LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
548             //
549             // So let's pull it from API REST call and save it locally
550             //
551             return this.pullPolicyType(policyTypeId, policyTypePath);
552         }
553         LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
554         try {
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();
560             //
561             // Search for our Policy Type, there really only should be one but
562             // this is returned as a map.
563             //
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());
570                     //
571                     // Just simply return the policy type right here
572                     //
573                     return entryPolicyType;
574                 } else {
575                     LOGGER.warn("local policy type contains different name version {} {}", entryPolicyType.getName(),
576                             entryPolicyType.getVersion());
577                 }
578             }
579             //
580             // This would be an error, if the file stored does not match what its supposed to be
581             //
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);
585         }
586         //
587         // Hopefully we never get here
588         //
589         LOGGER.error("Failed to find/load policy type {}", policyTypeId);
590         return null;
591     }
592
593     private synchronized ToscaPolicyType pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId, Path policyTypePath) {
594         //
595         // This is what we return
596         //
597         ToscaPolicyType policyType = null;
598         try {
599             PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
600
601             policyType = api.getPolicyType(policyTypeId);
602         } catch (PolicyApiException e) {
603             LOGGER.error("Failed to make API call", e);
604             LOGGER.error("parameters: {} ", this.apiRestParameters);
605             return null;
606         }
607         //
608         // Store it locally
609         //
610         try {
611             standardCoder.encode(policyTypePath.toFile(), policyType);
612         } catch (CoderException e) {
613             LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
614         }
615         //
616         // Done return the policy type
617         //
618         return policyType;
619     }
620
621     private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
622         return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
623                 + policyTypeId.getVersion() + ".json");
624     }
625 }