1babe30cdcabce6ba50ed047e8d6abcbb1865410
[policy/xacml-pdp.git] / applications / common / src / main / java / org / onap / policy / pdp / xacml / application / common / XacmlPolicyUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2019-2020 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;
24
25 import com.att.research.xacml.api.Identifier;
26 import com.att.research.xacml.util.XACMLPolicyWriter;
27 import com.att.research.xacml.util.XACMLProperties;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.OutputStream;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.util.Map.Entry;
38 import java.util.Properties;
39 import java.util.Set;
40 import java.util.StringJoiner;
41 import java.util.stream.Collectors;
42 import oasis.names.tc.xacml._3_0.core.schema.wd_17.IdReferenceType;
43 import oasis.names.tc.xacml._3_0.core.schema.wd_17.ObjectFactory;
44 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicySetType;
45 import oasis.names.tc.xacml._3_0.core.schema.wd_17.PolicyType;
46 import oasis.names.tc.xacml._3_0.core.schema.wd_17.TargetType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 public class XacmlPolicyUtils {
51
52     private static final Logger LOGGER = LoggerFactory.getLogger(XacmlPolicyUtils.class);
53
54     public static final String XACML_PROPERTY_FILE = "xacml.properties";
55     public static final String LINE_SEPARATOR = System.lineSeparator();
56
57     private static final String DOT_FILE_SUFFIX = ".file";
58     private static final String NOT_FOUND_MESSAGE = "NOT FOUND";
59
60     private XacmlPolicyUtils() {
61         super();
62     }
63
64     /**
65      * Creates an empty PolicySetType object given the id and combining algorithm. Note,there
66      * will also be an empty Target created. You can easily override that if need be.
67      *
68      * @param policyId Policy Id
69      * @param policyCombiningAlgorithm Policy Combining Algorithm
70      * @return PolicySetType object
71      */
72     public static PolicySetType createEmptyPolicySet(String policyId, Identifier policyCombiningAlgorithm) {
73         PolicySetType policy = new PolicySetType();
74         policy.setPolicySetId(policyId);
75         policy.setPolicyCombiningAlgId(policyCombiningAlgorithm.stringValue());
76         policy.setTarget(new TargetType());
77         return policy;
78     }
79
80     /**
81      * Creates an empty PolicySetType object given the id and combining algorithm. Note,there
82      * will also be an empty Target created. You can easily override that if need be.
83      *
84      * @param policyId Policy Id
85      * @param ruleCombiningAlgorithm Rule Combining Algorithm
86      * @return PolicyType object
87      */
88     public static PolicyType createEmptyPolicy(String policyId, Identifier ruleCombiningAlgorithm) {
89         PolicyType policy = new PolicyType();
90         policy.setPolicyId(policyId);
91         policy.setRuleCombiningAlgId(ruleCombiningAlgorithm.stringValue());
92         policy.setTarget(new TargetType());
93         return policy;
94     }
95
96     /**
97      * This method adds a list of PolicyType objects to a root PolicySetType as
98      * referenced policies.
99      *
100      * @param rootPolicy Root PolicySet being updated
101      * @param referencedPolicies A list of PolicyType being added as a references
102      * @return the rootPolicy PolicySet object
103      */
104     public static PolicySetType addPoliciesToXacmlRootPolicy(PolicySetType rootPolicy,
105             PolicyType... referencedPolicies) {
106         ObjectFactory factory = new ObjectFactory();
107         //
108         // Iterate each policy
109         //
110         for (PolicyType referencedPolicy : referencedPolicies) {
111             IdReferenceType reference = new IdReferenceType();
112             reference.setValue(referencedPolicy.getPolicyId());
113             //
114             // Add it in
115             //
116             rootPolicy.getPolicySetOrPolicyOrPolicySetIdReference().add(factory.createPolicyIdReference(reference));
117         }
118         //
119         // Return the updated object
120         //
121         return rootPolicy;
122     }
123
124     /**
125      * This method updates a root PolicySetType by adding in a PolicyType as a reference.
126      *
127      * @param rootPolicy Root PolicySet being updated
128      * @param referencedPolicySets A list of PolicySetType being added as a references
129      * @return the rootPolicy PolicySet object
130      */
131     public static PolicySetType addPolicySetsToXacmlRootPolicy(PolicySetType rootPolicy,
132             PolicySetType... referencedPolicySets) {
133         ObjectFactory factory = new ObjectFactory();
134         //
135         // Iterate each policy
136         //
137         for (PolicySetType referencedPolicySet : referencedPolicySets) {
138             IdReferenceType reference = new IdReferenceType();
139             reference.setValue(referencedPolicySet.getPolicySetId());
140             //
141             // Add it in
142             //
143             rootPolicy.getPolicySetOrPolicyOrPolicySetIdReference().add(factory.createPolicySetIdReference(reference));
144         }
145         //
146         // Return the updated object
147         //
148         return rootPolicy;
149     }
150
151     /**
152      * Adds in the root policy to the PDP properties object.
153      *
154      * @param properties Input properties
155      * @param rootPolicyPath Path to the root policy file
156      * @return Properties object
157      */
158     public static Properties addRootPolicy(Properties properties, Path rootPolicyPath) {
159         //
160         // Get the current set of referenced policy ids
161         //
162         Set<String> rootPolicies = XACMLProperties.getRootPolicyIDs(properties);
163         //
164         // Construct a unique id
165         //
166         int id = 1;
167         while (true) {
168             String refId = "root" + id;
169             if (rootPolicies.contains(refId)) {
170                 id++;
171             } else {
172                 rootPolicies.add(refId);
173                 properties.put(refId + DOT_FILE_SUFFIX, rootPolicyPath.toAbsolutePath().toString());
174                 break;
175             }
176         }
177         //
178         // Set the new comma separated list
179         //
180         properties.setProperty(XACMLProperties.PROP_ROOTPOLICIES,
181                 rootPolicies.stream().collect(Collectors.joining(",")));
182         return properties;
183     }
184
185     /**
186      * Adds in the referenced policy to the PDP properties object.
187      *
188      * @param properties Input properties
189      * @param refPolicyPath Path to the referenced policy file
190      * @return Properties object
191      */
192     public static Properties addReferencedPolicy(Properties properties, Path refPolicyPath) {
193         //
194         // Get the current set of referenced policy ids
195         //
196         Set<String> referencedPolicies = XACMLProperties.getReferencedPolicyIDs(properties);
197         //
198         // Construct a unique id
199         //
200         int id = 1;
201         while (true) {
202             String refId = "ref" + id;
203             if (referencedPolicies.contains(refId)) {
204                 id++;
205             } else {
206                 referencedPolicies.add(refId);
207                 properties.put(refId + DOT_FILE_SUFFIX, refPolicyPath.toAbsolutePath().toString());
208                 break;
209             }
210         }
211         //
212         // Set the new comma separated list
213         //
214         properties.setProperty(XACMLProperties.PROP_REFERENCEDPOLICIES,
215                 referencedPolicies.stream().collect(Collectors.joining(",")));
216         return properties;
217     }
218
219     /**
220      * Removes a root policy from the Properties object. Both in the line
221      * that identifies the policy and the .file property that points to the path.
222      *
223      * @param properties Input Properties object to remove
224      * @param rootPolicyPath The policy file path
225      * @return Properties object
226      */
227     public static Properties removeRootPolicy(Properties properties, Path rootPolicyPath) {
228         //
229         // Get the current set of referenced policy ids
230         //
231         StringJoiner join = new StringJoiner(",");
232         boolean found = false;
233         Set<String> rootPolicies = XACMLProperties.getRootPolicyIDs(properties);
234         for (String refPolicy : rootPolicies) {
235             String refPolicyFile = refPolicy + DOT_FILE_SUFFIX;
236             //
237             // If the key and value match, then it will return true
238             //
239             if (properties.remove(refPolicyFile, rootPolicyPath.toString())) {
240                 //
241                 // Record that we actually removed it
242                 //
243                 found = true;
244             } else {
245                 //
246                 // Retain it
247                 //
248                 join.add(refPolicy);
249             }
250         }
251         //
252         // Did we remove it?
253         //
254         if (found) {
255             //
256             // Now update the list of referenced properties
257             //
258             properties.setProperty(XACMLProperties.PROP_ROOTPOLICIES, join.toString());
259         }
260         return properties;
261     }
262
263     /**
264      * Removes a referenced policy from the Properties object. Both in the line
265      * that identifies the policy and the .file property that points to the path.
266      *
267      * @param properties Input Properties object to remove
268      * @param refPolicyPath The policy file path
269      * @return Properties object
270      */
271     public static Properties removeReferencedPolicy(Properties properties, Path refPolicyPath) {
272         //
273         // Get the current set of referenced policy ids
274         //
275         StringJoiner join = new StringJoiner(",");
276         boolean found = false;
277         Set<String> referencedPolicies = XACMLProperties.getReferencedPolicyIDs(properties);
278         for (String refPolicy : referencedPolicies) {
279             String refPolicyFile = refPolicy + DOT_FILE_SUFFIX;
280             //
281             // If the key and value match, then it will return true
282             //
283             if (properties.remove(refPolicyFile, refPolicyPath.toString())) {
284                 //
285                 // Record that we actually removed it
286                 //
287                 found = true;
288             } else {
289                 //
290                 // Retain it
291                 //
292                 join.add(refPolicy);
293             }
294         }
295         //
296         // Did we remove it?
297         //
298         if (found) {
299             //
300             // Now update the list of referenced properties
301             //
302             properties.setProperty(XACMLProperties.PROP_REFERENCEDPOLICIES, join.toString());
303         }
304         return properties;
305     }
306
307     /**
308      * Does a debug dump of referenced and root policy values.
309      *
310      * @param properties Input Properties object
311      * @param logger Logger object to use
312      */
313     public static void debugDumpPolicyProperties(Properties properties, Logger logger) {
314         //
315         // I hate surrounding this all with an if, but by
316         // doing so I clear sonar issues with passing System.lineSeparator()
317         // as an argument.
318         //
319         if (logger.isDebugEnabled()) {
320             //
321             // Get the current set of referenced policy ids
322             //
323             Set<String> rootPolicies = XACMLProperties.getRootPolicyIDs(properties);
324             logger.debug("Root Policies: {}", properties.getProperty(XACMLProperties.PROP_ROOTPOLICIES));
325             for (String root : rootPolicies) {
326                 logger.debug("{}", properties.getProperty(root + DOT_FILE_SUFFIX, NOT_FOUND_MESSAGE));
327             }
328             //
329             // Get the current set of referenced policy ids
330             //
331             Set<String> referencedPolicies = XACMLProperties.getReferencedPolicyIDs(properties);
332             logger.debug("Referenced Policies: {}", properties.getProperty(XACMLProperties.PROP_REFERENCEDPOLICIES));
333             for (String ref : referencedPolicies) {
334                 logger.debug("{}", properties.getProperty(ref + DOT_FILE_SUFFIX, NOT_FOUND_MESSAGE));
335             }
336         }
337     }
338
339     /**
340      * Constructs a unique policy filename for a given policy.
341      *
342      * <P>It could be dangerous to use policy-id and policy-version if the user
343      * gives us an invalid policy-id and policy-versions.
344      *
345      * <P>Should we append a UUID also to guarantee uniqueness?
346      *
347      * <P>How do we track that in case we need to know what policies we have loaded?
348      *
349      * @param policy PolicyType object
350      * @param path Path for policy
351      * @return Path unique file path for the Policy
352      */
353     public static Path constructUniquePolicyFilename(Object policy, Path path) {
354         String id;
355         String version;
356         if (policy instanceof PolicyType) {
357             id = ((PolicyType) policy).getPolicyId();
358             version = ((PolicyType) policy).getVersion();
359         } else if (policy instanceof PolicySetType) {
360             id = ((PolicySetType) policy).getPolicySetId();
361             version = ((PolicySetType) policy).getVersion();
362         } else {
363             throw new IllegalArgumentException("Must pass a PolicyType or PolicySetType");
364         }
365         //
366         //
367         // Can it be possible to produce an invalid filename?
368         // Should we insert a UUID
369         //
370         String filename = id + "_" + version + ".xml";
371         //
372         // Construct the Path
373         //
374         return Paths.get(path.toAbsolutePath().toString(), filename);
375     }
376
377     /**
378      * Load properties from given file.
379      *
380      * @throws IOException If unable to read file
381      */
382     public static Properties loadXacmlProperties(Path propertyPath) throws IOException {
383         LOGGER.info("Loading xacml properties {}", propertyPath);
384         try (InputStream is = Files.newInputStream(propertyPath)) {
385             Properties properties = new Properties();
386             properties.load(is);
387             if (LOGGER.isInfoEnabled()) {
388                 LOGGER.info("Loaded xacml properties {} {}", XacmlPolicyUtils.LINE_SEPARATOR, properties);
389                 for (Entry<Object, Object> entrySet : properties.entrySet()) {
390                     LOGGER.info("{} -> {}", entrySet.getKey(), entrySet.getValue());
391                 }
392             }
393             return properties;
394         }
395     }
396
397     /**
398      * Stores the XACML Properties to the given file location.
399      *
400      * @throws IOException If unable to store the file.
401      */
402     public static void storeXacmlProperties(Properties properties, Path propertyPath) throws IOException {
403         LOGGER.info("Storing xacml properties {} {} {}", properties, XacmlPolicyUtils.LINE_SEPARATOR, propertyPath);
404         try (OutputStream os = Files.newOutputStream(propertyPath)) {
405             String strComments = "#";
406             properties.store(os, strComments);
407         }
408     }
409
410     /**
411      * Appends 'xacml.properties' to a root Path object
412      *
413      * @param rootPath Root Path object
414      * @return Path to rootPath/xacml.properties file
415      */
416     public static Path getPropertiesPath(Path rootPath) {
417         return Paths.get(rootPath.toAbsolutePath().toString(), XACML_PROPERTY_FILE);
418     }
419
420     @FunctionalInterface
421     public interface FileCreator {
422         public File createAFile(String filename) throws IOException;
423
424     }
425
426     /**
427      * Copies a xacml.properties file to another location and all the policies defined within it.
428      *
429      * @param propertiesPath Path to an existing properties file
430      * @param properties Properties object
431      * @param creator A callback that can create files. Allows JUnit test to pass Temporary folder
432      * @return File object that points to new Properties file
433      * @throws IOException Could not read/write files
434      */
435     public static File copyXacmlPropertiesContents(String propertiesPath, Properties properties,
436             FileCreator creator) throws IOException {
437         //
438         // Open the properties file
439         //
440         try (InputStream is = new FileInputStream(propertiesPath)) {
441             //
442             // Load in the properties
443             //
444             properties.load(is);
445             //
446             // Now we create a new xacml.properties in the temporary folder location
447             //
448             File propertiesFile = creator.createAFile(XACML_PROPERTY_FILE);
449             //
450             // Iterate through any root policies defined
451             //
452             for (String root : XACMLProperties.getRootPolicyIDs(properties)) {
453                 //
454                 // Get a file
455                 //
456                 Path rootPath = Paths.get(properties.getProperty(root + DOT_FILE_SUFFIX));
457                 LOGGER.info("Root file {} {}", rootPath, rootPath.getFileName());
458                 //
459                 // Construct new path for the root policy
460                 //
461                 File newRootPath = creator.createAFile(rootPath.getFileName().toString());
462                 //
463                 // Copy the policy file to the temporary folder
464                 //
465                 com.google.common.io.Files.copy(rootPath.toFile(), newRootPath);
466                 //
467                 // Change the properties object to point to where the new policy is
468                 // in the temporary folder
469                 //
470                 properties.setProperty(root + DOT_FILE_SUFFIX, newRootPath.getAbsolutePath());
471             }
472             //
473             // Iterate through any referenced policies defined
474             //
475             for (String referenced : XACMLProperties.getReferencedPolicyIDs(properties)) {
476                 //
477                 // Get a file
478                 //
479                 Path refPath = Paths.get(properties.getProperty(referenced + DOT_FILE_SUFFIX));
480                 LOGGER.info("Referenced file {} {}", refPath, refPath.getFileName());
481                 //
482                 // Construct new path for the root policy
483                 //
484                 File newReferencedPath = creator.createAFile(refPath.getFileName().toString());
485                 //
486                 // Copy the policy file to the temporary folder
487                 //
488                 com.google.common.io.Files.copy(refPath.toFile(), newReferencedPath);
489                 //
490                 // Change the properties object to point to where the new policy is
491                 // in the temporary folder
492                 //
493                 properties.setProperty(referenced + DOT_FILE_SUFFIX, newReferencedPath.getAbsolutePath());
494             }
495             //
496             // Save the new properties file to the temporary folder
497             //
498             try (OutputStream os = new FileOutputStream(propertiesFile.getAbsolutePath())) {
499                 properties.store(os, "");
500             }
501             //
502             // Return the new path to the properties folder
503             //
504             return propertiesFile;
505         }
506     }
507
508     /**
509      * Wraps the call to XACMLPolicyWriter.
510      *
511      * @param path Path to file to be written to.
512      * @param policy PolicyType or PolicySetType
513      * @return Path - the same path passed in most likely from XACMLPolicyWriter. Or NULL if an error occurs.
514      */
515     public static Path writePolicyFile(Path path, Object policy) {
516         if (policy instanceof PolicyType) {
517             return XACMLPolicyWriter.writePolicyFile(path, (PolicyType) policy);
518         } else if (policy instanceof PolicySetType) {
519             return XACMLPolicyWriter.writePolicyFile(path, (PolicySetType) policy);
520         } else {
521             throw new IllegalArgumentException("Expecting PolicyType or PolicySetType");
522         }
523     }
524 }