2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 /* XMLAAP.cpp - XML AAP implementation
61 #include <log4cpp/Category.hh>
63 using namespace shibboleth;
65 using namespace log4cpp;
68 #include <xercesc/util/regx/RegularExpression.hpp>
72 class XMLAAPImpl : public ReloadableXMLFileImpl
75 XMLAAPImpl(const char* pathname) : ReloadableXMLFileImpl(pathname), anyAttribute(false) { init(); }
76 XMLAAPImpl(const DOMElement* e) : ReloadableXMLFileImpl(e), anyAttribute(false) { init(); }
80 class AttributeRule : public IAttributeRule
83 AttributeRule(const DOMElement* e);
86 const XMLCh* getName() const { return m_name; }
87 const XMLCh* getNamespace() const { return m_namespace; }
88 const char* getFactory() const { return m_factory.get(); }
89 const char* getAlias() const { return m_alias.get(); }
90 const char* getHeader() const { return m_header.get(); }
91 bool getCaseSensitive() const { return m_caseSensitive; }
92 bool getScoped() const { return m_scoped; }
93 void apply(SAMLAttribute& attribute, const IRoleDescriptor* role=NULL) const;
95 enum value_type { literal, regexp, xpath };
98 const XMLCh* m_namespace;
99 auto_ptr_char m_factory;
100 auto_ptr_char m_alias;
101 auto_ptr_char m_header;
102 bool m_caseSensitive;
105 value_type toValueType(const DOMElement* e);
106 bool scopeCheck(const DOMElement* e, const IScopedRoleDescriptor* role=NULL) const;
107 bool accept(const DOMElement* e, const IScopedRoleDescriptor* role=NULL) const;
111 SiteRule() : anyValue(false) {}
113 vector<pair<value_type,const XMLCh*> > valueRules;
114 vector<pair<value_type,const XMLCh*> > scopeDenials;
115 vector<pair<value_type,const XMLCh*> > scopeAccepts;
118 SiteRule m_anySiteRule;
120 typedef map<xstring,SiteRule> sitemap_t;
122 typedef map<string,SiteRule> sitemap_t;
128 vector<const IAttributeRule*> m_attrs;
129 map<string,const IAttributeRule*> m_aliasMap;
131 typedef map<xstring,AttributeRule*> attrmap_t;
133 typedef map<string,AttributeRule*> attrmap_t;
138 class XMLAAP : public IAAP, public ReloadableXMLFile
141 XMLAAP(const DOMElement* e) : ReloadableXMLFile(e) {}
144 bool anyAttribute() const {return static_cast<XMLAAPImpl*>(getImplementation())->anyAttribute;}
145 const IAttributeRule* lookup(const XMLCh* attrName, const XMLCh* attrNamespace=NULL) const;
146 const IAttributeRule* lookup(const char* alias) const;
147 Iterator<const IAttributeRule*> getAttributeRules() const;
150 virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
151 virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
156 IPlugIn* XMLAAPFactory(const DOMElement* e)
158 XMLAAP* aap=new XMLAAP(e);
161 aap->getImplementation();
171 ReloadableXMLFileImpl* XMLAAP::newImplementation(const DOMElement* e, bool first) const
173 return new XMLAAPImpl(e);
176 ReloadableXMLFileImpl* XMLAAP::newImplementation(const char* pathname, bool first) const
178 return new XMLAAPImpl(pathname);
181 void XMLAAPImpl::init()
183 NDC ndc("XMLAAPImpl");
184 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".XMLAAPImpl");
188 if (!saml::XML::isElementNamed(m_root,::XML::SHIB_NS,SHIB_L(AttributeAcceptancePolicy)))
190 log.error("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
191 throw MalformedException("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
194 // Check for AnyAttribute element.
195 DOMElement* anyAttr = saml::XML::getFirstChildElement(m_root,::XML::SHIB_NS,SHIB_L(AnyAttribute));
199 // Loop over the AttributeRule elements.
200 DOMNodeList* nlist = m_root->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AttributeRule));
201 for (int i=0; nlist && i<nlist->getLength(); i++)
203 AttributeRule* rule=new AttributeRule(static_cast<DOMElement*>(nlist->item(i)));
205 xstring key=rule->getName();
206 key=key + chBang + chBang + (rule->getNamespace() ? rule->getNamespace() : Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
208 auto_ptr_char aname(rule->getName());
209 string key(aname.get());
211 if (rule->getNamespace())
213 auto_ptr_char ans(rule->getNamespace());
217 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
220 m_attrs.push_back(rule);
221 if (rule->getAlias())
222 m_aliasMap[rule->getAlias()]=rule;
225 catch (SAMLException& e)
227 log.errorStream() << "Error while parsing AAP: " << e.what() << CategoryStream::ENDLINE;
228 for (attrmap_t::iterator i=m_attrMap.begin(); i!=m_attrMap.end(); i++)
234 log.error("Unexpected error while parsing AAP");
235 for (attrmap_t::iterator i=m_attrMap.begin(); i!=m_attrMap.end(); i++)
242 XMLAAPImpl::~XMLAAPImpl()
244 for (attrmap_t::iterator i=m_attrMap.begin(); i!=m_attrMap.end(); i++)
248 XMLAAPImpl::AttributeRule::AttributeRule(const DOMElement* e) :
249 m_factory(e->hasAttributeNS(NULL,SHIB_L(Factory)) ? e->getAttributeNS(NULL,SHIB_L(Factory)) : NULL),
250 m_alias(e->hasAttributeNS(NULL,SHIB_L(Alias)) ? e->getAttributeNS(NULL,SHIB_L(Alias)) : NULL),
251 m_header(e->hasAttributeNS(NULL,SHIB_L(Header)) ? e->getAttributeNS(NULL,SHIB_L(Header)) : NULL),
255 m_name=e->getAttributeNS(NULL,SHIB_L(Name));
256 m_namespace=e->getAttributeNS(NULL,SHIB_L(Namespace));
257 if (!m_namespace || !*m_namespace)
258 m_namespace=Constants::SHIB_ATTRIBUTE_NAMESPACE_URI;
260 const XMLCh* caseSensitive=e->getAttributeNS(NULL,SHIB_L(CaseSensitive));
261 m_caseSensitive=(!caseSensitive || !*caseSensitive || *caseSensitive==chDigit_1 || *caseSensitive==chLatin_t);
263 const XMLCh* scoped=e->getAttributeNS(NULL,SHIB_L(Scoped));
264 m_scoped=(scoped && (*scoped==chDigit_1 || *scoped==chLatin_t));
266 // Check for an AnySite rule.
267 DOMNode* anysite = e->getFirstChild();
268 while (anysite && anysite->getNodeType()!=DOMNode::ELEMENT_NODE)
270 anysite = anysite->getNextSibling();
274 if (anysite && saml::XML::isElementNamed(static_cast<DOMElement*>(anysite),::XML::SHIB_NS,SHIB_L(AnySite)))
276 // Process Scope elements.
277 DOMNodeList* vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Scope));
278 for (int i=0; vlist && i<vlist->getLength(); i++)
280 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
281 DOMNode* valnode=se->getFirstChild();
282 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
284 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
285 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
286 m_anySiteRule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
288 m_anySiteRule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
292 // Check for an AnyValue rule.
293 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AnyValue));
294 if (vlist && vlist->getLength())
296 m_anySiteRule.anyValue=true;
300 // Process each Value element.
301 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Value));
302 for (int j=0; vlist && j<vlist->getLength(); j++)
304 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
305 DOMNode* valnode=ve->getFirstChild();
306 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
307 m_anySiteRule.valueRules.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
312 // Loop over the SiteRule elements.
313 DOMNodeList* slist = e->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(SiteRule));
314 for (int k=0; slist && k<slist->getLength(); k++)
316 const XMLCh* srulename=static_cast<DOMElement*>(slist->item(k))->getAttributeNS(NULL,SHIB_L(Name));
318 m_siteMap[srulename]=SiteRule();
319 SiteRule& srule=m_siteMap[srulename];
321 auto_ptr_char srulename2(srulename);
322 m_siteMap[srulename2.get()]=SiteRule();
323 SiteRule& srule=m_siteMap[srulename2.get()];
326 // Process Scope elements.
327 DOMNodeList* vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Scope));
328 for (int i=0; vlist && i<vlist->getLength(); i++)
330 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
331 DOMNode* valnode=se->getFirstChild();
332 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
334 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
335 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
336 srule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
338 srule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
342 // Check for an AnyValue rule.
343 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AnyValue));
344 if (vlist && vlist->getLength())
350 // Process each Value element.
351 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Value));
352 for (int j=0; vlist && j<vlist->getLength(); j++)
354 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
355 DOMNode* valnode=ve->getFirstChild();
356 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
357 srule.valueRules.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
363 XMLAAPImpl::AttributeRule::value_type XMLAAPImpl::AttributeRule::toValueType(const DOMElement* e)
365 if (!XMLString::compareString(SHIB_L(literal),e->getAttributeNS(NULL,SHIB_L(Type))))
367 else if (!XMLString::compareString(SHIB_L(regexp),e->getAttributeNS(NULL,SHIB_L(Type))))
369 else if (!XMLString::compareString(SHIB_L(xpath),e->getAttributeNS(NULL,SHIB_L(Type))))
371 throw MalformedException("Found an invalid value or scope rule type.");
374 const IAttributeRule* XMLAAP::lookup(const XMLCh* attrName, const XMLCh* attrNamespace) const
377 xstring key=attrName;
378 key=key + chBang + chBang + (attrNamespace ? attrNamespace : Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
380 auto_ptr_char aname(attrName);
381 string key=aname.get();
385 auto_ptr_char ans(attrNamespace);
389 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
391 XMLAAPImpl* impl=dynamic_cast<XMLAAPImpl*>(getImplementation());
392 XMLAAPImpl::attrmap_t::const_iterator i=impl->m_attrMap.find(key);
393 return (i==impl->m_attrMap.end()) ? NULL : i->second;
396 const IAttributeRule* XMLAAP::lookup(const char* alias) const
398 XMLAAPImpl* impl=dynamic_cast<XMLAAPImpl*>(getImplementation());
399 map<string,const IAttributeRule*>::const_iterator i=impl->m_aliasMap.find(alias);
400 return (i==impl->m_aliasMap.end()) ? NULL : i->second;
403 Iterator<const IAttributeRule*> XMLAAP::getAttributeRules() const
405 return dynamic_cast<XMLAAPImpl*>(getImplementation())->m_attrs;
409 bool match(const XMLCh* exp, const XMLCh* test)
413 RegularExpression re(exp);
414 if (re.matches(test))
417 catch (XMLException& ex)
419 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
420 Category::getInstance(XMLPROVIDERS_LOGCAT".XMLAAPImpl").errorStream()
421 << "caught exception while parsing regular expression: " << tmp.get() << CategoryStream::ENDLINE;
427 bool XMLAAPImpl::AttributeRule::scopeCheck(const DOMElement* e, const IScopedRoleDescriptor* role) const
429 NDC ndc("scopeCheck");
430 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".XMLAAPImpl");
433 const XMLCh* scope=e->getAttributeNS(NULL,SHIB_L(Scope));
434 if (!scope || !*scope) {
435 // Are we allowed to be unscoped?
436 if (m_scoped && log.isWarnEnabled()) {
437 auto_ptr_char temp(m_name);
438 log.warn("attribute %s is scoped, no scope supplied, rejecting it",temp.get());
443 vector<pair<value_type,const XMLCh*> >::const_iterator i;
445 // Denials take precedence, always.
447 // Any site denials...
448 for (i=m_anySiteRule.scopeDenials.begin(); i!=m_anySiteRule.scopeDenials.end(); i++)
450 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
451 (i->first==regexp && match(i->second,scope)))
453 if (log.isWarnEnabled())
455 auto_ptr_char temp(m_name);
456 auto_ptr_char temp2(scope);
457 log.warn("attribute %s scope {%s} denied by any-site AAP, rejecting it",temp.get(),temp2.get());
461 else if (i->first==xpath)
462 log.warn("scope checking does not permit XPath rules");
465 sitemap_t::const_iterator srule=m_siteMap.end();
468 const XMLCh* os=role->getEntityDescriptor()->getId();
470 auto_ptr_char pos(role->getEntityDescriptor()->getId());
471 const char* os=pos.get();
473 srule=m_siteMap.find(os);
474 if (srule!=m_siteMap.end())
476 // Site-specific denials...
477 for (i=srule->second.scopeDenials.begin(); i!=srule->second.scopeDenials.end(); i++)
479 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
480 (i->first==regexp && match(i->second,scope)))
482 if (log.isWarnEnabled())
484 auto_ptr_char temp(m_name);
485 auto_ptr_char temp2(scope);
486 log.warn("attribute %s scope {%s} denied by site AAP, rejecting it",temp.get(),temp2.get());
490 else if (i->first==xpath)
491 log.warn("scope checking does not permit XPath rules");
496 // Any site accepts...
497 for (i=m_anySiteRule.scopeAccepts.begin(); i!=m_anySiteRule.scopeAccepts.end(); i++)
499 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
500 (i->first==regexp && match(i->second,scope)))
502 log.debug("any site, scope match");
505 else if (i->first==xpath)
506 log.warn("scope checking does not permit XPath rules");
509 if (srule!=m_siteMap.end())
511 // Site-specific accepts...
512 for (i=srule->second.scopeAccepts.begin(); i!=srule->second.scopeAccepts.end(); i++)
514 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
515 (i->first==regexp && match(i->second,scope)))
517 log.debug("matching site, scope match");
520 else if (i->first==xpath)
521 log.warn("scope checking does not permit XPath rules");
525 // If we still can't decide, defer to metadata.
527 Iterator<pair<const XMLCh*,bool> > domains=role->getScopes();
528 while (domains.hasNext())
530 const pair<const XMLCh*,bool>& p=domains.next();
531 if ((p.second && match(p.first,scope)) || !XMLString::compareString(p.first,scope))
533 log.debug("scope match via site metadata");
539 if (log.isWarnEnabled())
541 auto_ptr_char temp(m_name);
542 auto_ptr_char temp2(scope);
543 log.warn("attribute %s scope {%s} not accepted",temp.get(),temp2.get());
548 bool XMLAAPImpl::AttributeRule::accept(const DOMElement* e, const IScopedRoleDescriptor* role) const
551 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".XMLAAPImpl");
553 if (log.isDebugEnabled())
555 auto_ptr_char temp(m_name);
556 auto_ptr_char temp2(role ? role->getEntityDescriptor()->getId() : NULL);
557 log.debug("evaluating value for attribute %s from site %s",temp.get(),temp2.get() ? temp2.get() : "<unspecified>");
560 if (m_anySiteRule.anyValue)
562 log.debug("any site, any value, match");
563 return scopeCheck(e,role);
566 // Don't fully support complex content models...
567 DOMNode* n=e->getFirstChild();
568 bool bSimple=(n && n->getNodeType()==DOMNode::TEXT_NODE);
570 vector<pair<value_type,const XMLCh*> >::const_iterator i;
571 for (i=m_anySiteRule.valueRules.begin(); bSimple && i!=m_anySiteRule.valueRules.end(); i++)
573 if ((i->first==literal && !XMLString::compareString(i->second,n->getNodeValue())) ||
574 (i->first==regexp && match(i->second,n->getNodeValue())))
576 log.debug("any site, value match");
577 return scopeCheck(e,role);
579 else if (i->first==xpath)
580 log.warn("implementation does not support XPath value rules");
585 const XMLCh* os=role->getEntityDescriptor()->getId();
587 auto_ptr_char pos(role->getEntityDescriptor()->getId());
588 const char* os=pos.get();
590 sitemap_t::const_iterator srule=m_siteMap.find(os);
591 if (srule==m_siteMap.end())
593 if (log.isWarnEnabled())
595 auto_ptr_char temp(m_name);
596 auto_ptr_char temp2(role->getEntityDescriptor()->getId());
597 log.warn("site %s not found in attribute %s ruleset, any value is rejected",temp2.get(),temp.get());
602 if (srule->second.anyValue)
604 log.debug("matching site, any value, match");
605 return scopeCheck(e,role);
608 for (i=srule->second.valueRules.begin(); bSimple && i!=srule->second.valueRules.end(); i++) {
611 if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
612 (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
613 log.debug("matching site, value match");
614 return scopeCheck(e,role);
619 if (match(i->second,n->getNodeValue())) {
620 log.debug("matching site, value match");
621 return scopeCheck(e,role);
626 log.warn("implementation does not support XPath value rules");
632 if (log.isWarnEnabled())
634 auto_ptr_char temp(m_name);
635 auto_ptr_char temp2(n->getNodeValue());
636 log.warn("%sattribute %s value {%s} could not be validated by AAP, rejecting it",
637 (bSimple ? "" : "complex "),temp.get(),temp2.get());
642 void XMLAAPImpl::AttributeRule::apply(SAMLAttribute& attribute, const IRoleDescriptor* role) const
645 Iterator<const DOMElement*> vals=attribute.getValueElements();
646 for (unsigned int i=0; i < vals.size();) {
647 if (!accept(vals[i],role ? dynamic_cast<const IScopedRoleDescriptor*>(role) : NULL))
648 attribute.removeValue(i);
653 // Now see if we trashed it irrevocably.
654 attribute.checkValidity();