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 using namespace xmlproviders::logging;
28 using namespace shibboleth;
32 #include <xercesc/util/regx/RegularExpression.hpp>
36 class XMLAAPImpl : public ReloadableXMLFileImpl
39 XMLAAPImpl(const char* pathname) : ReloadableXMLFileImpl(pathname), anyAttribute(false) { init(); }
40 XMLAAPImpl(const DOMElement* e) : ReloadableXMLFileImpl(e), anyAttribute(false) { init(); }
44 class AttributeRule : public IAttributeRule
47 AttributeRule(const DOMElement* e);
50 const XMLCh* getName() const { return m_name; }
51 const XMLCh* getNamespace() const { return m_namespace; }
52 const char* getAlias() const { return m_alias.get(); }
53 const char* getHeader() const { return m_header.get(); }
54 bool getCaseSensitive() const { return m_caseSensitive; }
55 bool getScoped() const { return m_scoped; }
56 void apply(SAMLAttribute& attribute, const IRoleDescriptor* role=NULL) const;
58 enum value_type { literal, regexp, xpath };
61 const XMLCh* m_namespace;
62 auto_ptr_char m_alias;
63 auto_ptr_char m_header;
69 SiteRule() : anyValue(false) {}
71 vector<pair<value_type,const XMLCh*> > valueDenials;
72 vector<pair<value_type,const XMLCh*> > valueAccepts;
73 vector<pair<value_type,const XMLCh*> > scopeDenials;
74 vector<pair<value_type,const XMLCh*> > scopeAccepts;
77 value_type toValueType(const DOMElement* e);
80 const IScopedRoleDescriptor* role,
81 const vector<const SiteRule*>& ruleStack
83 bool accept(const DOMElement* e, const IScopedRoleDescriptor* role=NULL) const;
85 SiteRule m_anySiteRule;
87 typedef map<xstring,SiteRule> sitemap_t;
89 typedef map<string,SiteRule> sitemap_t;
95 vector<const IAttributeRule*> m_attrs;
96 map<string,const IAttributeRule*> m_aliasMap;
98 typedef map<xstring,AttributeRule*> attrmap_t;
100 typedef map<string,AttributeRule*> attrmap_t;
105 class XMLAAP : public IAAP, public ReloadableXMLFile
108 XMLAAP(const DOMElement* e) : ReloadableXMLFile(e) {}
111 bool anyAttribute() const {return static_cast<XMLAAPImpl*>(getImplementation())->anyAttribute;}
112 const IAttributeRule* lookup(const XMLCh* attrName, const XMLCh* attrNamespace=NULL) const;
113 const IAttributeRule* lookup(const char* alias) const;
114 Iterator<const IAttributeRule*> getAttributeRules() const;
117 virtual ReloadableXMLFileImpl* newImplementation(const char* pathname, bool first=true) const;
118 virtual ReloadableXMLFileImpl* newImplementation(const DOMElement* e, bool first=true) const;
123 IPlugIn* XMLAAPFactory(const DOMElement* e)
125 auto_ptr<XMLAAP> aap(new XMLAAP(e));
126 aap->getImplementation();
127 return aap.release();
130 ReloadableXMLFileImpl* XMLAAP::newImplementation(const DOMElement* e, bool first) const
132 return new XMLAAPImpl(e);
135 ReloadableXMLFileImpl* XMLAAP::newImplementation(const char* pathname, bool first) const
137 return new XMLAAPImpl(pathname);
140 void XMLAAPImpl::init()
143 saml::NDC ndc("init");
145 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
149 if (!saml::XML::isElementNamed(m_root,::XML::SHIB_NS,SHIB_L(AttributeAcceptancePolicy)))
151 log.error("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
152 throw MalformedException("Construction requires a valid AAP file: (shib:AttributeAcceptancePolicy as root element)");
155 // Check for AnyAttribute element.
156 DOMElement* anyAttr = saml::XML::getFirstChildElement(m_root,::XML::SHIB_NS,SHIB_L(AnyAttribute));
159 log.warn("<AnyAttribute> found, will short-circuit all attribute value and scope filtering");
162 // Loop over the AttributeRule elements.
163 DOMNodeList* nlist = m_root->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AttributeRule));
164 for (XMLSize_t i=0; nlist && i<nlist->getLength(); i++)
166 AttributeRule* rule=new AttributeRule(static_cast<DOMElement*>(nlist->item(i)));
168 xstring key=rule->getName();
169 key=key + chBang + chBang + (rule->getNamespace() ? rule->getNamespace() : Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
171 auto_ptr_char aname(rule->getName());
172 string key(aname.get());
174 if (rule->getNamespace())
176 auto_ptr_char ans(rule->getNamespace());
180 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
183 m_attrs.push_back(rule);
184 if (rule->getAlias()) {
185 // user can only apply to REMOTE_USER
186 if (!strcmp(rule->getAlias(),"user")) {
187 if (strcmp(rule->getHeader(),"REMOTE_USER"))
188 log.error("<AttributeRule> cannot specify Alias of 'user', please use alternate value");
190 m_aliasMap[rule->getAlias()]=rule;
193 m_aliasMap[rule->getAlias()]=rule;
198 catch (SAMLException& e)
200 log.errorStream() << "Error while parsing AAP: " << e.what() << xmlproviders::logging::eol;
207 log.error("Unexpected error while parsing AAP");
214 XMLAAPImpl::~XMLAAPImpl()
216 for (attrmap_t::iterator i=m_attrMap.begin(); i!=m_attrMap.end(); i++)
220 XMLAAPImpl::AttributeRule::AttributeRule(const DOMElement* e) :
221 m_alias(e->hasAttributeNS(NULL,SHIB_L(Alias)) ? e->getAttributeNS(NULL,SHIB_L(Alias)) : NULL),
222 m_header(e->hasAttributeNS(NULL,SHIB_L(Header)) ? e->getAttributeNS(NULL,SHIB_L(Header)) : NULL),
226 m_name=e->getAttributeNS(NULL,SHIB_L(Name));
227 m_namespace=e->getAttributeNS(NULL,SHIB_L(Namespace));
228 if (!m_namespace || !*m_namespace)
229 m_namespace=Constants::SHIB_ATTRIBUTE_NAMESPACE_URI;
231 const XMLCh* caseSensitive=e->getAttributeNS(NULL,SHIB_L(CaseSensitive));
232 m_caseSensitive=(!caseSensitive || !*caseSensitive || *caseSensitive==chDigit_1 || *caseSensitive==chLatin_t);
234 const XMLCh* scoped=e->getAttributeNS(NULL,SHIB_L(Scoped));
235 m_scoped=(scoped && (*scoped==chDigit_1 || *scoped==chLatin_t));
237 // Check for an AnySite rule.
238 DOMElement* anysite = saml::XML::getFirstChildElement(e);
239 if (anysite && saml::XML::isElementNamed(static_cast<DOMElement*>(anysite),::XML::SHIB_NS,SHIB_L(AnySite)))
241 // Process Scope elements.
242 DOMNodeList* vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Scope));
243 for (XMLSize_t i=0; vlist && i<vlist->getLength(); i++)
246 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
247 DOMNode* valnode=se->getFirstChild();
248 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
250 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
251 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
252 m_anySiteRule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
254 m_anySiteRule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
258 // Check for an AnyValue rule.
259 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AnyValue));
260 if (vlist && vlist->getLength())
262 m_anySiteRule.anyValue=true;
266 // Process each Value element.
267 vlist = static_cast<DOMElement*>(anysite)->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Value));
268 for (XMLSize_t j=0; vlist && j<vlist->getLength(); j++)
270 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
271 DOMNode* valnode=ve->getFirstChild();
272 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
273 const XMLCh* accept=ve->getAttributeNS(NULL,SHIB_L(Accept));
274 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
275 m_anySiteRule.valueAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
277 m_anySiteRule.valueDenials.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
283 // Loop over the SiteRule elements.
284 DOMNodeList* slist = e->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(SiteRule));
285 for (XMLSize_t k=0; slist && k<slist->getLength(); k++)
287 const XMLCh* srulename=static_cast<DOMElement*>(slist->item(k))->getAttributeNS(NULL,SHIB_L(Name));
289 m_siteMap[srulename]=SiteRule();
290 SiteRule& srule=m_siteMap[srulename];
292 auto_ptr_char srulename2(srulename);
293 m_siteMap[srulename2.get()]=SiteRule();
294 SiteRule& srule=m_siteMap[srulename2.get()];
297 // Process Scope elements.
298 DOMNodeList* vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Scope));
299 for (XMLSize_t i=0; vlist && i<vlist->getLength(); i++)
302 DOMElement* se=static_cast<DOMElement*>(vlist->item(i));
303 DOMNode* valnode=se->getFirstChild();
304 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE)
306 const XMLCh* accept=se->getAttributeNS(NULL,SHIB_L(Accept));
307 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
308 srule.scopeAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
310 srule.scopeDenials.push_back(pair<value_type,const XMLCh*>(toValueType(se),valnode->getNodeValue()));
314 // Check for an AnyValue rule.
315 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(AnyValue));
316 if (vlist && vlist->getLength())
322 // Process each Value element.
323 vlist = static_cast<DOMElement*>(slist->item(k))->getElementsByTagNameNS(::XML::SHIB_NS,SHIB_L(Value));
324 for (XMLSize_t j=0; vlist && j<vlist->getLength(); j++)
326 DOMElement* ve=static_cast<DOMElement*>(vlist->item(j));
327 DOMNode* valnode=ve->getFirstChild();
328 if (valnode && valnode->getNodeType()==DOMNode::TEXT_NODE) {
329 const XMLCh* accept=ve->getAttributeNS(NULL,SHIB_L(Accept));
330 if (!accept || !*accept || *accept==chDigit_1 || *accept==chLatin_t)
331 srule.valueAccepts.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
333 srule.valueDenials.push_back(pair<value_type,const XMLCh*>(toValueType(ve),valnode->getNodeValue()));
340 XMLAAPImpl::AttributeRule::value_type XMLAAPImpl::AttributeRule::toValueType(const DOMElement* e)
342 if (!XMLString::compareString(SHIB_L(literal),e->getAttributeNS(NULL,SHIB_L(Type))))
344 else if (!XMLString::compareString(SHIB_L(regexp),e->getAttributeNS(NULL,SHIB_L(Type))))
346 else if (!XMLString::compareString(SHIB_L(xpath),e->getAttributeNS(NULL,SHIB_L(Type))))
348 throw MalformedException("Found an invalid value or scope rule type.");
351 const IAttributeRule* XMLAAP::lookup(const XMLCh* attrName, const XMLCh* attrNamespace) const
354 xstring key=attrName;
355 key=key + chBang + chBang + (attrNamespace ? attrNamespace : Constants::SHIB_ATTRIBUTE_NAMESPACE_URI);
357 auto_ptr_char aname(attrName);
358 string key=aname.get();
362 auto_ptr_char ans(attrNamespace);
366 key+="urn:mace:shibboleth:1.0:attributeNamespace:uri";
368 XMLAAPImpl* impl=dynamic_cast<XMLAAPImpl*>(getImplementation());
369 XMLAAPImpl::attrmap_t::const_iterator i=impl->m_attrMap.find(key);
370 return (i==impl->m_attrMap.end()) ? NULL : i->second;
373 const IAttributeRule* XMLAAP::lookup(const char* alias) const
375 XMLAAPImpl* impl=dynamic_cast<XMLAAPImpl*>(getImplementation());
376 map<string,const IAttributeRule*>::const_iterator i=impl->m_aliasMap.find(alias);
377 return (i==impl->m_aliasMap.end()) ? NULL : i->second;
380 Iterator<const IAttributeRule*> XMLAAP::getAttributeRules() const
382 return dynamic_cast<XMLAAPImpl*>(getImplementation())->m_attrs;
386 bool match(const XMLCh* exp, const XMLCh* test)
390 RegularExpression re(exp);
391 if (re.matches(test))
394 catch (XMLException& ex)
396 auto_ptr<char> tmp(XMLString::transcode(ex.getMessage()));
397 Category::getInstance(XMLPROVIDERS_LOGCAT".XMLAAPImpl").errorStream()
398 << "caught exception while parsing regular expression: " << tmp.get() << xmlproviders::logging::eol;
404 bool XMLAAPImpl::AttributeRule::scopeCheck(
406 const IScopedRoleDescriptor* role,
407 const vector<const SiteRule*>& ruleStack
411 saml::NDC ndc("scopeCheck");
413 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
416 const XMLCh* scope=e->getAttributeNS(NULL,SHIB_L(Scope));
417 if (!scope || !*scope) {
418 // Are we allowed to be unscoped?
419 if (m_scoped && log.isWarnEnabled()) {
420 auto_ptr_char temp(m_name);
421 log.warn("attribute (%s) is scoped, no scope supplied, rejecting it",temp.get());
426 // With the new algorithm, we evaluate each matching rule in sequence, separately.
427 for (vector<const SiteRule*>::const_iterator rule=ruleStack.begin(); rule!=ruleStack.end(); rule++) {
429 // Now run any denials.
430 vector<pair<value_type,const XMLCh*> >::const_iterator i;
431 for (i=(*rule)->scopeDenials.begin(); i!=(*rule)->scopeDenials.end(); i++) {
432 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
433 (i->first==regexp && match(i->second,scope))) {
434 if (log.isWarnEnabled()) {
435 auto_ptr_char temp(m_name);
436 auto_ptr_char temp2(scope);
437 log.warn("attribute (%s) scope (%s) denied by site rule, rejecting it",temp.get(),temp2.get());
441 else if (i->first==xpath)
442 log.warn("scope checking does not permit XPath rules");
445 // Now run any accepts.
446 for (i=(*rule)->scopeAccepts.begin(); i!=(*rule)->scopeAccepts.end(); i++) {
447 if ((i->first==literal && !XMLString::compareString(i->second,scope)) ||
448 (i->first==regexp && match(i->second,scope))) {
449 log.debug("matching site rule, scope match");
452 else if (i->first==xpath)
453 log.warn("scope checking does not permit XPath rules");
457 // If we still can't decide, defer to metadata.
459 Iterator<pair<const XMLCh*,bool> > domains=role->getScopes();
460 while (domains.hasNext()) {
461 const pair<const XMLCh*,bool>& p=domains.next();
462 if ((p.second && match(p.first,scope)) || !XMLString::compareString(p.first,scope)) {
463 log.debug("scope match via site metadata");
469 if (log.isWarnEnabled()) {
470 auto_ptr_char temp(m_name);
471 auto_ptr_char temp2(scope);
472 log.warn("attribute (%s) scope (%s) not accepted",temp.get(),temp2.get());
477 bool XMLAAPImpl::AttributeRule::accept(const DOMElement* e, const IScopedRoleDescriptor* role) const
480 saml::NDC ndc("accept");
482 Category& log=Category::getInstance(XMLPROVIDERS_LOGCAT".AAP");
484 if (log.isDebugEnabled()) {
485 auto_ptr_char temp(m_name);
486 auto_ptr_char temp2(role ? role->getEntityDescriptor()->getId() : NULL);
487 log.debug("evaluating value for attribute (%s) from site (%s)",temp.get(),temp2.get() ? temp2.get() : "<unspecified>");
490 // This is a complete revamp. The "any" cases become a degenerate case, the "least-specific" matching rule.
491 // The first step is to build a list of matching rules, most-specific to least-specific.
493 vector<const SiteRule*> ruleStack;
495 // Primary match is against entityID.
497 const XMLCh* os=role->getEntityDescriptor()->getId();
499 auto_ptr_char pos(role->getEntityDescriptor()->getId());
500 const char* os=pos.get();
502 sitemap_t::const_iterator srule=m_siteMap.find(os);
503 if (srule!=m_siteMap.end())
504 ruleStack.push_back(&srule->second);
506 // Secondary matches are on groups.
507 const IEntitiesDescriptor* group=role->getEntityDescriptor()->getEntitiesDescriptor();
509 if (group->getName()) {
513 auto_ptr_char gname(group->getName());
514 const char* os=gname.get();
516 srule=m_siteMap.find(os);
517 if (srule!=m_siteMap.end())
518 ruleStack.push_back(&srule->second);
520 group = group->getEntitiesDescriptor();
523 // Tertiary match is the AnySite rule.
524 ruleStack.push_back(&m_anySiteRule);
526 // Still don't support complex content models...
527 DOMNode* n=e->getFirstChild();
528 bool bSimple=(n && n->getNodeType()==DOMNode::TEXT_NODE);
530 // With the new algorithm, we evaluate each matching rule in sequence, separately.
531 for (vector<const SiteRule*>::const_iterator rule=ruleStack.begin(); rule!=ruleStack.end(); rule++) {
533 // Check for shortcut AnyValue blanket rule.
534 if ((*rule)->anyValue) {
535 log.debug("matching site rule, any value match");
536 return scopeCheck(e,role,ruleStack);
539 // Now run any denials.
540 vector<pair<value_type,const XMLCh*> >::const_iterator i;
541 for (i=(*rule)->valueDenials.begin(); bSimple && i!=(*rule)->valueDenials.end(); i++) {
544 if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
545 (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
546 if (log.isWarnEnabled()) {
547 auto_ptr_char temp(m_name);
548 log.warn("attribute (%s) value explicitly denied by site rule, rejecting it",temp.get());
555 if (match(i->second,n->getNodeValue())) {
556 if (log.isWarnEnabled()) {
557 auto_ptr_char temp(m_name);
558 log.warn("attribute (%s) value explicitly denied by site rule, rejecting it",temp.get());
565 log.warn("implementation does not support XPath value rules");
570 // Now run any accepts.
571 for (i=(*rule)->valueAccepts.begin(); bSimple && i!=(*rule)->valueAccepts.end(); i++) {
574 if ((m_caseSensitive && !XMLString::compareString(i->second,n->getNodeValue())) ||
575 (!m_caseSensitive && !XMLString::compareIString(i->second,n->getNodeValue()))) {
576 log.debug("site rule, value match");
577 return scopeCheck(e,role,ruleStack);
582 if (match(i->second,n->getNodeValue())) {
583 log.debug("site rule, value match");
584 return scopeCheck(e,role,ruleStack);
589 log.warn("implementation does not support XPath value rules");
595 if (log.isWarnEnabled()) {
596 auto_ptr_char temp(m_name);
598 auto_ptr_char temp2(n->getNodeValue());
599 log.warn("%sattribute (%s) value (%s) could not be validated by policy, rejecting it",
600 (bSimple ? "" : "complex "),temp.get(),temp2.get());
603 log.warn("empty value in attribute (%s) is not valid SAML, rejecting", temp.get());
609 void XMLAAPImpl::AttributeRule::apply(SAMLAttribute& attribute, const IRoleDescriptor* role) const
612 DOMNodeList* vals=attribute.getValueElements();
614 for (XMLSize_t i=0; vals && i < vals->getLength(); i++) {
615 if (!accept(static_cast<DOMElement*>(vals->item(i)),role ? dynamic_cast<const IScopedRoleDescriptor*>(role) : NULL))
616 attribute.removeValue(i2);
621 // Now see if we trashed it irrevocably.
622 attribute.checkValidity();