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