6c53566a583015fa043f9f6a855090724f0f21ce
[policy/xacml-pdp.git] / applications / monitoring / src / main / java / org / onap / policy / xacml / pdp / engine / OnapXacmlPdpEngine.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.xacml.pdp.engine;
24
25 import com.att.research.xacml.api.Request;
26 import com.att.research.xacml.api.Response;
27 import com.att.research.xacml.api.XACML3;
28 import com.att.research.xacml.api.pdp.PDPEngine;
29 import com.att.research.xacml.api.pdp.PDPEngineFactory;
30 import com.att.research.xacml.api.pdp.PDPException;
31 import com.att.research.xacml.util.FactoryException;
32 import com.att.research.xacml.util.XACMLPolicyScanner;
33 import com.att.research.xacml.util.XACMLProperties;
34 import com.google.common.collect.Lists;
35
36 import java.io.FileInputStream;
37 import java.io.FileOutputStream;
38 import java.io.IOException;
39 import java.io.InputStream;
40 import java.io.OutputStream;
41 import java.nio.file.Path;
42 import java.nio.file.Paths;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Map.Entry;
49 import java.util.Properties;
50 import java.util.Set;
51
52 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AnyOfType;
53 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeAssignmentExpressionType;
54 import oasis.names.tc.xacml._3_0.core.schema.wd_17.AttributeValueType;
55 import oasis.names.tc.xacml._3_0.core.schema.wd_17.EffectType;
56 import oasis.names.tc.xacml._3_0.core.schema.wd_17.MatchType;
57 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
58 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionType;
59 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObligationExpressionsType;
60 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySetType;
61 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
62 import oasis.names.tc.xacml._3_0.core.schema.wd_17.RuleType;
63 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
64
65 import org.json.JSONObject;
66 import org.onap.policy.pdp.xacml.application.common.ToscaDictionary;
67 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConversionException;
68 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConverter;
69 import org.onap.policy.pdp.xacml.application.common.ToscaPolicyConverterUtils;
70 import org.onap.policy.pdp.xacml.application.common.XacmlApplicationServiceProvider;
71 import org.onap.policy.pdp.xacml.application.common.XacmlUpdatePolicyUtils;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74 import org.yaml.snakeyaml.Yaml;
75
76 /**
77  * This is the engine class that manages the instance of the XACML PDP engine.
78  *
79  * <p>It is responsible for initializing it and shutting it down properly in a thread-safe manner.
80  *
81  *
82  * @author pameladragosh
83  *
84  */
85 public class OnapXacmlPdpEngine implements ToscaPolicyConverter, XacmlApplicationServiceProvider {
86
87     private static final Logger LOGGER = LoggerFactory.getLogger(OnapXacmlPdpEngine.class);
88     private static final String ONAP_MONITORING_BASE_POLICY_TYPE = "onap.Monitoring";
89     private static final String ONAP_MONITORING_DERIVED_POLICY_TYPE = "onap.policies.monitoring";
90
91
92     private Path pathForData = null;
93     private Properties pdpProperties = null;
94     private PDPEngine pdpEngine = null;
95     private Map<String, String> supportedPolicyTypes = new HashMap<>();
96
97     /**
98      * Constructor.
99      */
100     public OnapXacmlPdpEngine() {
101         //
102         // By default this supports just Monitoring policy types
103         //
104         supportedPolicyTypes.put(ONAP_MONITORING_BASE_POLICY_TYPE, "1.0.0");
105     }
106
107     /**
108      * Load properties from given file.
109      *
110      * @param location Path and filename
111      * @throws IOException If unable to read file
112      */
113     public synchronized void loadXacmlProperties(String location) throws IOException {
114         try (InputStream is = new FileInputStream(location)) {
115             pdpProperties.load(is);
116         }
117     }
118
119     /**
120      * Stores the XACML Properties to the given file location.
121      *
122      * @param location File location including name
123      * @throws IOException If unable to store the file.
124      */
125     public synchronized void storeXacmlProperties(String location) throws IOException {
126         try (OutputStream os = new FileOutputStream(location)) {
127             String strComments = "#";
128             pdpProperties.store(os, strComments);
129         }
130     }
131
132     /**
133      * Make a decision call.
134      *
135      * @param request Incoming request object
136      * @return Response object
137      */
138     public synchronized Response decision(Request request) {
139         //
140         // This is what we need to return
141         //
142         Response response = null;
143         //
144         // Track some timing
145         //
146         long timeStart = System.currentTimeMillis();
147         try {
148             response = this.pdpEngine.decide(request);
149         } catch (PDPException e) {
150             LOGGER.error("{}", e);
151         } finally {
152             //
153             // Track the end of timing
154             //
155             long timeEnd = System.currentTimeMillis();
156             LOGGER.info("Elapsed Time: {}ms", (timeEnd - timeStart));
157         }
158         return response;
159     }
160
161     @Override
162     public String applicationName() {
163         return "Monitoring Application";
164     }
165
166     @Override
167     public List<String> actionDecisionsSupported() {
168         return Arrays.asList("configure");
169     }
170
171     @Override
172     public synchronized void initialize(Path pathForData) {
173         //
174         // Save our path
175         //
176         this.pathForData = pathForData;
177         LOGGER.debug("New Path is {}", this.pathForData.toAbsolutePath());
178         //
179         // Look for and load the properties object
180         //
181         Path propertyPath = Paths.get(this.pathForData.toAbsolutePath().toString(), "xacml.properties");
182         LOGGER.debug("Looking for {}", propertyPath.toAbsolutePath());
183         try (InputStream is = new FileInputStream(propertyPath.toAbsolutePath().toString()) ) {
184             //
185             // Create a new properties object
186             //
187             pdpProperties = new Properties();
188             //
189             // Load it with our values
190             //
191             pdpProperties.load(is);
192             LOGGER.debug("{}", pdpProperties);
193         } catch (IOException e) {
194             LOGGER.error("{}", e);
195         }
196         //
197         // Now initialize the XACML PDP Engine
198         //
199         try {
200             PDPEngineFactory factory = PDPEngineFactory.newInstance();
201             this.pdpEngine = factory.newEngine(pdpProperties);
202         } catch (FactoryException e) {
203             LOGGER.error("{}", e);
204         }
205     }
206
207     @Override
208     public synchronized List<String> supportedPolicyTypes() {
209         return Lists.newArrayList(supportedPolicyTypes.keySet());
210     }
211
212     @Override
213     public boolean canSupportPolicyType(String policyType, String policyTypeVersion) {
214         //
215         // For Monitoring, we will attempt to support all versions
216         // of the policy type. Since we are only packaging a decision
217         // back with a JSON payload of the property contents.
218         //
219         return (policyType.equals(ONAP_MONITORING_BASE_POLICY_TYPE)
220                 || policyType.startsWith(ONAP_MONITORING_DERIVED_POLICY_TYPE));
221     }
222
223     @Override
224     public synchronized void loadPolicies(Map<String, Object> toscaPolicies) {
225         //
226         //
227         //
228         try {
229             //
230             // Convert the policies first
231             //
232             List<PolicyType> listPolicies = this.convertPolicies(toscaPolicies);
233             if (listPolicies.isEmpty()) {
234                 throw new ToscaPolicyConversionException("Converted 0 policies");
235             }
236             //
237             // Read in our Root Policy
238             //
239             Set<String> roots = XACMLProperties.getRootPolicyIDs(pdpProperties);
240             if (roots.isEmpty()) {
241                 throw new ToscaPolicyConversionException("There are NO root policies defined");
242             }
243             //
244             // Really only should be one
245             //
246             String rootFile = pdpProperties.getProperty(roots.iterator().next() + ".file");
247             try (InputStream is = new FileInputStream(rootFile)) {
248                 Object policyData = XACMLPolicyScanner.readPolicy(is);
249                 //
250                 // Should be a PolicySet
251                 //
252                 if (policyData instanceof PolicySetType) {
253                     PolicyType[] newPolicies = listPolicies.toArray(new PolicyType[listPolicies.size()]);
254                     PolicySetType newRootPolicy =
255                             XacmlUpdatePolicyUtils.updateXacmlRootPolicy((PolicySetType) policyData, newPolicies);
256                     //
257                     // Save the new Policies to disk
258                     //
259
260                     //
261                     // Save the root policy to disk
262                     //
263
264                     //
265                     // Update properties to declare the referenced policies
266                     //
267
268                     //
269                     // Write the policies to disk
270                     //
271
272                 } else {
273                     throw new ToscaPolicyConversionException("Root policy isn't a PolicySet");
274                 }
275             }
276             //
277             // Add to the root policy
278             //
279         } catch (IOException | ToscaPolicyConversionException e) {
280             LOGGER.error("Failed to loadPolicies {}", e);
281         }
282     }
283
284     @Override
285     public synchronized JSONObject makeDecision(JSONObject jsonSchema) {
286         return null;
287     }
288
289     @Override
290     public List<PolicyType> convertPolicies(Map<String, Object> toscaObject) throws ToscaPolicyConversionException {
291         //
292         // Return the policies
293         //
294         return scanAndConvertPolicies(toscaObject);
295     }
296
297     @Override
298     public List<PolicyType> convertPolicies(InputStream isToscaPolicy) throws ToscaPolicyConversionException {
299         //
300         // Have snakeyaml parse the object
301         //
302         Yaml yaml = new Yaml();
303         Map<String, Object> toscaObject = yaml.load(isToscaPolicy);
304         //
305         // Return the policies
306         //
307         return scanAndConvertPolicies(toscaObject);
308     }
309
310     @SuppressWarnings("unchecked")
311     private List<PolicyType> scanAndConvertPolicies(Map<String, Object> toscaObject)
312             throws ToscaPolicyConversionException {
313         //
314         // Our return object
315         //
316         List<PolicyType> scannedPolicies = new ArrayList<>();
317         //
318         // Iterate each of the Policies
319         //
320         List<Object> policies = (List<Object>) toscaObject.get("policies");
321         for (Object policyObject : policies) {
322             //
323             // Get the contents
324             //
325             LOGGER.debug("Found policy {}", policyObject.getClass());
326             Map<String, Object> policyContents = (Map<String, Object>) policyObject;
327             for (Entry<String, Object> entrySet : policyContents.entrySet()) {
328                 LOGGER.info("Entry set {}", entrySet);
329                 //
330                 // Convert this policy
331                 //
332                 PolicyType policy = this.convertPolicy(entrySet);
333                 //
334                 // Convert and add in the new policy
335                 //
336                 scannedPolicies.add(policy);
337             }
338         }
339
340         return scannedPolicies;
341     }
342
343     @SuppressWarnings("unchecked")
344     private PolicyType convertPolicy(Entry<String, Object> entrySet) throws ToscaPolicyConversionException {
345         //
346         // Policy name should be at the root
347         //
348         String policyName = entrySet.getKey();
349         Map<String, Object> policyDefinition = (Map<String, Object>) entrySet.getValue();
350         //
351         // Set it as the policy ID
352         //
353         PolicyType newPolicyType = new PolicyType();
354         newPolicyType.setPolicyId(policyName);
355         //
356         // Optional description
357         //
358         if (policyDefinition.containsKey("description")) {
359             newPolicyType.setDescription(policyDefinition.get("description").toString());
360         }
361         //
362         // There should be a metadata section
363         //
364         if (! policyDefinition.containsKey("metadata")) {
365             throw new ToscaPolicyConversionException(policyName + " missing metadata section");
366         }
367         this.fillMetadataSection(newPolicyType,
368                 (Map<String, Object>) policyDefinition.get("metadata"));
369         //
370         // Set the combining rule
371         //
372         newPolicyType.setRuleCombiningAlgId(XACML3.ID_RULE_FIRST_APPLICABLE.stringValue());
373         //
374         // Generate the TargetType
375         //
376         //
377         // There should be a metadata section
378         //
379         if (! policyDefinition.containsKey("type")) {
380             throw new ToscaPolicyConversionException(policyName + " missing type value");
381         }
382         if (! policyDefinition.containsKey("version")) {
383             throw new ToscaPolicyConversionException(policyName + " missing version value");
384         }
385         TargetType target = this.generateTargetType(policyName,
386                 policyDefinition.get("type").toString(),
387                 policyDefinition.get("version").toString());
388         newPolicyType.setTarget(target);
389         //
390         // Now create the Permit Rule
391         // No target since the policy has a target
392         // With obligations.
393         //
394         RuleType rule = new RuleType();
395         rule.setDescription("Default is to PERMIT if the policy matches.");
396         rule.setRuleId(policyName + ":rule");
397         rule.setEffect(EffectType.PERMIT);
398         rule.setTarget(new TargetType());
399         //
400         // There should be properties section - this data ends up as a
401         // JSON BLOB that is returned back to calling application.
402         //
403         if (! policyDefinition.containsKey("properties")) {
404             throw new ToscaPolicyConversionException(policyName + " missing properties section");
405         }
406         addObligation(rule,
407                 (Map<String, Object>) policyDefinition.get("properties"));
408         //
409         // Add the rule to the policy
410         //
411         newPolicyType.getCombinerParametersOrRuleCombinerParametersOrVariableDefinition().add(rule);
412         //
413         // Return our new policy
414         //
415         return newPolicyType;
416     }
417
418     /**
419      * From the TOSCA metadata section, pull in values that are needed into the XACML policy.
420      *
421      * @param policy Policy Object to store the metadata
422      * @param metadata The Metadata TOSCA Map
423      * @return Same Policy Object
424      * @throws ToscaPolicyConversionException If there is something missing from the metadata
425      */
426     private PolicyType fillMetadataSection(PolicyType policy,
427             Map<String, Object> metadata) throws ToscaPolicyConversionException {
428         if (! metadata.containsKey("policy-id")) {
429             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-id");
430         } else {
431             //
432             // Do nothing here - the XACML PolicyId is used from TOSCA Policy Name field
433             //
434         }
435         if (! metadata.containsKey("policy-version")) {
436             throw new ToscaPolicyConversionException(policy.getPolicyId() + " missing metadata policy-version");
437         } else {
438             //
439             // Add in the Policy Version
440             //
441             policy.setVersion(metadata.get("policy-version").toString());
442         }
443         return policy;
444     }
445
446     private TargetType generateTargetType(String policyId, String policyType, String policyTypeVersion) {
447         //
448         // Create all the match's that are possible
449         //
450         // This is for the Policy Id
451         //
452         MatchType matchPolicyId = ToscaPolicyConverterUtils.buildMatchTypeDesignator(
453                 XACML3.ID_FUNCTION_STRING_EQUAL,
454                 policyId,
455                 XACML3.ID_DATATYPE_STRING,
456                 ToscaDictionary.ID_RESOURCE_POLICY_ID,
457                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
458         //
459         // This is for the Policy Type
460         //
461         MatchType matchPolicyType = ToscaPolicyConverterUtils.buildMatchTypeDesignator(
462                 XACML3.ID_FUNCTION_STRING_EQUAL,
463                 policyType,
464                 XACML3.ID_DATATYPE_STRING,
465                 ToscaDictionary.ID_RESOURCE_POLICY_TYPE,
466                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
467         //
468         // This is for the Policy Type version
469         //
470         MatchType matchPolicyTypeVersion = ToscaPolicyConverterUtils.buildMatchTypeDesignator(
471                 XACML3.ID_FUNCTION_STRING_EQUAL,
472                 policyTypeVersion,
473                 XACML3.ID_DATATYPE_STRING,
474                 ToscaDictionary.ID_RESOURCE_POLICY_TYPE_VERSION,
475                 XACML3.ID_ATTRIBUTE_CATEGORY_RESOURCE);
476         //
477         // This is our outer AnyOf - which is an OR
478         //
479         AnyOfType anyOf = new AnyOfType();
480         //
481         // Create AllOf (AND) of just Policy Id
482         //
483         anyOf.getAllOf().add(ToscaPolicyConverterUtils.buildAllOf(matchPolicyId));
484         //
485         // Create AllOf (AND) of just Policy Type
486         //
487         anyOf.getAllOf().add(ToscaPolicyConverterUtils.buildAllOf(matchPolicyType));
488         //
489         // Create AllOf (AND) of Policy Type and Policy Type Version
490         //
491         anyOf.getAllOf().add(ToscaPolicyConverterUtils.buildAllOf(matchPolicyType, matchPolicyTypeVersion));
492         //
493         // Now we can create the TargetType, add the top-level anyOf (OR),
494         // and return the value.
495         //
496         TargetType target = new TargetType();
497         target.getAnyOf().add(anyOf);
498         return target;
499     }
500
501     private RuleType addObligation(RuleType rule, Map<String, Object> properties) {
502         //
503         // Convert the YAML Policy to JSON Object
504         //
505         JSONObject jsonObject = new JSONObject(properties);
506         if (LOGGER.isDebugEnabled()) {
507             LOGGER.debug("JSON conversion {}{}", System.lineSeparator(), jsonObject);
508         }
509         //
510         // Create an AttributeValue for it
511         //
512         AttributeValueType value = new AttributeValueType();
513         value.setDataType(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_DATATYPE.stringValue());
514         value.getContent().add(jsonObject.toString());
515         //
516         // Create our AttributeAssignmentExpression where we will
517         // store the contents of the policy in JSON format.
518         //
519         AttributeAssignmentExpressionType expressionType = new AttributeAssignmentExpressionType();
520         expressionType.setAttributeId(ToscaDictionary.ID_OBLIGATION_POLICY_MONITORING_CONTENTS.stringValue());
521         ObjectFactory factory = new ObjectFactory();
522         expressionType.setExpression(factory.createAttributeValue(value));
523         //
524         // Create an ObligationExpression for it
525         //
526         ObligationExpressionType obligation = new ObligationExpressionType();
527         obligation.setFulfillOn(EffectType.PERMIT);
528         obligation.setObligationId(ToscaDictionary.ID_OBLIGATION_REST_BODY.stringValue());
529         obligation.getAttributeAssignmentExpression().add(expressionType);
530         //
531         // Now we can add it into the rule
532         //
533         ObligationExpressionsType obligations = new ObligationExpressionsType();
534         obligations.getObligationExpression().add(obligation);
535         rule.setObligationExpressions(obligations);
536         return rule;
537     }
538
539 }