First cut at Attribute Policy enforcement.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / arp / Rule.java
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
14  * Redistributions in binary form must reproduce the above copyright notice, 
15  * this list of conditions and the following disclaimer in the documentation 
16  * and/or other materials provided with the distribution, if any, must include 
17  * the following acknowledgment: "This product includes software developed by 
18  * the University Corporation for Advanced Internet Development 
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
20  * may appear in the software itself, if and wherever such third-party 
21  * acknowledgments normally appear.
22  * 
23  * Neither the name of Shibboleth nor the names of its contributors, nor 
24  * Internet2, nor the University Corporation for Advanced Internet Development, 
25  * Inc., nor UCAID may be used to endorse or promote products derived from this 
26  * software without specific prior written permission. For written permission, 
27  * please contact shibboleth@shibboleth.org
28  * 
29  * Products derived from this software may not be called Shibboleth, Internet2, 
30  * UCAID, or the University Corporation for Advanced Internet Development, nor 
31  * may Shibboleth appear in their name, without prior written permission of the 
32  * University Corporation for Advanced Internet Development.
33  * 
34  * 
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 package edu.internet2.middleware.shibboleth.aa.arp;
51
52 import java.net.URI;
53 import java.net.URISyntaxException;
54 import java.net.URL;
55 import java.util.ArrayList;
56 import java.util.HashSet;
57 import java.util.Iterator;
58 import java.util.Set;
59
60 import org.apache.log4j.Logger;
61 import org.apache.xerces.parsers.DOMParser;
62 import org.w3c.dom.CharacterData;
63 import org.w3c.dom.Document;
64 import org.w3c.dom.Element;
65 import org.w3c.dom.Node;
66 import org.w3c.dom.NodeList;
67
68 /**
69  *  An Attribute Release Policy Rule.
70  *
71  * @author Walter Hoehn (wassa@columbia.edu)
72  */
73
74 public class Rule {
75
76         private String description;
77         private Target target;
78         private static Logger log = Logger.getLogger(Rule.class.getName());
79         private ArrayList attributes = new ArrayList();
80
81         /**
82          * Returns the description for this <code>Rule</code>.
83          * @return String
84          */
85
86         public String getDescription() {
87                 return description;
88         }
89
90         /**
91          * Sets the description for this <code>Rule</code>.
92          * @param description The description to set
93          */
94
95         public void setDescription(String description) {
96                 this.description = description;
97         }
98
99         /**
100          * Unmarshalls the <code>Rule</code> into an xml <code>Element</code>.
101          * @return the xml <code>Element</code>
102          */
103         
104         public Attribute[] getAttributes() {
105                 return (Attribute[]) attributes.toArray(new Attribute[0]);      
106         }
107
108         public Element unmarshall() {
109
110                 DOMParser parser = new DOMParser();
111                 Document placeHolder = parser.getDocument();
112                 Element ruleNode = placeHolder.createElement("Rule");
113
114                 if (description != null) {
115                         Element descriptionNode = placeHolder.createElement("Description");
116                         descriptionNode.appendChild(placeHolder.createTextNode(description));
117                         ruleNode.appendChild(descriptionNode);
118                 }
119
120                 return ruleNode;
121         }
122
123         /**
124          * Creates an ARP Rule from an xml representation.
125          * @param the xml <code>Element</code> containing the ARP Rule.
126          */
127
128         public void marshall(Element element) throws ArpMarshallingException {
129
130                 //Make sure we are dealing with a Rule
131                 if (!element.getTagName().equals("Rule")) {
132                         log.error("Element data does not represent an ARP Rule.");
133                         throw new ArpMarshallingException("Element data does not represent an ARP Rule.");
134                 }
135
136                 //Grab the description
137                 NodeList descriptionNodes = element.getElementsByTagName("Description");
138                 if (descriptionNodes.getLength() > 0) {
139                         Element descriptionNode = (Element) descriptionNodes.item(0);
140                         if (descriptionNode.hasChildNodes()
141                                 && descriptionNode.getFirstChild().getNodeType() == Node.TEXT_NODE) {
142                                 description = ((CharacterData) descriptionNode.getFirstChild()).getData();
143                         }
144                 }
145
146                 //Create the Target
147                 NodeList targetNodes = element.getElementsByTagName("Target");
148                 if (targetNodes.getLength() != 1) {
149                         log.error(
150                                 "Element data does not represent an ARP Rule.  An ARP Rule must contain 1 and "
151                                         + "only 1 Target definition.");
152                         throw new ArpMarshallingException(
153                                 "Element data does not represent an ARP Rule.  An"
154                                         + " ARP Rule must contain 1 and only 1 Target definition.");
155                 }
156                 target = new Target();
157                 target.marshall((Element) targetNodes.item(0));
158
159                 //Create the Attributes
160                 NodeList attributeNodes = element.getElementsByTagName("Attribute");
161                 for (int i = 0; attributeNodes.getLength() > i; i++) {
162                         Attribute attribute = new Attribute();
163                         attribute.marshall((Element) attributeNodes.item(i));
164                         attributes.add(attribute);
165                 }
166         }
167
168         /**
169          * Method matchesRequest.
170          * @param requester
171          * @param resource
172          * @return boolean
173          */
174         public boolean matchesRequest(String requester, URL resource) {
175                 if (target.matchesAny()) {
176                         return true;
177                 }
178                 try {
179                         MatchFunction requesterFunction =
180                                 ArpEngine.lookupMatchFunction(target.getRequester().getMatchFunctionIdentifier());
181                         if (!requesterFunction.match(target.getRequester().getValue(), requester)) {
182                                 return false;
183                         }
184                         if (target.getResource().matchesAny()) {
185                                 return true;
186                         }
187                         MatchFunction resourceFunction =
188                                 ArpEngine.lookupMatchFunction(target.getResource().getMatchFunctionIdentifier());
189                         if (resourceFunction.match(target.getResource().getValue(), resource)) {
190                                 return true;
191                         }
192                         return false;
193                 } catch (ArpException e) {
194                         log.warn("Encountered a problem while trying to find matching ARP rules: " + e);
195                         return false;
196                 }
197         }
198
199         class Target {
200                 private Requester requester = null;
201                 private Resource resource = null;
202                 private boolean matchesAny = false;
203
204                 void marshall(Element element) throws ArpMarshallingException {
205
206                         //Make sure we are dealing with a Target
207                         if (!element.getTagName().equals("Target")) {
208                                 log.error("Element data does not represent an ARP Rule Target.");
209                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
210                         }
211
212                         //Handle <AnyTarget/> definitions
213                         NodeList anyTargetNodeList = element.getElementsByTagName("AnyTarget");
214                         if (anyTargetNodeList.getLength() == 1) {
215                                 matchesAny = true;
216                                 return;
217                         }
218
219                         //Create Requester
220                         NodeList requesterNodeList = element.getElementsByTagName("Requester");
221                         if (requesterNodeList.getLength() == 1) {
222                                 requester = new Requester();
223                                 requester.marshall((Element) requesterNodeList.item(0));
224                         } else {
225                                 log.error("ARP Rule Target contains invalid data: incorrectly specified <Requester>.");
226                                 throw new ArpMarshallingException("ARP Rule Target contains invalid data: incorrectly specified <Requester>.");
227                         }
228
229                         //Handle <AnyResource/>
230                         NodeList anyResourceNodeList = element.getElementsByTagName("AnyResource");
231                         if (anyResourceNodeList.getLength() == 1) {
232                                 resource = new Resource();
233                                 return;
234                         }
235
236                         //Create Resource
237                         NodeList resourceNodeList = element.getElementsByTagName("Resource");
238                         if (resourceNodeList.getLength() == 1) {
239                                 resource = new Resource();
240                                 resource.marshall((Element) resourceNodeList.item(0));
241                         } else {
242                                 log.error("ARP Rule Target contains invalid data: incorrectly specified <Resource>.");
243                                 throw new ArpMarshallingException("ARP Rule Target contains invalid data: incorrectly specified <Resource>.");
244                         }
245                 }
246
247                 boolean matchesAny() {
248                         return matchesAny;
249                 }
250                 Requester getRequester() {
251                         return requester;
252                 }
253                 Resource getResource() {
254                         return resource;
255                 }
256         }
257
258         class Resource {
259                 private String value;
260                 private URI matchFunctionIdentifier;
261                 private boolean matchesAny;
262                 Resource() {
263                         matchesAny = true;
264                 }
265                 boolean matchesAny() {
266                         return matchesAny;
267                 }
268                 URI getMatchFunctionIdentifier() {
269                         return matchFunctionIdentifier;
270                 }
271                 String getValue() {
272                         return value;
273                 }
274                 void marshall(Element element) throws ArpMarshallingException {
275                         //Make sure we are deling with a Resource
276                         if (!element.getTagName().equals("Resource")) {
277                                 log.error("Element data does not represent an ARP Rule Target.");
278                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
279                         }
280
281                         //Grab the value
282                         if (element.hasChildNodes() && element.getFirstChild().getNodeType() == Node.TEXT_NODE) {
283                                 value = ((CharacterData) element.getFirstChild()).getData();
284                         } else {
285                                 log.error("Element data does not represent an ARP Rule Target.");
286                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
287                         }
288
289                         //Grab the match function
290                         try {
291                                 if (element.hasAttribute("matchFunction")) {
292                                         matchFunctionIdentifier = new URI(element.getAttribute("matchFunction"));
293                                 } else {
294                                         matchFunctionIdentifier = new URI("urn:mace:shibboleth:arp:matchFunction:resourceTree");
295                                 }
296                         } catch (URISyntaxException e) {
297                                 log.error("ARP match function not identified by a proper URI.");
298                                 throw new ArpMarshallingException("ARP match function not identified by a proper URI.");
299                         }
300                 }
301         }
302
303         class Requester {
304                 private String value;
305                 private URI matchFunctionIdentifier;
306                 URI getMatchFunctionIdentifier() {
307                         return matchFunctionIdentifier;
308                 }
309                 String getValue() {
310                         return value;
311                 }
312                 void marshall(Element element) throws ArpMarshallingException {
313                         //Make sure we are deling with a Requester
314                         if (!element.getTagName().equals("Requester")) {
315                                 log.error("Element data does not represent an ARP Rule Target.");
316                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
317                         }
318
319                         //Grab the value
320                         if (element.hasChildNodes() && element.getFirstChild().getNodeType() == Node.TEXT_NODE) {
321                                 value = ((CharacterData) element.getFirstChild()).getData();
322                         } else {
323                                 log.error("Element data does not represent an ARP Rule Target.");
324                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
325                         }
326
327                         //Grab the match function
328                         try {
329                                 if (element.hasAttribute("matchFunction")) {
330                                         matchFunctionIdentifier = new URI(element.getAttribute("matchFunction"));
331                                 } else {
332                                         matchFunctionIdentifier = new URI("urn:mace:shibboleth:arp:matchFunction:exactShar");
333                                 }
334                         } catch (URISyntaxException e) {
335                                 log.error("ARP match function not identified by a proper URI.");
336                                 throw new ArpMarshallingException("ARP match function not identified by a proper URI.");
337                         }
338                 }
339         }
340
341         class Attribute {
342                 private URI name;
343                 private boolean anyValue = false;
344                 private String anyValueRelease = "permit";
345                 private Set values = new HashSet();
346
347                 boolean releaseAnyValue() {
348                         if (anyValueRelease.equals("permit")) {
349                                 return anyValue;
350                         }
351                         return false;
352                 }
353                 
354                 boolean denyAnyValue() {
355                         if (anyValueRelease.equals("deny")) {
356                                 return anyValue;
357                         }
358                         return false;
359                 }
360                 
361                 void setAnyValueDeny(boolean b) {
362                         if (b) {
363                         anyValue = true;
364                         anyValueRelease = "deny";
365                         values.clear();
366                         } else {
367                                 if (anyValueRelease.equals("deny") && anyValue) {
368                                         anyValue = false;       
369                                 }
370                         }
371                 }
372                 
373                 boolean isValuePermitted(Object value) {
374                         //Handle Deny All
375                         if (denyAnyValue()) {
376                                 return false;
377                         }
378
379                         //Handle Permit All with no specific values
380                         if (releaseAnyValue() && getValues().length == 0) {
381                                 return true;
382                         }
383
384                         //Handle Deny Specific
385                         Iterator iterator = values.iterator();
386                         while (iterator.hasNext()) {
387                                 AttributeValue valueSpec = (AttributeValue) iterator.next();
388                                 if (valueSpec.getValue().equals(value) && valueSpec.getRelease().equals("deny")) {
389                                         return false;
390                                 }
391                         }
392                         //Handle Permit All with no relevant specific denies
393                         if (releaseAnyValue()) {
394                                 return true;
395                         }
396
397                         //Handle Permit Specific
398                         iterator = values.iterator();
399                         while (iterator.hasNext()) {
400                                 AttributeValue valueSpec = (AttributeValue) iterator.next();
401                                 if (valueSpec.getValue().equals(value) && valueSpec.getRelease().equals("permit")) {
402                                         return true;
403                                 }
404                         }
405
406                         return false;
407                 }
408                 
409                 void setAnyValuePermit(boolean b) {
410                         if (b) {
411                                 anyValue = true;
412                                 anyValueRelease = "permit";
413                                 Iterator iterator = values.iterator();
414                                 while (iterator.hasNext()) {
415                                         AttributeValue value = (AttributeValue) iterator.next();
416                                         if (value.getRelease().equals("permit")) {
417                                                 values.remove(value);
418                                         }
419                                 }
420                         } else {
421                                 if (anyValueRelease.equals("permit") && anyValue) {
422                                         anyValue = false;
423                                 }
424                         }
425                 }
426                 
427                 URI getName() {
428                         return name;    
429                 }
430                 AttributeValue[] getValues() {
431                         return (AttributeValue[]) values.toArray(new AttributeValue[0]);        
432                 }
433                 
434                 void addValue(AttributeValue value) {
435                         if (denyAnyValue()) {
436                                 return;
437                         }
438                         if (releaseAnyValue() && value.getRelease().equals("permit")) {
439                                 return;
440                         }
441                         values.add(value);      
442                 }
443
444                 void marshall(Element element) throws ArpMarshallingException {
445                         //Make sure we are dealing with an Attribute
446                         if (!element.getTagName().equals("Attribute")) {
447                                 log.error("Element data does not represent an ARP Rule Target.");
448                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
449                         }
450
451                         //Get the attribute name
452                         try {
453                                 if (element.hasAttribute("name")) {
454                                         name = new URI(element.getAttribute("name"));
455                                 } else {
456                                         log.error("Attribute name not specified.");
457                                         throw new ArpMarshallingException("Attribute name not specified.");
458                                 }
459                         } catch (URISyntaxException e) {
460                                 log.error("Attribute name not identified by a proper URI: " + e);
461                                 throw new ArpMarshallingException("Attribute name not identified by a proper URI.");
462                         }
463
464                                 //Handle <AnyValue/> definitions
465                                 NodeList anyValueNodeList = element.getElementsByTagName("AnyValue");
466                                 if (anyValueNodeList.getLength() == 1) {
467                                         anyValue = true;
468                                         if (((Element) anyValueNodeList.item(0)).hasAttribute("release")) {
469                                                 anyValueRelease = ((Element) anyValueNodeList.item(0)).getAttribute("release");
470                                         }
471                                 }
472
473                                 //Handle Value definitions
474                                 if (!denyAnyValue()) {
475                                 NodeList valueNodeList = element.getElementsByTagName("Value");
476                                 for (int i = 0; valueNodeList.getLength() > i; i++) {
477                                         String release = null;
478                                         String value = null;
479                                         if (((Element) valueNodeList.item(i)).hasAttribute("release")) {
480                                                 release = ((Element) valueNodeList.item(i)).getAttribute("release");
481                                         }
482                                         if (((Element) valueNodeList.item(i)).hasChildNodes()
483                                                 && ((Element) valueNodeList.item(i)).getFirstChild().getNodeType() == Node.TEXT_NODE) {
484                                                 value = ((CharacterData) ((Element) valueNodeList.item(i)).getFirstChild()).getData();
485                                         }
486                                         if (releaseAnyValue() && release.equals("permit")) {
487                                                 continue;
488                                         }
489                                         AttributeValue aValue = new AttributeValue(release, value);
490                                         values.add(aValue);
491                                 }
492                                 }
493
494                         }
495         }
496         class AttributeValue {
497                 private String release = "permit";
498                 private String value;
499
500                 AttributeValue(String release, String value) {
501                         setRelease(release);
502                         this.value = value;
503                 }
504
505                 String getRelease() {
506                         return release;
507                 }
508
509                 String getValue() {
510                         return value;
511                 }
512
513                 void setRelease(String release) {
514                         if (release == null) {
515                                 return;
516                         }
517                         if (release.equals("permit") || release.equals("deny")) {
518                                 this.release = release;
519                         }
520                 }
521
522                 void setValue(String value) {
523                         this.value = value;
524                 }
525         }
526
527 }