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 /* XMLRequestMapper.cpp - an XML-based map of URLs to application names and settings
28 #include <shibsp/DOMPropertySet.h>
29 #include <xmltooling/util/ReloadableXMLFile.h>
30 #include <xmltooling/util/XMLHelper.h>
32 using namespace shibsp;
33 using namespace shibtarget;
34 using namespace xmltooling;
35 using namespace log4cpp;
38 namespace shibtarget {
40 // Blocks access when an ACL plugin fails to load.
41 class AccessControlDummy : public IAccessControl
50 bool authorized(ShibTarget* st, ISessionCacheEntry* entry) const {
55 class Override : public DOMPropertySet, public DOMNodeFilter
58 Override() : m_base(NULL), m_acl(NULL) {}
59 Override(const DOMElement* e, Category& log, const Override* base=NULL);
63 pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
64 pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
65 pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
66 pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;
67 pair<bool,int> getInt(const char* name, const char* ns=NULL) const;
68 const PropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const;
70 // Provides filter to exclude special config elements.
71 short acceptNode(const DOMNode* node) const;
73 const Override* locate(const char* path) const;
74 IAccessControl* getAC() const { return (m_acl ? m_acl : (m_base ? m_base->getAC() : NULL)); }
77 void loadACL(const DOMElement* e, Category& log);
79 map<string,Override*> m_map;
82 const Override* m_base;
83 IAccessControl* m_acl;
86 class XMLRequestMapperImpl : public Override
89 XMLRequestMapperImpl(const DOMElement* e, Category& log);
91 ~XMLRequestMapperImpl() {
93 m_document->release();
96 void setDocument(DOMDocument* doc) {
100 const Override* findOverride(const char* vhost, const char* path) const;
103 map<string,Override*> m_extras;
104 DOMDocument* m_document;
107 #if defined (_MSC_VER)
108 #pragma warning( push )
109 #pragma warning( disable : 4250 )
112 class XMLRequestMapper : public IRequestMapper, public xmltooling::ReloadableXMLFile
115 XMLRequestMapper(const DOMElement* e)
116 : xmltooling::ReloadableXMLFile(e), m_impl(NULL), m_log(Category::getInstance(SHIBT_LOGCAT".RequestMapper")) {
120 ~XMLRequestMapper() {}
122 virtual Settings getSettings(ShibTarget* st) const;
125 pair<bool,DOMElement*> load();
128 XMLRequestMapperImpl* m_impl;
132 #if defined (_MSC_VER)
133 #pragma warning( pop )
136 static const XMLCh AccessControl[] = UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);
137 static const XMLCh AccessControlProvider[] = UNICODE_LITERAL_21(A,c,c,e,s,s,C,o,n,t,r,o,l,P,r,o,v,i,d,e,r);
138 static const XMLCh htaccess[] = UNICODE_LITERAL_8(h,t,a,c,c,e,s,s);
139 static const XMLCh Host[] = UNICODE_LITERAL_4(H,o,s,t);
140 static const XMLCh Path[] = UNICODE_LITERAL_4(P,a,t,h);
141 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
142 static const XMLCh type[] = UNICODE_LITERAL_4(t,y,p,e);
145 saml::IPlugIn* XMLRequestMapFactory(const DOMElement* e)
147 return new XMLRequestMapper(e);
150 short Override::acceptNode(const DOMNode* node) const
152 if (!XMLString::equals(node->getNamespaceURI(),shibtarget::XML::SHIBTARGET_NS))
153 return FILTER_ACCEPT;
154 const XMLCh* name=node->getLocalName();
155 if (XMLString::equals(name,Host) ||
156 XMLString::equals(name,Path) ||
157 XMLString::equals(name,AccessControl) ||
158 XMLString::equals(name,htaccess) ||
159 XMLString::equals(name,AccessControlProvider))
160 return FILTER_REJECT;
162 return FILTER_ACCEPT;
165 void Override::loadACL(const DOMElement* e, Category& log)
168 saml::IPlugIn* plugin=NULL;
169 const DOMElement* acl=XMLHelper::getFirstChildElement(e,htaccess);
171 log.info("building Apache htaccess provider...");
172 plugin=saml::SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::htAccessControlType,acl);
175 acl=XMLHelper::getFirstChildElement(e,AccessControl);
177 log.info("building XML-based Access Control provider...");
178 plugin=saml::SAMLConfig::getConfig().getPlugMgr().newPlugin(shibtarget::XML::XMLAccessControlType,acl);
181 acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);
183 xmltooling::auto_ptr_char type(acl->getAttributeNS(NULL,type));
184 log.info("building Access Control provider of type %s...",type.get());
185 plugin=saml::SAMLConfig::getConfig().getPlugMgr().newPlugin(type.get(),acl);
190 IAccessControl* acl=dynamic_cast<IAccessControl*>(plugin);
195 throw UnknownExtensionException("plugin was not an Access Control provider");
199 catch (exception& ex) {
200 log.crit("exception building AccessControl provider: %s", ex.what());
201 m_acl = new AccessControlDummy();
205 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)
208 // Load the property set.
211 // Load any AccessControl provider.
214 // Handle nested Paths.
215 DOMElement* path = XMLHelper::getFirstChildElement(e,Path);
216 for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Path)) {
217 const XMLCh* n=path->getAttributeNS(NULL,name);
219 // Skip any leading slashes.
220 while (n && *n==chForwardSlash)
223 // Check for empty name.
225 log.warn("skipping Path element (%d) with empty name attribute", i);
229 // Check for an embedded slash.
230 int slash=XMLString::indexOf(n,chForwardSlash);
232 // Copy the first path segment.
233 XMLCh* namebuf=new XMLCh[slash + 1];
234 for (int pos=0; pos < slash; pos++)
236 namebuf[slash]=chNull;
238 // Move past the slash in the original pathname.
241 // Skip any leading slashes again.
242 while (*n==chForwardSlash)
246 // Create a placeholder Path element for the first path segment and replant under it.
247 DOMElement* newpath=path->getOwnerDocument()->createElementNS(shibtarget::XML::SHIBTARGET_NS,Path);
248 newpath->setAttributeNS(NULL,name,namebuf);
249 path->setAttributeNS(NULL,name,n);
250 path->getParentNode()->replaceChild(newpath,path);
251 newpath->appendChild(path);
253 // Repoint our locals at the new parent.
255 n=path->getAttributeNS(NULL,name);
258 // All we had was a pathname with trailing slash(es), so just reset it without them.
259 path->setAttributeNS(NULL,name,namebuf);
260 n=path->getAttributeNS(NULL,name);
265 Override* o=new Override(path,log,this);
266 pair<bool,const char*> name=o->getString("name");
267 char* dup=strdup(name.second);
268 for (char* pch=dup; *pch; pch++)
270 if (m_map.count(dup)) {
271 log.warn("Skipping duplicate Path element (%s)",dup);
282 for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
287 Override::~Override()
290 for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
293 pair<bool,bool> Override::getBool(const char* name, const char* ns) const
295 pair<bool,bool> ret=DOMPropertySet::getBool(name,ns);
298 return m_base ? m_base->getBool(name,ns) : ret;
301 pair<bool,const char*> Override::getString(const char* name, const char* ns) const
303 pair<bool,const char*> ret=DOMPropertySet::getString(name,ns);
306 return m_base ? m_base->getString(name,ns) : ret;
309 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const
311 pair<bool,const XMLCh*> ret=DOMPropertySet::getXMLString(name,ns);
314 return m_base ? m_base->getXMLString(name,ns) : ret;
317 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const
319 pair<bool,unsigned int> ret=DOMPropertySet::getUnsignedInt(name,ns);
322 return m_base ? m_base->getUnsignedInt(name,ns) : ret;
325 pair<bool,int> Override::getInt(const char* name, const char* ns) const
327 pair<bool,int> ret=DOMPropertySet::getInt(name,ns);
330 return m_base ? m_base->getInt(name,ns) : ret;
333 const PropertySet* Override::getPropertySet(const char* name, const char* ns) const
335 const PropertySet* ret=DOMPropertySet::getPropertySet(name,ns);
338 return m_base->getPropertySet(name,ns);
341 const Override* Override::locate(const char* path) const
343 char* dup=strdup(path);
344 char* sep=strchr(dup,'?');
347 for (char* pch=dup; *pch; pch++)
350 const Override* o=this;
354 const char* token=strtok_r(dup,"/",&pos);
356 const char* token=strtok(dup,"/");
360 map<string,Override*>::const_iterator i=o->m_map.find(token);
361 if (i==o->m_map.end())
365 token=strtok_r(NULL,"/",&pos);
367 token=strtok(NULL,"/");
375 XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(NULL)
378 xmltooling::NDC ndc("XMLRequestMapperImpl");
381 // Load the property set.
384 // Load any AccessControl provider.
387 // Loop over the Host elements.
388 const DOMElement* host = XMLHelper::getFirstChildElement(e,Host);
389 for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,Host)) {
390 const XMLCh* n=host->getAttributeNS(NULL,name);
392 log.warn("Skipping Host element (%d) with empty name attribute",i);
396 Override* o=new Override(host,log,this);
397 pair<bool,const char*> name=o->getString("name");
398 pair<bool,const char*> scheme=o->getString("scheme");
399 pair<bool,const char*> port=o->getString("port");
401 char* dup=strdup(name.second);
402 for (char* pch=dup; *pch; pch++)
404 auto_ptr<char> dupwrap(dup);
406 if (!scheme.first && port.first) {
407 // No scheme, but a port, so assume http.
408 scheme = pair<bool,const char*>(true,"http");
410 else if (scheme.first && !port.first) {
411 // Scheme, no port, so default it.
412 // XXX Use getservbyname instead?
414 if (!strcmp(scheme.second,"http"))
416 else if (!strcmp(scheme.second,"https"))
418 else if (!strcmp(scheme.second,"ftp"))
420 else if (!strcmp(scheme.second,"ldap"))
422 else if (!strcmp(scheme.second,"ldaps"))
427 string url(scheme.second);
428 url=url + "://" + dup;
430 // Is this the default port?
431 if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
432 (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
433 (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
434 (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
435 (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
436 // First store a port-less version.
437 if (m_map.count(url) || m_extras.count(url)) {
438 log.warn("Skipping duplicate Host element (%s)",url.c_str());
443 log.debug("Added <Host> mapping for %s",url.c_str());
445 // Now append the port. We use the extras vector, to avoid double freeing the object later.
446 url=url + ':' + port.second;
448 log.debug("Added <Host> mapping for %s",url.c_str());
451 url=url + ':' + port.second;
452 if (m_map.count(url) || m_extras.count(url)) {
453 log.warn("Skipping duplicate Host element (%s)",url.c_str());
458 log.debug("Added <Host> mapping for %s",url.c_str());
462 // No scheme or port, so we enter dual hosts on http:80 and https:443
463 string url("http://");
465 if (m_map.count(url) || m_extras.count(url)) {
466 log.warn("Skipping duplicate Host element (%s)",url.c_str());
471 log.debug("Added <Host> mapping for %s",url.c_str());
474 if (m_map.count(url) || m_extras.count(url)) {
475 log.warn("Skipping duplicate Host element (%s)",url.c_str());
479 log.debug("Added <Host> mapping for %s",url.c_str());
483 if (m_map.count(url) || m_extras.count(url)) {
484 log.warn("Skipping duplicate Host element (%s)",url.c_str());
488 log.debug("Added <Host> mapping for %s",url.c_str());
491 if (m_map.count(url) || m_extras.count(url)) {
492 log.warn("Skipping duplicate Host element (%s)",url.c_str());
496 log.debug("Added <Host> mapping for %s",url.c_str());
501 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const
503 const Override* o=NULL;
504 map<string,Override*>::const_iterator i=m_map.find(vhost);
508 i=m_extras.find(vhost);
509 if (i!=m_extras.end())
513 return o ? o->locate(path) : this;
516 pair<bool,DOMElement*> XMLRequestMapper::load()
518 // Load from source using base class.
519 pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
521 // If we own it, wrap it.
522 XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
524 XMLRequestMapperImpl* impl = new XMLRequestMapperImpl(raw.second,m_log);
526 // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
527 impl->setDocument(docjanitor.release());
532 return make_pair(false,(DOMElement*)NULL);
535 IRequestMapper::Settings XMLRequestMapper::getSettings(ShibTarget* st) const
538 vhost << st->getProtocol() << "://" << st->getHostname() << ':' << st->getPort();
540 const Override* o=m_impl->findOverride(vhost.str().c_str(), st->getRequestURI());
542 if (m_log.isDebugEnabled()) {
544 xmltooling::NDC ndc("getSettings");
546 pair<bool,const char*> ret=o->getString("applicationId");
547 m_log.debug("mapped %s%s to %s", vhost.str().c_str(), st->getRequestURI() ? st->getRequestURI() : "", ret.second);
550 return Settings(o,o->getAC());