Consolidate common translatable code some sonar
[policy/xacml-pdp.git] / applications / common / src / main / java / org / onap / policy / pdp / xacml / application / common / std / StdMatchableTranslator.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.pdp.xacml.application.common.std;
24
25 import com.att.research.xacml.api.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> policyTypes = this.getPolicyTypes(toscaPolicy.getTypeIdentifier());
113         //
114         // If we don't have any policy types, then we cannot know
115         // which properties are matchable.
116         //
117         if (policyTypes.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         this.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(generateTargetType(toscaPolicy.getProperties(), policyTypes));
146         //
147         // Now create the Permit Rule
148         // No target since the policy has a target
149         // With obligations.
150         //
151         RuleType rule = new RuleType();
152         rule.setDescription("Default is to PERMIT if the policy matches.");
153         rule.setRuleId(policyName + ":rule");
154         rule.setEffect(EffectType.PERMIT);
155         rule.setTarget(new TargetType());
156         //
157         // Now represent the policy as Json
158         //
159         StandardCoder coder = new StandardCoder();
160         String jsonPolicy;
161         try {
162             jsonPolicy = coder.encode(toscaPolicy);
163         } catch (CoderException e) {
164             throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
165         }
166         addObligation(rule, jsonPolicy);
167         //
168         // Add the rule to the policy
169         //
170         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
171         //
172         // Return our new policy
173         //
174         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
175             XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
176             LOGGER.info("{}", os);
177         } catch (IOException e) {
178             LOGGER.error("Failed to create byte array stream", e);
179         }
180         return newPolicyType;
181     }
182
183     /**
184      * For generating target type, we are scan for matchable properties
185      * and use those to build the policy.
186      *
187      * @param properties Properties section of policy
188      * @param policyTypes Collection of policy Type to find matchable metadata
189      * @return TargetType object
190      */
191     @SuppressWarnings("unchecked")
192     protected TargetType generateTargetType(Map<String, Object> properties, Collection<ToscaPolicyType> policyTypes) {
193         TargetType targetType = new TargetType();
194         //
195         // Iterate the properties
196         //
197         for (Entry<String, Object> entrySet : properties.entrySet()) {
198             //
199             // Find matchable properties
200             //
201             if (isMatchable(entrySet.getKey(), policyTypes)) {
202                 LOGGER.info("Found matchable property {}", entrySet.getValue());
203                 if (entrySet.getValue() instanceof Collection) {
204                     AnyOfType anyOf = generateMatches((Collection<Object>) entrySet.getValue(),
205                             new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + entrySet.getKey()));
206                     if (! anyOf.getAllOf().isEmpty()) {
207                         targetType.getAnyOf().add(anyOf);
208                     }
209                 } else {
210                     AnyOfType anyOf = generateMatches(Arrays.asList(entrySet.getValue()),
211                             new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + entrySet.getKey()));
212                     if (! anyOf.getAllOf().isEmpty()) {
213                         targetType.getAnyOf().add(anyOf);
214                     }
215                 }
216             }
217         }
218
219         return targetType;
220     }
221
222     protected boolean isMatchable(String propertyName, Collection<ToscaPolicyType> policyTypes) {
223         for (ToscaPolicyType policyType : policyTypes) {
224             for (Entry<String, ToscaProperty> propertiesEntry : policyType.getProperties().entrySet()) {
225                 if (! propertiesEntry.getKey().equals(propertyName)
226                         || propertiesEntry.getValue().getMetadata() == null) {
227                     continue;
228                 }
229                 for (Entry<String, String> entrySet : propertiesEntry.getValue().getMetadata().entrySet()) {
230                     if ("matchable".equals(entrySet.getKey()) && "true".equals(entrySet.getValue())) {
231                         return true;
232                     }
233                 }
234             }
235         }
236         return false;
237     }
238
239     protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
240         //
241         // This is our outer AnyOf - which is an OR
242         //
243         AnyOfType anyOf = new AnyOfType();
244         for (Object matchable : matchables) {
245             //
246             // Default to string
247             //
248             Identifier idFunction = XACML3.ID_FUNCTION_STRING_EQUAL;
249             Identifier idDatatype = XACML3.ID_DATATYPE_STRING;
250             //
251             // See if we are another datatype
252             //
253             // We should add datetime support. But to do that we need
254             // probably more metadata to describe how that would be translated.
255             //
256             if (matchable instanceof Integer) {
257                 idFunction = XACML3.ID_FUNCTION_INTEGER_EQUAL;
258                 idDatatype = XACML3.ID_DATATYPE_INTEGER;
259             } else if (matchable instanceof Double) {
260                 idFunction = XACML3.ID_FUNCTION_DOUBLE_EQUAL;
261                 idDatatype = XACML3.ID_DATATYPE_DOUBLE;
262             } else if (matchable instanceof Boolean) {
263                 idFunction = XACML3.ID_FUNCTION_BOOLEAN_EQUAL;
264                 idDatatype = XACML3.ID_DATATYPE_BOOLEAN;
265             }
266             //
267             // Create a match for this
268             //
269             MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
270                     idFunction,
271                     matchable.toString(),
272                     idDatatype,
273                     attributeId,
274                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
275             //
276             // Now create an anyOf (OR)
277             //
278             anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
279         }
280         return anyOf;
281     }
282
283     /**
284      * Get Policy Type definitions. This could be previously loaded, or could be
285      * stored in application path, or may need to be pulled from the API.
286      *
287      *
288      * @param policyTypeId Policy Type Id
289      * @return A list of PolicyTypes
290      */
291     private List<ToscaPolicyType> getPolicyTypes(ToscaPolicyTypeIdentifier policyTypeId) {
292         //
293         // Create identifier from the policy
294         //
295         ToscaPolicyTypeIdentifier typeId = new ToscaPolicyTypeIdentifier(policyTypeId);
296         //
297         // Find the Policy Type
298         //
299         ToscaPolicyType policyType = findPolicyType(typeId);
300         if (policyType == null)  {
301             return Collections.emptyList();
302         }
303         //
304         // Create our return object
305         //
306         List<ToscaPolicyType> listTypes = new ArrayList<>();
307         listTypes.add(policyType);
308         //
309         // Look for parent policy types that could also contain matchable properties
310         //
311         ToscaPolicyType childPolicyType = policyType;
312         while (! childPolicyType.getDerivedFrom().startsWith("tosca.policies.Root")) {
313             //
314             // Create parent policy type id.
315             //
316             // We will have to assume the same version between child and the
317             // parent policy type it derives from.
318             //
319             // Or do we assume 1.0.0?
320             //
321             ToscaPolicyTypeIdentifier parentId = new ToscaPolicyTypeIdentifier(childPolicyType.getDerivedFrom(),
322                     "1.0.0");
323             //
324             // Find the policy type
325             //
326             ToscaPolicyType parentPolicyType = findPolicyType(parentId);
327             if (parentPolicyType == null) {
328                 //
329                 // Probably would be best to throw an exception and
330                 // return nothing back.
331                 //
332                 // But instead we will log a warning
333                 //
334                 LOGGER.warn("Missing parent policy type - proceeding anyway {}", parentId);
335                 //
336                 // Break the loop
337                 //
338                 break;
339             }
340             //
341             // Great save it
342             //
343             listTypes.add(parentPolicyType);
344             //
345             // Move to the next parent
346             //
347             childPolicyType = parentPolicyType;
348         }
349
350
351         return listTypes;
352     }
353
354     private ToscaPolicyType findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
355         //
356         // Is it loaded in memory?
357         //
358         ToscaPolicyType policyType = this.matchablePolicyTypes.get(policyTypeId);
359         if (policyType == null)  {
360             //
361             // Load the policy
362             //
363             policyType = this.loadPolicyType(policyTypeId);
364         }
365         //
366         // Yep return it
367         //
368         return policyType;
369     }
370
371     private ToscaPolicyType loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
372         //
373         // Construct what the file name should be
374         //
375         Path policyTypePath = this.constructLocalFilePath(policyTypeId);
376         //
377         // See if it exists
378         //
379         byte[] bytes;
380         try {
381             //
382             // If it exists locally, read the bytes in
383             //
384             bytes = Files.readAllBytes(policyTypePath);
385         } catch (IOException e) {
386             //
387             // Does not exist locally, so let's GET it from the policy api
388             //
389             LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
390             //
391             // So let's pull it from API REST call and save it locally
392             //
393             return this.pullPolicyType(policyTypeId, policyTypePath);
394         }
395         LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
396         try {
397             ToscaServiceTemplate serviceTemplate = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
398                     ToscaServiceTemplate.class);
399             JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
400             jtst.fromAuthorative(serviceTemplate);
401             ToscaServiceTemplate completedJtst = jtst.toAuthorative();
402             //
403             // Search for our Policy Type, there really only should be one but
404             // this is returned as a map.
405             //
406             for ( Entry<String, ToscaPolicyType> entrySet : completedJtst.getPolicyTypes().entrySet()) {
407                 ToscaPolicyType entryPolicyType = entrySet.getValue();
408                 if (policyTypeId.getName().equals(entryPolicyType.getName())
409                         && policyTypeId.getVersion().equals(entryPolicyType.getVersion())) {
410                     LOGGER.info("Found existing local policy type {} {}", entryPolicyType.getName(),
411                             entryPolicyType.getVersion());
412                     //
413                     // Just simply return the policy type right here
414                     //
415                     return entryPolicyType;
416                 } else {
417                     LOGGER.warn("local policy type contains different name version {} {}", entryPolicyType.getName(),
418                             entryPolicyType.getVersion());
419                 }
420             }
421             //
422             // This would be an error, if the file stored does not match what its supposed to be
423             //
424             LOGGER.error("Existing policy type file does not contain right name and version");
425         } catch (CoderException e) {
426             LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
427         }
428         //
429         // Hopefully we never get here
430         //
431         LOGGER.error("Failed to find/load policy type {}", policyTypeId);
432         return null;
433     }
434
435     private synchronized ToscaPolicyType pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId, Path policyTypePath) {
436         //
437         // This is what we return
438         //
439         ToscaPolicyType policyType = null;
440         try {
441             PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
442
443             policyType = api.getPolicyType(policyTypeId);
444         } catch (PolicyApiException e) {
445             LOGGER.error("Failed to make API call", e);
446             LOGGER.error("parameters: {} ", this.apiRestParameters);
447             return null;
448         }
449         //
450         // Store it locally
451         //
452         try {
453             standardYamlCoder.encode(policyTypePath.toFile(), policyType);
454         } catch (CoderException e) {
455             LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
456         }
457         //
458         // Done return the policy type
459         //
460         return policyType;
461     }
462
463     private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
464         return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
465                 + policyTypeId.getVersion() + ".yaml");
466     }
467 }