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