7b47ad149865b2a3a4ea56af2e54bf15901d772d
[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.Identifier;
26 import com.att.research.xacml.api.Request;
27 import com.att.research.xacml.api.XACML3;
28 import com.att.research.xacml.std.IdentifierImpl;
29 import com.att.research.xacml.util.XACMLPolicyWriter;
30 import java.io.ByteArrayOutputStream;
31 import java.io.IOException;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.Files;
34 import java.nio.file.Path;
35 import java.nio.file.Paths;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Map.Entry;
44 import lombok.Setter;
45 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
47 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
48 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
49 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
50 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
51 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
52 import org.onap.policy.common.utils.coder.CoderException;
53 import org.onap.policy.common.utils.coder.StandardCoder;
54 import org.onap.policy.common.utils.coder.StandardYamlCoder;
55 import org.onap.policy.models.decisions.concepts.DecisionRequest;
56 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
57 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
58 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
59 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
60 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
61 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
62 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
63 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
64 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
65 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
66 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
67 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationException;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
70
71 /**
72  * This standard matchable translator uses Policy Types that contain "matchable" field in order
73  * to translate policies.
74  *
75  * @author pameladragosh
76  *
77  */
78 public class StdMatchableTranslator  extends StdBaseTranslator {
79
80     private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
81     private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
82
83     private final Map<ToscaPolicyTypeIdentifier, ToscaPolicyType> matchablePolicyTypes = new HashMap<>();
84     @Setter
85     private RestServerParameters apiRestParameters;
86     @Setter
87     private Path pathForData;
88
89     public StdMatchableTranslator() {
90         super();
91     }
92
93     @Override
94     public Request convertRequest(DecisionRequest request) {
95         LOGGER.info("Converting Request {}", request);
96         try {
97             return StdMatchablePolicyRequest.createInstance(request);
98         } catch (XacmlApplicationException e) {
99             LOGGER.error("Failed to convert DecisionRequest: {}", e);
100         }
101         //
102         // TODO throw exception
103         //
104         return null;
105     }
106
107     @Override
108     public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
109         //
110         // Get the TOSCA Policy Type for this policy
111         //
112         Collection<ToscaPolicyType> toscaPolicyTypes = this.getPolicyTypes(toscaPolicy.getTypeIdentifier());
113         //
114         // If we don't have any TOSCA policy types, then we cannot know
115         // which properties are matchable.
116         //
117         if (toscaPolicyTypes.isEmpty()) {
118             throw new ToscaPolicyConversionException(
119                     "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
120         }
121         //
122         // Policy name should be at the root
123         //
124         String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
125         //
126         // Set it as the policy ID
127         //
128         PolicyType newPolicyType = new PolicyType();
129         newPolicyType.setPolicyId(policyName);
130         //
131         // Optional description
132         //
133         newPolicyType.setDescription(toscaPolicy.getDescription());
134         //
135         // There should be a metadata section
136         //
137         fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
138         //
139         // Set the combining rule
140         //
141         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
142         //
143         // Generate the TargetType
144         //
145         newPolicyType.setTarget(new TargetType());
146         //
147         // Now represent the policy as Json
148         //
149         StandardCoder coder = new StandardCoder();
150         String jsonPolicy;
151         try {
152             jsonPolicy = coder.encode(toscaPolicy);
153         } catch (CoderException e) {
154             throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
155         }
156         //
157         // Add it as an obligation
158         //
159         addObligation(newPolicyType, jsonPolicy);
160         //
161         // Now create the Permit Rule for all the
162         // matchable properties.
163         //
164         RuleType rule = new RuleType();
165         rule.setDescription("Default is to PERMIT if the policy matches.");
166         rule.setRuleId(policyName + ":rule");
167         rule.setEffect(EffectType.PERMIT);
168         rule.setTarget(generateTargetType(toscaPolicy.getProperties(), toscaPolicyTypes));
169         rule.setCondition(generateConditionForPolicyType(toscaPolicy.getType()));
170         //
171         // Add the rule to the policy
172         //
173         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
174         //
175         // If a Decision is for a specific policy-type, make sure
176         // there is a rule for it.
177         //
178         rule = new RuleType();
179         rule.setDescription("Match on policy-type " + toscaPolicy.getType());
180         rule.setRuleId(policyName + ":rule:policy-type");
181         rule.setEffect(EffectType.PERMIT);
182         TargetType target = new TargetType();
183         target.getAnyOf().add(this.generateAnyOfForPolicyType(toscaPolicy.getType()));
184         rule.setTarget(target);
185         //
186         // Add the rule to the policy
187         //
188         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
189         //
190         // Log output of the policy
191         //
192         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
193             XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
194             LOGGER.info("{}", os);
195         } catch (IOException e) {
196             LOGGER.error("Failed to create byte array stream", e);
197         }
198         //
199         // Done
200         //
201         return newPolicyType;
202     }
203
204     /**
205      * For generating target type, we scan for matchable properties
206      * and use those to build the policy.
207      *
208      * @param properties Properties section of policy
209      * @param policyTypes Collection of policy Type to find matchable metadata
210      * @return TargetType object
211      */
212     protected TargetType generateTargetType(Map<String, Object> properties, Collection<ToscaPolicyType> policyTypes) {
213         TargetType targetType = new TargetType();
214         //
215         // Iterate the properties
216         //
217         for (Entry<String, Object> entrySet : properties.entrySet()) {
218             //
219             // Find matchable properties
220             //
221             if (isMatchable(entrySet.getKey(), policyTypes)) {
222                 LOGGER.info("Found matchable property {}", entrySet.getValue());
223                 generateMatchable(targetType, entrySet.getKey(), entrySet.getValue());
224             }
225         }
226
227         return targetType;
228     }
229
230     /**
231      * isMatchable - Iterates through available TOSCA Policy Types to determine if a property
232      * should be treated as matchable.
233      *
234      * @param propertyName Name of property
235      * @param policyTypes Collection of TOSCA Policy Types to scan
236      * @return true if matchable
237      */
238     protected boolean isMatchable(String propertyName, Collection<ToscaPolicyType> policyTypes) {
239         for (ToscaPolicyType policyType : policyTypes) {
240             for (Entry<String, ToscaProperty> propertiesEntry : policyType.getProperties().entrySet()) {
241                 if (! propertiesEntry.getKey().equals(propertyName)
242                         || propertiesEntry.getValue().getMetadata() == null) {
243                     continue;
244                 }
245                 for (Entry<String, String> entrySet : propertiesEntry.getValue().getMetadata().entrySet()) {
246                     if ("matchable".equals(entrySet.getKey()) && "true".equals(entrySet.getValue())) {
247                         return true;
248                     }
249                 }
250             }
251         }
252         return false;
253     }
254
255     /**
256      * generateMatchable - Given the object, generates list of MatchType objects and add them
257      * to the TargetType object.
258      *
259      * @param targetType TargetType object to add matches to
260      * @param key Property key
261      * @param value Object is the value - which can be a Collection or single Object
262      * @return TargetType incoming TargetType returned as a convenience
263      */
264     @SuppressWarnings("unchecked")
265     protected TargetType generateMatchable(TargetType targetType, String key, Object value) {
266         if (value instanceof Collection) {
267             AnyOfType anyOf = generateMatches((Collection<Object>) value,
268                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
269             if (! anyOf.getAllOf().isEmpty()) {
270                 targetType.getAnyOf().add(anyOf);
271             }
272         } else {
273             AnyOfType anyOf = generateMatches(Arrays.asList(value),
274                     new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + key));
275             if (! anyOf.getAllOf().isEmpty()) {
276                 targetType.getAnyOf().add(anyOf);
277             }
278         }
279         return targetType;
280     }
281
282     /**
283      * generateMatches - Goes through the collection of objects, creates a MatchType object
284      * for each object and associates it with the given attribute Id. Returns the AnyOfType
285      * object that contains all the generated MatchType objects.
286      *
287      * @param matchables Collection of object to generate MatchType from
288      * @param attributeId Given attribute Id for each MatchType
289      * @return AnyOfType object
290      */
291     protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
292         //
293         // This is our outer AnyOf - which is an OR
294         //
295         AnyOfType anyOf = new AnyOfType();
296         for (Object matchable : matchables) {
297             //
298             // Default to string
299             //
300             Identifier idFunction = XACML3.ID_FUNCTION_STRING_EQUAL;
301             Identifier idDatatype = XACML3.ID_DATATYPE_STRING;
302             //
303             // See if we are another datatype
304             //
305             // We should add datetime support. But to do that we need
306             // probably more metadata to describe how that would be translated.
307             //
308             if (matchable instanceof Integer) {
309                 idFunction = XACML3.ID_FUNCTION_INTEGER_EQUAL;
310                 idDatatype = XACML3.ID_DATATYPE_INTEGER;
311             } else if (matchable instanceof Double) {
312                 idFunction = XACML3.ID_FUNCTION_DOUBLE_EQUAL;
313                 idDatatype = XACML3.ID_DATATYPE_DOUBLE;
314             } else if (matchable instanceof Boolean) {
315                 idFunction = XACML3.ID_FUNCTION_BOOLEAN_EQUAL;
316                 idDatatype = XACML3.ID_DATATYPE_BOOLEAN;
317             }
318             //
319             // Create a match for this
320             //
321             MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
322                     idFunction,
323                     matchable.toString(),
324                     idDatatype,
325                     attributeId,
326                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
327             //
328             // Now create an anyOf (OR)
329             //
330             anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
331         }
332         return anyOf;
333     }
334
335     /**
336      * Get Policy Type definitions. This could be previously loaded, or could be
337      * stored in application path, or may need to be pulled from the API.
338      *
339      *
340      * @param policyTypeId Policy Type Id
341      * @return A list of PolicyTypes
342      */
343     private List<ToscaPolicyType> getPolicyTypes(ToscaPolicyTypeIdentifier policyTypeId) {
344         //
345         // Create identifier from the policy
346         //
347         ToscaPolicyTypeIdentifier typeId = new ToscaPolicyTypeIdentifier(policyTypeId);
348         //
349         // Find the Policy Type
350         //
351         ToscaPolicyType policyType = findPolicyType(typeId);
352         if (policyType == null)  {
353             return Collections.emptyList();
354         }
355         //
356         // Create our return object
357         //
358         List<ToscaPolicyType> listTypes = new ArrayList<>();
359         listTypes.add(policyType);
360         //
361         // Look for parent policy types that could also contain matchable properties
362         //
363         ToscaPolicyType childPolicyType = policyType;
364         while (! childPolicyType.getDerivedFrom().startsWith("tosca.policies.Root")) {
365             //
366             // Create parent policy type id.
367             //
368             // We will have to assume the same version between child and the
369             // parent policy type it derives from.
370             //
371             // Or do we assume 1.0.0?
372             //
373             ToscaPolicyTypeIdentifier parentId = new ToscaPolicyTypeIdentifier(childPolicyType.getDerivedFrom(),
374                     "1.0.0");
375             //
376             // Find the policy type
377             //
378             ToscaPolicyType parentPolicyType = findPolicyType(parentId);
379             if (parentPolicyType == null) {
380                 //
381                 // Probably would be best to throw an exception and
382                 // return nothing back.
383                 //
384                 // But instead we will log a warning
385                 //
386                 LOGGER.warn("Missing parent policy type - proceeding anyway {}", parentId);
387                 //
388                 // Break the loop
389                 //
390                 break;
391             }
392             //
393             // Great save it
394             //
395             listTypes.add(parentPolicyType);
396             //
397             // Move to the next parent
398             //
399             childPolicyType = parentPolicyType;
400         }
401         return listTypes;
402     }
403
404     /**
405      * findPolicyType - given the ToscaPolicyTypeIdentifier, finds it in memory, or
406      * then tries to find it either locally on disk or pull it from the Policy
407      * Lifecycle API the given TOSCA Policy Type.
408      *
409      * @param policyTypeId ToscaPolicyTypeIdentifier to find
410      * @return ToscaPolicyType object. Can be null if failure.
411      */
412     private ToscaPolicyType findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
413         //
414         // Is it loaded in memory?
415         //
416         ToscaPolicyType policyType = this.matchablePolicyTypes.get(policyTypeId);
417         if (policyType == null)  {
418             //
419             // Load the policy
420             //
421             policyType = this.loadPolicyType(policyTypeId);
422         }
423         //
424         // Yep return it
425         //
426         return policyType;
427     }
428
429     /**
430      * loadPolicyType - Tries to load the given ToscaPolicyTypeIdentifier from local
431      * storage. If it does not exist, will then attempt to pull from Policy Lifecycle
432      * API.
433      *
434      * @param policyTypeId ToscaPolicyTypeIdentifier input
435      * @return ToscaPolicyType object. Null if failure.
436      */
437     private ToscaPolicyType loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
438         //
439         // Construct what the file name should be
440         //
441         Path policyTypePath = this.constructLocalFilePath(policyTypeId);
442         //
443         // See if it exists
444         //
445         byte[] bytes;
446         try {
447             //
448             // If it exists locally, read the bytes in
449             //
450             bytes = Files.readAllBytes(policyTypePath);
451         } catch (IOException e) {
452             //
453             // Does not exist locally, so let's GET it from the policy api
454             //
455             LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
456             //
457             // So let's pull it from API REST call and save it locally
458             //
459             return this.pullPolicyType(policyTypeId, policyTypePath);
460         }
461         //
462         // Success - we have read locally the policy type. Now bring it into our
463         // return object.
464         //
465         LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
466         try {
467             ToscaServiceTemplate serviceTemplate = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
468                     ToscaServiceTemplate.class);
469             JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
470             jtst.fromAuthorative(serviceTemplate);
471             ToscaServiceTemplate completedJtst = jtst.toAuthorative();
472             //
473             // Search for our Policy Type, there really only should be one but
474             // this is returned as a map.
475             //
476             for ( Entry<String, ToscaPolicyType> entrySet : completedJtst.getPolicyTypes().entrySet()) {
477                 ToscaPolicyType entryPolicyType = entrySet.getValue();
478                 if (policyTypeId.getName().equals(entryPolicyType.getName())
479                         && policyTypeId.getVersion().equals(entryPolicyType.getVersion())) {
480                     LOGGER.info("Found existing local policy type {} {}", entryPolicyType.getName(),
481                             entryPolicyType.getVersion());
482                     //
483                     // Just simply return the policy type right here
484                     //
485                     return entryPolicyType;
486                 } else {
487                     LOGGER.warn("local policy type contains different name version {} {}", entryPolicyType.getName(),
488                             entryPolicyType.getVersion());
489                 }
490             }
491             //
492             // This would be an error, if the file stored does not match what its supposed to be
493             //
494             LOGGER.error("Existing policy type file does not contain right name and version");
495         } catch (CoderException e) {
496             LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
497         }
498         //
499         // Hopefully we never get here
500         //
501         LOGGER.error("Failed to find/load policy type {}", policyTypeId);
502         return null;
503     }
504
505     /**
506      * pullPolicyType - pulls the given ToscaPolicyTypeIdentifier from the Policy Lifecycle API.
507      * If successful, will store it locally given the policyTypePath.
508      *
509      * @param policyTypeId ToscaPolicyTypeIdentifier
510      * @param policyTypePath Path object to store locally
511      * @return ToscaPolicyType object. Null if failure.
512      */
513     private synchronized ToscaPolicyType pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId, Path policyTypePath) {
514         //
515         // This is what we return
516         //
517         ToscaPolicyType policyType = null;
518         try {
519             PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
520
521             policyType = api.getPolicyType(policyTypeId);
522         } catch (PolicyApiException e) {
523             LOGGER.error("Failed to make API call", e);
524             LOGGER.error("parameters: {} ", this.apiRestParameters);
525             return null;
526         }
527         //
528         // Store it locally
529         //
530         try {
531             standardYamlCoder.encode(policyTypePath.toFile(), policyType);
532         } catch (CoderException e) {
533             LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
534         }
535         //
536         // Done return the policy type
537         //
538         return policyType;
539     }
540
541     /**
542      * constructLocalFilePath - common method to ensure the name of the local file for the
543      * policy type is the same.
544      *
545      * @param policyTypeId ToscaPolicyTypeIdentifier
546      * @return Path object
547      */
548     private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
549         return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
550                 + policyTypeId.getVersion() + ".yaml");
551     }
552 }