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