Update copyright.
[shibboleth/sp.git] / xmlproviders / XMLAAP.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
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 /* XMLAAP.cpp - XML AAP implementation
18
19    Scott Cantor
20    12/21/02
21
22    $History:$
23 */
24
25 #include "internal.h"
26 #include <algorithm>
27 #include <log4cpp/Category.hh>
28 #include <shibsp/metadata/MetadataExt.h>
29 #include <shibsp/util/SPConstants.h>
30 #include <xmltooling/util/ReloadableXMLFile.h>
31 #include <xmltooling/util/XMLHelper.h>
32
33 using namespace shibsp;
34 using namespace shibboleth;
35 using namespace saml;
36 using namespace opensaml::saml2md;
37 using namespace xmltooling;
38 using namespace log4cpp;
39 using namespace std;
40
41 #include <xercesc/util/regx/RegularExpression.hpp>
42
43 namespace {
44
45     class XMLAAPImpl
46     {
47     public:
48         XMLAAPImpl(const DOMElement* e);
49         ~XMLAAPImpl();
50
51         void setDocument(DOMDocument* doc) {
52             m_document = doc;
53         }
54     
55         class AttributeRule : public IAttributeRule
56         {
57         public:
58             AttributeRule(const DOMElement* e);
59             ~AttributeRule() {}
60             
61             const XMLCh* getName() const { return m_name; }
62             const XMLCh* getNamespace() const { return m_namespace; }
63             const char* getAlias() const { return m_alias.get(); }
64             const char* getHeader() const { return m_header.get(); }
65             bool getCaseSensitive() const { return m_caseSensitive; }
66             bool getScoped() const { return m_scoped; }
67             void apply(SAMLAttribute& attribute, const RoleDescriptor* role=NULL) const;
68     
69             enum value_type { literal, regexp, xpath };
70         private:    
71             const XMLCh* m_name;
72             const XMLCh* m_namespace;
73             xmltooling::auto_ptr_char m_alias;
74             xmltooling::auto_ptr_char m_header;
75             bool m_caseSensitive;
76             bool m_scoped;
77
78             struct SiteRule
79             {
80                 SiteRule() : anyValue(false) {}
81                 bool anyValue;
82                 vector<pair<value_type,const XMLCh*> > valueDenials;
83                 vector<pair<value_type,const XMLCh*> > valueAccepts;
84                 vector<pair<value_type,const XMLCh*> > scopeDenials;
85                 vector<pair<value_type,const XMLCh*> > scopeAccepts;
86             };
87             
88             value_type toValueType(const DOMElement* e);
89             bool scopeCheck(
90                 const DOMElement* e,
91                 const RoleDescriptor* role,
92                 const vector<const SiteRule*>& ruleStack
93                 ) const;
94             bool accept(const DOMElement* e, const RoleDescriptor* role=NULL) const;
95             
96             SiteRule m_anySiteRule;
97     #ifdef HAVE_GOOD_STL
98             typedef map<xmltooling::xstring,SiteRule> sitemap_t;
99     #else
100             typedef map<string,SiteRule> sitemap_t;
101     #endif
102             sitemap_t m_siteMap;
103         };
104     
105         DOMDocument* m_document;
106         bool anyAttribute;
107         vector<const IAttributeRule*> m_attrs;
108         map<string,const IAttributeRule*> m_aliasMap;
109     #ifdef HAVE_GOOD_STL
110         typedef map<xmltooling::xstring,AttributeRule*> attrmap_t;
111     #else
112         typedef map<string,AttributeRule*> attrmap_t;
113     #endif
114         attrmap_t m_attrMap;
115     };
116
117 #if defined (_MSC_VER)
118     #pragma warning( push )
119     #pragma warning( disable : 4250 )
120 #endif
121
122     class XMLAAP : public IAAP, public ReloadableXMLFile
123     {
124     public:
125         XMLAAP(const DOMElement* e) : ReloadableXMLFile(e), m_impl(NULL) {
126             load();
127         }
128         ~XMLAAP() {
129             delete m_impl;
130         }
131         
132         bool anyAttribute() const {return m_impl->anyAttribute;}
133         const IAttributeRule* lookup(const XMLCh* attrName, const XMLCh* attrNamespace=NULL) const;
134         const IAttributeRule* lookup(const char* alias) const;
135         Iterator<const IAttributeRule*> getAttributeRules() const;
136
137     protected:
138         pair<bool,DOMElement*> load();
139         XMLAAPImpl* m_impl;
140     };
141
142 #if defined (_MSC_VER)
143     #pragma warning( pop )
144 #endif
145
146     static const XMLCh Accept[]=        UNICODE_LITERAL_6(A,c,c,e,p,t);
147     static const XMLCh Alias[]=         UNICODE_LITERAL_5(A,l,i,a,s);
148     static const XMLCh AnyAttribute[]=  UNICODE_LITERAL_12(A,n,y,A,t,t,r,i,b,u,t,e);
149     static const XMLCh AnySite[]=       UNICODE_LITERAL_7(A,n,y,S,i,t,e);
150     static const XMLCh AnyValue[]=      UNICODE_LITERAL_8(A,n,y,V,a,l,u,e);
151     static const XMLCh _AttributeRule[]=UNICODE_LITERAL_13(A,t,t,r,i,b,u,t,e,R,u,l,e);
152     static const XMLCh CaseSensitive[]= UNICODE_LITERAL_13(C,a,s,e,S,e,n,s,i,t,i,v,e);
153     static const XMLCh Header[]=        UNICODE_LITERAL_6(H,e,a,d,e,r);
154     static const XMLCh Name[]=          UNICODE_LITERAL_4(N,a,m,e);
155     static const XMLCh Namespace[]=     UNICODE_LITERAL_9(N,a,m,e,s,p,a,c,e);
156     static const XMLCh Scoped[]=        UNICODE_LITERAL_6(S,c,o,p,e,d);
157     static const XMLCh _SiteRule[]=     UNICODE_LITERAL_8(S,i,t,e,R,u,l,e);
158     static const XMLCh Type[]=          UNICODE_LITERAL_4(T,y,p,e);
159     static const XMLCh Value[]=         UNICODE_LITERAL_5(V,a,l,u,e);
160
161     static const XMLCh _literal[]=      UNICODE_LITERAL_7(l,i,t,e,r,a,l);
162     static const XMLCh _regexp[]=       UNICODE_LITERAL_6(r,e,g,e,x,p);
163     static const XMLCh _xpath[]=        UNICODE_LITERAL_5(x,p,a,t,h);
164 }
165
166 IPlugIn* XMLAAPFactory(const DOMElement* e)
167 {
168     return new XMLAAP(e);
169 }
170
171 pair<bool,DOMElement*> XMLAAP::load()
172 {
173     // Load from source using base class.
174     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
175     
176     // If we own it, wrap it.
177     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
178
179     XMLAAPImpl* impl = new XMLAAPImpl(raw.second);
180     
181     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
182     impl->setDocument(docjanitor.release());
183
184     delete m_impl;
185     m_impl = impl;
186
187     return make_pair(false,(DOMElement*)NULL);
188 }
189
190 XMLAAPImpl::XMLAAPImpl(const DOMElement* e) : anyAttribute(false), m_document(NULL)
191 {
192 #ifdef _DEBUG
193     xmltooling::NDC ndc("XMLAAPImpl");
194 #endif
195     Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
196
197     try {
198         // Check for AnyAttribute element.
199         if (XMLHelper::getFirstChildElement(e,AnyAttribute)) {
200             anyAttribute = true;
201             log.warn("<AnyAttribute> found, will short-circuit all attribute value and scope filtering");
202         }
203
204         // Loop over the AttributeRule elements.
205         e = XMLHelper::getFirstChildElement(e, _AttributeRule); 
206         while (e) {
207             AttributeRule* rule=new AttributeRule(e);
208 #ifdef HAVE_GOOD_STL
209             xmltooling::xstring key=rule->getName();
210             key=key + chBang + chBang + (rule->getNamespace() ? rule->getNamespace() : shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI);
211 #else
212             xmltooling::auto_ptr_char aname(rule->getName());
213             string key(aname.get());
214             key+="!!";
215             if (rule->getNamespace()) {
216                 xmltooling::auto_ptr_char ans(rule->getNamespace());
217                 key+=ans.get();
218             }
219             else {
220                 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
221             }
222 #endif
223             m_attrMap[key]=rule;
224             m_attrs.push_back(rule);
225             if (rule->getAlias()) {
226                 // user can only apply to REMOTE_USER
227                 if (!strcmp(rule->getAlias(),"user")) {
228                     if (strcmp(rule->getHeader(),"REMOTE_USER"))
229                         log.error("<AttributeRule> cannot specify Alias of 'user', please use alternate value");
230                     else {
231                         m_aliasMap[rule->getAlias()]=rule;
232                     }
233                 }
234                 else {
235                     m_aliasMap[rule->getAlias()]=rule;
236                 }
237             }
238             
239             e = XMLHelper::getNextSiblingElement(e, _AttributeRule);
240         }
241     }
242     catch (exception&) {
243 #ifdef HAVE_GOOD_STL
244         for_each(m_attrMap.begin(),m_attrMap.end(),xmltooling::cleanup_pair<xmltooling::xstring,AttributeRule>());
245 #else
246         for_each(m_attrMap.begin(),m_attrMap.end(),xmltooling::cleanup_pair<string,AttributeRule>());
247 #endif
248         throw;
249     }
250 }
251
252 XMLAAPImpl::~XMLAAPImpl()
253 {
254 #ifdef HAVE_GOOD_STL
255     for_each(m_attrMap.begin(),m_attrMap.end(),xmltooling::cleanup_pair<xmltooling::xstring,AttributeRule>());
256 #else
257     for_each(m_attrMap.begin(),m_attrMap.end(),xmltooling::cleanup_pair<string,AttributeRule>());
258 #endif
259     if (m_document)
260         m_document->release();
261 }
262
263 XMLAAPImpl::AttributeRule::AttributeRule(const DOMElement* e) :
264     m_alias(e->hasAttributeNS(NULL,Alias) ? e->getAttributeNS(NULL,Alias) : NULL),
265     m_header(e->hasAttributeNS(NULL,Header) ? e->getAttributeNS(NULL,Header) : NULL),
266     m_scoped(false)
267     
268 {
269     m_name=e->getAttributeNS(NULL,Name);
270     m_namespace=e->getAttributeNS(NULL,Namespace);
271     if (!m_namespace || !*m_namespace)
272         m_namespace=shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI;
273     
274     const XMLCh* caseSensitive=e->getAttributeNS(NULL,CaseSensitive);
275     m_caseSensitive=(!caseSensitive || !*caseSensitive || *caseSensitive==chDigit_1 || *caseSensitive==chLatin_t);
276     
277     const XMLCh* scoped=e->getAttributeNS(NULL,Scoped);
278     m_scoped=(scoped && (*scoped==chDigit_1 || *scoped==chLatin_t));
279     
280     // Check for an AnySite rule.
281     const DOMElement* anysite = XMLHelper::getFirstChildElement(e);
282     if (anysite && XMLString::equals(anysite->getLocalName(),AnySite)) {
283         // Process Scope elements.
284         const DOMElement* se = XMLHelper::getFirstChildElement(anysite,Scope::LOCAL_NAME);
285         while (se) {
286             m_scoped=true;
287             DOMNode* valnode=se->getFirstChild();
288             if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
289                 const XMLCh* accept=se->getAttributeNS(NULL,Accept);
290                 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
291                     m_anySiteRule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
292                 else
293                     m_anySiteRule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
294             }
295             
296             se = XMLHelper::getNextSiblingElement(se,Scope::LOCAL_NAME);
297         }
298
299         // Check for an AnyValue rule.
300         if (XMLHelper::getFirstChildElement(anysite,AnyValue)) {
301             m_anySiteRule.anyValue=true;
302         }
303         else {
304             // Process each Value element.
305             const DOMElement* ve = XMLHelper::getFirstChildElement(anysite,Value);
306             while (ve) {
307                 DOMNode* valnode=ve->getFirstChild();
308                 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
309                     const XMLCh* accept=ve->getAttributeNS(NULL,Accept);
310                     if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
311                         m_anySiteRule.valueAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
312                     else
313                         m_anySiteRule.valueDenials.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
314                 }
315                 
316                 ve = XMLHelper::getNextSiblingElement(ve,Value);
317             }
318         }
319     }
320
321     // Loop over the SiteRule elements.
322     const DOMElement* sr = XMLHelper::getFirstChildElement(e,_SiteRule);
323     while (sr) {
324         const XMLCh* srulename=sr->getAttributeNS(NULL,Name);
325 #ifdef HAVE_GOOD_STL
326         m_siteMap[srulename]=SiteRule();
327         SiteRule& srule=m_siteMap[srulename];
328 #else
329         xmltooling::auto_ptr_char srulename2(srulename);
330         m_siteMap[srulename2.get()]=SiteRule();
331         SiteRule& srule=m_siteMap[srulename2.get()];
332 #endif
333
334         // Process Scope elements.
335         const DOMElement* se = XMLHelper::getFirstChildElement(sr,Scope::LOCAL_NAME);
336         while (se) {
337             m_scoped=true;
338             DOMNode* valnode=se->getFirstChild();
339             if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
340                 const XMLCh* accept=se->getAttributeNS(NULL,Accept);
341                 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
342                     srule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
343                 else
344                     srule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
345             }
346             
347             se = XMLHelper::getNextSiblingElement(se,Scope::LOCAL_NAME);
348         }
349
350         // Check for an AnyValue rule.
351         if (XMLHelper::getFirstChildElement(sr,AnyValue)) {
352             srule.anyValue=true;
353         }
354         else
355         {
356             // Process each Value element.
357             const DOMElement* ve = XMLHelper::getFirstChildElement(sr,Value);
358             while (ve) {
359                 DOMNode* valnode=ve->getFirstChild();
360                 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
361                     const XMLCh* accept=ve->getAttributeNS(NULL,Accept);
362                     if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
363                         srule.valueAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
364                     else
365                         srule.valueDenials.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
366                 }
367                 
368                 ve = XMLHelper::getNextSiblingElement(ve,Value);
369             }
370         }
371         
372         sr = XMLHelper::getNextSiblingElement(sr,_SiteRule);
373     }
374 }
375
376 XMLAAPImpl::AttributeRule::value_type XMLAAPImpl::AttributeRule::toValueType(const DOMElement* e)
377 {
378     if (XMLString::equals(_literal,e->getAttributeNS(NULL,Type)))
379         return literal;
380     else if (XMLString::equals(_regexp,e->getAttributeNS(NULL,Type)))
381         return regexp;
382     else if (XMLString::equals(_xpath,e->getAttributeNS(NULL,Type)))
383         return xpath;
384     throw ConfigurationException("Found an invalid value or scope rule type.");
385 }
386
387 const IAttributeRule* XMLAAP::lookup(const XMLCh* attrName, const XMLCh* attrNamespace) const
388 {
389 #ifdef HAVE_GOOD_STL
390     xmltooling::xstring key=attrName;
391     key=key + chBang + chBang + (attrNamespace ? attrNamespace : shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI);
392 #else
393     xmltooling::auto_ptr_char aname(attrName);
394     string key=aname.get();
395     key+="!!";
396     if (attrNamespace) {
397         xmltooling::auto_ptr_char ans(attrNamespace);
398         key+=ans.get();
399     }
400     else {
401         key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
402     }
403 #endif
404     XMLAAPImpl::attrmap_t::const_iterator i=m_impl->m_attrMap.find(key);
405     return (i==m_impl->m_attrMap.end()) ? NULL : i->second;
406 }
407
408 const IAttributeRule* XMLAAP::lookup(const char* alias) const
409 {
410     map<string,const IAttributeRule*>::const_iterator i=m_impl->m_aliasMap.find(alias);
411     return (i==m_impl->m_aliasMap.end()) ? NULL : i->second;
412 }
413
414 Iterator<const IAttributeRule*> XMLAAP::getAttributeRules() const
415 {
416     return m_impl->m_attrs;
417 }
418
419 namespace {
420     bool match(const XMLCh* exp, const XMLCh* test)
421     {
422         try {
423             RegularExpression re(exp);
424             if (re.matches(test))
425                 return true;
426         }
427         catch (XMLException& ex) {
428             xmltooling::auto_ptr_char tmp(ex.getMessage());
429             Category::getInstance(XMLPROVIDERS_LOGCAT".AAP").errorStream()
430                 << "caught exception while parsing regular expression: " << tmp.get() << CategoryStream::ENDLINE;
431         }
432         return false;
433     }
434 }
435
436 bool XMLAAPImpl::AttributeRule::scopeCheck(
437     const DOMElement* e,
438     const RoleDescriptor* role,
439     const vector<const SiteRule*>& ruleStack
440     ) const
441 {
442 #ifdef _DEBUG
443     xmltooling::NDC ndc("scopeCheck");
444 #endif
445     Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
446
447     // Are we scoped?
448     const XMLCh* scope=e->getAttributeNS(NULL,Scope::LOCAL_NAME);
449     if (!scope || !*scope) {
450         // Are we allowed to be unscoped?
451         if (m_scoped && log.isWarnEnabled()) {
452                 xmltooling::auto_ptr_char temp(m_name);
453                 log.warn("attribute (%s) is scoped, no scope supplied, rejecting it",temp.get());
454         }
455         return !m_scoped;
456     }
457
458     // With the new algorithm, we evaluate each matching rule in sequence, separately.
459     for (vector<const SiteRule*>::const_iterator rule=ruleStack.begin(); rule!=ruleStack.end(); rule++) {
460
461         // Now run any denials.
462         vector<pair<value_type,const XMLCh*> >::const_iterator i;
463         for (i=(*rule)->scopeDenials.begin(); i!=(*rule)->scopeDenials.end(); i++) {
464             if ((i->first==literal && XMLString::equals(i->second,scope)) ||
465                 (i->first==regexp && match(i->second,scope))) {
466                 if (log.isWarnEnabled()) {
467                     xmltooling::auto_ptr_char temp(m_name);
468                     xmltooling::auto_ptr_char temp2(scope);
469                     log.warn("attribute (%s) scope (%s) denied by site rule, rejecting it",temp.get(),temp2.get());
470                 }
471                 return false;
472             }
473             else if (i->first==xpath)
474                 log.warn("scope checking does not permit XPath rules");
475         }
476
477         // Now run any accepts.
478         for (i=(*rule)->scopeAccepts.begin(); i!=(*rule)->scopeAccepts.end(); i++) {
479             if ((i->first==literal && XMLString::equals(i->second,scope)) ||
480                 (i->first==regexp && match(i->second,scope))) {
481                 log.debug("matching site rule, scope match");
482                 return true;
483             }
484             else if (i->first==xpath)
485                 log.warn("scope checking does not permit XPath rules");
486         }
487     }
488
489     // If we still can't decide, defer to metadata.
490     if (role && role->getExtensions()) {
491         const vector<XMLObject*>& exts=const_cast<const Extensions*>(role->getExtensions())->getUnknownXMLObjects();
492         for (vector<XMLObject*>::const_iterator it=exts.begin(); it!=exts.end(); ++it) {
493             const Scope* s=dynamic_cast<const Scope*>(*it);
494             if (!s)
495                 continue;
496             if ((s->Regexp() && match(s->getValue(),scope)) || XMLString::equals(s->getValue(),scope)) {
497                 log.debug("scope match via site metadata");
498                 return true;
499             }
500         }
501     }
502     
503     if (log.isWarnEnabled()) {
504         xmltooling::auto_ptr_char temp(m_name);
505         xmltooling::auto_ptr_char temp2(scope);
506         log.warn("attribute (%s) scope (%s) not accepted",temp.get(),temp2.get());
507     }
508     return false;
509 }
510
511 bool XMLAAPImpl::AttributeRule::accept(const DOMElement* e, const RoleDescriptor* role) const
512 {
513 #ifdef _DEBUG
514     xmltooling::NDC ndc("accept");
515 #endif
516     Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
517
518     const EntityDescriptor* source = role ? dynamic_cast<const EntityDescriptor*>(role->getParent()) : NULL;
519
520     if (log.isDebugEnabled()) {
521         xmltooling::auto_ptr_char temp(m_name);
522         xmltooling::auto_ptr_char temp2(source ? source->getEntityID() : NULL);
523         log.debug("evaluating value for attribute (%s) from site (%s)",temp.get(),temp2.get() ? temp2.get() : "<unspecified>");
524     }
525     
526     // This is a complete revamp. The "any" cases become a degenerate case, the "least-specific" matching rule.
527     // The first step is to build a list of matching rules, most-specific to least-specific.
528     
529     vector<const SiteRule*> ruleStack;
530     if (source) {
531         // Primary match is against entityID.
532 #ifdef HAVE_GOOD_STL
533         const XMLCh* os=source->getEntityID();
534 #else
535         auto_ptr_char pos(source->getEntityID());
536         const char* os=pos.get();
537 #endif
538         sitemap_t::const_iterator srule=m_siteMap.find(os);
539         if (srule!=m_siteMap.end())
540             ruleStack.push_back(&srule->second);
541         
542         // Secondary matches are on groups.
543         const EntitiesDescriptor* group=dynamic_cast<const EntitiesDescriptor*>(source->getParent());
544         while (group) {
545             if (group->getName()) {
546 #ifdef HAVE_GOOD_STL
547                 os=group->getName();
548 #else
549                 auto_ptr_char gname(group->getName());
550                 const char* os=gname.get();
551 #endif
552                 srule=m_siteMap.find(os);
553                 if (srule!=m_siteMap.end())
554                     ruleStack.push_back(&srule->second);
555             }
556             group=dynamic_cast<const EntitiesDescriptor*>(group->getParent());
557         }
558     }
559     // Tertiary match is the AnySite rule.
560     ruleStack.push_back(&m_anySiteRule);
561
562     // Still don't support complex content models...
563     DOMNode* n=e->getFirstChild();
564     bool bSimple=(n && n->getNodeType()==DOMNode::TEXT_NODE);
565
566     // With the new algorithm, we evaluate each matching rule in sequence, separately.
567     for (vector<const SiteRule*>::const_iterator rule=ruleStack.begin(); rule!=ruleStack.end(); rule++) {
568
569         // Check for shortcut AnyValue blanket rule.
570         if ((*rule)->anyValue) {
571             log.debug("matching site rule, any value match");
572             return scopeCheck(e,role,ruleStack);
573         }
574
575         // Now run any denials.
576         vector<pair<value_type,const XMLCh*> >::const_iterator i;
577         for (i=(*rule)->valueDenials.begin(); bSimple && i!=(*rule)->valueDenials.end(); i++) {
578             switch (i->first) {
579                 case literal:
580                     if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
581                         (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
582                         if (log.isWarnEnabled()) {
583                             xmltooling::auto_ptr_char temp(m_name);
584                             log.warn("attribute (%s) value explicitly denied by site rule, rejecting it",temp.get());
585                         }
586                         return false;
587                     }
588                     break;
589                 
590                 case regexp:
591                     if (match(i->second,n->getNodeValue())) {
592                         if (log.isWarnEnabled()) {
593                             xmltooling::auto_ptr_char temp(m_name);
594                             log.warn("attribute (%s) value explicitly denied by site rule, rejecting it",temp.get());
595                         }
596                         return false;
597                     }
598                     break;
599                 
600                 case xpath:
601                     log.warn("implementation does not support XPath value rules");
602                     break;
603             }
604         }
605
606         // Now run any accepts.
607         for (i=(*rule)->valueAccepts.begin(); bSimple && i!=(*rule)->valueAccepts.end(); i++) {
608             switch (i->first) {
609                 case literal:
610                     if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
611                         (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
612                         log.debug("site rule, value match");
613                         return scopeCheck(e,role,ruleStack);
614                     }
615                     break;
616                 
617                 case regexp:
618                     if (match(i->second,n->getNodeValue())) {
619                         log.debug("site rule, value match");
620                         return scopeCheck(e,role,ruleStack);
621                     }
622                     break;
623                 
624                 case xpath:
625                     log.warn("implementation does not support XPath value rules");
626                     break;
627             }
628         }
629     }
630
631     if (log.isWarnEnabled()) {
632         xmltooling::auto_ptr_char temp(m_name);
633         xmltooling::auto_ptr_char temp2(n->getNodeValue());
634         log.warn("%sattribute (%s) value (%s) could not be validated by policy, rejecting it",
635                  (bSimple ? "" : "complex "),temp.get(),temp2.get());
636     }
637     return false;
638 }
639
640 void XMLAAPImpl::AttributeRule::apply(SAMLAttribute& attribute, const RoleDescriptor* role) const
641 {
642     // Check each value.
643     DOMNodeList* vals=attribute.getValueElements();
644     int i2=0;
645     for (XMLSize_t i=0; vals && i < vals->getLength(); i++) {
646         if (!accept(static_cast<DOMElement*>(vals->item(i)),role))
647             attribute.removeValue(i2);
648         else
649             i2++;
650     }
651     
652     // Now see if we trashed it irrevocably.
653     attribute.checkValidity();
654 }