2 * Copyright 2001-2005 Internet2
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 /* XMLAAP.cpp - XML AAP implementation
27 #include <log4cpp/Category.hh>
28 #include <shibsp/SPConstants.h>
30 using namespace shibboleth;
32 using namespace log4cpp;
35 #include <xercesc/util/regx/RegularExpression.hpp>
39 class XMLAAPImpl : public ReloadableXMLFileImpl
42 XMLAAPImpl(const char* pathname) : ReloadableXMLFileImpl(pathname), anyAttribute(false) { init(); }
43 XMLAAPImpl(const DOMElement* e) : ReloadableXMLFileImpl(e), anyAttribute(false) { init(); }
47 class AttributeRule : public IAttributeRule
50 AttributeRule(const DOMElement* e);
53 const XMLCh* getName() const { return m_name; }
54 const XMLCh* getNamespace() const { return m_namespace; }
55 const char* getAlias() const { return m_alias.get(); }
56 const char* getHeader() const { return m_header.get(); }
57 bool getCaseSensitive() const { return m_caseSensitive; }
58 bool getScoped() const { return m_scoped; }
59 void apply(SAMLAttribute& attribute, const IEntityDescriptor* source=NULL) const;
61 enum value_type { literal, regexp, xpath };
64 const XMLCh* m_namespace;
65 auto_ptr_char m_alias;
66 auto_ptr_char m_header;
72 SiteRule() : anyValue(false) {}
74 vector<pair<value_type,const XMLCh*> > valueDenials;
75 vector<pair<value_type,const XMLCh*> > valueAccepts;
76 vector<pair<value_type,const XMLCh*> > scopeDenials;
77 vector<pair<value_type,const XMLCh*> > scopeAccepts;
80 value_type toValueType(const DOMElement* e);
83 const IExtendedEntityDescriptor* source,
84 const vector<const SiteRule*>& ruleStack
86 bool accept(const DOMElement* e, const IExtendedEntityDescriptor* source=NULL) const;
88 SiteRule m_anySiteRule;
90 typedef map<xstring,SiteRule> sitemap_t;
92 typedef map<string,SiteRule> sitemap_t;
98 vector<const IAttributeRule*> m_attrs;
99 map<string,const IAttributeRule*> m_aliasMap;
101 typedef map<xstring,AttributeRule*> attrmap_t;
103 typedef map<string,AttributeRule*> attrmap_t;
108 class XMLAAP : public IAAP, public ReloadableXMLFile
111 XMLAAP(const DOMElement* e) : ReloadableXMLFile(e) {}
114 bool anyAttribute() const {return static_cast<XMLAAPImpl*>(getImplementation())->anyAttribute;}
115 const IAttributeRule* lookup(const XMLCh* attrName, const XMLCh* attrNamespace=NULL) const;
116 const IAttributeRule* lookup(const char* alias) const;
117 Iterator<const IAttributeRule*> getAttributeRules() const;
120 virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
121 virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
126 IPlugIn* XMLAAPFactory(const DOMElement* e)
128 auto_ptr<XMLAAP> aap(new XMLAAP(e));
129 aap->getImplementation();
130 return aap.release();
133 ReloadableXMLFileImpl* XMLAAP::newImplementation(const DOMElement* e, bool first) const
135 return new XMLAAPImpl(e);
138 ReloadableXMLFileImpl* XMLAAP::newImplementation(const char* pathname, bool first) const
140 return new XMLAAPImpl(pathname);
143 void XMLAAPImpl::init()
146 saml::NDC ndc("init");
148 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
152 if (!saml::XML::isElementNamed(m_root,::XML::SHIB_NS,SHIB_L(AttributeAcceptancePolicy)))
154 log.error("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
155 throw MalformedException("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
158 // Check for AnyAttribute element.
159 DOMElement* anyAttr = saml::XML::getFirstChildElement(m_root,::XML::SHIB_NS,SHIB_L(AnyAttribute));
162 log.warn("<AnyAttribute> found, will short-circuit all attribute value and scope filtering");
165 // Loop over the AttributeRule elements.
166 DOMNodeList* nlist = m_root->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AttributeRule));
167 for (XMLSize_t i=0; nlist && i<nlist->getLength(); i++)
169 AttributeRule* rule=new AttributeRule(static_cast<DOMElement*>(nlist->item(i)));
171 xstring key=rule->getName();
172 key=key + chBang + chBang + (rule->getNamespace() ? rule->getNamespace() : shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI);
174 auto_ptr_char aname(rule->getName());
175 string key(aname.get());
177 if (rule->getNamespace())
179 auto_ptr_char ans(rule->getNamespace());
183 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
186 m_attrs.push_back(rule);
187 if (rule->getAlias()) {
188 // user can only apply to REMOTE_USER
189 if (!strcmp(rule->getAlias(),"user")) {
190 if (strcmp(rule->getHeader(),"REMOTE_USER"))
191 log.error("<AttributeRule> cannot specify Alias of 'user', please use alternate value");
193 m_aliasMap[rule->getAlias()]=rule;
196 m_aliasMap[rule->getAlias()]=rule;
201 catch (SAMLException& e)
203 log.errorStream() << "Error while parsing AAP: " << e.what() << CategoryStream::ENDLINE;
210 log.error("Unexpected error while parsing AAP");
217 XMLAAPImpl::~XMLAAPImpl()
220 for_each(m_attrMap.begin(),m_attrMap.end(),xmltooling::cleanup_pair<xstring,AttributeRule>());
222 for_each(m_attrMap.begin(),m_attrMap.end(),xmltooling::cleanup_pair<string,AttributeRule>());
226 XMLAAPImpl::AttributeRule::AttributeRule(const DOMElement* e) :
227 m_alias(e->hasAttributeNS(NULL,SHIB_L(Alias)) ? e->getAttributeNS(NULL,SHIB_L(Alias)) : NULL),
228 m_header(e->hasAttributeNS(NULL,SHIB_L(Header)) ? e->getAttributeNS(NULL,SHIB_L(Header)) : NULL),
232 m_name=e->getAttributeNS(NULL,SHIB_L(Name));
233 m_namespace=e->getAttributeNS(NULL,SHIB_L(Namespace));
234 if (!m_namespace || !*m_namespace)
235 m_namespace=shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI;
237 const XMLCh* caseSensitive=e->getAttributeNS(NULL,SHIB_L(CaseSensitive));
238 m_caseSensitive=(!caseSensitive || !*caseSensitive || *caseSensitive==chDigit_1 || *caseSensitive==chLatin_t);
240 const XMLCh* scoped=e->getAttributeNS(NULL,SHIB_L(Scoped));
241 m_scoped=(scoped && (*scoped==chDigit_1 || *scoped==chLatin_t));
243 // Check for an AnySite rule.
244 DOMElement* anysite = saml::XML::getFirstChildElement(e);
245 if (anysite && saml::XML::isElementNamed(static_cast<DOMElement*>(anysite),::XML::SHIB_NS,SHIB_L(AnySite)))
247 // Process Scope elements.
248 DOMNodeList* vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Scope));
249 for (XMLSize_t i=0; vlist && i<vlist->getLength(); i++)
252 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
253 DOMNode* valnode=se->getFirstChild();
254 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
256 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
257 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
258 m_anySiteRule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
260 m_anySiteRule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
264 // Check for an AnyValue rule.
265 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AnyValue));
266 if (vlist && vlist->getLength())
268 m_anySiteRule.anyValue=true;
272 // Process each Value element.
273 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Value));
274 for (XMLSize_t j=0; vlist && j<vlist->getLength(); j++)
276 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
277 DOMNode* valnode=ve->getFirstChild();
278 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
279 const XMLCh* accept=ve->getAttributeNS(NULL,SHIB_L(Accept));
280 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
281 m_anySiteRule.valueAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
283 m_anySiteRule.valueDenials.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
289 // Loop over the SiteRule elements.
290 DOMNodeList* slist = e->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(SiteRule));
291 for (XMLSize_t k=0; slist && k<slist->getLength(); k++)
293 const XMLCh* srulename=static_cast<DOMElement*>(slist->item(k))->getAttributeNS(NULL,SHIB_L(Name));
295 m_siteMap[srulename]=SiteRule();
296 SiteRule& srule=m_siteMap[srulename];
298 auto_ptr_char srulename2(srulename);
299 m_siteMap[srulename2.get()]=SiteRule();
300 SiteRule& srule=m_siteMap[srulename2.get()];
303 // Process Scope elements.
304 DOMNodeList* vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Scope));
305 for (XMLSize_t i=0; vlist && i<vlist->getLength(); i++)
308 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
309 DOMNode* valnode=se->getFirstChild();
310 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
312 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
313 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
314 srule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
316 srule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
320 // Check for an AnyValue rule.
321 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AnyValue));
322 if (vlist && vlist->getLength())
328 // Process each Value element.
329 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Value));
330 for (XMLSize_t j=0; vlist && j<vlist->getLength(); j++)
332 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
333 DOMNode* valnode=ve->getFirstChild();
334 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
335 const XMLCh* accept=ve->getAttributeNS(NULL,SHIB_L(Accept));
336 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
337 srule.valueAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
339 srule.valueDenials.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
346 XMLAAPImpl::AttributeRule::value_type XMLAAPImpl::AttributeRule::toValueType(const DOMElement* e)
348 if (!XMLString::compareString(SHIB_L(literal),e->getAttributeNS(NULL,SHIB_L(Type))))
350 else if (!XMLString::compareString(SHIB_L(regexp),e->getAttributeNS(NULL,SHIB_L(Type))))
352 else if (!XMLString::compareString(SHIB_L(xpath),e->getAttributeNS(NULL,SHIB_L(Type))))
354 throw MalformedException("Found an invalid value or scope rule type.");
357 const IAttributeRule* XMLAAP::lookup(const XMLCh* attrName, const XMLCh* attrNamespace) const
360 xstring key=attrName;
361 key=key + chBang + chBang + (attrNamespace ? attrNamespace : shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI);
363 auto_ptr_char aname(attrName);
364 string key=aname.get();
368 auto_ptr_char ans(attrNamespace);
372 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
374 XMLAAPImpl* impl=dynamic_cast<XMLAAPImpl*>(getImplementation());
375 XMLAAPImpl::attrmap_t::const_iterator i=impl->m_attrMap.find(key);
376 return (i==impl->m_attrMap.end()) ? NULL : i->second;
379 const IAttributeRule* XMLAAP::lookup(const char* alias) const
381 XMLAAPImpl* impl=dynamic_cast<XMLAAPImpl*>(getImplementation());
382 map<string,const IAttributeRule*>::const_iterator i=impl->m_aliasMap.find(alias);
383 return (i==impl->m_aliasMap.end()) ? NULL : i->second;
386 Iterator<const IAttributeRule*> XMLAAP::getAttributeRules() const
388 return dynamic_cast<XMLAAPImpl*>(getImplementation())->m_attrs;
392 bool match(const XMLCh* exp, const XMLCh* test)
396 RegularExpression re(exp);
397 if (re.matches(test))
400 catch (XMLException& ex)
402 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
403 Category::getInstance(XMLPROVIDERS_LOGCAT".XMLAAPImpl").errorStream()
404 << "caught exception while parsing regular expression: " << tmp.get() << CategoryStream::ENDLINE;
410 bool XMLAAPImpl::AttributeRule::scopeCheck(
412 const IExtendedEntityDescriptor* source,
413 const vector<const SiteRule*>& ruleStack
417 saml::NDC ndc("scopeCheck");
419 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
422 const XMLCh* scope=e->getAttributeNS(NULL,SHIB_L(Scope));
423 if (!scope || !*scope) {
424 // Are we allowed to be unscoped?
425 if (m_scoped && log.isWarnEnabled()) {
426 auto_ptr_char temp(m_name);
427 log.warn("attribute (%s) is scoped, no scope supplied, rejecting it",temp.get());
432 // With the new algorithm, we evaluate each matching rule in sequence, separately.
433 for (vector<const SiteRule*>::const_iterator rule=ruleStack.begin(); rule!=ruleStack.end(); rule++) {
435 // Now run any denials.
436 vector<pair<value_type,const XMLCh*> >::const_iterator i;
437 for (i=(*rule)->scopeDenials.begin(); i!=(*rule)->scopeDenials.end(); i++) {
438 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
439 (i->first==regexp && match(i->second,scope))) {
440 if (log.isWarnEnabled()) {
441 auto_ptr_char temp(m_name);
442 auto_ptr_char temp2(scope);
443 log.warn("attribute (%s) scope (%s) denied by site rule, rejecting it",temp.get(),temp2.get());
447 else if (i->first==xpath)
448 log.warn("scope checking does not permit XPath rules");
451 // Now run any accepts.
452 for (i=(*rule)->scopeAccepts.begin(); i!=(*rule)->scopeAccepts.end(); i++) {
453 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
454 (i->first==regexp && match(i->second,scope))) {
455 log.debug("matching site rule, scope match");
458 else if (i->first==xpath)
459 log.warn("scope checking does not permit XPath rules");
463 // If we still can't decide, defer to metadata.
465 Iterator<pair<const XMLCh*,bool> > domains=source->getScopes();
466 while (domains.hasNext()) {
467 const pair<const XMLCh*,bool>& p=domains.next();
468 if ((p.second && match(p.first,scope)) || !XMLString::compareString(p.first,scope)) {
469 log.debug("scope match via site metadata");
475 if (log.isWarnEnabled()) {
476 auto_ptr_char temp(m_name);
477 auto_ptr_char temp2(scope);
478 log.warn("attribute (%s) scope (%s) not accepted",temp.get(),temp2.get());
483 bool XMLAAPImpl::AttributeRule::accept(const DOMElement* e, const IExtendedEntityDescriptor* source) const
486 saml::NDC ndc("accept");
488 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
490 if (log.isDebugEnabled()) {
491 auto_ptr_char temp(m_name);
492 auto_ptr_char temp2(source ? source->getId() : NULL);
493 log.debug("evaluating value for attribute (%s) from site (%s)",temp.get(),temp2.get() ? temp2.get() : "<unspecified>");
496 // This is a complete revamp. The "any" cases become a degenerate case, the "least-specific" matching rule.
497 // The first step is to build a list of matching rules, most-specific to least-specific.
499 vector<const SiteRule*> ruleStack;
501 // Primary match is against entityID.
503 const XMLCh* os=source->getId();
505 auto_ptr_char pos(source->getId());
506 const char* os=pos.get();
508 sitemap_t::const_iterator srule=m_siteMap.find(os);
509 if (srule!=m_siteMap.end())
510 ruleStack.push_back(&srule->second);
512 // Secondary matches are on groups.
513 const IEntitiesDescriptor* group=source->getEntitiesDescriptor();
515 if (group->getName()) {
519 auto_ptr_char gname(group->getName());
520 const char* os=gname.get();
522 srule=m_siteMap.find(os);
523 if (srule!=m_siteMap.end())
524 ruleStack.push_back(&srule->second);
526 group = group->getEntitiesDescriptor();
529 // Tertiary match is the AnySite rule.
530 ruleStack.push_back(&m_anySiteRule);
532 // Still don't support complex content models...
533 DOMNode* n=e->getFirstChild();
534 bool bSimple=(n && n->getNodeType()==DOMNode::TEXT_NODE);
536 // With the new algorithm, we evaluate each matching rule in sequence, separately.
537 for (vector<const SiteRule*>::const_iterator rule=ruleStack.begin(); rule!=ruleStack.end(); rule++) {
539 // Check for shortcut AnyValue blanket rule.
540 if ((*rule)->anyValue) {
541 log.debug("matching site rule, any value match");
542 return scopeCheck(e,source,ruleStack);
545 // Now run any denials.
546 vector<pair<value_type,const XMLCh*> >::const_iterator i;
547 for (i=(*rule)->valueDenials.begin(); bSimple && i!=(*rule)->valueDenials.end(); i++) {
550 if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
551 (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
552 if (log.isWarnEnabled()) {
553 auto_ptr_char temp(m_name);
554 log.warn("attribute (%s) value explicitly denied by site rule, rejecting it",temp.get());
561 if (match(i->second,n->getNodeValue())) {
562 if (log.isWarnEnabled()) {
563 auto_ptr_char temp(m_name);
564 log.warn("attribute (%s) value explicitly denied by site rule, rejecting it",temp.get());
571 log.warn("implementation does not support XPath value rules");
576 // Now run any accepts.
577 for (i=(*rule)->valueAccepts.begin(); bSimple && i!=(*rule)->valueAccepts.end(); i++) {
580 if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
581 (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
582 log.debug("site rule, value match");
583 return scopeCheck(e,source,ruleStack);
588 if (match(i->second,n->getNodeValue())) {
589 log.debug("site rule, value match");
590 return scopeCheck(e,source,ruleStack);
595 log.warn("implementation does not support XPath value rules");
601 if (log.isWarnEnabled()) {
602 auto_ptr_char temp(m_name);
603 auto_ptr_char temp2(n->getNodeValue());
604 log.warn("%sattribute (%s) value (%s) could not be validated by policy, rejecting it",
605 (bSimple ? "" : "complex "),temp.get(),temp2.get());
610 void XMLAAPImpl::AttributeRule::apply(SAMLAttribute& attribute, const IEntityDescriptor* source) const
613 DOMNodeList* vals=attribute.getValueElements();
615 for (XMLSize_t i=0; vals && i < vals->getLength(); i++) {
616 if (!accept(static_cast<DOMElement*>(vals->item(i)),source ? dynamic_cast<const IExtendedEntityDescriptor*>(source) : NULL))
617 attribute.removeValue(i2);
622 // Now see if we trashed it irrevocably.
623 attribute.checkValidity();