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