1a5e9dd854d0bb4ee7821de39eef15c0cb3e4c73
[policy/xacml-pdp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2020 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.Identifier;
27 import com.att.research.xacml.api.Obligation;
28 import com.att.research.xacml.api.Request;
29 import com.att.research.xacml.api.XACML3;
30 import com.att.research.xacml.std.IdentifierImpl;
31 import com.att.research.xacml.util.XACMLPolicyWriter;
32 import java.io.ByteArrayOutputStream;
33 import java.io.IOException;
34 import java.nio.charset.StandardCharsets;
35 import java.nio.file.Files;
36 import java.nio.file.Path;
37 import java.nio.file.Paths;
38 import java.util.Collection;
39 import java.util.HashMap;
40 import java.util.LinkedHashMap;
41 import java.util.LinkedList;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Map.Entry;
45 import lombok.Setter;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AllOfType;
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.EffectType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
51 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
52 import org.apache.commons.lang3.tuple.Pair;
53 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
54 import org.onap.policy.common.utils.coder.CoderException;
55 import org.onap.policy.common.utils.coder.StandardCoder;
56 import org.onap.policy.common.utils.coder.StandardYamlCoder;
57 import org.onap.policy.models.decisions.concepts.DecisionRequest;
58 import org.onap.policy.models.decisions.concepts.DecisionResponse;
59 import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
60 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
61 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
62 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
63 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
64 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
65 import org.onap.policy.pdp.xacml.application.common.OnapObligation;
66 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
67 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
68 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
69 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
70 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
71 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationException;
72 import org.onap.policy.pdp.xacml.application.common.matchable.MatchableCallback;
73 import org.onap.policy.pdp.xacml.application.common.matchable.MatchablePolicyType;
74 import org.onap.policy.pdp.xacml.application.common.matchable.MatchableProperty;
75 import org.slf4j.Logger;
76 import org.slf4j.LoggerFactory;
77
78 /**
79  * This standard matchable translator uses Policy Types that contain "matchable" field in order
80  * to translate policies.
81  *
82  * @author pameladragosh
83  *
84  */
85 public class StdMatchableTranslator  extends StdBaseTranslator implements MatchableCallback {
86
87     private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
88     private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
89
90     private final Map<ToscaPolicyTypeIdentifier, ToscaServiceTemplate> matchablePolicyTypes = new HashMap<>();
91     private final Map<ToscaPolicyTypeIdentifier, MatchablePolicyType> matchableCache = new HashMap<>();
92
93     @Setter
94     private RestServerParameters apiRestParameters;
95     @Setter
96     private Path pathForData;
97
98     public StdMatchableTranslator() {
99         super();
100     }
101
102     @Override
103     public Request convertRequest(DecisionRequest request) throws ToscaPolicyConversionException {
104         LOGGER.info("Converting Request {}", request);
105         try {
106             return StdMatchablePolicyRequest.createInstance(request);
107         } catch (XacmlApplicationException e) {
108             throw new ToscaPolicyConversionException("Failed to convert DecisionRequest", e);
109         }
110     }
111
112     /**
113      * scanObligations - scans the list of obligations and make appropriate method calls to process
114      * obligations.
115      *
116      * @param obligations Collection of obligation objects
117      * @param decisionResponse DecisionResponse object used to store any results from obligations.
118      */
119     @Override
120     protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
121         //
122         // Implementing a crude "closest match" on the results, which means we will strip out
123         // any policies that has the lower weight than any of the others.
124         //
125         // Most likely these are "default" policies with a weight of zero, but not always.
126         //
127         // It is possible to have multiple policies with an equal weight, that is desired.
128         //
129         // So we need to track each policy type separately and the weights for each policy.
130         //
131         // policy-type -> weight -> List({policy-id, policy-content}, {policy-id, policy-content})
132         //
133         Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches = new LinkedHashMap<>();
134         //
135         // Now scan the list of obligations
136         //
137         for (Obligation obligation : obligations) {
138             Identifier obligationId = obligation.getId();
139             LOGGER.info("Obligation: {}", obligationId);
140             if (ToscaDictionary.ID_OBLIGATION_REST_BODY.equals(obligationId)) {
141                 scanClosestMatchObligation(closestMatches, obligation);
142             } else {
143                 LOGGER.warn("Unsupported Obligation Id {}", obligation.getId());
144             }
145         }
146         //
147         // Now add all the policies to the DecisionResponse
148         //
149         closestMatches.forEach((thePolicyType, weightMap) ->
150             weightMap.forEach((weight, policies) ->
151                 policies.forEach(policy -> {
152                     LOGGER.info("Policy {}", policy);
153                     decisionResponse.getPolicies().put(policy.getLeft(), policy.getRight());
154                 })
155             )
156         );
157     }
158
159     @Override
160     protected void scanAdvice(Collection<Advice> advice, DecisionResponse decisionResponse) {
161         LOGGER.warn("scanAdvice not supported by {}", this.getClass());
162     }
163
164     /**
165      * scanClosestMatchObligation - scans for the obligation specifically holding policy
166      * contents and their details.
167      *
168      * @param closestMatches Map holding the current set of highest weight policy types
169      * @param Obligation Obligation object
170      */
171     protected void scanClosestMatchObligation(
172             Map<String, Map<Integer, List<Pair<String, Map<String, Object>>>>> closestMatches, Obligation obligation) {
173         //
174         // Create our OnapObligation object
175         //
176         OnapObligation onapObligation = new OnapObligation(obligation);
177         //
178         // All 4 *should* be there
179         //
180         if (onapObligation.getPolicyId() == null || onapObligation.getPolicyContent() == null
181                 || onapObligation.getPolicyType() == null || onapObligation.getWeight() == null) {
182             LOGGER.error("Missing an expected attribute in obligation.");
183             return;
184         }
185         //
186         // Save the values
187         //
188         String policyId = onapObligation.getPolicyId();
189         String policyType = onapObligation.getPolicyType();
190         Map<String, Object> policyContent = onapObligation.getPolicyContentAsMap();
191         int policyWeight = onapObligation.getWeight();
192         //
193         // If the Policy Type exists, get the weight map.
194         //
195         Map<Integer, List<Pair<String, Map<String, Object>>>> weightMap = closestMatches.get(policyType);
196         if (weightMap != null) {
197             //
198             // Only need to check first one - as we will ensure there is only one weight
199             //
200             Entry<Integer, List<Pair<String, Map<String, Object>>>> firstEntry =
201                     weightMap.entrySet().iterator().next();
202             if (policyWeight < firstEntry.getKey()) {
203                 //
204                 // Existing policies have a greater weight, so we will not add it
205                 //
206                 LOGGER.info("{} is lesser weight {} than current policies, will not return it", policyWeight,
207                         firstEntry.getKey());
208             } else if (firstEntry.getKey().equals(policyWeight)) {
209                 //
210                 // Same weight - we will add it
211                 //
212                 LOGGER.info("Same weight {}, adding policy", policyWeight);
213                 firstEntry.getValue().add(Pair.of(policyId, policyContent));
214             } else {
215                 //
216                 // The weight is greater, so we need to remove the other policies
217                 // and point to this one.
218                 //
219                 LOGGER.info("New policy has greater weight {}, replacing {}", policyWeight, firstEntry.getKey());
220                 List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
221                 listPolicies.add(Pair.of(policyId, policyContent));
222                 weightMap.clear();
223                 weightMap.put(policyWeight, listPolicies);
224             }
225         } else {
226             //
227             // Create a new entry
228             //
229             LOGGER.info("New entry {} weight {}", policyType, policyWeight);
230             List<Pair<String, Map<String, Object>>> listPolicies = new LinkedList<>();
231             listPolicies.add(Pair.of(policyId, policyContent));
232             Map<Integer, List<Pair<String, Map<String, Object>>>> newWeightMap = new LinkedHashMap<>();
233             newWeightMap.put(policyWeight, listPolicies);
234             closestMatches.put(policyType, newWeightMap);
235         }
236     }
237
238     @Override
239     public Object convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
240         //
241         // Get the TOSCA Policy Type for this policy
242         //
243         ToscaServiceTemplate toscaPolicyTypeTemplate = this.findPolicyType(toscaPolicy.getTypeIdentifier());
244         //
245         // If we don't have any TOSCA policy types, then we cannot know
246         // which properties are matchable.
247         //
248         if (toscaPolicyTypeTemplate == null) {
249             throw new ToscaPolicyConversionException(
250                     "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
251         }
252         //
253         // Policy name should be at the root
254         //
255         String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
256         //
257         // Set it as the policy ID
258         //
259         PolicyType newPolicyType = new PolicyType();
260         newPolicyType.setPolicyId(policyName);
261         //
262         // Optional description
263         //
264         newPolicyType.setDescription(toscaPolicy.getDescription());
265         //
266         // There should be a metadata section
267         //
268         fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
269         //
270         // Set the combining rule
271         //
272         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
273         //
274         // Generate the TargetType - the policy should not be evaluated
275         // unless all the matchable properties it cares about are matched.
276         //
277         Pair<TargetType, Integer> pairGenerated = generateTargetType(toscaPolicy, toscaPolicyTypeTemplate);
278         newPolicyType.setTarget(pairGenerated.getLeft());
279         //
280         // Now represent the policy as Json
281         //
282         StandardCoder coder = new StandardCoder();
283         String jsonPolicy;
284         try {
285             jsonPolicy = coder.encode(toscaPolicy);
286         } catch (CoderException e) {
287             throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
288         }
289         //
290         // Add it as an obligation
291         //
292         addObligation(newPolicyType, policyName, jsonPolicy, pairGenerated.getRight(), toscaPolicy.getType());
293         //
294         // Now create the Permit Rule.
295         //
296         RuleType rule = new RuleType();
297         rule.setDescription("Default is to PERMIT if the policy matches.");
298         rule.setRuleId(policyName + ":rule");
299         rule.setEffect(EffectType.PERMIT);
300         rule.setTarget(new TargetType());
301         //
302         // The rule contains the Condition which adds logic for
303         // optional policy-type filtering.
304         //
305         rule.setCondition(generateConditionForPolicyType(toscaPolicy.getType()));
306         //
307         // Add the rule to the policy
308         //
309         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
310         //
311         // Log output of the policy
312         //
313         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
314             XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
315             LOGGER.info("{}", os);
316         } catch (IOException e) {
317             LOGGER.error("Failed to create byte array stream", e);
318         }
319         //
320         // Done
321         //
322         return newPolicyType;
323     }
324
325     @Override
326     public ToscaPolicyType retrievePolicyType(String derivedFrom) {
327         ToscaServiceTemplate template = this.findPolicyType(new ToscaPolicyTypeIdentifier(derivedFrom, "1.0.0"));
328         if (template == null) {
329             LOGGER.error("Could not retrieve Policy Type {}", derivedFrom);
330             return null;
331         }
332         return template.getPolicyTypes().get(derivedFrom);
333     }
334
335     @Override
336     public ToscaDataType retrieveDataType(String datatype) {
337         //
338         // Our outer class is not storing the current template being scanned
339         //
340         LOGGER.error("this retrieveDataType should not be called.");
341         return null;
342     }
343
344     private class MyMatchableCallback implements MatchableCallback {
345         private StdMatchableTranslator translator;
346         private ToscaServiceTemplate template;
347
348         public MyMatchableCallback(StdMatchableTranslator translator, ToscaServiceTemplate template) {
349             this.translator = translator;
350             this.template = template;
351         }
352
353         @Override
354         public ToscaPolicyType retrievePolicyType(String derivedFrom) {
355             ToscaPolicyType policyType = this.template.getPolicyTypes().get(derivedFrom);
356             if (policyType != null) {
357                 return policyType;
358             }
359             return translator.retrievePolicyType(derivedFrom);
360         }
361
362         @Override
363         public ToscaDataType retrieveDataType(String datatype) {
364             return this.template.getDataTypes().get(datatype);
365         }
366
367     }
368
369     /**
370      * For generating target type, we scan for matchable properties
371      * and use those to build the policy.
372      *
373      * @param properties Properties section of policy
374      * @param policyTypes Collection of policy Type to find matchable metadata
375      * @return {@code Pair<TargetType, Integer>} Returns a TargetType and a Total Weight of matchables.
376      */
377     protected Pair<TargetType, Integer> generateTargetType(ToscaPolicy policy, ToscaServiceTemplate template) {
378         //
379         // Our return object
380         //
381         TargetType target = new TargetType();
382         //
383         // See if we have a matchable in the cache already
384         //
385         MatchablePolicyType matchablePolicyType = matchableCache.get(policy.getTypeIdentifier());
386         //
387         // If not found, create one
388         //
389         if (matchablePolicyType == null) {
390             //
391             // Our callback
392             //
393             MyMatchableCallback myCallback = new MyMatchableCallback(this, template);
394             //
395             // Create the matchable
396             //
397             matchablePolicyType = new MatchablePolicyType(
398                     template.getPolicyTypes().get(policy.getType()), myCallback);
399             //
400             // Cache it
401             //
402             matchableCache.put(policy.getTypeIdentifier(), matchablePolicyType);
403         }
404         //
405         // Fill in the target type with potential matchables
406         //
407         try {
408             fillTargetTypeWithMatchables(target, matchablePolicyType, policy.getProperties());
409         } catch (ToscaPolicyConversionException e) {
410             LOGGER.error("Could not generate target type", e);
411         }
412         //
413         // There may be a case for default policies there is no weight - need to clean
414         // up the target then else PDP will report bad policy missing AnyOf
415         //
416         int weight = calculateWeight(target);
417         LOGGER.debug("Weight is {} for policy {}", weight, policy.getName());
418         //
419         // Assume the number of AllOf's is the weight for now
420         //
421         return Pair.of(target, weight);
422     }
423
424     @SuppressWarnings("unchecked")
425     protected void fillTargetTypeWithMatchables(TargetType target, MatchablePolicyType matchablePolicyType,
426             Map<String, Object> properties) throws ToscaPolicyConversionException {
427         for (Entry<String, Object> entrySet : properties.entrySet()) {
428             String propertyName = entrySet.getKey();
429             Object propertyValue = entrySet.getValue();
430             MatchableProperty matchable = matchablePolicyType.get(propertyName);
431             if (matchable != null) {
432                 //
433                 // Construct attribute id
434                 //
435                 Identifier id = new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + propertyName);
436                 //
437                 // Depending on what type it is, add it into the target
438                 //
439                 ToscaPolicyTranslatorUtils.buildAndAppendTarget(target,
440                         matchable.getType().generate(propertyValue, id));
441
442                 continue;
443             }
444             //
445             // Here is the special case where we look for a Collection of values that may
446             // contain potential matchables
447             //
448             if (propertyValue instanceof List) {
449                 for (Object listValue : ((List<?>) propertyValue)) {
450                     if (listValue instanceof Map) {
451                         fillTargetTypeWithMatchables(target, matchablePolicyType, (Map<String, Object>) listValue);
452                     }
453                 }
454             } else if (propertyValue instanceof Map) {
455                 fillTargetTypeWithMatchables(target, matchablePolicyType, (Map<String, Object>) propertyValue);
456             }
457         }
458     }
459
460     protected int calculateWeight(TargetType target) {
461         int weight = 0;
462         for (AnyOfType anyOf : target.getAnyOf()) {
463             for (AllOfType allOf : anyOf.getAllOf()) {
464                 weight += allOf.getMatch().size();
465             }
466         }
467
468         return weight;
469     }
470
471     /**
472      * findPolicyType - given the ToscaPolicyTypeIdentifier, finds it in memory, or
473      * then tries to find it either locally on disk or pull it from the Policy
474      * Lifecycle API the given TOSCA Policy Type.
475      *
476      * @param policyTypeId ToscaPolicyTypeIdentifier to find
477      * @return ToscaPolicyType object. Can be null if failure.
478      */
479     private ToscaServiceTemplate findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
480         //
481         // Is it loaded in memory?
482         //
483         ToscaServiceTemplate policyTemplate = this.matchablePolicyTypes.get(policyTypeId);
484         if (policyTemplate == null)  {
485             //
486             // Load the policy
487             //
488             policyTemplate = this.loadPolicyType(policyTypeId);
489             //
490             // Save it
491             //
492             if (policyTemplate != null) {
493                 this.matchablePolicyTypes.put(policyTypeId, policyTemplate);
494             }
495         }
496         //
497         // Yep return it
498         //
499         return policyTemplate;
500     }
501
502     /**
503      * loadPolicyType - Tries to load the given ToscaPolicyTypeIdentifier from local
504      * storage. If it does not exist, will then attempt to pull from Policy Lifecycle
505      * API.
506      *
507      * @param policyTypeId ToscaPolicyTypeIdentifier input
508      * @return ToscaPolicyType object. Null if failure.
509      */
510     private ToscaServiceTemplate loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
511         //
512         // Construct what the file name should be
513         //
514         Path policyTypePath = this.constructLocalFilePath(policyTypeId);
515         //
516         // See if it exists
517         //
518         byte[] bytes;
519         try {
520             //
521             // If it exists locally, read the bytes in
522             //
523             bytes = Files.readAllBytes(policyTypePath);
524         } catch (IOException e) {
525             //
526             // Does not exist locally, so let's GET it from the policy api
527             //
528             LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
529             //
530             // So let's pull it from API REST call and save it locally
531             //
532             return this.pullPolicyType(policyTypeId, policyTypePath);
533         }
534         //
535         // Success - we have read locally the policy type. Now bring it into our
536         // return object.
537         //
538         LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
539         try {
540             //
541             // Decode the template
542             //
543             ToscaServiceTemplate template = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
544                     ToscaServiceTemplate.class);
545             //
546             // Ensure all the fields are setup correctly
547             //
548             JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
549             jtst.fromAuthorative(template);
550             return jtst.toAuthorative();
551         } catch (CoderException e) {
552             LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
553         }
554         //
555         // Hopefully we never get here
556         //
557         LOGGER.error("Failed to find/load policy type {}", policyTypeId);
558         return null;
559     }
560
561     /**
562      * pullPolicyType - pulls the given ToscaPolicyTypeIdentifier from the Policy Lifecycle API.
563      * If successful, will store it locally given the policyTypePath.
564      *
565      * @param policyTypeId ToscaPolicyTypeIdentifier
566      * @param policyTypePath Path object to store locally
567      * @return ToscaPolicyType object. Null if failure.
568      */
569     private synchronized ToscaServiceTemplate pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId,
570             Path policyTypePath) {
571         //
572         // This is what we return
573         //
574         ToscaServiceTemplate policyTemplate = null;
575         try {
576             PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
577
578             policyTemplate = api.getPolicyType(policyTypeId);
579         } catch (PolicyApiException e) {
580             LOGGER.error("Failed to make API call", e);
581             LOGGER.error("parameters: {} ", this.apiRestParameters);
582             return null;
583         }
584         LOGGER.info("Successfully pulled {}", policyTypeId);
585         //
586         // Store it locally
587         //
588         try {
589             standardYamlCoder.encode(policyTypePath.toFile(), policyTemplate);
590         } catch (CoderException e) {
591             LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
592         }
593         //
594         // Done return the policy type
595         //
596         return policyTemplate;
597     }
598
599     /**
600      * constructLocalFilePath - common method to ensure the name of the local file for the
601      * policy type is the same.
602      *
603      * @param policyTypeId ToscaPolicyTypeIdentifier
604      * @return Path object
605      */
606     private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
607         return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
608                 + policyTypeId.getVersion() + ".yaml");
609     }
610 }