2d831f62aa2e9829502653685c0d0e59cb5b010b
[policy/xacml-pdp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  *
19  * SPDX-License-Identifier: Apache-2.0
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.pdp.xacml.application.common.std;
24
25 import com.att.research.xacml.api.AttributeAssignment;
26 import com.att.research.xacml.api.Decision;
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.Response;
31 import com.att.research.xacml.api.Result;
32 import com.att.research.xacml.api.XACML3;
33 import com.att.research.xacml.std.IdentifierImpl;
34 import com.att.research.xacml.util.XACMLPolicyWriter;
35 import com.google.gson.Gson;
36 import java.io.ByteArrayOutputStream;
37 import java.io.IOException;
38 import java.nio.charset.StandardCharsets;
39 import java.nio.file.Files;
40 import java.nio.file.Path;
41 import java.nio.file.Paths;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collection;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Map.Entry;
50 import lombok.Setter;
51 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
52 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeAssignmentExpressionType;
53 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
54 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
55 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
56 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
57 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionType;
58 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionsType;
59 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
60 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
61 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
62 import org.onap.policy.common.endpoints.parameters.RestServerParameters;
63 import org.onap.policy.common.utils.coder.CoderException;
64 import org.onap.policy.common.utils.coder.StandardCoder;
65 import org.onap.policy.common.utils.coder.StandardYamlCoder;
66 import org.onap.policy.models.decisions.concepts.DecisionRequest;
67 import org.onap.policy.models.decisions.concepts.DecisionResponse;
68 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy;
69 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
70 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyTypeIdentifier;
71 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
72 import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate;
73 import org.onap.policy.models.tosca.simple.concepts.JpaToscaServiceTemplate;
74 import org.onap.policy.pdp.xacml.application.common.PolicyApiCaller;
75 import org.onap.policy.pdp.xacml.application.common.PolicyApiException;
76 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
77 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
78 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslator;
79 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyTranslatorUtils;
80 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationException;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83
84 /**
85  * This standard matchable translator uses Policy Types that contain "matchable" field in order
86  * to translate policies.
87  *
88  * @author pameladragosh
89  *
90  */
91 public class StdMatchableTranslator implements ToscaPolicyTranslator {
92
93     private static final Logger LOGGER = LoggerFactory.getLogger(StdMatchableTranslator.class);
94     private static final String POLICY_ID = "policy-id";
95     private static final StandardYamlCoder standardYamlCoder = new StandardYamlCoder();
96
97     private final Map<ToscaPolicyTypeIdentifier, ToscaPolicyType> matchablePolicyTypes = new HashMap<>();
98     @Setter
99     private RestServerParameters apiRestParameters;
100     @Setter
101     private Path pathForData;
102
103     public StdMatchableTranslator() {
104         super();
105     }
106
107     @Override
108     public Request convertRequest(DecisionRequest request) {
109         LOGGER.info("Converting Request {}", request);
110         try {
111             return StdMatchablePolicyRequest.createInstance(request);
112         } catch (XacmlApplicationException e) {
113             LOGGER.error("Failed to convert DecisionRequest: {}", e);
114         }
115         //
116         // TODO throw exception
117         //
118         return null;
119     }
120
121     @Override
122     public DecisionResponse convertResponse(Response xacmlResponse) {
123         LOGGER.info("Converting Response {}", xacmlResponse);
124         DecisionResponse decisionResponse = new DecisionResponse();
125         //
126         // Setup policies
127         //
128         decisionResponse.setPolicies(new HashMap<>());
129         //
130         // Iterate through all the results
131         //
132         for (Result xacmlResult : xacmlResponse.getResults()) {
133             //
134             // Check the result
135             //
136             if (xacmlResult.getDecision() == Decision.PERMIT) {
137                 //
138                 // Go through obligations
139                 //
140                 scanObligations(xacmlResult.getObligations(), decisionResponse);
141             }
142             if (xacmlResult.getDecision() == Decision.DENY
143                     || xacmlResult.getDecision() == Decision.INDETERMINATE) {
144                 //
145                 // TODO we have to return an ErrorResponse object instead
146                 //
147                 decisionResponse.setStatus("A better error message");
148             }
149         }
150
151         return decisionResponse;
152     }
153
154     protected void scanObligations(Collection<Obligation> obligations, DecisionResponse decisionResponse) {
155         for (Obligation obligation : obligations) {
156             LOGGER.info("Obligation: {}", obligation);
157             for (AttributeAssignment assignment : obligation.getAttributeAssignments()) {
158                 LOGGER.info("Attribute Assignment: {}", assignment);
159                 //
160                 // We care about the content attribute
161                 //
162                 if (! ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS
163                         .equals(assignment.getAttributeId())) {
164                     //
165                     // If its not there, move on
166                     //
167                     continue;
168                 }
169                 //
170                 // The contents are in Json form
171                 //
172                 Object stringContents = assignment.getAttributeValue().getValue();
173                 if (LOGGER.isInfoEnabled()) {
174                     LOGGER.info("Policy contents: {}{}", System.lineSeparator(), stringContents);
175                 }
176                 //
177                 // Let's parse it into a map using Gson
178                 //
179                 Gson gson = new Gson();
180                 @SuppressWarnings("unchecked")
181                 Map<String, Object> result = gson.fromJson(stringContents.toString() ,Map.class);
182                 //
183                 // Find the metadata section
184                 //
185                 @SuppressWarnings("unchecked")
186                 Map<String, Object> metadata = (Map<String, Object>) result.get("metadata");
187                 if (metadata != null) {
188                     decisionResponse.getPolicies().put(metadata.get(POLICY_ID).toString(), result);
189                 } else {
190                     LOGGER.error("Missing metadata section in policy contained in obligation.");
191                 }
192             }
193         }
194
195     }
196
197     @Override
198     public PolicyType convertPolicy(ToscaPolicy toscaPolicy) throws ToscaPolicyConversionException {
199         //
200         // Get the TOSCA Policy Type for this policy
201         //
202         Collection<ToscaPolicyType> policyTypes = this.getPolicyTypes(toscaPolicy.getTypeIdentifier());
203         //
204         // If we don't have any policy types, then we cannot know
205         // which properties are matchable.
206         //
207         if (policyTypes.isEmpty()) {
208             throw new ToscaPolicyConversionException(
209                     "Cannot retrieve Policy Type definition for policy " + toscaPolicy.getName());
210         }
211         //
212         // Policy name should be at the root
213         //
214         String policyName = toscaPolicy.getMetadata().get(POLICY_ID);
215         //
216         // Set it as the policy ID
217         //
218         PolicyType newPolicyType = new PolicyType();
219         newPolicyType.setPolicyId(policyName);
220         //
221         // Optional description
222         //
223         newPolicyType.setDescription(toscaPolicy.getDescription());
224         //
225         // There should be a metadata section
226         //
227         this.fillMetadataSection(newPolicyType, toscaPolicy.getMetadata());
228         //
229         // Set the combining rule
230         //
231         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
232         //
233         // Generate the TargetType
234         //
235         newPolicyType.setTarget(generateTargetType(toscaPolicy.getProperties(), policyTypes));
236         //
237         // Now create the Permit Rule
238         // No target since the policy has a target
239         // With obligations.
240         //
241         RuleType rule = new RuleType();
242         rule.setDescription("Default is to PERMIT if the policy matches.");
243         rule.setRuleId(policyName + ":rule");
244         rule.setEffect(EffectType.PERMIT);
245         rule.setTarget(new TargetType());
246         //
247         // Now represent the policy as Json
248         //
249         StandardCoder coder = new StandardCoder();
250         String jsonPolicy;
251         try {
252             jsonPolicy = coder.encode(toscaPolicy);
253         } catch (CoderException e) {
254             throw new ToscaPolicyConversionException("Failed to encode policy to json", e);
255         }
256         addObligation(rule, jsonPolicy);
257         //
258         // Add the rule to the policy
259         //
260         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
261         //
262         // Return our new policy
263         //
264         try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
265             XACMLPolicyWriter.writePolicyFile(os, newPolicyType);
266             LOGGER.info("{}", os);
267         } catch (IOException e) {
268             LOGGER.error("Failed to create byte array stream", e);
269         }
270         return newPolicyType;
271     }
272
273     /**
274      * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
275      *
276      * @param policy Policy Object to store the metadata
277      * @param map The Metadata TOSCA Map
278      * @return Same Policy Object
279      * @throws ToscaPolicyConversionException If there is something missing from the metadata
280      */
281     protected PolicyType fillMetadataSection(PolicyType policy,
282             Map<String, String> map) throws ToscaPolicyConversionException {
283         if (! map.containsKey(POLICY_ID)) {
284             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-id");
285         } else {
286             //
287             // Do nothing here - the XACML PolicyId is used from TOSCA Policy Name field
288             //
289         }
290         if (! map.containsKey("policy-version")) {
291             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-version");
292         } else {
293             //
294             // Add in the Policy Version
295             //
296             policy.setVersion(map.get("policy-version"));
297         }
298         return policy;
299     }
300
301     /**
302      * For generating target type, we are scan for matchable properties
303      * and use those to build the policy.
304      *
305      * @param properties Properties section of policy
306      * @param policyTypes Collection of policy Type to find matchable metadata
307      * @return TargetType object
308      */
309     @SuppressWarnings("unchecked")
310     protected TargetType generateTargetType(Map<String, Object> properties, Collection<ToscaPolicyType> policyTypes) {
311         TargetType targetType = new TargetType();
312         //
313         // Iterate the properties
314         //
315         for (Entry<String, Object> entrySet : properties.entrySet()) {
316             //
317             // Find matchable properties
318             //
319             if (isMatchable(entrySet.getKey(), policyTypes)) {
320                 LOGGER.info("Found matchable property {}", entrySet.getValue());
321                 if (entrySet.getValue() instanceof Collection) {
322                     AnyOfType anyOf = generateMatches((Collection<Object>) entrySet.getValue(),
323                             new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + entrySet.getKey()));
324                     if (! anyOf.getAllOf().isEmpty()) {
325                         targetType.getAnyOf().add(anyOf);
326                     }
327                 } else {
328                     AnyOfType anyOf = generateMatches(Arrays.asList(entrySet.getValue()),
329                             new IdentifierImpl(ToscaDictionary.ID_RESOURCE_MATCHABLE + entrySet.getKey()));
330                     if (! anyOf.getAllOf().isEmpty()) {
331                         targetType.getAnyOf().add(anyOf);
332                     }
333                 }
334             }
335         }
336
337         return targetType;
338     }
339
340     protected boolean isMatchable(String propertyName, Collection<ToscaPolicyType> policyTypes) {
341         for (ToscaPolicyType policyType : policyTypes) {
342             for (Entry<String, ToscaProperty> propertiesEntry : policyType.getProperties().entrySet()) {
343                 if (! propertiesEntry.getKey().equals(propertyName)
344                         || propertiesEntry.getValue().getMetadata() == null) {
345                     continue;
346                 }
347                 for (Entry<String, String> entrySet : propertiesEntry.getValue().getMetadata().entrySet()) {
348                     if ("matchable".equals(entrySet.getKey()) && "true".equals(entrySet.getValue())) {
349                         return true;
350                     }
351                 }
352             }
353         }
354         return false;
355     }
356
357     protected AnyOfType generateMatches(Collection<Object> matchables, Identifier attributeId) {
358         //
359         // This is our outer AnyOf - which is an OR
360         //
361         AnyOfType anyOf = new AnyOfType();
362         for (Object matchable : matchables) {
363             //
364             // Default to string
365             //
366             Identifier idFunction = XACML3.ID_FUNCTION_STRING_EQUAL;
367             Identifier idDatatype = XACML3.ID_DATATYPE_STRING;
368             //
369             // See if we are another datatype
370             //
371             // TODO We should add datetime support. But to do that we need
372             // probably more metadata to describe how that would be translated.
373             //
374             if (matchable instanceof Integer) {
375                 idFunction = XACML3.ID_FUNCTION_INTEGER_EQUAL;
376                 idDatatype = XACML3.ID_DATATYPE_INTEGER;
377             } else if (matchable instanceof Double) {
378                 idFunction = XACML3.ID_FUNCTION_DOUBLE_EQUAL;
379                 idDatatype = XACML3.ID_DATATYPE_DOUBLE;
380             } else if (matchable instanceof Boolean) {
381                 idFunction = XACML3.ID_FUNCTION_BOOLEAN_EQUAL;
382                 idDatatype = XACML3.ID_DATATYPE_BOOLEAN;
383             }
384             //
385             // Create a match for this
386             //
387             MatchType match = ToscaPolicyTranslatorUtils.buildMatchTypeDesignator(
388                     idFunction,
389                     matchable.toString(),
390                     idDatatype,
391                     attributeId,
392                     XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
393             //
394             // Now create an anyOf (OR)
395             //
396             anyOf.getAllOf().add(ToscaPolicyTranslatorUtils.buildAllOf(match));
397         }
398         return anyOf;
399     }
400
401     protected RuleType addObligation(RuleType rule, String jsonPolicy) {
402         //
403         // Convert the YAML Policy to JSON Object
404         //
405         if (LOGGER.isInfoEnabled()) {
406             LOGGER.info("JSON Optimization Policy {}{}", System.lineSeparator(), jsonPolicy);
407         }
408         //
409         // Create an AttributeValue for it
410         //
411         AttributeValueType value = new AttributeValueType();
412         value.setDataType(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_DATATYPE.stringValue());
413         value.getContent().add(jsonPolicy);
414         //
415         // Create our AttributeAssignmentExpression where we will
416         // store the contents of the policy in JSON format.
417         //
418         AttributeAssignmentExpressionType expressionType = new AttributeAssignmentExpressionType();
419         expressionType.setAttributeId(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS.stringValue());
420         ObjectFactory factory = new ObjectFactory();
421         expressionType.setExpression(factory.createAttributeValue(value));
422         //
423         // Create an ObligationExpression for it
424         //
425         ObligationExpressionType obligation = new ObligationExpressionType();
426         obligation.setFulfillOn(EffectType.PERMIT);
427         obligation.setObligationId(ToscaDictionary.ID_OBLIGATION_REST_BODY.stringValue());
428         obligation.getAttributeAssignmentExpression().add(expressionType);
429         //
430         // Now we can add it into the rule
431         //
432         ObligationExpressionsType obligations = new ObligationExpressionsType();
433         obligations.getObligationExpression().add(obligation);
434         rule.setObligationExpressions(obligations);
435         return rule;
436     }
437
438
439     /**
440      * Get Policy Type definitions. This could be previously loaded, or could be
441      * stored in application path, or may need to be pulled from the API.
442      *
443      *
444      * @param policyTypeId Policy Type Id
445      * @return A list of PolicyTypes
446      */
447     private List<ToscaPolicyType> getPolicyTypes(ToscaPolicyTypeIdentifier policyTypeId) {
448         //
449         // Create identifier from the policy
450         //
451         ToscaPolicyTypeIdentifier typeId = new ToscaPolicyTypeIdentifier(policyTypeId);
452         //
453         // Find the Policy Type
454         //
455         ToscaPolicyType policyType = findPolicyType(typeId);
456         if (policyType == null)  {
457             return Collections.emptyList();
458         }
459         //
460         // Create our return object
461         //
462         List<ToscaPolicyType> listTypes = new ArrayList<>();
463         listTypes.add(policyType);
464         //
465         // Look for parent policy types that could also contain matchable properties
466         //
467         ToscaPolicyType childPolicyType = policyType;
468         while (! childPolicyType.getDerivedFrom().startsWith("tosca.policies.Root")) {
469             //
470             // Create parent policy type id.
471             //
472             // We will have to assume the same version between child and the
473             // parent policy type it derives from.
474             //
475             // Or do we assume 1.0.0?
476             //
477             ToscaPolicyTypeIdentifier parentId = new ToscaPolicyTypeIdentifier(childPolicyType.getDerivedFrom(),
478                     "1.0.0");
479             //
480             // Find the policy type
481             //
482             ToscaPolicyType parentPolicyType = findPolicyType(parentId);
483             if (parentPolicyType == null) {
484                 //
485                 // Probably would be best to throw an exception and
486                 // return nothing back.
487                 //
488                 // But instead we will log a warning
489                 //
490                 LOGGER.warn("Missing parent policy type - proceeding anyway {}", parentId);
491                 //
492                 // Break the loop
493                 //
494                 break;
495             }
496             //
497             // Great save it
498             //
499             listTypes.add(parentPolicyType);
500             //
501             // Move to the next parent
502             //
503             childPolicyType = parentPolicyType;
504         }
505
506
507         return listTypes;
508     }
509
510     private ToscaPolicyType findPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
511         //
512         // Is it loaded in memory?
513         //
514         ToscaPolicyType policyType = this.matchablePolicyTypes.get(policyTypeId);
515         if (policyType == null)  {
516             //
517             // Load the policy
518             //
519             policyType = this.loadPolicyType(policyTypeId);
520         }
521         //
522         // Yep return it
523         //
524         return policyType;
525     }
526
527     private ToscaPolicyType loadPolicyType(ToscaPolicyTypeIdentifier policyTypeId) {
528         //
529         // Construct what the file name should be
530         //
531         Path policyTypePath = this.constructLocalFilePath(policyTypeId);
532         //
533         // See if it exists
534         //
535         byte[] bytes;
536         try {
537             //
538             // If it exists locally, read the bytes in
539             //
540             bytes = Files.readAllBytes(policyTypePath);
541         } catch (IOException e) {
542             //
543             // Does not exist locally, so let's GET it from the policy api
544             //
545             LOGGER.error("PolicyType not found in data area yet {}", policyTypePath, e);
546             //
547             // So let's pull it from API REST call and save it locally
548             //
549             return this.pullPolicyType(policyTypeId, policyTypePath);
550         }
551         LOGGER.info("Read in local policy type {}", policyTypePath.toAbsolutePath());
552         try {
553             ToscaServiceTemplate serviceTemplate = standardYamlCoder.decode(new String(bytes, StandardCharsets.UTF_8),
554                     ToscaServiceTemplate.class);
555             JpaToscaServiceTemplate jtst = new JpaToscaServiceTemplate();
556             jtst.fromAuthorative(serviceTemplate);
557             ToscaServiceTemplate completedJtst = jtst.toAuthorative();
558             //
559             // Search for our Policy Type, there really only should be one but
560             // this is returned as a map.
561             //
562             for ( Entry<String, ToscaPolicyType> entrySet : completedJtst.getPolicyTypes().entrySet()) {
563                 ToscaPolicyType entryPolicyType = entrySet.getValue();
564                 if (policyTypeId.getName().equals(entryPolicyType.getName())
565                         && policyTypeId.getVersion().equals(entryPolicyType.getVersion())) {
566                     LOGGER.info("Found existing local policy type {} {}", entryPolicyType.getName(),
567                             entryPolicyType.getVersion());
568                     //
569                     // Just simply return the policy type right here
570                     //
571                     return entryPolicyType;
572                 } else {
573                     LOGGER.warn("local policy type contains different name version {} {}", entryPolicyType.getName(),
574                             entryPolicyType.getVersion());
575                 }
576             }
577             //
578             // This would be an error, if the file stored does not match what its supposed to be
579             //
580             LOGGER.error("Existing policy type file does not contain right name and version");
581         } catch (CoderException e) {
582             LOGGER.error("Failed to decode tosca template for {}", policyTypePath, e);
583         }
584         //
585         // Hopefully we never get here
586         //
587         LOGGER.error("Failed to find/load policy type {}", policyTypeId);
588         return null;
589     }
590
591     private synchronized ToscaPolicyType pullPolicyType(ToscaPolicyTypeIdentifier policyTypeId, Path policyTypePath) {
592         //
593         // This is what we return
594         //
595         ToscaPolicyType policyType = null;
596         try {
597             PolicyApiCaller api = new PolicyApiCaller(this.apiRestParameters);
598
599             policyType = api.getPolicyType(policyTypeId);
600         } catch (PolicyApiException e) {
601             LOGGER.error("Failed to make API call", e);
602             LOGGER.error("parameters: {} ", this.apiRestParameters);
603             return null;
604         }
605         //
606         // Store it locally
607         //
608         try {
609             standardYamlCoder.encode(policyTypePath.toFile(), policyType);
610         } catch (CoderException e) {
611             LOGGER.error("Failed to store {} locally to {}", policyTypeId, policyTypePath, e);
612         }
613         //
614         // Done return the policy type
615         //
616         return policyType;
617     }
618
619     private Path constructLocalFilePath(ToscaPolicyTypeIdentifier policyTypeId) {
620         return Paths.get(this.pathForData.toAbsolutePath().toString(), policyTypeId.getName() + "-"
621                 + policyTypeId.getVersion() + ".yaml");
622     }
623 }