Built-in pluggable for AAP XML
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / XMLAAPImpl.java
1 /*
2  * XMLAAPImpl.java
3  * 
4  * Implement the AAP and AttributeRule interfaces using the XML Beans
5  * generated from the <AttributeAcceptancePolicy> root element.
6  * 
7  * If an external AAP file is changed and reparsed, then a new instance
8  * of this object must be created from the new XMLBean to replace the
9  * previous object in the Config Map of AAP interface implementing 
10  * objects key by its URI.
11  * 
12  * --------------------
13  * Copyright 2002, 2004 
14  * University Corporation for Advanced Internet Development, Inc. 
15  * All rights reserved
16  * [Thats all we have to say to protect ourselves]
17  * Your permission to use this code is governed by "The Shibboleth License".
18  * A copy may be found at http://shibboleth.internet2.edu/license.html
19  * [Nothing in copyright law requires license text in every file.]
20  */
21 package edu.internet2.middleware.shibboleth.serviceprovider;
22
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.Map;
26 import java.util.regex.Pattern;
27
28 import org.apache.log4j.Logger;
29 import org.apache.xmlbeans.XmlException;
30 import org.opensaml.SAMLAttribute;
31
32 import x0.maceShibboleth1.AttributeAcceptancePolicyDocument;
33 import x0.maceShibboleth1.AttributeRuleType;
34 import x0.maceShibboleth1.AttributeRuleValueType;
35 import x0.maceShibboleth1.AttributeAcceptancePolicyDocument.AttributeAcceptancePolicy;
36 import x0.maceShibboleth1.SiteRuleDocument.SiteRule;
37 import x0.maceShibboleth1.SiteRuleType.Scope;
38 import x0.maceShibboleth1.SiteRuleType;
39 import edu.internet2.middleware.shibboleth.common.AAP;
40 import edu.internet2.middleware.shibboleth.common.AttributeRule;
41 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
42 import org.w3c.dom.Element;
43 import org.w3c.dom.Node;
44
45 /**
46  * An XMLAAPImpl object implements the AAP interface by creating
47  * and maintaining objects that implement the AttributeRule interface.
48  * The real work is done in AttributeRule.apply() where a 
49  * SAML Attribute Assertion is compared to policy and invalid values
50  * or assertions are removed.
51  * 
52  * A new instance of this object should be created whenever the
53  * AAP XML configuration file is changed and reparsed. The new object
54  * should then replace the old object in the Map that ServiceProviderConfig
55  * maintains keyed by file URI, holding implementors of the AAP interface.
56  */
57 public class XMLAAPImpl 
58         implements AAP,
59         PluggableConfigurationComponent {
60         
61         private static Logger log = Logger.getLogger(XMLAAPImpl.class);
62         
63         private boolean anyAttribute=false;
64         private AttributeRule[] attributeRules;
65
66
67         public void initialize(Node dom) 
68                 throws XmlException {
69             AttributeAcceptancePolicyDocument bean = AttributeAcceptancePolicyDocument.Factory.parse(dom);
70             AttributeAcceptancePolicy aapbean = bean.getAttributeAcceptancePolicy();
71                 if  (null!=aapbean.getAnyAttribute())
72                         anyAttribute=true; // There is an anyAttribute element
73                 
74                 AttributeRuleType[] rulebeans = aapbean.getAttributeRuleArray();
75                 attributeRules = new AttributeRule[rulebeans.length];
76                 for (int i=0;i<rulebeans.length;i++) {
77                         attributeRules[i]=new XMLAttributeRuleImpl(rulebeans[i]);
78                 }
79         }
80         
81         
82         public boolean isAnyAttribute() {
83                 return anyAttribute;
84         }
85
86         // The lookup could use a Map, but the array is not expected to
87         // be long, and there are three keys to process. We can add a
88         // Map later if needed.
89         
90         public AttributeRule lookup(String attrName, String attrNamespace) {
91                 for (int i=0;i<attributeRules.length;i++) {
92                         AttributeRule attributeRule = attributeRules[i];
93                         String name = attributeRule.getName();
94                         String namespace = attributeRule.getNamespace();
95                         if (name!=null && 
96                                 name.equals(attrName)) {
97                                 if (attrNamespace==null ||
98                                    (namespace!=null &&
99                                             namespace.equals(attrNamespace)))
100                                      return attributeRule;
101                         }
102                 }
103                 return null;
104         }
105
106         public AttributeRule lookup(String alias) {
107                 for (int i=0;i<attributeRules.length;i++) {
108                         AttributeRule attributeRule = attributeRules[i];
109                         if (attributeRule.getAlias().equals(alias))
110                                 return attributeRule;
111                 }
112                 return null;
113         }
114
115         public AttributeRule[] getAttributeRules() {
116                 return attributeRules;
117         }
118         
119         
120         /**
121          * Implement the ...commmon.AttributeRules interface by wrapping the XMLBean
122          * 
123          * @author Howard Gilbert
124          */
125         public class XMLAttributeRuleImpl implements AttributeRule {
126                 
127                 AttributeRuleType bean;
128                 Map /*<Entityname-String,SiteRule>*/ siteMap = new HashMap();
129                 
130                 XMLAttributeRuleImpl(AttributeRuleType bean) {
131                         this.bean=bean;
132                         SiteRule[] siteRules = bean.getSiteRuleArray();
133                         for (int i=0;i<siteRules.length;i++) {
134                                 SiteRule siteRule = siteRules[i];
135                                 String entityName = siteRule.getName();
136                                 siteMap.put(entityName,siteRule);
137                         }
138                 }
139
140                 public String getName() {
141                         return bean.getName();
142                 }
143
144                 public String getNamespace() {
145                         return bean.getNamespace();
146                 }
147
148                 public String getAlias() {
149                         return bean.getAlias();
150                 }
151
152                 public String getHeader() {
153                         return bean.getHeader();
154                 }
155
156                 /**
157                  * Apply this AttributeRule to values of a SAMLAttribute
158                  */
159                 public void apply(EntityDescriptor originSite, SAMLAttribute attribute) {
160                         Iterator values = attribute.getValues();
161                         int i=0;
162                         while(values.hasNext()) {
163                                 Element valueElement = (Element) values.next();
164                                 if (!acceptableValue(originSite,valueElement)|| 
165                                         !scopeCheck(originSite,valueElement)) {
166                                         attribute.removeValue(i);
167                                 } else {
168                                         i++;
169                                 }
170                         }
171                 }
172                 
173                 
174                 
175                 /**
176                  * Apply an array of Scope elements to a SAML scope attribute.
177                  * 
178                  * <p>Scope rules can accept or reject their matches.
179                  * Any match to a rejection is immediately fatal. Otherwise,
180                  * there must have been one accept by the end of the scan.</p>
181                  * 
182                  * <p>The return is a three state Boolean object. A Boolean(false)
183                  * is a rejection. A Boolean(true) is a tentative approval. A 
184                  * null is neutral (no rejection, but no match).
185                  */
186                 private Boolean applyScopeRules(Scope[] scopeArray, String scopeAttribute) {
187                         Boolean decision = null;
188                         
189                         for (int i=0;i<scopeArray.length;i++) {
190                                 Scope scoperule = scopeArray[i];
191                                 
192                                 boolean accept = scoperule.getAccept();
193                                 int type = scoperule.getType().intValue();
194                                 String value = scoperule.getStringValue();
195                                 
196                                 switch (type) {
197                                 case AttributeRuleValueType.INT_REGEXP:
198                                         if (Pattern.matches(scopeAttribute,value)) {
199                                                 if (accept && decision==null)
200                                                         decision=Boolean.TRUE; // Tentative approval
201                                                 else
202                                                         return Boolean.FALSE;  // Deny immediate
203                                         }
204                                 break;
205                                 case AttributeRuleValueType.INT_XPATH:
206                                         log.warn("implementation does not support XPath value rules");
207                                 break;
208                                 default:
209                                         if (scopeAttribute.equals(value)) {
210                                                 if (accept && decision==null)
211                                                         decision=Boolean.TRUE; // Tentative approval
212                                                 else
213                                                         return Boolean.FALSE;  // Deny immediate
214                                         }
215                                 break;
216                                 }
217                         }
218                         return decision;
219                         
220                 }
221                 
222                 /**
223                  * Apply AnySite scope rules, then rules for Origin site.
224                  * 
225                  * @param originSite Metadata for origin site
226                  * @param ele        SAML attribute value
227                  * @return           true if OK, false if failed test
228                  */
229                 private boolean scopeCheck(EntityDescriptor originSite, Element ele) {
230                         
231                         String scopeAttribute = ele.getAttributeNS(null,"Scope");
232                         if (scopeAttribute==null || 
233                                 scopeAttribute.length()==0)
234                                 return true;  // Nothing to verify, so its OK
235                         
236                         Boolean anypermit = null; // null is neutral on decision
237                         Boolean sitepermit = null;
238                         
239                         // AnySite scope test
240                         Scope[] scopeArray = bean.getAnySite().getScopeArray();
241                         anypermit = applyScopeRules(scopeArray,scopeAttribute);
242                         if (anypermit!=null && // if null (neutral) fall through
243                                 !anypermit.booleanValue()) // if tentative true, fall through
244                                 return false; // Boolean(false) is immediate deny
245                         
246                         // Now find origin site rule, if present
247                         String os = originSite.getId();
248                         SiteRule siteRule = (SiteRule) siteMap.get(os);
249                         
250                         if (siteRule!=null) {
251                                 scopeArray = siteRule.getScopeArray();
252                                 sitepermit = applyScopeRules(scopeArray,scopeAttribute);
253                                 if (sitepermit!=null &&
254                                         !sitepermit.booleanValue()) 
255                                         return false;
256                         }
257
258                         // Now, since any Boolean(false) would have generated a 
259                         // rejection, any non-null value is a Boolean(true). 
260                         // Accept if either found an accept
261                         return anypermit!=null || sitepermit!=null;
262                 }
263
264                 /**
265                  * Determine if SAML value matches any Value rule in list
266                  * 
267                  * @param values Value rules from AAP
268                  * @param node   SAML value
269                  * @return
270                  */
271                 private boolean checkForMatchingValue(SiteRuleType.Value[] values, Node node) {
272                         String nodeValue = node.getNodeValue();
273                         for (int i=0;i<values.length;i++) {
274                                 SiteRuleType.Value value = values[i];
275                                 String valueContents = value.getStringValue();
276                                 switch (value.getType().intValue()) {
277                                         case AttributeRuleValueType.INT_REGEXP:
278                                                 if (Pattern.matches(valueContents,nodeValue))
279                                                         return true;
280                                                 break;
281                                         case AttributeRuleValueType.INT_XPATH:
282                                                 //log.warn("implementation does not support XPath value rules");
283                                                 break;
284                                         default:
285                                                 if (nodeValue.equals(valueContents))
286                                                         return true;
287                                                 break;
288                                 }
289                         }
290                         return false;
291                         
292                 }
293
294                 
295                 /**
296                  * Apply AnySite Value rules, then rules for Origin site
297                  * @param originSite Metadata for Origin site
298                  * @param ele SAML Attribute value
299                  * @return true to continue with scope check, false to reject now
300                  */
301                 private boolean acceptableValue(EntityDescriptor originSite, Element ele) {
302                         
303                         // any site, any value
304                         if (bean.getAnySite().getAnyValue()!=null) 
305                                 return true;
306                         
307                         Node node = ele.getFirstChild();
308                         boolean simple = node.getNodeType()==Node.TEXT_NODE;
309                         
310                         // any site, specific value
311                         if (simple) {
312                                 SiteRuleType.Value[] values = bean.getAnySite().getValueArray();
313                                 if (checkForMatchingValue(values,node))
314                                         return true;
315                         }
316                         
317                         // Specific site
318                         String os = originSite.getId();
319                         SiteRule siteRule = (SiteRule) siteMap.get(os);
320                         if (siteRule==null) {
321                                 log.warn("Site "+os+" not found in ruleset "+this.getName());
322                                 return false;
323                         }
324                         if (siteRule.getAnyValue()!=null) {
325                                 return true;
326                         }
327                         SiteRuleType.Value[] values = siteRule.getValueArray();
328                         if (checkForMatchingValue(values,node))
329                                 return true;
330                         
331                         return false;
332                 }
333         }
334
335
336     /**
337      * @return
338      */
339     public String getSchemaPathname() {
340        return null;
341     }
342 }