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