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 ReloadableXMLFile
115 XMLRequestMapper(const DOMElement* e)
116 : ReloadableXMLFile(e), m_impl(NULL), m_log(Category::getInstance(SHIBT_LOGCAT".RequestMapper")) {
120 ~XMLRequestMapper() {
124 virtual Settings getSettings(ShibTarget* st) const;
127 pair<bool,DOMElement*> load();
130 XMLRequestMapperImpl* m_impl;
134 #if defined (_MSC_VER)
135 #pragma warning( pop )
138 static const XMLCh AccessControl[] = UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);
139 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);
140 static const XMLCh htaccess[] = UNICODE_LITERAL_8(h,t,a,c,c,e,s,s);
141 static const XMLCh Host[] = UNICODE_LITERAL_4(H,o,s,t);
142 static const XMLCh Path[] = UNICODE_LITERAL_4(P,a,t,h);
143 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
144 static const XMLCh type[] = UNICODE_LITERAL_4(t,y,p,e);
147 saml::IPlugIn* XMLRequestMapFactory(const DOMElement* e)
149 return new XMLRequestMapper(e);
152 short Override::acceptNode(const DOMNode* node) const
154 if (!XMLString::equals(node->getNamespaceURI(),shibspconstants::SHIB1SPCONFIG_NS))
155 return FILTER_ACCEPT;
156 const XMLCh* name=node->getLocalName();
157 if (XMLString::equals(name,Host) ||
158 XMLString::equals(name,Path) ||
159 XMLString::equals(name,AccessControl) ||
160 XMLString::equals(name,htaccess) ||
161 XMLString::equals(name,AccessControlProvider))
162 return FILTER_REJECT;
164 return FILTER_ACCEPT;
167 void Override::loadACL(const DOMElement* e, Category& log)
170 saml::IPlugIn* plugin=NULL;
171 const DOMElement* acl=XMLHelper::getFirstChildElement(e,htaccess);
173 log.info("building Apache htaccess provider...");
174 plugin=saml::SAMLConfig::getConfig().getPlugMgr().newPlugin(HTACCESS_ACCESSCONTROL,acl);
177 acl=XMLHelper::getFirstChildElement(e,AccessControl);
179 log.info("building XML-based Access Control provider...");
180 plugin=saml::SAMLConfig::getConfig().getPlugMgr().newPlugin(XML_ACCESSCONTROL,acl);
183 acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);
185 xmltooling::auto_ptr_char type(acl->getAttributeNS(NULL,type));
186 log.info("building Access Control provider of type %s...",type.get());
187 plugin=saml::SAMLConfig::getConfig().getPlugMgr().newPlugin(type.get(),acl);
192 IAccessControl* acl=dynamic_cast<IAccessControl*>(plugin);
197 throw UnknownExtensionException("plugin was not an Access Control provider");
201 catch (exception& ex) {
202 log.crit("exception building AccessControl provider: %s", ex.what());
203 m_acl = new AccessControlDummy();
207 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)
210 // Load the property set.
213 // Load any AccessControl provider.
216 // Handle nested Paths.
217 DOMElement* path = XMLHelper::getFirstChildElement(e,Path);
218 for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Path)) {
219 const XMLCh* n=path->getAttributeNS(NULL,name);
221 // Skip any leading slashes.
222 while (n && *n==chForwardSlash)
225 // Check for empty name.
227 log.warn("skipping Path element (%d) with empty name attribute", i);
231 // Check for an embedded slash.
232 int slash=XMLString::indexOf(n,chForwardSlash);
234 // Copy the first path segment.
235 XMLCh* namebuf=new XMLCh[slash + 1];
236 for (int pos=0; pos < slash; pos++)
238 namebuf[slash]=chNull;
240 // Move past the slash in the original pathname.
243 // Skip any leading slashes again.
244 while (*n==chForwardSlash)
248 // Create a placeholder Path element for the first path segment and replant under it.
249 DOMElement* newpath=path->getOwnerDocument()->createElementNS(shibspconstants::SHIB1SPCONFIG_NS,Path);
250 newpath->setAttributeNS(NULL,name,namebuf);
251 path->setAttributeNS(NULL,name,n);
252 path->getParentNode()->replaceChild(newpath,path);
253 newpath->appendChild(path);
255 // Repoint our locals at the new parent.
257 n=path->getAttributeNS(NULL,name);
260 // All we had was a pathname with trailing slash(es), so just reset it without them.
261 path->setAttributeNS(NULL,name,namebuf);
262 n=path->getAttributeNS(NULL,name);
267 Override* o=new Override(path,log,this);
268 pair<bool,const char*> name=o->getString("name");
269 char* dup=strdup(name.second);
270 for (char* pch=dup; *pch; pch++)
272 if (m_map.count(dup)) {
273 log.warn("Skipping duplicate Path element (%s)",dup);
284 for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
289 Override::~Override()
292 for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
295 pair<bool,bool> Override::getBool(const char* name, const char* ns) const
297 pair<bool,bool> ret=DOMPropertySet::getBool(name,ns);
300 return m_base ? m_base->getBool(name,ns) : ret;
303 pair<bool,const char*> Override::getString(const char* name, const char* ns) const
305 pair<bool,const char*> ret=DOMPropertySet::getString(name,ns);
308 return m_base ? m_base->getString(name,ns) : ret;
311 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const
313 pair<bool,const XMLCh*> ret=DOMPropertySet::getXMLString(name,ns);
316 return m_base ? m_base->getXMLString(name,ns) : ret;
319 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const
321 pair<bool,unsigned int> ret=DOMPropertySet::getUnsignedInt(name,ns);
324 return m_base ? m_base->getUnsignedInt(name,ns) : ret;
327 pair<bool,int> Override::getInt(const char* name, const char* ns) const
329 pair<bool,int> ret=DOMPropertySet::getInt(name,ns);
332 return m_base ? m_base->getInt(name,ns) : ret;
335 const PropertySet* Override::getPropertySet(const char* name, const char* ns) const
337 const PropertySet* ret=DOMPropertySet::getPropertySet(name,ns);
340 return m_base->getPropertySet(name,ns);
343 const Override* Override::locate(const char* path) const
345 char* dup=strdup(path);
346 char* sep=strchr(dup,'?');
349 for (char* pch=dup; *pch; pch++)
352 const Override* o=this;
356 const char* token=strtok_r(dup,"/",&pos);
358 const char* token=strtok(dup,"/");
362 map<string,Override*>::const_iterator i=o->m_map.find(token);
363 if (i==o->m_map.end())
367 token=strtok_r(NULL,"/",&pos);
369 token=strtok(NULL,"/");
377 XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(NULL)
380 xmltooling::NDC ndc("XMLRequestMapperImpl");
383 // Load the property set.
386 // Load any AccessControl provider.
389 // Loop over the Host elements.
390 const DOMElement* host = XMLHelper::getFirstChildElement(e,Host);
391 for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,Host)) {
392 const XMLCh* n=host->getAttributeNS(NULL,name);
394 log.warn("Skipping Host element (%d) with empty name attribute",i);
398 Override* o=new Override(host,log,this);
399 pair<bool,const char*> name=o->getString("name");
400 pair<bool,const char*> scheme=o->getString("scheme");
401 pair<bool,const char*> port=o->getString("port");
403 char* dup=strdup(name.second);
404 for (char* pch=dup; *pch; pch++)
406 auto_ptr<char> dupwrap(dup);
408 if (!scheme.first && port.first) {
409 // No scheme, but a port, so assume http.
410 scheme = pair<bool,const char*>(true,"http");
412 else if (scheme.first && !port.first) {
413 // Scheme, no port, so default it.
414 // XXX Use getservbyname instead?
416 if (!strcmp(scheme.second,"http"))
418 else if (!strcmp(scheme.second,"https"))
420 else if (!strcmp(scheme.second,"ftp"))
422 else if (!strcmp(scheme.second,"ldap"))
424 else if (!strcmp(scheme.second,"ldaps"))
429 string url(scheme.second);
430 url=url + "://" + dup;
432 // Is this the default port?
433 if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
434 (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
435 (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
436 (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
437 (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
438 // First store a port-less version.
439 if (m_map.count(url) || m_extras.count(url)) {
440 log.warn("Skipping duplicate Host element (%s)",url.c_str());
445 log.debug("Added <Host> mapping for %s",url.c_str());
447 // Now append the port. We use the extras vector, to avoid double freeing the object later.
448 url=url + ':' + port.second;
450 log.debug("Added <Host> mapping for %s",url.c_str());
453 url=url + ':' + port.second;
454 if (m_map.count(url) || m_extras.count(url)) {
455 log.warn("Skipping duplicate Host element (%s)",url.c_str());
460 log.debug("Added <Host> mapping for %s",url.c_str());
464 // No scheme or port, so we enter dual hosts on http:80 and https:443
465 string url("http://");
467 if (m_map.count(url) || m_extras.count(url)) {
468 log.warn("Skipping duplicate Host element (%s)",url.c_str());
473 log.debug("Added <Host> mapping for %s",url.c_str());
476 if (m_map.count(url) || m_extras.count(url)) {
477 log.warn("Skipping duplicate Host element (%s)",url.c_str());
481 log.debug("Added <Host> mapping for %s",url.c_str());
485 if (m_map.count(url) || m_extras.count(url)) {
486 log.warn("Skipping duplicate Host element (%s)",url.c_str());
490 log.debug("Added <Host> mapping for %s",url.c_str());
493 if (m_map.count(url) || m_extras.count(url)) {
494 log.warn("Skipping duplicate Host element (%s)",url.c_str());
498 log.debug("Added <Host> mapping for %s",url.c_str());
503 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const
505 const Override* o=NULL;
506 map<string,Override*>::const_iterator i=m_map.find(vhost);
510 i=m_extras.find(vhost);
511 if (i!=m_extras.end())
515 return o ? o->locate(path) : this;
518 pair<bool,DOMElement*> XMLRequestMapper::load()
520 // Load from source using base class.
521 pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
523 // If we own it, wrap it.
524 XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
526 XMLRequestMapperImpl* impl = new XMLRequestMapperImpl(raw.second,m_log);
528 // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
529 impl->setDocument(docjanitor.release());
534 return make_pair(false,(DOMElement*)NULL);
537 IRequestMapper::Settings XMLRequestMapper::getSettings(ShibTarget* st) const
540 vhost << st->getProtocol() << "://" << st->getHostname() << ':' << st->getPort();
542 const Override* o=m_impl->findOverride(vhost.str().c_str(), st->getRequestURI());
544 if (m_log.isDebugEnabled()) {
546 xmltooling::NDC ndc("getSettings");
548 pair<bool,const char*> ret=o->getString("applicationId");
549 m_log.debug("mapped %s%s to %s", vhost.str().c_str(), st->getRequestURI() ? st->getRequestURI() : "", ret.second);
552 return Settings(o,o->getAC());