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 /* AAP.cpp - XML AAP implementation
61 #include <log4cpp/Category.hh>
63 using namespace shibboleth;
65 using namespace log4cpp;
68 #include <xercesc/framework/URLInputSource.hpp>
69 #include <xercesc/util/regx/RegularExpression.hpp>
71 class shibboleth::XMLAAPImpl
74 XMLAAPImpl(const char* pathname);
77 void regAttributes() const;
79 class AttributeRule : public IAttributeRule
82 AttributeRule(const DOMElement* e);
85 const XMLCh* getName() const { return m_name; }
86 const XMLCh* getNamespace() const { return m_namespace; }
87 const char* getFactory() const { return m_factory.get(); }
88 const char* getAlias() const { return m_alias.get(); }
89 const char* getHeader() const { return m_header.get(); }
90 bool accept(const XMLCh* originSite, const DOMElement* e) const;
92 enum value_type { literal, regexp, xpath };
95 const XMLCh* m_namespace;
96 auto_ptr<char> m_factory;
97 auto_ptr<char> m_alias;
98 auto_ptr<char> m_header;
100 value_type toValueType(const DOMElement* e);
101 bool scopeCheck(const XMLCh* originSite, const DOMElement* e) const;
105 SiteRule() : anyValue(false) {}
107 vector<pair<value_type,const XMLCh*> > valueRules;
108 vector<pair<value_type,const XMLCh*> > scopeDenials;
109 vector<pair<value_type,const XMLCh*> > scopeAccepts;
112 SiteRule m_anySiteRule;
113 map<xstring,SiteRule> m_siteMap;
116 vector<const IAttributeRule*> m_attrs;
117 map<string,const IAttributeRule*> m_aliasMap;
118 map<xstring,AttributeRule*> m_attrMap;
122 XMLAAPImpl::XMLAAPImpl(const char* pathname) : m_doc(NULL)
124 NDC ndc("XMLAAPImpl");
125 Category& log=Category::getInstance(SHIB_LOGCAT".XMLAAPImpl");
130 static XMLCh base[]={chLatin_f, chLatin_i, chLatin_l, chLatin_e, chColon, chForwardSlash, chForwardSlash, chForwardSlash, chNull};
131 URLInputSource src(base,pathname);
132 Wrapper4InputSource dsrc(&src,false);
135 log.infoStream() << "Loaded and parsed AAP file (" << pathname << ")" << CategoryStream::ENDLINE;
137 DOMElement* e = m_doc->getDocumentElement();
138 if (XMLString::compareString(XML::SHIB_NS,e->getNamespaceURI()) ||
139 XMLString::compareString(SHIB_L(AttributeAcceptancePolicy),e->getLocalName()))
141 log.error("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
142 throw MalformedException("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
145 // Loop over the AttributeRule elements.
146 DOMNodeList* nlist = e->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(AttributeRule));
147 for (int i=0; nlist && i<nlist->getLength(); i++)
149 AttributeRule* rule=new AttributeRule(static_cast<DOMElement*>(nlist->item(i)));
150 m_attrMap[xstring(rule->getName()) + chBang + chBang + (rule->getNamespace() ? rule->getNamespace() : Constants::SHIB_ATTRIBUTE_NAMESPACE_URI)]=rule;
151 m_attrs.push_back(rule);
152 if (rule->getAlias())
153 m_aliasMap[rule->getAlias()]=rule;
156 catch (SAMLException& e)
158 log.errorStream() << "XML error while parsing AAP: " << e.what() << CategoryStream::ENDLINE;
159 for (map<xstring,AttributeRule*>::iterator i=m_attrMap.begin(); i!=m_attrMap.end(); i++)
167 log.error("Unexpected error while parsing AAP");
168 for (map<xstring,AttributeRule*>::iterator i=m_attrMap.begin(); i!=m_attrMap.end(); i++)
177 XMLAAPImpl::~XMLAAPImpl()
179 for (map<xstring,AttributeRule*>::iterator i=m_attrMap.begin(); i!=m_attrMap.end(); i++)
181 SAMLAttribute::unregFactory(i->second->getName(),i->second->getNamespace());
188 void XMLAAPImpl::regAttributes() const
190 for (map<xstring,AttributeRule*>::const_iterator i=m_attrMap.begin(); i!=m_attrMap.end(); i++)
192 SAMLAttributeFactory* f=ShibConfig::getConfig().getAttributeFactory(i->second->getFactory());
194 SAMLAttribute::regFactory(i->second->getName(),i->second->getNamespace(),f);
198 XMLAAPImpl::AttributeRule::AttributeRule(const DOMElement* e) :
199 m_factory(e->hasAttributeNS(NULL,SHIB_L(Factory)) ? XMLString::transcode(e->getAttributeNS(NULL,SHIB_L(Factory))) : NULL),
200 m_alias(e->hasAttributeNS(NULL,SHIB_L(Alias)) ? XMLString::transcode(e->getAttributeNS(NULL,SHIB_L(Alias))) : NULL),
201 m_header(e->hasAttributeNS(NULL,SHIB_L(Header)) ? XMLString::transcode(e->getAttributeNS(NULL,SHIB_L(Header))) : NULL)
204 static const XMLCh wTrue[] = {chLatin_t, chLatin_r, chLatin_u, chLatin_e, chNull};
206 m_name=e->getAttributeNS(NULL,SHIB_L(Name));
207 m_namespace=e->getAttributeNS(NULL,SHIB_L(Namespace));
208 if (!m_namespace || !*m_namespace)
209 m_namespace=Constants::SHIB_ATTRIBUTE_NAMESPACE_URI;
211 // Check for an AnySite rule.
212 DOMNode* anysite = e->getFirstChild();
213 while (anysite && anysite->getNodeType()!=DOMNode::ELEMENT_NODE)
215 anysite = anysite->getNextSibling();
219 if (anysite && !XMLString::compareString(XML::SHIB_NS,static_cast<DOMElement*>(anysite)->getNamespaceURI()) &&
220 !XMLString::compareString(SHIB_L(AnySite),static_cast<DOMElement*>(anysite)->getLocalName()))
222 // Process Scope elements.
223 DOMNodeList* vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(Scope));
224 for (int i=0; vlist && i<vlist->getLength(); i++)
226 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
227 DOMNode* valnode=se->getFirstChild();
228 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
230 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
231 if (!accept || !*accept || *accept==chDigit_1 || !XMLString::compareString(accept,wTrue))
232 m_anySiteRule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
234 m_anySiteRule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
238 // Check for an AnyValue rule.
239 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(AnyValue));
240 if (vlist && vlist->getLength())
242 m_anySiteRule.anyValue=true;
246 // Process each Value element.
247 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(XML::SHIB_NS,XML::Literals::Value);
248 for (int j=0; vlist && j<vlist->getLength(); j++)
250 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
251 DOMNode* valnode=ve->getFirstChild();
252 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
253 m_anySiteRule.valueRules.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
258 // Loop over the SiteRule elements.
259 DOMNodeList* slist = e->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(SiteRule));
260 for (int k=0; slist && k<slist->getLength(); k++)
262 m_siteMap[static_cast<DOMElement*>(slist->item(k))->getAttributeNS(NULL,SHIB_L(Name))]=SiteRule();
263 SiteRule& srule=m_siteMap[static_cast<DOMElement*>(slist->item(k))->getAttributeNS(NULL,SHIB_L(Name))];
265 // Process Scope elements.
266 DOMNodeList* vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(Scope));
267 for (int i=0; vlist && i<vlist->getLength(); i++)
269 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
270 DOMNode* valnode=se->getFirstChild();
271 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
273 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
274 if (!accept || *accept==chDigit_1 || !XMLString::compareString(accept,wTrue))
275 srule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
277 srule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
281 // Check for an AnyValue rule.
282 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(AnyValue));
283 if (vlist && vlist->getLength())
289 // Process each Value element.
290 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(XML::SHIB_NS,SHIB_L(Value));
291 for (int j=0; vlist && j<vlist->getLength(); j++)
293 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
294 DOMNode* valnode=ve->getFirstChild();
295 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
296 srule.valueRules.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
302 XMLAAPImpl::AttributeRule::value_type XMLAAPImpl::AttributeRule::toValueType(const DOMElement* e)
304 if (!XMLString::compareString(SHIB_L(literal),e->getAttributeNS(NULL,SHIB_L(Type))))
306 else if (!XMLString::compareString(SHIB_L(regexp),e->getAttributeNS(NULL,SHIB_L(Type))))
308 else if (!XMLString::compareString(SHIB_L(xpath),e->getAttributeNS(NULL,SHIB_L(Type))))
310 throw MalformedException("Found an invalid value or scope rule type.");
313 XMLAAP::XMLAAP(const char* pathname) : m_filestamp(0), m_source(pathname), m_impl(NULL)
316 struct _stat stat_buf;
317 if (_stat(pathname, &stat_buf) == 0)
319 struct stat stat_buf;
320 if (stat(pathname, &stat_buf) == 0)
322 m_filestamp=stat_buf.st_mtime;
323 m_impl=new XMLAAPImpl(pathname);
324 SAMLConfig::getConfig().saml_lock();
325 m_impl->regAttributes();
326 SAMLConfig::getConfig().saml_unlock();
327 m_lock=RWLock::create();
340 // Check if we need to refresh.
342 struct _stat stat_buf;
343 if (_stat(m_source.c_str(), &stat_buf) == 0)
345 struct stat stat_buf;
346 if (stat(m_source.c_str(), &stat_buf) == 0)
349 if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
351 // Elevate lock and recheck.
354 if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime)
358 XMLAAPImpl* new_mapper=new XMLAAPImpl(m_source.c_str());
359 SAMLConfig::getConfig().saml_lock();
362 m_impl->regAttributes();
363 SAMLConfig::getConfig().saml_unlock();
364 m_filestamp=stat_buf.st_mtime;
367 catch(SAMLException& e)
370 saml::NDC ndc("lock");
371 Category::getInstance(SHIB_LOGCAT".XMLAAP").error("failed to reload AAP, sticking with what we have: %s", e.what());
376 saml::NDC ndc("lock");
377 Category::getInstance(SHIB_LOGCAT".XMLAAP").error("caught an unknown exception, sticking with what we have");
389 void XMLAAP::unlock()
394 const IAttributeRule* XMLAAP::lookup(const XMLCh* attrName, const XMLCh* attrNamespace) const
396 map<xstring,XMLAAPImpl::AttributeRule*>::const_iterator i=m_impl->m_attrMap.find(
397 xstring(attrName) + chBang + chBang + (attrNamespace ? attrNamespace : Constants::SHIB_ATTRIBUTE_NAMESPACE_URI)
399 return (i==m_impl->m_attrMap.end()) ? NULL : i->second;
402 const IAttributeRule* XMLAAP::lookup(const char* alias) const
404 map<string,const IAttributeRule*>::const_iterator i=m_impl->m_aliasMap.find(alias);
405 return (i==m_impl->m_aliasMap.end()) ? NULL : i->second;
408 Iterator<const IAttributeRule*> XMLAAP::getAttributeRules() const
410 return m_impl->m_attrs;
414 bool match(const XMLCh* exp, const XMLCh* test)
418 RegularExpression re(exp);
419 if (re.matches(test))
422 catch (XMLException& ex)
424 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
425 Category::getInstance(SHIB_LOGCAT".XMLAAPImpl").errorStream()
426 << "caught exception while parsing regular expression: " << tmp.get() << CategoryStream::ENDLINE;
432 bool XMLAAPImpl::AttributeRule::scopeCheck(const XMLCh* originSite, const DOMElement* e) const
435 const XMLCh* scope=e->getAttributeNS(NULL,SHIB_L(Scope));
436 if (!scope || !*scope)
439 NDC ndc("scopeCheck");
440 Category& log=Category::getInstance(SHIB_LOGCAT".XMLAAPImpl");
442 vector<pair<value_type,const XMLCh*> >::const_iterator i;
444 // Denials take precedence, always.
446 // Any site denials...
447 for (i=m_anySiteRule.scopeDenials.begin(); i!=m_anySiteRule.scopeDenials.end(); i++)
449 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
450 (i->first==regexp && match(i->second,scope)))
452 if (log.isWarnEnabled())
454 auto_ptr<char> temp(XMLString::transcode(m_name));
455 auto_ptr<char> temp2(XMLString::transcode(scope));
456 log.warn("attribute %s scope {%s} denied by any-site AAP, rejecting it",temp.get(),temp2.get());
460 else if (i->first==xpath)
461 log.warn("scope checking does not permit XPath rules");
464 map<xstring,SiteRule>::const_iterator srule=m_siteMap.find(originSite);
465 if (srule!=m_siteMap.end())
467 // Site-specific denials...
468 for (i=srule->second.scopeDenials.begin(); i!=srule->second.scopeDenials.end(); i++)
470 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
471 (i->first==regexp && match(i->second,scope)))
473 if (log.isWarnEnabled())
475 auto_ptr<char> temp(XMLString::transcode(m_name));
476 auto_ptr<char> temp2(XMLString::transcode(scope));
477 log.warn("attribute %s scope {%s} denied by site AAP, rejecting it",temp.get(),temp2.get());
481 else if (i->first==xpath)
482 log.warn("scope checking does not permit XPath rules");
486 // Any site accepts...
487 for (i=m_anySiteRule.scopeAccepts.begin(); i!=m_anySiteRule.scopeAccepts.end(); i++)
489 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
490 (i->first==regexp && match(i->second,scope)))
492 log.debug("any site, scope match");
495 else if (i->first==xpath)
496 log.warn("scope checking does not permit XPath rules");
499 if (srule!=m_siteMap.end())
501 // Site-specific accepts...
502 for (i=srule->second.scopeAccepts.begin(); i!=srule->second.scopeAccepts.end(); i++)
504 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
505 (i->first==regexp && match(i->second,scope)))
507 log.debug("matching site, scope match");
510 else if (i->first==xpath)
511 log.warn("scope checking does not permit XPath rules");
515 // If we still can't decide, defer to site metadata.
516 OriginMetadata mapper(originSite);
517 Iterator<pair<const XMLCh*,bool> > domains=
518 (mapper.fail()) ? Iterator<pair<const XMLCh*,bool> >() : mapper->getSecurityDomains();
519 while (domains.hasNext())
521 const pair<const XMLCh*,bool>& p=domains.next();
522 if ((p.second && match(p.first,scope)) || !XMLString::compareString(p.first,scope))
524 log.debug("scope match via site metadata");
529 if (log.isWarnEnabled())
531 auto_ptr<char> temp(XMLString::transcode(m_name));
532 auto_ptr<char> temp2(XMLString::transcode(scope));
533 log.warn("attribute %s scope {%s} not accepted",temp.get(),temp2.get());
538 bool XMLAAPImpl::AttributeRule::accept(const XMLCh* originSite, const DOMElement* e) const
541 Category& log=Category::getInstance(SHIB_LOGCAT".XMLAAPImpl");
543 if (log.isDebugEnabled())
545 auto_ptr<char> temp(XMLString::transcode(m_name));
546 auto_ptr<char> temp2(XMLString::transcode(originSite));
547 log.debug("evaluating value for attribute %s from site %s",temp.get(),temp2.get());
550 if (m_anySiteRule.anyValue)
552 log.debug("any site, any value, match");
553 return scopeCheck(originSite,e);
556 // Don't fully support complex content models...
557 DOMNode* n=e->getFirstChild();
558 bool bSimple=(n && n->getNodeType()==DOMNode::TEXT_NODE);
560 vector<pair<value_type,const XMLCh*> >::const_iterator i;
561 for (i=m_anySiteRule.valueRules.begin(); bSimple && i!=m_anySiteRule.valueRules.end(); i++)
563 if ((i->first==literal && !XMLString::compareString(i->second,n->getNodeValue())) ||
564 (i->first==regexp && match(i->second,n->getNodeValue())))
566 log.debug("any site, value match");
567 return scopeCheck(originSite,e);
569 else if (i->first==xpath)
570 log.warn("implementation does not support XPath value rules");
573 map<xstring,SiteRule>::const_iterator srule=m_siteMap.find(originSite);
574 if (srule==m_siteMap.end())
576 if (log.isWarnEnabled())
578 auto_ptr<char> temp(XMLString::transcode(m_name));
579 auto_ptr<char> temp2(XMLString::transcode(originSite));
580 log.warn("site %s not found in attribute %s ruleset, any value is rejected",temp2.get(),temp.get());
585 if (srule->second.anyValue)
587 log.debug("matching site, any value, match");
588 return scopeCheck(originSite,e);
591 for (i=srule->second.valueRules.begin(); bSimple && i!=srule->second.valueRules.end(); i++)
593 if ((i->first==literal && !XMLString::compareString(i->second,n->getNodeValue())) ||
594 (i->first==regexp && match(i->second,n->getNodeValue())))
596 log.debug("matching site, value match");
597 return scopeCheck(originSite,e);
599 else if (i->first==xpath)
600 log.warn("implementation does not support XPath value rules");
603 if (log.isWarnEnabled())
605 auto_ptr<char> temp(XMLString::transcode(m_name));
606 auto_ptr<char> temp2(XMLString::transcode(n->getNodeValue()));
607 log.warn("%sattribute %s value {%s} could not be validated by AAP, rejecting it",
608 (bSimple ? "" : "complex "),temp.get(),temp2.get());