Replace DOMParser with calls to pool or helper class.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / arp / ArpEngine.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved
4  * 
5  * 
6  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 
9  * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
10  * disclaimer.
11  * 
12  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided with the distribution, if any, must include the
14  * following acknowledgment: "This product includes software developed by the University Corporation for Advanced
15  * Internet Development <http://www.ucaid.edu> Internet2 Project. Alternately, this acknowledegement may appear in the
16  * software itself, if and wherever such third-party acknowledgments normally appear.
17  * 
18  * Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor the University Corporation for
19  * Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote products derived from this software
20  * without specific prior written permission. For written permission, please contact shibboleth@shibboleth.org
21  * 
22  * Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the University Corporation
23  * for Advanced Internet Development, nor may Shibboleth appear in their name, without prior written permission of the
24  * University Corporation for Advanced Internet Development.
25  * 
26  * 
27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR
28  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
29  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE,
30  * ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
31  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  */
37
38 package edu.internet2.middleware.shibboleth.aa.arp;
39
40 import java.net.URI;
41 import java.net.URISyntaxException;
42 import java.net.URL;
43 import java.security.Principal;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.Iterator;
49 import java.util.Map;
50 import java.util.Set;
51
52 import javax.xml.parsers.DocumentBuilderFactory;
53 import javax.xml.parsers.ParserConfigurationException;
54
55 import org.apache.log4j.Logger;
56 import org.w3c.dom.Document;
57 import org.w3c.dom.Element;
58 import org.w3c.dom.NodeList;
59 import org.w3c.dom.Text;
60
61 import edu.internet2.middleware.shibboleth.aa.arp.ArpAttributeSet.ArpAttributeIterator;
62 import edu.internet2.middleware.shibboleth.common.ShibbolethOriginConfig;
63 import edu.internet2.middleware.shibboleth.xml.Parser;
64
65 /**
66  * Defines a processing engine for Attribute Release Policies.
67  * 
68  * @author Walter Hoehn (wassa@columbia.edu)
69  */
70
71 public class ArpEngine {
72
73         private static Logger log = Logger.getLogger(ArpEngine.class.getName());
74         private ArpRepository repository;
75         private static Map matchFunctions = Collections.synchronizedMap(new HashMap());
76         static {
77                 //Initialize built-in match functions
78                 try {
79                         
80                         // Current
81                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:regexMatch"),
82                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.RegexMatchFunction");
83                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:regexNotMatch"),
84                         "edu.internet2.middleware.shibboleth.aa.arp.provider.RegexNotMatchFunction");
85                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:stringMatch"),
86                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueMatchFunction");
87                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:stringNotMatch"),
88                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueNotMatchFunction");
89                         
90                         // Legacy
91                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:exactShar"),
92                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueMatchFunction");
93                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:resourceTree"),
94                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.ResourceTreeMatchFunction");
95                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:stringValue"),
96                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueMatchFunction");
97                         
98                 } catch (URISyntaxException e) {
99                         log.error("Error mapping standard match functions: " + e);
100                 }
101         }
102
103         /**
104          * Loads Arp Engine with default configuration
105          * 
106          * @throws ArpException
107          *             if engine cannot be loaded
108          */
109         public ArpEngine(Element config) throws ArpException {
110
111                 if (!config.getLocalName().equals("ReleasePolicyEngine")) {
112                         throw new IllegalArgumentException();
113                 }
114
115                 NodeList itemElements =
116                         config.getElementsByTagNameNS(ShibbolethOriginConfig.originConfigNamespace, "ArpRepository");
117
118                 if (itemElements.getLength() > 1) {
119                         log.warn(
120                                 "Encountered multiple <ArpRepository> configuration elements.  Arp Engine currently only supports one.  Using first...");
121                 }
122
123                 if (itemElements.getLength() == 0) {
124                         log.error("No <ArpRepsitory/> specified for this Arp Endine.");
125                         throw new ArpException("Could not start Arp Engine.");
126                 }
127
128                 try {
129                         repository = ArpRepositoryFactory.getInstance((Element) itemElements.item(0));
130                 } catch (ArpRepositoryException e) {
131                         log.error("Could not start Arp Engine: " + e);
132                         throw new ArpException("Could not start Arp Engine.");
133                 }
134         }
135
136         public ArpEngine(ArpRepository preLoadedRepository) throws ArpException {
137                 repository = preLoadedRepository;
138         }
139
140         /**
141          * Loads Arp Engine based on XML configurationf
142          * 
143          * @throws ArpException
144          *             if configuration is invalid or there is a problem loading the engine
145          */
146         public ArpEngine() throws ArpException {
147
148                 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
149                 docFactory.setNamespaceAware(true);
150                 Document placeHolder;
151                 try {
152                         placeHolder = docFactory.newDocumentBuilder().newDocument();
153
154                         Element defRepository =
155                                 placeHolder.createElementNS(ShibbolethOriginConfig.originConfigNamespace, "ArpRepository");
156                         defRepository.setAttributeNS(
157                                 ShibbolethOriginConfig.originConfigNamespace,
158                                 "implementation",
159                                 "edu.internet2.middleware.shibboleth.aa.arp.provider.FileSystemArpRepository");
160
161                         Element path = placeHolder.createElementNS(ShibbolethOriginConfig.originConfigNamespace, "Path");
162                         Text text = placeHolder.createTextNode("/conf/arps/");
163                         path.appendChild(text);
164
165                         defRepository.appendChild(path);
166
167                         repository = ArpRepositoryFactory.getInstance(defRepository);
168
169                 } catch (ArpRepositoryException e) {
170                         log.error("Could not start Arp Engine: " + e);
171                         throw new ArpException("Could not start Arp Engine.");
172                 } catch (ParserConfigurationException e) {
173                         log.error("Problem loading parser to create default Arp Engine configuration: " + e);
174                         throw new ArpException("Could not start Arp Engine.");
175                 }
176         }
177
178         public static MatchFunction lookupMatchFunction(URI functionIdentifier) throws ArpException {
179                 String className = null;
180
181                 synchronized (matchFunctions) {
182                         className = (String) matchFunctions.get(functionIdentifier);
183                 }
184
185                 if (className == null) {
186                         return null;
187                 }
188                 try {
189                         Class matchFunction = Class.forName(className);
190                         Object functionObject = matchFunction.newInstance();
191                         if (functionObject instanceof MatchFunction) {
192                                 return (MatchFunction) functionObject;
193                         } else {
194                                 log.error("Improperly specified match function, (" + className + ") is not a match function.");
195                                 throw new ArpException(
196                                         "Improperly specified match function, (" + className + ") is not a match function.");
197                         }
198                 } catch (Exception e) {
199                         log.error("Could not load Match Function: (" + className + "): " + e);
200                         throw new ArpException("Could not load Match Function.");
201                 }
202         }
203
204         private Arp createEffectiveArp(Principal principal, String requester, URL resource) throws ArpProcessingException {
205                 try {
206                         Arp effectiveArp = new Arp(principal);
207                         effectiveArp.setDescription("Effective ARP.");
208
209                         Arp[] userPolicies = repository.getAllPolicies(principal);
210
211                         if (log.isDebugEnabled()) {
212                                 log.debug("Creating effective ARP from (" + userPolicies.length + ") polic(y|ies).");
213                                 try {
214                                         for (int i = 0; userPolicies.length > i; i++) {
215                                                 String dump=Parser.serialize(userPolicies[i].unmarshall());
216                                                 log.debug("Dumping ARP:" + System.getProperty("line.separator") + dump);
217                                         }
218                                 } catch (Exception e) {
219                                         log.error(
220                                                 "Encountered a strange error while writing ARP debug messages.  This should never happen.");
221                                 }
222                         }
223
224                         for (int i = 0; userPolicies.length > i; i++) {
225                                 Rule[] rules = userPolicies[i].getMatchingRules(requester, resource);
226
227                                 for (int j = 0; rules.length > j; j++) {
228                                         effectiveArp.addRule(rules[j]);
229                                 }
230                         }
231                         return effectiveArp;
232
233                 } catch (ArpRepositoryException e) {
234                         log.error("Error creating effective policy: " + e);
235                         throw new ArpProcessingException("Error creating effective policy.");
236                 }
237         }
238
239         /**
240          * Determines which attributes MIGHT be releasable for a given request. This function may be used to determine
241          * which attributes to resolve when a request for all attributes is made. This is done for performance reasons
242          * only. ie: The resulting attributes must still be filtered before release.
243          * 
244          * @return an array of <code>URI</code> objects that name the possible attributes
245          */
246         public URI[] listPossibleReleaseAttributes(Principal principal, String requester, URL resource)
247                 throws ArpProcessingException {
248                 Set possibleReleaseSet = new HashSet();
249                 Set anyValueDenies = new HashSet();
250                 Rule[] rules = createEffectiveArp(principal, requester, resource).getAllRules();
251                 for (int i = 0; rules.length > i; i++) {
252                         Rule.Attribute[] attributes = rules[i].getAttributes();
253                         for (int j = 0; attributes.length > j; j++) {
254                                 if (attributes[j].releaseAnyValue()) {
255                                         possibleReleaseSet.add(attributes[j].getName());
256                                 } else if (attributes[j].denyAnyValue()) {
257                                         anyValueDenies.add(attributes[j].getName());
258                                 } else {
259                                         Rule.AttributeValue[] values = attributes[j].getValues();
260                                         for (int k = 0; values.length > k; k++) {
261                                                 if (values[k].getRelease().equals("permit")) {
262                                                         possibleReleaseSet.add(attributes[j].getName());
263                                                         break;
264                                                 }
265                                         }
266                                 }
267                         }
268                 }
269                 possibleReleaseSet.removeAll(anyValueDenies);
270                 if (log.isDebugEnabled()) {
271                         log.debug("Computed possible attribute release set.");
272                         Iterator iterator = possibleReleaseSet.iterator();
273                         while (iterator.hasNext()) {
274                                 log.debug("Possible attribute: " + iterator.next().toString());
275                         }
276                 }
277                 return (URI[]) possibleReleaseSet.toArray(new URI[0]);
278         }
279
280         /**
281          * Applies all applicable ARPs to a set of attributes.
282          * 
283          * @return the attributes to be released
284          */
285         public void filterAttributes(ArpAttributeSet attributes, Principal principal, String requester, URL resource)
286                 throws ArpProcessingException {
287
288                 ArpAttributeIterator iterator = attributes.arpAttributeIterator();
289                 if (!iterator.hasNext()) {
290                         log.debug("ARP Engine was asked to apply filter to empty attribute set.");
291                         return;
292                 }
293
294                 log.info("Applying Attribute Release Policies.");
295                 if (log.isDebugEnabled()) {
296                         log.debug("Processing the following attributes:");
297                         for (ArpAttributeIterator attrIterator = attributes.arpAttributeIterator(); attrIterator.hasNext();) {
298                                 log.debug("Attribute: (" + attrIterator.nextArpAttribute().getName() + ")");
299                         }
300                 }
301
302                 //Gather all applicable ARP attribute specifiers
303                 Set attributeNames = new HashSet();
304                 for (ArpAttributeIterator nameIterator = attributes.arpAttributeIterator(); nameIterator.hasNext();) {
305                         attributeNames.add(nameIterator.nextArpAttribute().getName());
306                 }
307                 Rule[] rules = createEffectiveArp(principal, requester, resource).getAllRules();
308                 Set applicableRuleAttributes = new HashSet();
309                 for (int i = 0; rules.length > i; i++) {
310                         Rule.Attribute[] ruleAttributes = rules[i].getAttributes();
311                         for (int j = 0; ruleAttributes.length > j; j++) {
312                                 if (attributeNames.contains(ruleAttributes[j].getName().toString())) {
313                                         applicableRuleAttributes.add(ruleAttributes[j]);
314                                 }
315                         }
316                 }
317
318                 //Canonicalize specifiers
319                 Map arpAttributeSpecs =
320                         createCanonicalAttributeSpec((Rule.Attribute[]) applicableRuleAttributes.toArray(new Rule.Attribute[0]));
321
322                 //Filter
323                 for (ArpAttributeIterator returnIterator = attributes.arpAttributeIterator(); returnIterator.hasNext();) {
324
325                         ArpAttribute arpAttribute = returnIterator.nextArpAttribute();
326                         Rule.Attribute attribute = (Rule.Attribute) arpAttributeSpecs.get(arpAttribute.getName());
327
328                         //Handle no specifier
329                         if (attribute == null) {
330                                 returnIterator.remove();
331                                 continue;
332                         }
333
334                         //Handle Deny All
335                         if (attribute.denyAnyValue()) {
336                                 returnIterator.remove();
337                                 continue;
338                         }
339
340                         //Handle Permit All
341                         if (attribute.releaseAnyValue() && attribute.getValues().length == 0) {
342                                 continue;
343                         }
344
345                         //Handle "Permit All-Except" and "Permit Specific"
346                         ArrayList releaseValues = new ArrayList();
347                         for (Iterator valueIterator = arpAttribute.getValues(); valueIterator.hasNext();) {
348                                 Object value = valueIterator.next();
349                                 if (attribute.isValuePermitted(value)) {
350                                         releaseValues.add(value);
351                                 }
352                         }
353
354                         if (!releaseValues.isEmpty()) {
355                                 arpAttribute.setValues((Object[]) releaseValues.toArray(new Object[0]));
356                         } else {
357                                 returnIterator.remove();
358                         }
359
360                 }
361         }
362
363         private Map createCanonicalAttributeSpec(Rule.Attribute[] attributes) {
364                 Map canonicalSpec = new HashMap();
365                 for (int i = 0; attributes.length > i; i++) {
366                         if (!canonicalSpec.containsKey(attributes[i].getName().toString())) {
367                                 canonicalSpec.put(attributes[i].getName().toString(), attributes[i]);
368                         } else {
369                                 if (((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).denyAnyValue()) {
370                                         continue;
371                                 }
372                                 if (attributes[i].denyAnyValue()) {
373                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).setAnyValueDeny(true);
374                                         continue;
375                                 }
376                                 if (attributes[i].releaseAnyValue()) {
377                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).setAnyValuePermit(true);
378                                 }
379                                 Rule.AttributeValue[] values = attributes[i].getValues();
380                                 for (int j = 0; values.length > j; j++) {
381                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).addValue(values[j]);
382                                 }
383                         }
384                 }
385                 return canonicalSpec;
386         }
387
388         /**
389          * Cleanup resources that won't be released when this object is garbage-collected
390          */
391         public void destroy() {
392                 repository.destroy();
393         }
394
395 }