Added ARP match functions for string and regular expressions that do not match. ...
[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.io.StringWriter;
41 import java.net.URI;
42 import java.net.URISyntaxException;
43 import java.net.URL;
44 import java.security.Principal;
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.Iterator;
50 import java.util.Map;
51 import java.util.Set;
52
53 import javax.xml.parsers.DocumentBuilderFactory;
54 import javax.xml.parsers.ParserConfigurationException;
55
56 import org.apache.log4j.Logger;
57 import org.apache.xml.serialize.OutputFormat;
58 import org.apache.xml.serialize.XMLSerializer;
59 import org.w3c.dom.Document;
60 import org.w3c.dom.Element;
61 import org.w3c.dom.NodeList;
62 import org.w3c.dom.Text;
63
64 import edu.internet2.middleware.shibboleth.aa.arp.ArpAttributeSet.ArpAttributeIterator;
65 import edu.internet2.middleware.shibboleth.common.ShibbolethOriginConfig;
66
67 /**
68  * Defines a processing engine for Attribute Release Policies.
69  * 
70  * @author Walter Hoehn (wassa@columbia.edu)
71  */
72
73 public class ArpEngine {
74
75         private static Logger log = Logger.getLogger(ArpEngine.class.getName());
76         private ArpRepository repository;
77         private static Map matchFunctions = Collections.synchronizedMap(new HashMap());
78         static {
79                 //Initialize built-in match functions
80                 try {
81                         
82                         // Current
83                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:regexMatch"),
84                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.RegexMatchFunction");
85                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:regexNotMatch"),
86                         "edu.internet2.middleware.shibboleth.aa.arp.provider.RegexNotMatchFunction");
87                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:stringMatch"),
88                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueMatchFunction");
89                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:stringNotMatch"),
90                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueNotMatchFunction");
91                         
92                         // Legacy
93                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:exactShar"),
94                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueMatchFunction");
95                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:resourceTree"),
96                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.ResourceTreeMatchFunction");
97                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:stringValue"),
98                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueMatchFunction");
99                         
100                 } catch (URISyntaxException e) {
101                         log.error("Error mapping standard match functions: " + e);
102                 }
103         }
104
105         /**
106          * Loads Arp Engine with default configuration
107          * 
108          * @throws ArpException
109          *             if engine cannot be loaded
110          */
111         public ArpEngine(Element config) throws ArpException {
112
113                 if (!config.getLocalName().equals("ReleasePolicyEngine")) {
114                         throw new IllegalArgumentException();
115                 }
116
117                 NodeList itemElements =
118                         config.getElementsByTagNameNS(ShibbolethOriginConfig.originConfigNamespace, "ArpRepository");
119
120                 if (itemElements.getLength() > 1) {
121                         log.warn(
122                                 "Encountered multiple <ArpRepository> configuration elements.  Arp Engine currently only supports one.  Using first...");
123                 }
124
125                 if (itemElements.getLength() == 0) {
126                         log.error("No <ArpRepsitory/> specified for this Arp Endine.");
127                         throw new ArpException("Could not start Arp Engine.");
128                 }
129
130                 try {
131                         repository = ArpRepositoryFactory.getInstance((Element) itemElements.item(0));
132                 } catch (ArpRepositoryException e) {
133                         log.error("Could not start Arp Engine: " + e);
134                         throw new ArpException("Could not start Arp Engine.");
135                 }
136         }
137
138         public ArpEngine(ArpRepository preLoadedRepository) throws ArpException {
139                 repository = preLoadedRepository;
140         }
141
142         /**
143          * Loads Arp Engine based on XML configurationf
144          * 
145          * @throws ArpException
146          *             if configuration is invalid or there is a problem loading the engine
147          */
148         public ArpEngine() throws ArpException {
149
150                 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
151                 docFactory.setNamespaceAware(true);
152                 Document placeHolder;
153                 try {
154                         placeHolder = docFactory.newDocumentBuilder().newDocument();
155
156                         Element defRepository =
157                                 placeHolder.createElementNS(ShibbolethOriginConfig.originConfigNamespace, "ArpRepository");
158                         defRepository.setAttributeNS(
159                                 ShibbolethOriginConfig.originConfigNamespace,
160                                 "implementation",
161                                 "edu.internet2.middleware.shibboleth.aa.arp.provider.FileSystemArpRepository");
162
163                         Element path = placeHolder.createElementNS(ShibbolethOriginConfig.originConfigNamespace, "Path");
164                         Text text = placeHolder.createTextNode("/conf/arps/");
165                         path.appendChild(text);
166
167                         defRepository.appendChild(path);
168
169                         repository = ArpRepositoryFactory.getInstance(defRepository);
170
171                 } catch (ArpRepositoryException e) {
172                         log.error("Could not start Arp Engine: " + e);
173                         throw new ArpException("Could not start Arp Engine.");
174                 } catch (ParserConfigurationException e) {
175                         log.error("Problem loading parser to create default Arp Engine configuration: " + e);
176                         throw new ArpException("Could not start Arp Engine.");
177                 }
178         }
179
180         public static MatchFunction lookupMatchFunction(URI functionIdentifier) throws ArpException {
181                 String className = null;
182
183                 synchronized (matchFunctions) {
184                         className = (String) matchFunctions.get(functionIdentifier);
185                 }
186
187                 if (className == null) {
188                         return null;
189                 }
190                 try {
191                         Class matchFunction = Class.forName(className);
192                         Object functionObject = matchFunction.newInstance();
193                         if (functionObject instanceof MatchFunction) {
194                                 return (MatchFunction) functionObject;
195                         } else {
196                                 log.error("Improperly specified match function, (" + className + ") is not a match function.");
197                                 throw new ArpException(
198                                         "Improperly specified match function, (" + className + ") is not a match function.");
199                         }
200                 } catch (Exception e) {
201                         log.error("Could not load Match Function: (" + className + "): " + e);
202                         throw new ArpException("Could not load Match Function.");
203                 }
204         }
205
206         private Arp createEffectiveArp(Principal principal, String requester, URL resource) throws ArpProcessingException {
207                 try {
208                         Arp effectiveArp = new Arp(principal);
209                         effectiveArp.setDescription("Effective ARP.");
210
211                         Arp[] userPolicies = repository.getAllPolicies(principal);
212
213                         if (log.isDebugEnabled()) {
214                                 log.debug("Creating effective ARP from (" + userPolicies.length + ") polic(y|ies).");
215                                 try {
216                                         for (int i = 0; userPolicies.length > i; i++) {
217                                                 StringWriter writer = new StringWriter();
218                                                 OutputFormat format = new OutputFormat();
219                                                 format.setIndent(4);
220                                                 XMLSerializer serializer = new XMLSerializer(writer, format);
221                                                 serializer.serialize(userPolicies[i].unmarshall());
222                                                 log.debug("Dumping ARP:" + System.getProperty("line.separator") + writer.toString());
223                                         }
224                                 } catch (Exception e) {
225                                         log.error(
226                                                 "Encountered a strange error while writing ARP debug messages.  This should never happen.");
227                                 }
228                         }
229
230                         for (int i = 0; userPolicies.length > i; i++) {
231                                 Rule[] rules = userPolicies[i].getMatchingRules(requester, resource);
232
233                                 for (int j = 0; rules.length > j; j++) {
234                                         effectiveArp.addRule(rules[j]);
235                                 }
236                         }
237                         return effectiveArp;
238
239                 } catch (ArpRepositoryException e) {
240                         log.error("Error creating effective policy: " + e);
241                         throw new ArpProcessingException("Error creating effective policy.");
242                 }
243         }
244
245         /**
246          * Determines which attributes MIGHT be releasable for a given request. This function may be used to determine
247          * which attributes to resolve when a request for all attributes is made. This is done for performance reasons
248          * only. ie: The resulting attributes must still be filtered before release.
249          * 
250          * @return an array of <code>URI</code> objects that name the possible attributes
251          */
252         public URI[] listPossibleReleaseAttributes(Principal principal, String requester, URL resource)
253                 throws ArpProcessingException {
254                 Set possibleReleaseSet = new HashSet();
255                 Set anyValueDenies = new HashSet();
256                 Rule[] rules = createEffectiveArp(principal, requester, resource).getAllRules();
257                 for (int i = 0; rules.length > i; i++) {
258                         Rule.Attribute[] attributes = rules[i].getAttributes();
259                         for (int j = 0; attributes.length > j; j++) {
260                                 if (attributes[j].releaseAnyValue()) {
261                                         possibleReleaseSet.add(attributes[j].getName());
262                                 } else if (attributes[j].denyAnyValue()) {
263                                         anyValueDenies.add(attributes[j].getName());
264                                 } else {
265                                         Rule.AttributeValue[] values = attributes[j].getValues();
266                                         for (int k = 0; values.length > k; k++) {
267                                                 if (values[k].getRelease().equals("permit")) {
268                                                         possibleReleaseSet.add(attributes[j].getName());
269                                                         break;
270                                                 }
271                                         }
272                                 }
273                         }
274                 }
275                 possibleReleaseSet.removeAll(anyValueDenies);
276                 if (log.isDebugEnabled()) {
277                         log.debug("Computed possible attribute release set.");
278                         Iterator iterator = possibleReleaseSet.iterator();
279                         while (iterator.hasNext()) {
280                                 log.debug("Possible attribute: " + iterator.next().toString());
281                         }
282                 }
283                 return (URI[]) possibleReleaseSet.toArray(new URI[0]);
284         }
285
286         /**
287          * Applies all applicable ARPs to a set of attributes.
288          * 
289          * @return the attributes to be released
290          */
291         public void filterAttributes(ArpAttributeSet attributes, Principal principal, String requester, URL resource)
292                 throws ArpProcessingException {
293
294                 ArpAttributeIterator iterator = attributes.arpAttributeIterator();
295                 if (!iterator.hasNext()) {
296                         log.debug("ARP Engine was asked to apply filter to empty attribute set.");
297                         return;
298                 }
299
300                 log.info("Applying Attribute Release Policies.");
301                 if (log.isDebugEnabled()) {
302                         log.debug("Processing the following attributes:");
303                         for (ArpAttributeIterator attrIterator = attributes.arpAttributeIterator(); attrIterator.hasNext();) {
304                                 log.debug("Attribute: (" + attrIterator.nextArpAttribute().getName() + ")");
305                         }
306                 }
307
308                 //Gather all applicable ARP attribute specifiers
309                 Set attributeNames = new HashSet();
310                 for (ArpAttributeIterator nameIterator = attributes.arpAttributeIterator(); nameIterator.hasNext();) {
311                         attributeNames.add(nameIterator.nextArpAttribute().getName());
312                 }
313                 Rule[] rules = createEffectiveArp(principal, requester, resource).getAllRules();
314                 Set applicableRuleAttributes = new HashSet();
315                 for (int i = 0; rules.length > i; i++) {
316                         Rule.Attribute[] ruleAttributes = rules[i].getAttributes();
317                         for (int j = 0; ruleAttributes.length > j; j++) {
318                                 if (attributeNames.contains(ruleAttributes[j].getName().toString())) {
319                                         applicableRuleAttributes.add(ruleAttributes[j]);
320                                 }
321                         }
322                 }
323
324                 //Canonicalize specifiers
325                 Map arpAttributeSpecs =
326                         createCanonicalAttributeSpec((Rule.Attribute[]) applicableRuleAttributes.toArray(new Rule.Attribute[0]));
327
328                 //Filter
329                 for (ArpAttributeIterator returnIterator = attributes.arpAttributeIterator(); returnIterator.hasNext();) {
330
331                         ArpAttribute arpAttribute = returnIterator.nextArpAttribute();
332                         Rule.Attribute attribute = (Rule.Attribute) arpAttributeSpecs.get(arpAttribute.getName());
333
334                         //Handle no specifier
335                         if (attribute == null) {
336                                 returnIterator.remove();
337                                 continue;
338                         }
339
340                         //Handle Deny All
341                         if (attribute.denyAnyValue()) {
342                                 returnIterator.remove();
343                                 continue;
344                         }
345
346                         //Handle Permit All
347                         if (attribute.releaseAnyValue() && attribute.getValues().length == 0) {
348                                 continue;
349                         }
350
351                         //Handle "Permit All-Except" and "Permit Specific"
352                         ArrayList releaseValues = new ArrayList();
353                         for (Iterator valueIterator = arpAttribute.getValues(); valueIterator.hasNext();) {
354                                 Object value = valueIterator.next();
355                                 if (attribute.isValuePermitted(value)) {
356                                         releaseValues.add(value);
357                                 }
358                         }
359
360                         if (!releaseValues.isEmpty()) {
361                                 arpAttribute.setValues((Object[]) releaseValues.toArray(new Object[0]));
362                         } else {
363                                 returnIterator.remove();
364                         }
365
366                 }
367         }
368
369         private Map createCanonicalAttributeSpec(Rule.Attribute[] attributes) {
370                 Map canonicalSpec = new HashMap();
371                 for (int i = 0; attributes.length > i; i++) {
372                         if (!canonicalSpec.containsKey(attributes[i].getName().toString())) {
373                                 canonicalSpec.put(attributes[i].getName().toString(), attributes[i]);
374                         } else {
375                                 if (((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).denyAnyValue()) {
376                                         continue;
377                                 }
378                                 if (attributes[i].denyAnyValue()) {
379                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).setAnyValueDeny(true);
380                                         continue;
381                                 }
382                                 if (attributes[i].releaseAnyValue()) {
383                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).setAnyValuePermit(true);
384                                 }
385                                 Rule.AttributeValue[] values = attributes[i].getValues();
386                                 for (int j = 0; values.length > j; j++) {
387                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).addValue(values[j]);
388                                 }
389                         }
390                 }
391                 return canonicalSpec;
392         }
393
394         /**
395          * Cleanup resources that won't be released when this object is garbage-collected
396          */
397         public void destroy() {
398                 repository.destroy();
399         }
400
401 }