Hit a small patch of code that was relying on validation for default values.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / arp / Rule.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.util.ArrayList;
22 import java.util.Collection;
23 import java.util.HashSet;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Set;
27
28 import javax.xml.parsers.DocumentBuilderFactory;
29 import javax.xml.parsers.ParserConfigurationException;
30
31 import org.apache.log4j.Logger;
32 import org.w3c.dom.CharacterData;
33 import org.w3c.dom.Document;
34 import org.w3c.dom.Element;
35 import org.w3c.dom.Node;
36 import org.w3c.dom.NodeList;
37 import org.w3c.dom.Text;
38
39 /**
40  * An Attribute Release Policy Rule.
41  * 
42  * @author Walter Hoehn (wassa@memphis.edu)
43  */
44
45 public class Rule {
46
47         private String description;
48         private Target target;
49         private static Logger log = Logger.getLogger(Rule.class.getName());
50         private ArrayList<Attribute> attributes = new ArrayList<Attribute>();
51         private ArrayList<Constraint> constraints = new ArrayList<Constraint>();
52         private NodeList attributeReferences;
53
54         private URI identifier;
55
56         /**
57          * Returns the description for this <code>Rule</code>.
58          * 
59          * @return String
60          */
61
62         public String getDescription() {
63
64                 return description;
65         }
66
67         /**
68          * Sets the description for this <code>Rule</code>.
69          * 
70          * @param description
71          *            The description to set
72          */
73
74         public void setDescription(String description) {
75
76                 this.description = description;
77         }
78
79         /**
80          * Returns all of the attribute specifications associated with this Rule.
81          * 
82          * @return the attributes
83          */
84
85         public Collection<Attribute> getAttributes() {
86
87                 return attributes;
88         }
89
90         /**
91          * Returns all of the constraint specifications associated with this Rule.
92          * 
93          * @return the constraints
94          */
95
96         public Collection<Constraint> getConstraints() {
97
98                 return constraints;
99         }
100
101         /**
102          * Unmarshalls the <code>Rule</code> into an xml <code>Element</code>.
103          * 
104          * @return the xml <code>Element</code>
105          */
106
107         public Element unmarshall() throws ArpMarshallingException {
108
109                 try {
110                         Document placeHolder = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
111                         Element ruleNode = placeHolder.createElementNS(Arp.arpNamespace, "Rule");
112
113                         if (identifier != null) {
114                                 ruleNode.setAttributeNS(Arp.arpNamespace, "identifier", identifier.toString());
115                         }
116
117                         if (description != null) {
118                                 Element descriptionNode = placeHolder.createElementNS(Arp.arpNamespace, "Description");
119                                 descriptionNode.appendChild(placeHolder.createTextNode(description));
120                                 ruleNode.appendChild(descriptionNode);
121                         }
122
123                         for (Constraint constraint : constraints) {
124                                 ruleNode.appendChild(placeHolder.importNode(constraint.unmarshall(), true));
125                         }
126
127                         ruleNode.appendChild(placeHolder.importNode(target.unmarshall(), true));
128                         Iterator attrIterator = attributes.iterator();
129                         while (attrIterator.hasNext()) {
130                                 ruleNode.appendChild(placeHolder.importNode(((Attribute) attrIterator.next()).unmarshall(), true));
131                         }
132
133                         if (attributeReferences != null) {
134                                 for (int i = 0; i < attributeReferences.getLength(); i++) {
135                                         ruleNode.appendChild(placeHolder.importNode(attributeReferences.item(i), true));
136                                 }
137                         }
138                         return ruleNode;
139                 } catch (ParserConfigurationException e) {
140                         log.error("Encountered a problem unmarshalling an ARP Rule: " + e);
141                         throw new ArpMarshallingException("Encountered a problem unmarshalling an ARP Rule.");
142                 }
143         }
144
145         /**
146          * Creates an ARP Rule from an xml representation.
147          * 
148          * @param element
149          *            the xml <code>Element</code> containing the ARP Rule.
150          */
151
152         public void marshall(Element element) throws ArpMarshallingException {
153
154                 // Make sure we are dealing with a Rule
155                 if (!element.getTagName().equals("Rule")) {
156                         log.error("Element data does not represent an ARP Rule.");
157                         throw new ArpMarshallingException("Element data does not represent an ARP Rule.");
158                 }
159
160                 // Get the rule identifier
161                 try {
162                         if (element.hasAttribute("identifier")) {
163                                 identifier = new URI(element.getAttribute("identifier"));
164                         }
165                 } catch (URISyntaxException e) {
166                         log.error("Rule not identified by a proper URI: " + e);
167                         throw new ArpMarshallingException("Rule not identified by a proper URI.");
168                 }
169
170                 // Grab the description
171                 NodeList descriptionNodes = element.getElementsByTagNameNS(Arp.arpNamespace, "Description");
172                 if (descriptionNodes.getLength() > 0) {
173                         Element descriptionNode = (Element) descriptionNodes.item(0);
174                         if (descriptionNode.hasChildNodes() && descriptionNode.getFirstChild().getNodeType() == Node.TEXT_NODE) {
175                                 description = ((CharacterData) descriptionNode.getFirstChild()).getData();
176                         }
177                 }
178
179                 // Create the Constraints
180                 NodeList constraintNodes = element.getElementsByTagNameNS(Arp.arpNamespace, "Constraint");
181                 for (int i = 0; constraintNodes.getLength() > i; i++) {
182                         Constraint constraint = new Constraint();
183                         constraint.marshall((Element) constraintNodes.item(i));
184                         constraints.add(constraint);
185                 }
186
187                 // Create the Target
188                 NodeList targetNodes = element.getElementsByTagNameNS(Arp.arpNamespace, "Target");
189                 if (targetNodes.getLength() != 1) {
190                         log.error("Element data does not represent an ARP Rule.  An ARP Rule must contain 1 and "
191                                         + "only 1 Target definition.");
192                         throw new ArpMarshallingException("Element data does not represent an ARP Rule.  An"
193                                         + " ARP Rule must contain 1 and only 1 Target definition.");
194                 }
195                 target = new Target();
196                 target.marshall((Element) targetNodes.item(0));
197
198                 // Create the Attributes
199                 NodeList attributeNodes = element.getElementsByTagNameNS(Arp.arpNamespace, "Attribute");
200                 for (int i = 0; attributeNodes.getLength() > i; i++) {
201                         Attribute attribute = new Attribute();
202                         attribute.marshall((Element) attributeNodes.item(i));
203                         attributes.add(attribute);
204                 }
205
206                 // Retain Attribute references
207                 // Not enforced!
208                 NodeList attributeReferenceNodes = element.getElementsByTagNameNS(Arp.arpNamespace, "AttributeReference");
209                 if (attributeReferenceNodes.getLength() > 0) {
210                         log.warn("Encountered an Attribute Reference while marshalling an ARP.  "
211                                         + "References are currently unsupported by the ARP Engine.  Ignoring...");
212                         attributeReferences = attributeReferenceNodes;
213                 }
214         }
215
216         /**
217          * Returns a boolean indication of whether this rule is applicable to a given attribute request.
218          * 
219          * @param requester
220          *            the SHAR making the request
221          */
222
223         public boolean matchesRequest(String requester, Collection<? extends ArpAttribute> attributes) {
224
225                 // if we have attributes for the user, then verify all constraints are met.
226                 // the only time we won't have attributes should be when listing possible attributes
227                 // to be released -- ArpEngine.listPossibleReleaseAttributes()
228                 if (attributes != null) {
229                         for (Constraint constraint : constraints) {
230                                 if (!constraint.allowed(attributes)) { return false; }
231                         }
232                 }
233
234                 if (target.matchesAny()) { return true; }
235
236                 if (requester == null) { return false; }
237
238                 for (Requester arpRequester : target.getRequesters()) {
239
240                         try {
241                                 MatchFunction requesterFunction = ArpEngine.lookupMatchFunction(arpRequester
242                                                 .getMatchFunctionIdentifier());
243                                 if (requesterFunction.match(arpRequester.getValue(), requester)) { return true; }
244
245                         } catch (ArpException e) {
246                                 log.warn("Encountered a problem while trying to find matching ARP rules: " + e);
247                                 return false; // Always err on the side of caution
248                         }
249                 }
250
251                 return false;
252         }
253
254         class Target {
255
256                 private List<Requester> requesters = new ArrayList<Requester>();
257                 private boolean matchesAny = false;
258
259                 /**
260                  * Unmarshalls the <code>Rule.Target</code> into an xml <code>Element</code>.
261                  * 
262                  * @return the xml <code>Element</code>
263                  */
264
265                 Element unmarshall() throws ArpMarshallingException {
266
267                         try {
268                                 Document placeHolder = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
269                                 Element targetNode = placeHolder.createElementNS(Arp.arpNamespace, "Target");
270
271                                 if (matchesAny) {
272                                         Element anyTargetNode = placeHolder.createElementNS(Arp.arpNamespace, "AnyTarget");
273                                         targetNode.appendChild(anyTargetNode);
274                                         return targetNode;
275                                 }
276                                 for (Requester requester : requesters) {
277                                         targetNode.appendChild(placeHolder.importNode(requester.unmarshall(), true));
278                                 }
279                                 return targetNode;
280                         } catch (ParserConfigurationException e) {
281                                 log.error("Encountered a problem unmarshalling an ARP Rule: " + e);
282                                 throw new ArpMarshallingException("Encountered a problem unmarshalling an ARP Rule.");
283                         }
284                 }
285
286                 /**
287                  * Creates an ARP Rule Target from an xml representation.
288                  * 
289                  * @param element
290                  *            the xml <code>Element</code> containing the ARP Rule.
291                  */
292                 void marshall(Element element) throws ArpMarshallingException {
293
294                         // Make sure we are dealing with a Target
295                         if (!element.getTagName().equals("Target")) {
296                                 log.error("Element data does not represent an ARP Rule Target.");
297                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
298                         }
299
300                         // Handle <AnyTarget/> definitions
301                         NodeList anyTargetNodeList = element.getElementsByTagNameNS(Arp.arpNamespace, "AnyTarget");
302                         if (anyTargetNodeList.getLength() == 1) {
303                                 matchesAny = true;
304                                 return;
305                         }
306
307                         // Create Requester
308                         NodeList requesterNodeList = element.getElementsByTagNameNS(Arp.arpNamespace, "Requester");
309
310                         if (requesterNodeList.getLength() < 1) {
311                                 log.error("ARP Rule Target contains invalid data: no specified <Requester/>.");
312                                 throw new ArpMarshallingException("ARP Rule Target contains invalid data: no specified <Requester/>.");
313                         }
314
315                         for (int i = 0; i < requesterNodeList.getLength(); i++) {
316                                 Requester requester = new Requester();
317                                 requester.marshall((Element) requesterNodeList.item(i));
318                                 requesters.add(requester);
319                         }
320                 }
321
322                 boolean matchesAny() {
323
324                         return matchesAny;
325                 }
326
327                 Collection<Requester> getRequesters() {
328
329                         return requesters;
330                 }
331         }
332
333         class Requester {
334
335                 private String value;
336                 private URI matchFunctionIdentifier;
337
338                 URI getMatchFunctionIdentifier() {
339
340                         return matchFunctionIdentifier;
341                 }
342
343                 String getValue() {
344
345                         return value;
346                 }
347
348                 /**
349                  * Unmarshalls the <code>Rule.Requester</code> into an xml <code>Element</code>.
350                  * 
351                  * @return the xml <code>Element</code>
352                  */
353
354                 Element unmarshall() throws ArpMarshallingException {
355
356                         try {
357                                 Document placeHolder = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
358                                 Element requesterNode = placeHolder.createElementNS(Arp.arpNamespace, "Requester");
359                                 if (!matchFunctionIdentifier.equals(new URI("urn:mace:shibboleth:arp:matchFunction:stringMatch"))) {
360                                         requesterNode.setAttributeNS(Arp.arpNamespace, "matchFunction", matchFunctionIdentifier.toString());
361                                 }
362                                 Text valueNode = placeHolder.createTextNode(value);
363                                 requesterNode.appendChild(valueNode);
364                                 return requesterNode;
365
366                         } catch (URISyntaxException e) {
367                                 log.error("Encountered a problem unmarshalling an ARP Rule Requester: " + e);
368                                 throw new ArpMarshallingException("Encountered a problem unmarshalling an ARP Rule Requester.");
369                         } catch (ParserConfigurationException e) {
370                                 log.error("Encountered a problem unmarshalling an ARP Rule Requester: " + e);
371                                 throw new ArpMarshallingException("Encountered a problem unmarshalling an ARP Rule Requester.");
372                         }
373                 }
374
375                 /**
376                  * Creates an ARP Rule Target Requester from an xml representation.
377                  * 
378                  * @param element
379                  *            the xml <code>Element</code> containing the ARP Rule.
380                  */
381                 void marshall(Element element) throws ArpMarshallingException {
382
383                         // Make sure we are deling with a Requester
384                         if (!element.getTagName().equals("Requester")) {
385                                 log.error("Element data does not represent an ARP Rule Target.");
386                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
387                         }
388
389                         // Grab the value
390                         if (element.hasChildNodes() && element.getFirstChild().getNodeType() == Node.TEXT_NODE) {
391                                 value = ((CharacterData) element.getFirstChild()).getData();
392                         } else {
393                                 log.error("Element data does not represent an ARP Rule Target.");
394                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
395                         }
396
397                         // Grab the match function
398                         try {
399                                 if (element.hasAttribute("matchFunction")) {
400                                         matchFunctionIdentifier = new URI(element.getAttribute("matchFunction"));
401                                 } else {
402                                         matchFunctionIdentifier = new URI("urn:mace:shibboleth:arp:matchFunction:stringMatch");
403                                 }
404                         } catch (URISyntaxException e) {
405                                 log.error("ARP match function not identified by a proper URI.");
406                                 throw new ArpMarshallingException("ARP match function not identified by a proper URI.");
407                         }
408                 }
409         }
410
411         class Attribute {
412
413                 private URI name;
414                 private boolean anyValue = false;
415                 private String anyValueRelease = "permit";
416                 private Set<AttributeValue> values = new HashSet<AttributeValue>();
417                 private URI identifier;
418
419                 boolean releaseAnyValue() {
420
421                         if (anyValueRelease.equals("permit")) { return anyValue; }
422                         return false;
423                 }
424
425                 boolean denyAnyValue() {
426
427                         if (anyValueRelease.equals("deny")) { return anyValue; }
428                         return false;
429                 }
430
431                 void setAnyValueDeny(boolean b) {
432
433                         if (b) {
434                                 anyValue = true;
435                                 anyValueRelease = "deny";
436                                 values.clear();
437                         } else {
438                                 if (anyValueRelease.equals("deny") && anyValue) {
439                                         anyValue = false;
440                                 }
441                         }
442                 }
443
444                 boolean isValuePermitted(Object value) {
445
446                         // Handle Deny All
447                         if (denyAnyValue()) { return false; }
448
449                         // Handle Permit All with no specific values
450                         if (releaseAnyValue() && getValues().size() == 0) { return true; }
451
452                         // Handle Deny Specific
453                         Iterator iterator = values.iterator();
454                         while (iterator.hasNext()) {
455                                 AttributeValue valueSpec = (AttributeValue) iterator.next();
456
457                                 MatchFunction resourceFunction;
458                                 try {
459                                         resourceFunction = ArpEngine.lookupMatchFunction(valueSpec.getMatchFunctionIdentifier());
460                                         // For safety, err on the side of caution
461                                         if (resourceFunction == null) {
462                                                 log.warn("Could not locate matching function for ARP value. Function: "
463                                                                 + valueSpec.getMatchFunctionIdentifier().toString());
464                                                 return false;
465                                         }
466
467                                 } catch (ArpException e) {
468                                         log.error("Error while attempting to find referenced matching function for ARP values: " + e);
469                                         return false;
470                                 }
471
472                                 try {
473                                         if (valueSpec.getRelease().equals("deny") && resourceFunction.match(valueSpec.getValue(), value)) { return false; }
474                                 } catch (MatchingException e) {
475                                         log.error("Could not apply referenced matching function to ARP value: " + e);
476                                         return false;
477                                 }
478                         }
479
480                         // Handle Permit All with no relevant specific denies
481                         if (releaseAnyValue()) { return true; }
482
483                         // Handle Permit Specific
484                         iterator = values.iterator();
485                         while (iterator.hasNext()) {
486                                 AttributeValue valueSpec = (AttributeValue) iterator.next();
487
488                                 MatchFunction resourceFunction;
489                                 try {
490                                         resourceFunction = ArpEngine.lookupMatchFunction(valueSpec.getMatchFunctionIdentifier());
491                                         // Ignore non-functional permits
492                                         if (resourceFunction == null) {
493                                                 log.warn("Could not locate matching function for ARP value. Function: "
494                                                                 + valueSpec.getMatchFunctionIdentifier().toString());
495                                                 continue;
496                                         }
497
498                                 } catch (ArpException e) {
499                                         log.error("Error while attempting to find referenced matching function for ARP values: " + e);
500                                         continue;
501                                 }
502
503                                 try {
504                                         if (valueSpec.getRelease().equals("permit") && resourceFunction.match(valueSpec.getValue(), value)) { return true; }
505                                 } catch (MatchingException e) {
506                                         log.error("Could not apply referenced matching function to ARP value: " + e);
507                                         continue;
508                                 }
509                         }
510                         return false;
511                 }
512
513                 void setAnyValuePermit(boolean b) {
514
515                         if (b) {
516                                 anyValue = true;
517                                 anyValueRelease = "permit";
518                                 Iterator<AttributeValue> iterator = values.iterator();
519                                 Set<AttributeValue> permittedValues = new HashSet<AttributeValue>();
520                                 while (iterator.hasNext()) {
521                                         AttributeValue value = (AttributeValue) iterator.next();
522                                         if (value.getRelease().equals("permit")) {
523                                                 permittedValues.add(value);
524                                         }
525                                 }
526                                 values.removeAll(permittedValues);
527                         } else {
528                                 if (anyValueRelease.equals("permit") && anyValue) {
529                                         anyValue = false;
530                                 }
531                         }
532                 }
533
534                 URI getName() {
535
536                         return name;
537                 }
538
539                 Collection<AttributeValue> getValues() {
540
541                         return values;
542                 }
543
544                 void addValue(AttributeValue value) {
545
546                         if (denyAnyValue()) { return; }
547                         if (releaseAnyValue() && value.getRelease().equals("permit")) { return; }
548                         values.add(value);
549                 }
550
551                 /**
552                  * Unmarshalls an <code>Attribute</code> into an xml <code>Element</code>.
553                  * 
554                  * @return the xml <code>Element</code>
555                  */
556
557                 Element unmarshall() throws ArpMarshallingException {
558
559                         try {
560                                 Document placeHolder = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
561                                 Element attributeNode = placeHolder.createElementNS(Arp.arpNamespace, "Attribute");
562
563                                 attributeNode.setAttributeNS(Arp.arpNamespace, "name", name.toString());
564
565                                 if (identifier != null) {
566                                         attributeNode.setAttributeNS(Arp.arpNamespace, "identifier", identifier.toString());
567                                 }
568
569                                 if (anyValue) {
570                                         Element anyValueNode = placeHolder.createElementNS(Arp.arpNamespace, "AnyValue");
571                                         anyValueNode.setAttributeNS(Arp.arpNamespace, "release", anyValueRelease);
572                                         attributeNode.appendChild(anyValueNode);
573                                 }
574                                 Iterator valueIterator = values.iterator();
575                                 while (valueIterator.hasNext()) {
576                                         AttributeValue value = (AttributeValue) valueIterator.next();
577                                         Element valueNode = placeHolder.createElementNS(Arp.arpNamespace, "Value");
578                                         valueNode.setAttributeNS(Arp.arpNamespace, "release", value.getRelease());
579                                         if (!value.getMatchFunctionIdentifier().equals(
580                                                         new URI("urn:mace:shibboleth:arp:matchFunction:stringMatch"))) {
581                                                 valueNode.setAttributeNS(Arp.arpNamespace, "matchFunction", value.getMatchFunctionIdentifier()
582                                                                 .toString());
583                                         }
584                                         Text valueTextNode = placeHolder.createTextNode(value.getValue());
585                                         valueNode.appendChild(valueTextNode);
586                                         attributeNode.appendChild(valueNode);
587                                 }
588                                 return attributeNode;
589
590                         } catch (URISyntaxException e) {
591                                 log.error("Encountered a problem unmarshalling an ARP Rule Resource: " + e);
592                                 throw new ArpMarshallingException("Encountered a problem unmarshalling an ARP Rule Resource.");
593                         } catch (ParserConfigurationException e) {
594                                 log.error("Encountered a problem unmarshalling an ARP Rule: " + e);
595                                 throw new ArpMarshallingException("Encountered a problem unmarshalling an ARP Rule.");
596                         }
597                 }
598
599                 /**
600                  * Creates an ARP Rule Attribute from an xml representation.
601                  * 
602                  * @param element
603                  *            the xml <code>Element</code> containing the ARP Rule.
604                  */
605                 void marshall(Element element) throws ArpMarshallingException {
606
607                         // Make sure we are dealing with an Attribute
608                         if (!element.getTagName().equals("Attribute")) {
609                                 log.error("Element data does not represent an ARP Rule Target.");
610                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule target.");
611                         }
612
613                         // Get the attribute identifier
614                         try {
615                                 if (element.hasAttribute("identifier")) {
616                                         identifier = new URI(element.getAttribute("identifier"));
617                                 }
618                         } catch (URISyntaxException e) {
619                                 log.error("Attribute not identified by a proper URI: " + e);
620                                 throw new ArpMarshallingException("Attribute not identified by a proper URI.");
621                         }
622
623                         // Get the attribute name
624                         try {
625                                 if (element.hasAttribute("name")) {
626                                         name = new URI(element.getAttribute("name"));
627                                 } else {
628                                         log.error("Attribute name not specified.");
629                                         throw new ArpMarshallingException("Attribute name not specified.");
630                                 }
631                         } catch (URISyntaxException e) {
632                                 log.error("Attribute name not identified by a proper URI: " + e);
633                                 throw new ArpMarshallingException("Attribute name not identified by a proper URI.");
634                         }
635
636                         // Handle <AnyValue/> definitions
637                         NodeList anyValueNodeList = element.getElementsByTagNameNS(Arp.arpNamespace, "AnyValue");
638                         if (anyValueNodeList.getLength() == 1) {
639                                 anyValue = true;
640                                 if (((Element) anyValueNodeList.item(0)).hasAttribute("release")) {
641                                         anyValueRelease = ((Element) anyValueNodeList.item(0)).getAttribute("release");
642                                 }
643                         }
644
645                         // Handle Value definitions
646                         if (!denyAnyValue()) {
647                                 NodeList valueNodeList = element.getElementsByTagNameNS(Arp.arpNamespace, "Value");
648                                 for (int i = 0; valueNodeList.getLength() > i; i++) {
649                                         String release = null;
650                                         String value = null;
651                                         URI matchFunctionIdentifier = null;
652                                         if (((Element) valueNodeList.item(i)).hasAttribute("release")) {
653                                                 release = ((Element) valueNodeList.item(i)).getAttribute("release");
654                                         }
655
656                                         // Grab the match function
657                                         try {
658                                                 if (((Element) valueNodeList.item(i)).hasAttribute("matchFunction")) {
659                                                         matchFunctionIdentifier = new URI(((Element) valueNodeList.item(i))
660                                                                         .getAttribute("matchFunction"));
661                                                 }
662                                         } catch (URISyntaxException e) {
663                                                 log.error("ARP match function not identified by a proper URI: "
664                                                                 + ((Element) valueNodeList.item(i)).getAttribute("matchFunction"));
665                                                 throw new ArpMarshallingException("ARP match function not identified by a proper URI.");
666                                         }
667
668                                         if (((Element) valueNodeList.item(i)).hasChildNodes()
669                                                         && ((Element) valueNodeList.item(i)).getFirstChild().getNodeType() == Node.TEXT_NODE) {
670                                                 value = ((CharacterData) ((Element) valueNodeList.item(i)).getFirstChild()).getData();
671                                         }
672                                         if (releaseAnyValue() && release.equals("permit")) {
673                                                 continue;
674                                         }
675                                         AttributeValue aValue = new AttributeValue(release, matchFunctionIdentifier, value);
676                                         values.add(aValue);
677                                 }
678                         }
679
680                 }
681         }
682
683         class AttributeValue {
684
685                 private String release = "permit";
686                 private String value;
687                 private URI matchFunctionIdentifier;
688
689                 AttributeValue(String release, URI matchFunctionIdentifier, String value) throws ArpMarshallingException {
690
691                         setRelease(release);
692                         this.value = value;
693                         if (matchFunctionIdentifier != null) {
694                                 this.matchFunctionIdentifier = matchFunctionIdentifier;
695                         } else {
696                                 try {
697                                         this.matchFunctionIdentifier = new URI("urn:mace:shibboleth:arp:matchFunction:stringMatch");
698                                 } catch (URISyntaxException e) {
699                                         throw new ArpMarshallingException(
700                                                         "ARP Engine internal error: could not set default matching function for attribute value.");
701                                 }
702                         }
703                 }
704
705                 String getRelease() {
706
707                         return release;
708                 }
709
710                 String getValue() {
711
712                         return value;
713                 }
714
715                 URI getMatchFunctionIdentifier() {
716
717                         return matchFunctionIdentifier;
718                 }
719
720                 void setRelease(String release) {
721
722                         if (release == null) { return; }
723                         if (release.equals("permit") || release.equals("deny")) {
724                                 this.release = release;
725                         }
726                 }
727
728                 void setValue(String value) {
729
730                         this.value = value;
731                 }
732         }
733
734         /**
735          * ARP Rule Constraints define attribute-based limits on which user a given rule applies to.
736          * 
737          * @author Will Norris (wnorris@usc.edu)
738          */
739         class Constraint {
740
741                 private URI attributeName;
742                 private URI matchFunctionIdentifier;
743                 private String matches;
744                 private String value;
745
746                 URI getAttributeName() {
747
748                         return attributeName;
749                 }
750
751                 /**
752                  * Unmarshalls a <code>Constraint</code> into an xml <code>Element</code>.
753                  * 
754                  * @return the xml <code>Element</code>
755                  */
756                 Element unmarshall() throws ArpMarshallingException {
757
758                         try {
759                                 Document placeHolder = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
760                                 Element constraintNode = placeHolder.createElementNS(Arp.arpNamespace, "Constraint");
761
762                                 constraintNode.setAttributeNS(Arp.arpNamespace, "attributeName", attributeName.toString());
763                                 constraintNode.setAttributeNS(Arp.arpNamespace, "matchFunction", matchFunctionIdentifier.toString());
764                                 constraintNode.setAttributeNS(Arp.arpNamespace, "matches", matches);
765
766                                 Text textNode = placeHolder.createTextNode(value);
767                                 constraintNode.appendChild(textNode);
768
769                                 return constraintNode;
770
771                         } catch (ParserConfigurationException e) {
772                                 log.error("Encountered a problem unmarshalling an ARP Rule Constraint: " + e);
773                                 throw new ArpMarshallingException("Encountered a problem unmarshalling an ARP Rule Constraint.");
774                         }
775                 }
776
777                 /**
778                  * Creates an ARP Rule Constraint from an xml representation.
779                  * 
780                  * @param element
781                  *            the xml <code>Element</code> containing the ARP Rule Constraint.
782                  */
783                 void marshall(Element element) throws ArpMarshallingException {
784
785                         // Make sure we are dealing with a Constraint
786                         if (!element.getTagName().equals("Constraint")) {
787                                 log.error("Element data does not represent an ARP Rule Constraint.");
788                                 throw new ArpMarshallingException("Element data does not represent an ARP Rule Constraint.");
789                         }
790
791                         // Get the attribute name
792                         try {
793                                 if (element.hasAttribute("attributeName")) {
794                                         attributeName = new URI(element.getAttribute("attributeName"));
795                                 } else {
796                                         log.error("Constraint attribute name not specified.");
797                                         throw new ArpMarshallingException("Constraint attribute name not specified.");
798                                 }
799                         } catch (URISyntaxException e) {
800                                 log.error("Constraint attribute name not identified by a proper URI: " + e);
801                                 throw new ArpMarshallingException("Constraint attribute name not identified by a proper URI.");
802                         }
803
804                         // Get the matchFunction identifier
805                         try {
806                                 if (element.hasAttribute("matchFunction")) {
807                                         matchFunctionIdentifier = new URI(element.getAttribute("matchFunction"));
808                                 } else {
809                                         this.matchFunctionIdentifier = new URI("urn:mace:shibboleth:arp:matchFunction:stringMatch");
810                                 }
811                         } catch (URISyntaxException e) {
812                                 log.error("Constraint attribute name not identified by a proper URI: " + e);
813                                 throw new ArpMarshallingException("Constraint attribute name not identified by a proper URI.");
814                         }
815
816                         // Get the matches value
817                         if (element.hasAttribute("matches")) {
818                                 matches = element.getAttribute("matches");
819                         } else {
820                                 matches = "any";
821                         }
822
823                         // Get the element value
824                         if (element.hasChildNodes() && element.getFirstChild().getNodeType() == Node.TEXT_NODE) {
825                                 value = ((CharacterData) element.getFirstChild()).getData();
826                         }
827
828                 }
829
830                 boolean allowed(Collection<? extends ArpAttribute> arpAttributes) {
831
832                         boolean allowed;
833
834                         if (matches.equalsIgnoreCase("none")) {
835                                 allowed = true;
836                         } else {
837                                 allowed = false;
838                         }
839
840                         for (ArpAttribute attribute : arpAttributes) {
841                                 if (attribute.getName().equals(attributeName.toString())) {
842
843                                         Iterator iterator = attribute.getValues();
844                                         while (iterator.hasNext()) {
845                                                 Object attributeValue = iterator.next();
846
847                                                 MatchFunction resourceFunction;
848                                                 try {
849                                                         resourceFunction = ArpEngine.lookupMatchFunction(matchFunctionIdentifier);
850                                                         // For safety, err on the side of caution
851                                                         if (resourceFunction == null) {
852                                                                 log.warn("Could not locate matching function for ARP constraint. Function: "
853                                                                                 + matchFunctionIdentifier.toString());
854                                                                 return false;
855                                                         }
856                                                 } catch (ArpException e) {
857                                                         log.error("Error while attempting to find referenced matching "
858                                                                         + "function for ARP constraint: " + e);
859                                                         return false;
860                                                 }
861
862                                                 // TODO this would be better as an enum switch
863                                                 try {
864                                                         if (matches.equalsIgnoreCase("any")) {
865                                                                 if (resourceFunction.match(value, attributeValue)) {
866                                                                         return true;
867                                                                 } else {
868                                                                         continue;
869                                                                 }
870                                                         } else if (matches.equalsIgnoreCase("all")) {
871                                                                 if (resourceFunction.match(value, attributeValue)) {
872                                                                         allowed = true;
873                                                                         continue;
874                                                                 } else {
875                                                                         return false;
876                                                                 }
877                                                         } else if (matches.equalsIgnoreCase("none")) {
878                                                                 if (resourceFunction.match(value, attributeValue)) {
879                                                                         return false;
880                                                                 } else {
881                                                                         allowed = true;
882                                                                         continue;
883                                                                 }
884                                                         }
885                                                 } catch (MatchingException e) {
886                                                         log.error("Could not apply referenced matching function to ARP value: " + e);
887                                                         return false;
888                                                 }
889                                         }
890                                 }
891                         }
892
893                         return allowed;
894                 }
895         }
896
897 }