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;
107 SiteRule() : anyValue(false) {}
109 vector<pair<value_type,const XMLCh*> > valueDenials;
110 vector<pair<value_type,const XMLCh*> > valueAccepts;
111 vector<pair<value_type,const XMLCh*> > scopeDenials;
112 vector<pair<value_type,const XMLCh*> > scopeAccepts;
115 value_type toValueType(const DOMElement* e);
118 const IScopedRoleDescriptor* role,
119 const vector<const SiteRule*>& ruleStack
121 bool accept(const DOMElement* e, const IScopedRoleDescriptor* role=NULL) const;
123 SiteRule m_anySiteRule;
125 typedef map<xstring,SiteRule> sitemap_t;
127 typedef map<string,SiteRule> sitemap_t;
133 vector<const IAttributeRule*> m_attrs;
134 map<string,const IAttributeRule*> m_aliasMap;
136 typedef map<xstring,AttributeRule*> attrmap_t;
138 typedef map<string,AttributeRule*> attrmap_t;
143 class XMLAAP : public IAAP, public ReloadableXMLFile
146 XMLAAP(const DOMElement* e) : ReloadableXMLFile(e) {}
149 bool anyAttribute() const {return static_cast<XMLAAPImpl*>(getImplementation())->anyAttribute;}
150 const IAttributeRule* lookup(const XMLCh* attrName, const XMLCh* attrNamespace=NULL) const;
151 const IAttributeRule* lookup(const char* alias) const;
152 Iterator<const IAttributeRule*> getAttributeRules() const;
155 virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
156 virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
161 IPlugIn* XMLAAPFactory(const DOMElement* e)
163 auto_ptr<XMLAAP> aap(new XMLAAP(e));
164 aap->getImplementation();
165 return aap.release();
168 ReloadableXMLFileImpl* XMLAAP::newImplementation(const DOMElement* e, bool first) const
170 return new XMLAAPImpl(e);
173 ReloadableXMLFileImpl* XMLAAP::newImplementation(const char* pathname, bool first) const
175 return new XMLAAPImpl(pathname);
178 void XMLAAPImpl::init()
181 saml::NDC ndc("init");
183 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
187 if (!saml::XML::isElementNamed(m_root,::XML::SHIB_NS,SHIB_L(AttributeAcceptancePolicy)))
189 log.error("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
190 throw MalformedException("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
193 // Check for AnyAttribute element.
194 DOMElement* anyAttr = saml::XML::getFirstChildElement(m_root,::XML::SHIB_NS,SHIB_L(AnyAttribute));
197 log.warn("<AnyAttribute> found, will short-circuit all attribute value and scope filtering");
200 // Loop over the AttributeRule elements.
201 DOMNodeList* nlist = m_root->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AttributeRule));
202 for (int i=0; nlist && i<nlist->getLength(); i++)
204 AttributeRule* rule=new AttributeRule(static_cast<DOMElement*>(nlist->item(i)));
206 xstring key=rule->getName();
207 key=key + chBang + chBang + (rule->getNamespace() ? rule->getNamespace() : Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
209 auto_ptr_char aname(rule->getName());
210 string key(aname.get());
212 if (rule->getNamespace())
214 auto_ptr_char ans(rule->getNamespace());
218 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
221 m_attrs.push_back(rule);
222 if (rule->getAlias())
223 m_aliasMap[rule->getAlias()]=rule;
226 catch (SAMLException& e)
228 log.errorStream() << "Error while parsing AAP: " << e.what() << CategoryStream::ENDLINE;
235 log.error("Unexpected error while parsing AAP");
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 DOMElement* anysite = saml::XML::getFirstChildElement(e);
268 if (anysite && saml::XML::isElementNamed(static_cast<DOMElement*>(anysite),::XML::SHIB_NS,SHIB_L(AnySite)))
270 // Process Scope elements.
271 DOMNodeList* vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Scope));
272 for (int i=0; vlist && i<vlist->getLength(); i++)
275 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
276 DOMNode* valnode=se->getFirstChild();
277 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
279 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
280 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
281 m_anySiteRule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
283 m_anySiteRule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
287 // Check for an AnyValue rule.
288 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AnyValue));
289 if (vlist && vlist->getLength())
291 m_anySiteRule.anyValue=true;
295 // Process each Value element.
296 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Value));
297 for (int j=0; vlist && j<vlist->getLength(); j++)
299 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
300 DOMNode* valnode=ve->getFirstChild();
301 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
302 const XMLCh* accept=ve->getAttributeNS(NULL,SHIB_L(Accept));
303 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
304 m_anySiteRule.valueAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
306 m_anySiteRule.valueDenials.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++)
331 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
332 DOMNode* valnode=se->getFirstChild();
333 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
335 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
336 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
337 srule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
339 srule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
343 // Check for an AnyValue rule.
344 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AnyValue));
345 if (vlist && vlist->getLength())
351 // Process each Value element.
352 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Value));
353 for (int j=0; vlist && j<vlist->getLength(); j++)
355 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
356 DOMNode* valnode=ve->getFirstChild();
357 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
358 const XMLCh* accept=ve->getAttributeNS(NULL,SHIB_L(Accept));
359 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
360 srule.valueAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
362 srule.valueDenials.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
369 XMLAAPImpl::AttributeRule::value_type XMLAAPImpl::AttributeRule::toValueType(const DOMElement* e)
371 if (!XMLString::compareString(SHIB_L(literal),e->getAttributeNS(NULL,SHIB_L(Type))))
373 else if (!XMLString::compareString(SHIB_L(regexp),e->getAttributeNS(NULL,SHIB_L(Type))))
375 else if (!XMLString::compareString(SHIB_L(xpath),e->getAttributeNS(NULL,SHIB_L(Type))))
377 throw MalformedException("Found an invalid value or scope rule type.");
380 const IAttributeRule* XMLAAP::lookup(const XMLCh* attrName, const XMLCh* attrNamespace) const
383 xstring key=attrName;
384 key=key + chBang + chBang + (attrNamespace ? attrNamespace : Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
386 auto_ptr_char aname(attrName);
387 string key=aname.get();
391 auto_ptr_char ans(attrNamespace);
395 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
397 XMLAAPImpl* impl=dynamic_cast<XMLAAPImpl*>(getImplementation());
398 XMLAAPImpl::attrmap_t::const_iterator i=impl->m_attrMap.find(key);
399 return (i==impl->m_attrMap.end()) ? NULL : i->second;
402 const IAttributeRule* XMLAAP::lookup(const char* alias) const
404 XMLAAPImpl* impl=dynamic_cast<XMLAAPImpl*>(getImplementation());
405 map<string,const IAttributeRule*>::const_iterator i=impl->m_aliasMap.find(alias);
406 return (i==impl->m_aliasMap.end()) ? NULL : i->second;
409 Iterator<const IAttributeRule*> XMLAAP::getAttributeRules() const
411 return dynamic_cast<XMLAAPImpl*>(getImplementation())->m_attrs;
415 bool match(const XMLCh* exp, const XMLCh* test)
419 RegularExpression re(exp);
420 if (re.matches(test))
423 catch (XMLException& ex)
425 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
426 Category::getInstance(XMLPROVIDERS_LOGCAT".XMLAAPImpl").errorStream()
427 << "caught exception while parsing regular expression: " << tmp.get() << CategoryStream::ENDLINE;
433 bool XMLAAPImpl::AttributeRule::scopeCheck(
435 const IScopedRoleDescriptor* role,
436 const vector<const SiteRule*>& ruleStack
440 saml::NDC ndc("scopeCheck");
442 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
445 const XMLCh* scope=e->getAttributeNS(NULL,SHIB_L(Scope));
446 if (!scope || !*scope) {
447 // Are we allowed to be unscoped?
448 if (m_scoped && log.isWarnEnabled()) {
449 auto_ptr_char temp(m_name);
450 log.warn("attribute (%s) is scoped, no scope supplied, rejecting it",temp.get());
455 // With the new algorithm, we evaluate each matching rule in sequence, separately.
456 for (vector<const SiteRule*>::const_iterator rule=ruleStack.begin(); rule!=ruleStack.end(); rule++) {
458 // Now run any denials.
459 vector<pair<value_type,const XMLCh*> >::const_iterator i;
460 for (i=(*rule)->scopeDenials.begin(); i!=(*rule)->scopeDenials.end(); i++) {
461 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
462 (i->first==regexp && match(i->second,scope))) {
463 if (log.isWarnEnabled()) {
464 auto_ptr_char temp(m_name);
465 auto_ptr_char temp2(scope);
466 log.warn("attribute (%s) scope (%s) denied by site rule, rejecting it",temp.get(),temp2.get());
470 else if (i->first==xpath)
471 log.warn("scope checking does not permit XPath rules");
474 // Now run any accepts.
475 for (i=(*rule)->scopeAccepts.begin(); i!=(*rule)->scopeAccepts.end(); i++) {
476 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
477 (i->first==regexp && match(i->second,scope))) {
478 log.debug("matching site rule, scope match");
481 else if (i->first==xpath)
482 log.warn("scope checking does not permit XPath rules");
486 // If we still can't decide, defer to metadata.
488 Iterator<pair<const XMLCh*,bool> > domains=role->getScopes();
489 while (domains.hasNext()) {
490 const pair<const XMLCh*,bool>& p=domains.next();
491 if ((p.second && match(p.first,scope)) || !XMLString::compareString(p.first,scope)) {
492 log.debug("scope match via site metadata");
498 if (log.isWarnEnabled()) {
499 auto_ptr_char temp(m_name);
500 auto_ptr_char temp2(scope);
501 log.warn("attribute (%s) scope (%s) not accepted",temp.get(),temp2.get());
506 bool XMLAAPImpl::AttributeRule::accept(const DOMElement* e, const IScopedRoleDescriptor* role) const
509 saml::NDC ndc("accept");
511 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
513 if (log.isDebugEnabled()) {
514 auto_ptr_char temp(m_name);
515 auto_ptr_char temp2(role ? role->getEntityDescriptor()->getId() : NULL);
516 log.debug("evaluating value for attribute (%s) from site (%s)",temp.get(),temp2.get() ? temp2.get() : "<unspecified>");
519 // This is a complete revamp. The "any" cases become a degenerate case, the "least-specific" matching rule.
520 // The first step is to build a list of matching rules, most-specific to least-specific.
522 vector<const SiteRule*> ruleStack;
524 // Primary match is against entityID.
526 const XMLCh* os=role->getEntityDescriptor()->getId();
528 auto_ptr_char pos(role->getEntityDescriptor()->getId());
529 const char* os=pos.get();
531 sitemap_t::const_iterator srule=m_siteMap.find(os);
532 if (srule!=m_siteMap.end())
533 ruleStack.push_back(&srule->second);
535 // Secondary matches are on groups.
536 const IEntitiesDescriptor* group=role->getEntityDescriptor()->getEntitiesDescriptor();
541 auto_ptr_char gname(group->getName());
542 const char* os=gname.get();
544 srule=m_siteMap.find(os);
545 if (srule!=m_siteMap.end())
546 ruleStack.push_back(&srule->second);
547 group = group->getEntitiesDescriptor();
550 // Tertiary match is the AnySite rule.
551 ruleStack.push_back(&m_anySiteRule);
553 // Still don't support complex content models...
554 DOMNode* n=e->getFirstChild();
555 bool bSimple=(n && n->getNodeType()==DOMNode::TEXT_NODE);
557 // With the new algorithm, we evaluate each matching rule in sequence, separately.
558 for (vector<const SiteRule*>::const_iterator rule=ruleStack.begin(); rule!=ruleStack.end(); rule++) {
560 // Check for shortcut AnyValue blanket rule.
561 if ((*rule)->anyValue) {
562 log.debug("matching site rule, any value match");
563 return scopeCheck(e,role,ruleStack);
566 // Now run any denials.
567 vector<pair<value_type,const XMLCh*> >::const_iterator i;
568 for (i=(*rule)->valueDenials.begin(); bSimple && i!=(*rule)->valueDenials.end(); i++) {
571 if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
572 (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
573 if (log.isWarnEnabled()) {
574 auto_ptr_char temp(m_name);
575 log.warn("attribute (%s) value explicitly denied by site rule, rejecting it",temp.get());
582 if (match(i->second,n->getNodeValue())) {
583 if (log.isWarnEnabled()) {
584 auto_ptr_char temp(m_name);
585 log.warn("attribute (%s) value explicitly denied by site rule, rejecting it",temp.get());
592 log.warn("implementation does not support XPath value rules");
597 // Now run any accepts.
598 for (i=(*rule)->valueAccepts.begin(); bSimple && i!=(*rule)->valueAccepts.end(); i++) {
601 if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
602 (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
603 log.debug("site rule, value match");
604 return scopeCheck(e,role,ruleStack);
609 if (match(i->second,n->getNodeValue())) {
610 log.debug("site rule, value match");
611 return scopeCheck(e,role,ruleStack);
616 log.warn("implementation does not support XPath value rules");
622 if (log.isWarnEnabled()) {
623 auto_ptr_char temp(m_name);
624 auto_ptr_char temp2(n->getNodeValue());
625 log.warn("%sattribute (%s) value (%s) could not be validated by policy, rejecting it",
626 (bSimple ? "" : "complex "),temp.get(),temp2.get());
631 void XMLAAPImpl::AttributeRule::apply(SAMLAttribute& attribute, const IRoleDescriptor* role) const
634 DOMNodeList* vals=attribute.getValueElements();
636 for (int i=0; vals && i < vals->getLength(); i++) {
637 if (!accept(static_cast<DOMElement*>(vals->item(i)),role ? dynamic_cast<const IScopedRoleDescriptor*>(role) : NULL))
638 attribute.removeValue(i2);
643 // Now see if we trashed it irrevocably.
644 attribute.checkValidity();