ARP match function that matches against any string value.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / arp / ArpEngine.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.aa.arp;
18
19 import java.net.URI;
20 import java.net.URISyntaxException;
21 import java.security.Principal;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.Map;
29 import java.util.Set;
30
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import javax.xml.parsers.ParserConfigurationException;
33
34 import org.apache.log4j.Logger;
35 import org.w3c.dom.Document;
36 import org.w3c.dom.Element;
37 import org.w3c.dom.NodeList;
38 import org.w3c.dom.Text;
39
40 import edu.internet2.middleware.shibboleth.idp.IdPConfig;
41 import edu.internet2.middleware.shibboleth.xml.Parser;
42
43 /**
44  * Defines a processing engine for Attribute Release Policies.
45  * 
46  * @author Walter Hoehn (wassa@memphis.edu)
47  */
48
49 public class ArpEngine {
50
51         private static Logger log = Logger.getLogger(ArpEngine.class.getName());
52         private ArpRepository repository;
53         private static Map<URI, String> matchFunctions = Collections.synchronizedMap(new HashMap<URI, String>());
54         static {
55                 // Initialize built-in match functions
56                 try {
57
58                         // Current
59                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:regexMatch"),
60                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.RegexMatchFunction");
61                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:regexNotMatch"),
62                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.RegexNotMatchFunction");
63                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:stringMatch"),
64                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueMatchFunction");
65                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:stringNotMatch"),
66                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.StringValueNotMatchFunction");
67                         matchFunctions.put(new URI("urn:mace:shibboleth:arp:matchFunction:anyValueMatch"),
68                         "edu.internet2.middleware.shibboleth.aa.arp.provider.AnyValueMatchFunction");
69
70                 } catch (URISyntaxException e) {
71                         log.error("Error mapping standard match functions: " + e);
72                 }
73         }
74
75         /**
76          * Loads Arp Engine with default configuration
77          * 
78          * @throws ArpException
79          *             if engine cannot be loaded
80          */
81         public ArpEngine(Element config) throws ArpException {
82
83                 if (!config.getLocalName().equals("ReleasePolicyEngine")) { throw new IllegalArgumentException(); }
84
85                 NodeList itemElements = config.getElementsByTagNameNS(IdPConfig.configNameSpace, "ArpRepository");
86
87                 if (itemElements.getLength() > 1) {
88                         log
89                                         .warn("Encountered multiple <ArpRepository> configuration elements.  Arp Engine currently only supports one.  Using first...");
90                 }
91
92                 if (itemElements.getLength() == 0) {
93                         log.error("No <ArpRepsitory/> specified for this Arp Endine.");
94                         throw new ArpException("Could not start Arp Engine.");
95                 }
96
97                 try {
98                         repository = ArpRepositoryFactory.getInstance((Element) itemElements.item(0));
99                 } catch (ArpRepositoryException e) {
100                         log.error("Could not start Arp Engine: " + e);
101                         throw new ArpException("Could not start Arp Engine.");
102                 }
103         }
104
105         public ArpEngine(ArpRepository preLoadedRepository) throws ArpException {
106
107                 repository = preLoadedRepository;
108         }
109
110         /**
111          * Loads Arp Engine based on XML configurationf
112          * 
113          * @throws ArpException
114          *             if configuration is invalid or there is a problem loading the engine
115          */
116         public ArpEngine() throws ArpException {
117
118                 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
119                 docFactory.setNamespaceAware(true);
120                 Document placeHolder;
121                 try {
122                         placeHolder = docFactory.newDocumentBuilder().newDocument();
123
124                         Element defRepository = placeHolder.createElementNS(IdPConfig.configNameSpace, "ArpRepository");
125                         defRepository.setAttributeNS(IdPConfig.configNameSpace, "implementation",
126                                         "edu.internet2.middleware.shibboleth.aa.arp.provider.FileSystemArpRepository");
127
128                         Element path = placeHolder.createElementNS(IdPConfig.configNameSpace, "Path");
129                         Text text = placeHolder.createTextNode("/conf/arps/");
130                         path.appendChild(text);
131
132                         defRepository.appendChild(path);
133
134                         repository = ArpRepositoryFactory.getInstance(defRepository);
135
136                 } catch (ArpRepositoryException e) {
137                         log.error("Could not start Arp Engine: " + e);
138                         throw new ArpException("Could not start Arp Engine.");
139                 } catch (ParserConfigurationException e) {
140                         log.error("Problem loading parser to create default Arp Engine configuration: " + e);
141                         throw new ArpException("Could not start Arp Engine.");
142                 }
143         }
144
145         public static MatchFunction lookupMatchFunction(URI functionIdentifier) throws ArpException {
146
147                 String className = null;
148
149                 synchronized (matchFunctions) {
150                         className = (String) matchFunctions.get(functionIdentifier);
151                 }
152
153                 if (className == null) { return null; }
154                 try {
155                         Class matchFunction = Class.forName(className);
156                         Object functionObject = matchFunction.newInstance();
157                         if (functionObject instanceof MatchFunction) {
158                                 return (MatchFunction) functionObject;
159                         } else {
160                                 log.error("Improperly specified match function, (" + className + ") is not a match function.");
161                                 throw new ArpException("Improperly specified match function, (" + className
162                                                 + ") is not a match function.");
163                         }
164                 } catch (Exception e) {
165                         log.error("Could not load Match Function: (" + className + "): " + e);
166                         throw new ArpException("Could not load Match Function.");
167                 }
168         }
169
170         private Arp createEffectiveArp(Principal principal, String requester) throws ArpProcessingException {
171
172                 try {
173                         Arp effectiveArp = new Arp(principal);
174                         effectiveArp.setDescription("Effective ARP.");
175
176                         Arp[] userPolicies = repository.getAllPolicies(principal);
177
178                         if (log.isDebugEnabled()) {
179                                 log.debug("Creating effective ARP from (" + userPolicies.length + ") polic(y|ies).");
180                                 try {
181                                         for (int i = 0; userPolicies.length > i; i++) {
182                                                 String dump = Parser.serialize(userPolicies[i].unmarshall());
183                                                 log.debug("Dumping ARP:" + System.getProperty("line.separator") + dump);
184                                         }
185                                 } catch (Exception e) {
186                                         log
187                                                         .error("Encountered a strange error while writing ARP debug messages.  This should never happen.");
188                                 }
189                         }
190
191                         for (int i = 0; userPolicies.length > i; i++) {
192                                 Collection<Rule> rules = userPolicies[i].getMatchingRules(requester);
193
194                                 for (Rule rule : rules) {
195                                         effectiveArp.addRule(rule);
196                                 }
197                         }
198                         return effectiveArp;
199
200                 } catch (ArpRepositoryException e) {
201                         log.error("Error creating effective policy: " + e);
202                         throw new ArpProcessingException("Error creating effective policy.");
203                 }
204         }
205
206         /**
207          * Determines which attributes MIGHT be releasable for a given request. This function may be used to determine which
208          * attributes to resolve when a request for all attributes is made. This is done for performance reasons only. ie:
209          * The resulting attributes must still be filtered before release.
210          * 
211          * @return an array of <code>URI</code> objects that name the possible attributes
212          */
213         public Set<URI> listPossibleReleaseAttributes(Principal principal, String requester) throws ArpProcessingException {
214
215                 Set<URI> possibleReleaseSet = new HashSet<URI>();
216                 Set<URI> anyValueDenies = new HashSet<URI>();
217                 Collection<Rule> rules = createEffectiveArp(principal, requester).getAllRules();
218                 for (Rule rule : rules) {
219                         Collection<Rule.Attribute> attributes = rule.getAttributes();
220                         for (Rule.Attribute attribute : attributes) {
221                                 if (attribute.releaseAnyValue()) {
222                                         possibleReleaseSet.add(attribute.getName());
223                                 } else if (attribute.denyAnyValue()) {
224                                         anyValueDenies.add(attribute.getName());
225                                 } else {
226                                         Collection<Rule.AttributeValue> values = attribute.getValues();
227                                         for (Rule.AttributeValue value : values) {
228                                                 if (value.getRelease().equals("permit")) {
229                                                         possibleReleaseSet.add(attribute.getName());
230                                                         break;
231                                                 }
232                                         }
233                                 }
234                         }
235                 }
236                 possibleReleaseSet.removeAll(anyValueDenies);
237                 if (log.isDebugEnabled()) {
238                         log.debug("Computed possible attribute release set.");
239                         Iterator iterator = possibleReleaseSet.iterator();
240                         while (iterator.hasNext()) {
241                                 log.debug("Possible attribute: " + iterator.next().toString());
242                         }
243                 }
244                 return possibleReleaseSet;
245         }
246
247         /**
248          * Applies all applicable ARPs to a set of attributes.
249          * 
250          * @return the attributes to be released
251          */
252         public void filterAttributes(Collection<? extends ArpAttribute> attributes, Principal principal, String requester)
253                         throws ArpProcessingException {
254
255                 if (attributes.isEmpty()) {
256                         log.debug("ARP Engine was asked to apply filter to empty attribute set.");
257                         return;
258                 }
259
260                 log.info("Applying Attribute Release Policies.");
261                 if (log.isDebugEnabled()) {
262                         log.debug("Processing the following attributes:");
263                         for (Iterator<? extends ArpAttribute> attrIterator = attributes.iterator(); attrIterator.hasNext();) {
264                                 log.debug("Attribute: (" + attrIterator.next().getName() + ")");
265                         }
266                 }
267
268                 // Gather all applicable ARP attribute specifiers
269                 Set<String> attributeNames = new HashSet<String>();
270                 for (Iterator<? extends ArpAttribute> nameIterator = attributes.iterator(); nameIterator.hasNext();) {
271                         attributeNames.add(nameIterator.next().getName());
272                 }
273                 Collection<Rule> rules = createEffectiveArp(principal, requester).getAllRules();
274                 Set<Rule.Attribute> applicableRuleAttributes = new HashSet<Rule.Attribute>();
275                 for (Rule rule : rules) {
276                         Collection<Rule.Attribute> ruleAttributes = rule.getAttributes();
277                         for (Rule.Attribute ruleAttribute : ruleAttributes) {
278                                 if (attributeNames.contains(ruleAttribute.getName().toString())) {
279                                         applicableRuleAttributes.add(ruleAttribute);
280                                 }
281                         }
282                 }
283
284                 // Canonicalize specifiers
285                 Map arpAttributeSpecs = createCanonicalAttributeSpec((Rule.Attribute[]) applicableRuleAttributes
286                                 .toArray(new Rule.Attribute[0]));
287
288                 // Filter
289                 for (Iterator<? extends ArpAttribute> returnIterator = attributes.iterator(); returnIterator.hasNext();) {
290
291                         ArpAttribute arpAttribute = returnIterator.next();
292                         Rule.Attribute attribute = (Rule.Attribute) arpAttributeSpecs.get(arpAttribute.getName());
293
294                         // Handle no specifier
295                         if (attribute == null) {
296                                 returnIterator.remove();
297                                 continue;
298                         }
299
300                         // Handle Deny All
301                         if (attribute.denyAnyValue()) {
302                                 returnIterator.remove();
303                                 continue;
304                         }
305
306                         // Handle Permit All
307                         if (attribute.releaseAnyValue() && attribute.getValues().size() == 0) {
308                                 continue;
309                         }
310
311                         // Handle "Permit All-Except" and "Permit Specific"
312                         ArrayList<Object> releaseValues = new ArrayList<Object>();
313                         for (Iterator valueIterator = arpAttribute.getValues(); valueIterator.hasNext();) {
314                                 Object value = valueIterator.next();
315                                 if (attribute.isValuePermitted(value)) {
316                                         releaseValues.add(value);
317                                 }
318                         }
319
320                         if (!releaseValues.isEmpty()) {
321                                 arpAttribute.setValues((Object[]) releaseValues.toArray(new Object[0]));
322                         } else {
323                                 returnIterator.remove();
324                         }
325
326                 }
327         }
328
329         private Map<String, Rule.Attribute> createCanonicalAttributeSpec(Rule.Attribute[] attributes) {
330
331                 Map<String, Rule.Attribute> canonicalSpec = new HashMap<String, Rule.Attribute>();
332                 for (int i = 0; attributes.length > i; i++) {
333                         if (!canonicalSpec.containsKey(attributes[i].getName().toString())) {
334                                 canonicalSpec.put(attributes[i].getName().toString(), attributes[i]);
335                         } else {
336                                 if (((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).denyAnyValue()) {
337                                         continue;
338                                 }
339                                 if (attributes[i].denyAnyValue()) {
340                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).setAnyValueDeny(true);
341                                         continue;
342                                 }
343                                 if (attributes[i].releaseAnyValue()) {
344                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).setAnyValuePermit(true);
345                                 }
346                                 Collection<Rule.AttributeValue> values = attributes[i].getValues();
347                                 for (Rule.AttributeValue value : values) {
348                                         ((Rule.Attribute) canonicalSpec.get(attributes[i].getName().toString())).addValue(value);
349                                 }
350                         }
351                 }
352                 return canonicalSpec;
353         }
354
355         /**
356          * Cleanup resources that won't be released when this object is garbage-collected
357          */
358         public void destroy() {
359
360                 repository.destroy();
361         }
362
363 }