[cpp-sp] branch main updated: Remove legacy SP classes and clean up header usage.

Scott Cantor cantor.2 at osu.edu
Tue Jan 7 17:23:03 UTC 2025


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository cpp-sp.

View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=019c17951091fe091f1f89c8aa2a94d7598cd0bc

The following commit(s) were added to refs/heads/main by this push:
     new 019c1795 Remove legacy SP classes and clean up header usage.
019c1795 is described below

commit 019c17951091fe091f1f89c8aa2a94d7598cd0bc
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Jan 7 12:22:59 2025 -0500

    Remove legacy SP classes and clean up header usage.
---
 shibsp/Agent.cpp           | 553 ++++++++++++++++++++++++++++++++++++++++--
 shibsp/SPConfig.cpp        | 286 ----------------------
 shibsp/SPConfig.h          | 231 ------------------
 shibsp/ServiceProvider.cpp | 592 ---------------------------------------------
 shibsp/ServiceProvider.h   | 173 -------------
 5 files changed, 533 insertions(+), 1302 deletions(-)

diff --git a/shibsp/Agent.cpp b/shibsp/Agent.cpp
index 90422ca6..2d041fd9 100644
--- a/shibsp/Agent.cpp
+++ b/shibsp/Agent.cpp
@@ -13,16 +13,17 @@
  */
 
 /**
- * Agent.cpp
+ * ServiceProvider.cpp
  *
- * Base class implementation for a Shibboleth agent.
+ * Interface to a Shibboleth ServiceProvider instance.
  */
 
 #include "internal.h"
-#include "Agent.h"
 #include "AgentConfig.h"
 #include "exceptions.h"
 #include "AccessControl.h"
+#include "Application.h"
+#include "ServiceProvider.h"
 #include "SessionCache.h"
 #include "SPRequest.h"
 #include "attribute/Attribute.h"
@@ -39,41 +40,553 @@
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
 
+#ifndef HAVE_STRCASECMP
+# define strcasecmp _stricmp
+#endif
+
 using namespace shibsp;
+using namespace xmltooling;
 using namespace std;
 
-Agent::Agent()
+namespace shibsp {
+    SHIBSP_DLLLOCAL PluginManager<ServiceProvider,string,const DOMElement*>::Factory XMLServiceProviderFactory;
+
+    long SHIBSP_DLLLOCAL handleError(
+        Category& log, SPRequest& request, const Session* session=nullptr, const exception* ex=nullptr, bool mayRedirect=true
+        )
+    {
+        // The properties we need can be set in the RequestMap, or the Errors element.
+        bool externalParameters = false;
+        const char* redirectErrors = nullptr;
+
+        const agent_exception* richEx = dynamic_cast<const agent_exception*>(ex);
+
+        // Now look for settings in the request map.
+        try {
+            RequestMapper::Settings settings = request.getRequestSettings();
+            externalParameters = settings.first->getBool("externalParameters", false);
+            if (mayRedirect)
+                redirectErrors = settings.first->getString("redirectErrors");
+        }
+        catch (const exception& ex) {
+            log.error(ex.what());
+        }
+
+        // Check for redirection on errors.
+        if (mayRedirect && redirectErrors) {
+            string loc(redirectErrors);
+            request.absolutize(loc);
+            const agent_exception* richEx = dynamic_cast<const agent_exception*>(ex);
+            if (richEx) {
+                // TODO: probably alter how this works or what's included.
+                loc = loc + '?' + richEx->toQueryString();
+            }
+            return request.sendRedirect(loc.c_str());
+        }
+
+        // TODO: this probably changes significantly, but ultimately we're trying to pass
+        // back a status code.
+
+        istringstream msg("Internal Server Error. Please contact the site administrator.");
+        return request.sendResponse(msg, richEx ? richEx->getStatusCode() : HTTPResponse::SHIBSP_HTTP_STATUS_ERROR);
+    }
+
+    void SHIBSP_DLLLOCAL clearHeaders(SPRequest& request) {
+        request.clearHeader("Shib-Cookie-Name", "HTTP_SHIB_COOKIE_NAME");
+        request.clearHeader("Shib-Session-ID", "HTTP_SHIB_SESSION_ID");
+        request.clearHeader("Shib-Session-Index", "HTTP_SHIB_SESSION_INDEX");
+        request.clearHeader("Shib-Session-Expires", "HTTP_SHIB_SESSION_EXPIRES");
+        request.clearHeader("Shib-Session-Inactivity", "HTTP_SHIB_SESSION_INACTIVITY");
+        request.clearHeader("Shib-Identity-Provider", "HTTP_SHIB_IDENTITY_PROVIDER");
+        request.clearHeader("Shib-Authentication-Method", "HTTP_SHIB_AUTHENTICATION_METHOD");
+        request.clearHeader("Shib-Authentication-Instant", "HTTP_SHIB_AUTHENTICATION_INSTANT");
+        request.clearHeader("Shib-AuthnContext-Class", "HTTP_SHIB_AUTHNCONTEXT_CLASS");
+        request.clearHeader("Shib-AuthnContext-Decl", "HTTP_SHIB_AUTHNCONTEXT_DECL");
+        request.clearHeader("Shib-Assertion-Count", "HTTP_SHIB_ASSERTION_COUNT");
+        request.clearHeader("Shib-Handler", "HTTP_SHIB_HANDLER");
+        request.clearHeader("REMOTE_USER", "HTTP_REMOTE_USER");
+        // TODO: Redo the handling of attribute headers in the code, likely supplanting all of the above...
+        //request.clearAttributeHeaders();
+    }
+
+    void SHIBSP_DLLLOCAL exportAttributes(SPRequest& request, const Session* session, RequestMapper::Settings settings) {
+
+        const char* enc = settings.first->getString("encoding");
+        if (enc && strcmp(enc, "URL"))
+            throw ConfigurationException(string("Unsupported value for 'encoding' content setting: ") + enc);
+
+        const URLEncoder& encoder = AgentConfig::getConfig().getURLEncoder();
+
+        // Default delimiter is semicolon but is now configurable.
+        const char* delim = settings.first->getString("attributeValueDelimiter", ";");
+        size_t delim_len = strlen(delim);
+
+        bool exportDups = settings.first->getBool("exportDuplicateValues", true);
+        const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
+
+        // Default export strategy will include duplicates.
+        if (exportDups) {
+            for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
+                if (a->second->isInternal())
+                    continue;
+                string header(request.getSecureHeader(a->first.c_str()));
+                const vector<string>& vals = a->second->getSerializedValues();
+                for (vector<string>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
+                    if (!header.empty())
+                        header += delim;
+                    if (enc) {
+                        // If URL-encoding, any semicolons will get escaped anyway.
+                        header += encoder.encode(v->c_str());
+                    }
+                    else {
+                        string::size_type pos = v->find(delim, string::size_type(0));
+                        if (pos != string::npos) {
+                            string value(*v);
+                            for (; pos != string::npos; pos = value.find(delim, pos)) {
+                                value.insert(pos, "\\");
+                                pos += delim_len + 1;
+                            }
+                            header += value;
+                        }
+                        else {
+                            header += (*v);
+                        }
+                    }
+                }
+                request.setHeader(a->first.c_str(), header.c_str());
+            }
+        }
+        else {
+            // Capture values in a map of sets to check for duplicates on the fly.
+            map< string,set<string> > valueMap;
+            for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
+                if (a->second->isInternal())
+                    continue;
+                const vector<string>& vals = a->second->getSerializedValues();
+                valueMap[a->first].insert(vals.begin(), vals.end());
+            }
+
+            // Export the mapped sets to the headers.
+            for (map< string,set<string> >::const_iterator deduped = valueMap.begin(); deduped != valueMap.end(); ++deduped) {
+                string header;
+                for (set<string>::const_iterator v = deduped->second.begin(); v != deduped->second.end(); ++v) {
+                    if (!header.empty())
+                        header += delim;
+                    if (enc) {
+                        // If URL-encoding, any semicolons will get escaped anyway.
+                        header += encoder.encode(v->c_str());
+                    }
+                    else {
+                        string::size_type pos = v->find(delim, string::size_type(0));
+                        if (pos != string::npos) {
+                            string value(*v);
+                            for (; pos != string::npos; pos = value.find(delim, pos)) {
+                                value.insert(pos, "\\");
+                                pos += delim_len + 1;
+                            }
+                            header += value;
+                        }
+                        else {
+                            header += (*v);
+                        }
+                    }
+                }
+                request.setHeader(deduped->first.c_str(), header.c_str());
+            }
+        }
+
+        // Check for REMOTE_USER.
+        bool remoteUserSet = false;
+        vector<string> dummy;
+        const vector<string>& rmids = dummy; // app.getRemoteUserAttributeIds(); TODO: re implement this elsewhere
+        for (vector<string>::const_iterator rmid = rmids.begin(); !remoteUserSet && rmid != rmids.end(); ++rmid) {
+            pair<multimap<string,const Attribute*>::const_iterator,multimap<string,const Attribute*>::const_iterator> matches =
+                attributes.equal_range(*rmid);
+            for (; matches.first != matches.second; ++matches.first) {
+                const vector<string>& vals = matches.first->second->getSerializedValues();
+                if (!vals.empty()) {
+                    if (enc)
+                        request.setRemoteUser(encoder.encode(vals.front().c_str()).c_str());
+                    else
+                        request.setRemoteUser(vals.front().c_str());
+                    remoteUserSet = true;
+                    break;
+                }
+            }
+        }
+    }
+};
+
+void SHIBSP_API shibsp::registerServiceProviders()
 {
-    m_authTypes.insert("shibboleth");
+    SPConfig::getConfig().ServiceProviderManager.registerFactory(XML_SERVICE_PROVIDER, XMLServiceProviderFactory);
 }
 
-Agent::~Agent()
+ServiceProvider::ServiceProvider()
 {
+    m_authTypes.insert("shibboleth");
 }
 
-// TODO: we'll eventually copy/port in substantially similar versions of the old ServiceProvider
-// method impls.
+ServiceProvider::~ServiceProvider()
+{
+}
 
-pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
+pair<bool,long> ServiceProvider::doAuthentication(SPRequest& request, bool handler) const
 {
-    pair<bool,long> foo;
-    return foo;
+    Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
+
+    const Application* app = nullptr;
+    string targetURL = request.getRequestURL();
+
+    try {
+        RequestMapper::Settings settings = request.getRequestSettings();
+
+        // If not SSL, check to see if we should block or redirect it.
+        if (!request.isSecure()) {
+            const char* redirectToSSL = settings.first->getString("redirectToSSL");
+            if (redirectToSSL) {
+                if (!strcasecmp("GET",request.getMethod()) || !strcasecmp("HEAD",request.getMethod())) {
+                    // Compute the new target URL
+                    string redirectURL = string("https://") + request.getHostname();
+                    if (strcmp(redirectToSSL,"443")) {
+                        redirectURL = redirectURL + ':' + redirectToSSL;
+                    }
+                    redirectURL += request.getRequestURI();
+                    return make_pair(true, request.sendRedirect(redirectURL.c_str()));
+                }
+                else {
+                    agent_exception ex("Access via unencrypted HTTP was blocked.");
+                    return make_pair(true, handleError(log, request, nullptr, &ex, false));
+                }
+            }
+        }
+
+        const char* handlerURL=request.getHandlerURL(targetURL.c_str());
+        if (!handlerURL)
+            throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
+
+        // If the request URL contains the handler base URL for this application, either dispatch
+        // directly (mainly Apache 2.0) or just pass back control.
+        if (boost::contains(targetURL, handlerURL)) {
+            if (handler)
+                return doHandler(request);
+            else
+                return make_pair(true, request.returnOK());
+        }
+
+        // These settings dictate how to proceed.
+        const char* authType = settings.first->getString("authType");
+        bool requireSession = settings.first->getBool("requireSession", false);
+        const char* requireSessionWith = settings.first->getString("requireSessionWith");
+        const char* requireLogoutWith = settings.first->getString("requireLogoutWith");
+
+        // If no session is required AND the AuthType (an Apache-derived concept) isn't recognized,
+        // then we ignore this request and consider it unprotected. Apache might lie to us if
+        // ShibBasicHijack is on, but that's up to it.
+        if (!requireSession && !requireSessionWith &&
+            (!authType || m_authTypes.find(boost::to_lower_copy(string(authType))) == m_authTypes.end()))
+            return make_pair(true, request.returnDecline());
+
+        // Fix for secadv 20050901
+        clearHeaders(request);
+
+        Session* session = nullptr;
+        try {
+            session = request.getSession(true, false, false);   // don't cache it
+        }
+        catch (const exception& e) {
+            log.warn("error during session lookup: %s", e.what());
+            // If it's not a retryable session failure, we throw to the outer handler for reporting.
+            throw;
+        }
+
+        lock_guard<Session> slocker(*session, adopt_lock); // pop existing lock on exit
+        if (session) {
+            // Check for logout interception.
+            if (requireLogoutWith) {
+                // Check for a completion parameter on the query string.
+                const char* qstr = request.getQueryString();
+                if (!qstr || !strstr(qstr, "shiblogoutdone=1")) {
+                    // First leg of circuit, so we redirect to the logout endpoint specified with this URL as a return location.
+                    string selfurl = request.getRequestURL();
+                    if (qstr)
+                        selfurl += '&';
+                    else
+                        selfurl += '?';
+                    selfurl += "shiblogoutdone=1";
+                    string loc(requireLogoutWith);
+                    request.absolutize(loc);
+                    if (loc.find('?') != string::npos)
+                        loc += '&';
+                    else
+                        loc += '?';
+                    loc += "return=" + AgentConfig::getConfig().getURLEncoder().encode(selfurl.c_str());
+                    return make_pair(true, request.sendRedirect(loc.c_str()));
+                }
+            }
+            app->setHeader(request, "Shib-Handler", handlerURL);
+        }
+        else {
+            // No session.  Maybe that's acceptable?
+            if (!requireSession && !requireSessionWith) {
+                app->setHeader(request, "Shib-Handler", handlerURL);
+                return make_pair(true, request.returnOK());
+            }
+
+            // No session, but we require one. Initiate a new session using the indicated method.
+            const SessionInitiator* initiator=nullptr;
+            if (requireSessionWith) {
+                SPConfig::getConfig().deprecation().warn("requireSessionWith");
+                initiator=app->getSessionInitiatorById(requireSessionWith);
+                if (!initiator) {
+                    throw ConfigurationException(string("No session initiator found with id: ") + requireSessionWith);
+                }
+            }
+            else {
+                initiator=app->getDefaultSessionInitiator();
+                if (!initiator)
+                    throw ConfigurationException("No default session initiator found, check configuration.");
+            }
+
+            // Dispatch to SessionInitiator. This MUST handle the request, or we want to fail here.
+            // Used to fall through into doExport, but this is a cleaner exit path.
+            pair<bool, long> ret = initiator->run(request, false);
+            if (ret.first)
+                return ret;
+            throw ConfigurationException("Session initiator did not handle request for a new session, check configuration.");
+        }
+
+        request.setAuthType(authType);
+
+        // We're done.  Everything is okay.  Nothing to report.  Nothing to do..
+        // Let the caller decide how to proceed.
+        log.debug("doAuthentication succeeded");
+        return make_pair(false,0L);
+    }
+    catch (const exception& e) {
+        request.log(Priority::SHIB_ERROR, e.what());
+        return make_pair(true, handleError(log, request, nullptr, &e));
+    }
 }
 
-pair<bool,long> Agent::doAuthorization(SPRequest& request) const
+pair<bool,long> ServiceProvider::doAuthorization(SPRequest& request) const
 {
-    pair<bool, long> foo;
-    return foo;
+    Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
+
+    const Application* app = nullptr;
+    Session* session = nullptr;
+    unique_lock<Session> slocker;
+    string targetURL = request.getRequestURL();
+
+    try {
+        RequestMapper::Settings settings = request.getRequestSettings();
+
+        // Three settings dictate how to proceed.
+        const char* authType = settings.first->getString("authType");
+        bool requireSession = settings.first->getBool("requireSession", false);
+        const char* requireSessionWith = settings.first->getString("requireSessionWith");
+
+        // If no session is required AND the AuthType (an Apache-derived concept) isn't recognized,
+        // then we ignore this request and consider it unprotected. Apache might lie to us if
+        // ShibBasicHijack is on, but that's up to it.
+        if (!requireSession && !requireSessionWith &&
+                (!authType || m_authTypes.find(boost::to_lower_copy(string(authType))) == m_authTypes.end()))
+            return make_pair(true, request.returnDecline());
+
+        // Do we have an access control plugin?
+        if (settings.second) {
+            try {
+                session = request.getSession(false, false, false);  // ignore timeout and do not cache
+                if (session) {
+                    unique_lock<Session> slocker2(*session, adopt_lock);
+                    slocker.swap(slocker2); // assign to lock popper
+                }
+            }
+            catch (const exception& e) {
+                log.warn("unable to obtain session to pass to access control provider: %s", e.what());
+            }
+
+#ifdef HAVE_CXX14
+            shared_lock<AccessControl> acllock(*settings.second);
+#endif
+            switch (settings.second->authorized(request, session)) {
+                case AccessControl::shib_acl_true:
+                    log.debug("access control provider granted access");
+                    return make_pair(true, request.returnOK());
+
+                case AccessControl::shib_acl_false:
+                {
+                    log.warn("access control provider denied access");
+                    agent_exception ex("Access to resource denied.");
+                    ex.setStatusCode(HTTPResponse::SHIBSP_HTTP_STATUS_FORBIDDEN);
+                    return make_pair(true, handleError(log, request, session, nullptr, false));
+                }
+
+                default:
+                    // Use the "DECLINE" interface to signal we don't know what to do.
+                    return make_pair(true, request.returnDecline());
+            }
+        }
+        else {
+            return make_pair(true, request.returnDecline());
+        }
+    }
+    catch (const exception& e) {
+        request.log(Priority::SHIB_ERROR, e.what());
+        return make_pair(true, handleError(log, request, nullptr, &e));
+    }
 }
 
-pair<bool,long> Agent::doExport(SPRequest& request, bool requireSession) const
+pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSession) const
 {
-    pair<bool, long> foo;
-    return foo;
+    Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
+
+    const Application* app = nullptr;
+    Session* session = nullptr;
+    unique_lock<Session> slocker;
+    string targetURL = request.getRequestURL();
+
+    try {
+        RequestMapper::Settings settings = request.getRequestSettings();
+
+        try {
+            session = request.getSession(false, false, false);  // ignore timeout and do not cache
+            if (session) {
+                unique_lock<Session> slocker2(*session, adopt_lock);
+                slocker.swap(slocker2); // assign to lock popper
+            }
+        }
+        catch (const exception& e) {
+            log.warn("unable to obtain session to export to request: %s", e.what());
+        	// If we have to have a session, then this is a fatal error.
+        	if (requireSession)
+        		throw;
+        }
+
+		// Still no data?
+        if (!session) {
+        	if (requireSession)
+                throw SessionException("Unable to obtain session to export to request.");
+        	else
+        		return make_pair(false, 0L);	// just bail silently
+        }
+
+        app->setHeader(request, "Shib-Application-ID", app->getId());
+        app->setHeader(request, "Shib-Session-ID", session->getID());
+
+        const PropertySet* sessionProps = app->getPropertySet("Sessions");
+
+        // Check for export of "standard" variables.
+        // A 3.0 release would switch this default to false and rely solely on the
+        // Assertion extractor plugin and ship out of the box with the same defaults.
+        bool stdvars = settings.first->getBool("exportStdVars", true);
+        if (stdvars) {
+            const char* hval = session->getEntityID();
+            if (hval)
+                app->setHeader(request, "Shib-Identity-Provider", hval);
+            time_t ts = session->getAuthnInstant();
+            if (ts > 0) {
+                // TODO: Need to see what the output format of this really is.
+                ostringstream os;
+                os << date::format("%FT%TZ", chrono::system_clock::from_time_t(ts));
+                app->setHeader(request, "Shib-Authentication-Instant", os.str().c_str());
+            }
+            hval = session->getAuthnContextClassRef();
+            if (hval) {
+                app->setHeader(request, "Shib-Authentication-Method", hval);
+                app->setHeader(request, "Shib-AuthnContext-Class", hval);
+            }
+
+            app->setHeader(request, "Shib-Session-Expires", boost::lexical_cast<string>(session->getExpiration()).c_str());
+            pair<bool,unsigned int> timeout = sessionProps ? sessionProps->getUnsignedInt("timeout") : pair<bool,unsigned int>(false, 0);
+            if (timeout.first && timeout.second > 0) {
+                app->setHeader(request, "Shib-Session-Inactivity", boost::lexical_cast<string>(session->getLastAccess() + timeout.second).c_str());
+            }
+        }
+
+        // Check for export of algorithmically-derived portion of cookie names.
+        bool exportCookie = settings.first->getBool("exportCookie", false);
+        if (exportCookie) {
+            pair<string,const char*> cookieprops = app->getCookieNameProps(nullptr);
+            app->setHeader(request, "Shib-Cookie-Name", cookieprops.first.c_str());
+        }
+
+        // Export the attributes.
+        exportAttributes(request, session, settings);
+
+        return make_pair(false,0L);
+    }
+    catch (const exception& e) {
+        request.log(Priority::SHIB_ERROR, e.what());
+        return make_pair(true, handleError(log, request, session, &e));
+    }
 }
 
-pair<bool,long> Agent::doHandler(SPRequest& request) const
+pair<bool,long> ServiceProvider::doHandler(SPRequest& request) const
 {
-    pair<bool, long> foo;
-    return foo;
+    Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
+
+    const Application* app = nullptr;
+    string targetURL = request.getRequestURL();
+
+    try {
+        RequestMapper::Settings settings = request.getRequestSettings();
+
+        // If not SSL, check to see if we should block or redirect it.
+        if (!request.isSecure()) {
+            const char* redirectToSSL = settings.first->getString("redirectToSSL");
+            if (redirectToSSL) {
+                if (!strcasecmp("GET",request.getMethod()) || !strcasecmp("HEAD",request.getMethod())) {
+                    // Compute the new target URL
+                    string redirectURL = string("https://") + request.getHostname();
+                    if (strcmp(redirectToSSL,"443")) {
+                        redirectURL = redirectURL + ':' + redirectToSSL;
+                    }
+                    redirectURL += request.getRequestURI();
+                    return make_pair(true, request.sendRedirect(redirectURL.c_str()));
+                }
+                else {
+                    throw IOException("Blocked non-SSL access to Shibboleth handler.");
+                }
+            }
+        }
+
+        const char* handlerURL = request.getHandlerURL(targetURL.c_str());
+        if (!handlerURL)
+            throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
+
+        // Make sure we only process handler requests.
+        if (!boost::contains(targetURL, handlerURL))
+            return make_pair(true, request.returnDecline());
+
+        const PropertySet* sessionProps = app->getPropertySet("Sessions");
+        if (!sessionProps)
+            throw ConfigurationException("Unable to map request to application session settings, check configuration.");
+
+        // Process incoming request.
+        pair<bool,bool> handlerSSL = sessionProps->getBool("handlerSSL");
+
+        // We dispatch based on our path info. We know the request URL begins with or equals the handler URL,
+        // so the path info is the next character (or null).
+        const Handler* handler = app->getHandler(targetURL.c_str() + strlen(handlerURL));
+        if (!handler)
+            throw ConfigurationException("Shibboleth handler invoked at an unconfigured location.");
+
+        pair<bool, long> hret = handler->run(request);
+        // Did the handler run successfully?
+        if (hret.first)
+            return hret;
+        throw ConfigurationException("Configured Shibboleth handler failed to process the request.");
+    }
+    catch (const exception& e) {
+        request.log(Priority::SHIB_ERROR, e.what());
+        Session* session = nullptr;
+        try {
+            session = request.getSession(false, true, false);   // do not cache
+        }
+        catch (const exception&) {
+        }
+        lock_guard<Session> slocker(*session, adopt_lock); // pop existing lock on exit
+        return make_pair(true, handleError(log, request, session, &e));
+    }
 }
diff --git a/shibsp/SPConfig.cpp b/shibsp/SPConfig.cpp
deleted file mode 100644
index ce967758..00000000
--- a/shibsp/SPConfig.cpp
+++ /dev/null
@@ -1,286 +0,0 @@
-/**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * UCAID licenses this file to you under the Apache License,
- * Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the
- * License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific
- * language governing permissions and limitations under the License.
- */
-
-/**
- * SPConfig.cpp
- *
- * Library configuration.
- */
-
-#include "internal.h"
-
-#include "exceptions.h"
-#include "version.h"
-#include "AccessControl.h"
-#include "RequestMapper.h"
-#include "ServiceProvider.h"
-#include "SessionCache.h"
-#include "SPConfig.h"
-#include "attribute/Attribute.h"
-#include "handler/LogoutInitiator.h"
-#include "handler/SessionInitiator.h"
-
-#include <ctime>
-#include <sstream>
-#include <xercesc/util/XMLUniDefs.hpp>
-#include <xmltooling/version.h>
-#include <xmltooling/XMLToolingConfig.h>
-#include <xmltooling/util/ParserPool.h>
-#include <xmltooling/util/Threads.h>
-#include <xmltooling/util/XMLHelper.h>
-
-using namespace shibsp;
-using namespace xmltooling;
-using namespace boost;
-using namespace std;
-
-namespace shibsp {
-    class SHIBSP_DLLLOCAL SPInternalConfig : public SPConfig
-    {
-    public:
-        SPInternalConfig() : m_initCount(0), m_lock(Mutex::create()) {}
-        ~SPInternalConfig() {}
-
-        bool init(const char* catalog_path=nullptr, const char* inst_prefix=nullptr);
-        void term();
-
-    private:
-        int m_initCount;
-        scoped_ptr<Mutex> m_lock;
-    };
-    
-    SPInternalConfig g_config;
-}
-
-SPConfig& SPConfig::getConfig()
-{
-    return g_config;
-}
-
-SPConfig::SPConfig() : attribute_value_delimeter(';'), m_serviceProvider(nullptr), m_features(0), m_configDoc(nullptr)
-{
-}
-
-SPConfig::~SPConfig()
-{
-}
-
-void SPConfig::setFeatures(unsigned long enabled)
-{
-    m_features = enabled;
-}
-
-unsigned long SPConfig::getFeatures() const {
-    return m_features;
-}
-
-bool SPConfig::isEnabled(components_t feature) const
-{
-    return (m_features & feature)>0;
-}
-
-ServiceProvider* SPConfig::getServiceProvider() const
-{
-    return m_serviceProvider;
-}
-
-void SPConfig::setServiceProvider(ServiceProvider* serviceProvider)
-{
-    delete m_serviceProvider;
-    m_serviceProvider = serviceProvider;
-}
-
-bool SPConfig::init(const char* catalog_path, const char* inst_prefix)
-{
-    if (!inst_prefix)
-        inst_prefix = getenv("SHIBSP_PREFIX");
-    if (!inst_prefix)
-        inst_prefix = SHIBSP_PREFIX;
-    std::string inst_prefix2;
-    while (*inst_prefix) {
-        inst_prefix2.push_back((*inst_prefix=='\\') ? ('/') : (*inst_prefix));
-        ++inst_prefix;
-    }
-
-    Category& log=Category::getInstance(SHIBSP_LOGCAT ".Config");
-    log.debug("%s library initialization started", PACKAGE_STRING);
-
-    XMLToolingConfig::getConfig().user_agent = string(PACKAGE_NAME) + '/' + PACKAGE_VERSION;
-
-    if (!catalog_path)
-        catalog_path = getenv("SHIBSP_SCHEMAS");
-    if (!catalog_path || !*catalog_path)
-        catalog_path = SHIBSP_SCHEMAS;
-    if (!XMLToolingConfig::getConfig().getValidatingParser().loadCatalogs(catalog_path)) {
-        log.warn("failed to load schema catalogs into validating parser");
-    }
-
-    registerAttributeFactories();
-
-    if (isEnabled(Handlers)) {
-        registerHandlers();
-    }
-
-    registerServiceProviders();
-
-    if (isEnabled(RequestMapping)) {
-        registerAccessControls();
-        registerRequestMappers();
-    }
-
-    if (isEnabled(Caching))
-        registerSessionCaches();
-
-    // Yes, this isn't insecure, will review where we do any random generation
-    // after full code cleanup is done.
-    srand(static_cast<unsigned int>(std::time(nullptr)));
-
-    log.info("%s library initialization complete", PACKAGE_STRING);
-    return true;
-}
-
-void SPConfig::term()
-{
-    Category& log=Category::getInstance(SHIBSP_LOGCAT ".Config");
-    log.info("%s library shutting down", PACKAGE_STRING);
-
-    setServiceProvider(nullptr);
-    if (m_configDoc)
-        m_configDoc->release();
-    m_configDoc = nullptr;
-
-    if (isEnabled(Handlers)) {
-        AssertionConsumerServiceManager.deregisterFactories();
-        LogoutInitiatorManager.deregisterFactories();
-        SessionInitiatorManager.deregisterFactories();
-        SingleLogoutServiceManager.deregisterFactories();
-        HandlerManager.deregisterFactories();
-    }
-
-    ServiceProviderManager.deregisterFactories();
-    Attribute::deregisterFactories();
-
-    if (isEnabled(RequestMapping)) {
-        AccessControlManager.deregisterFactories();
-        RequestMapperManager.deregisterFactories();
-    }
-
-    if (isEnabled(Caching))
-        SessionCacheManager.deregisterFactories();
-
-    log.info("%s library shutdown complete", PACKAGE_STRING);
-}
-
-bool SPConfig::instantiate(const char* config, bool rethrow)
-{
-    if (!config)
-        config = getenv("SHIBSP_CONFIG");
-    if (!config) {
-        config = SHIBSP_CONFIG;
-    }
-    try {
-        xercesc::DOMDocument* dummydoc;
-        if (*config == '"' || *config == '\'') {
-            throw ConfigurationException("The value of SHIBSP_CONFIG started with a quote.");
-        }
-        else if (*config != '<') {
-            // Mock up some XML.
-            string resolved(config);
-            stringstream snippet;
-            snippet
-                << "<Dummy path='"
-                << resolved
-                << "' validate='1'/>";
-            dummydoc = XMLToolingConfig::getConfig().getParser().parse(snippet);
-            XercesJanitor<xercesc::DOMDocument> docjanitor(dummydoc);
-            setServiceProvider(ServiceProviderManager.newPlugin(XML_SERVICE_PROVIDER, dummydoc->getDocumentElement(), true));
-            if (m_configDoc)
-                m_configDoc->release();
-            m_configDoc = docjanitor.release();
-        }
-        else {
-            stringstream snippet(config);
-            dummydoc = XMLToolingConfig::getConfig().getParser().parse(snippet);
-            XercesJanitor<xercesc::DOMDocument> docjanitor(dummydoc);
-            static const XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e);
-            auto_ptr_char type(dummydoc->getDocumentElement()->getAttributeNS(nullptr,_type));
-            if (type.get() && *type.get())
-                setServiceProvider(ServiceProviderManager.newPlugin(type.get(), dummydoc->getDocumentElement(), true));
-            else
-                throw ConfigurationException("The supplied XML bootstrapping configuration did not include a type attribute.");
-            if (m_configDoc)
-                m_configDoc->release();
-            m_configDoc = docjanitor.release();
-        }
-
-        getServiceProvider()->init();
-        return true;
-    }
-    catch (const std::exception& ex) {
-        if (rethrow) {
-            throw;
-        }
-        else {
-            Category::getInstance(SHIBSP_LOGCAT ".Config").crit("caught exception while loading configuration: %s", ex.what());
-        }
-    }
-    return false;
-}
-
-bool SPInternalConfig::init(const char* catalog_path, const char* inst_prefix)
-{
-    Lock initLock(m_lock);
-
-    if (m_initCount == INT_MAX) {
-        Category::getInstance(SHIBSP_LOGCAT ".Config").crit("library initialized too many times");
-        return false;
-    }
-
-    if (m_initCount >= 1) {
-        ++m_initCount;
-        return true;
-    }
-
-    if (!SPConfig::init(catalog_path, inst_prefix)) {
-        return false;
-    }
-
-    ++m_initCount;
-    return true;
-}
-
-void SPInternalConfig::term()
-{
-    Lock initLock(m_lock);
-    if (m_initCount == 0) {
-        Category::getInstance(SHIBSP_LOGCAT ".Config").crit("term without corresponding init");
-        return;
-    }
-    else if (--m_initCount > 0) {
-        return;
-    }
-
-    SPConfig::term();
-}
-
-Category& SPConfig::deprecation() const
-{
-    return Category::getInstance(SHIBSP_LOGCAT".DEPRECATION");
-}
diff --git a/shibsp/SPConfig.h b/shibsp/SPConfig.h
deleted file mode 100644
index 7cb837a2..00000000
--- a/shibsp/SPConfig.h
+++ /dev/null
@@ -1,231 +0,0 @@
-/**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * UCAID licenses this file to you under the Apache License,
- * Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the
- * License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- * either express or implied. See the License for the specific
- * language governing permissions and limitations under the License.
- */
-
-/**
- * @file shibsp/SPConfig.h
- *
- * Library configuration.
- */
-
-#ifndef __shibsp_config_h__
-#define __shibsp_config_h__
-
-#include <shibsp/base.h>
-#include <shibsp/logging/Category.h>
-#include <shibsp/util/PluginManager.h>
-
-#include <string>
-#include <xmltooling/QName.h>
-#include <xercesc/dom/DOM.hpp>
-
-/**
- * @namespace shibsp
- * Shibboleth Service Provider Library
- */
-namespace shibsp {
-
-    class SHIBSP_API AccessControl;
-    class SHIBSP_API Handler;
-    class SHIBSP_API ListenerService;
-    class SHIBSP_API RequestMapper;
-    class SHIBSP_API ServiceProvider;
-    class SHIBSP_API SessionCache;
-    class SHIBSP_API SessionInitiator;
-
-#if defined (_MSC_VER)
-    #pragma warning( push )
-    #pragma warning( disable : 4250 4251 )
-#endif
-
-    /**
-     * Singleton object that manages library startup/shutdown.
-     */
-    class SHIBSP_API SPConfig
-    {
-        MAKE_NONCOPYABLE(SPConfig);
-    public:
-        SPConfig();
-
-        virtual ~SPConfig();
-
-        /**
-         * Returns the global configuration object for the library.
-         *
-         * @return reference to the global library configuration object
-         */
-        static SPConfig& getConfig();
-
-        /**
-         * Bitmask values representing subsystems of the library.
-         */
-        enum components_t {
-            Listener = 1,
-            Caching = 2,
-            RequestMapping = 64,
-            OutOfProcess = 128,
-            InProcess = 256,
-            Logging = 512,
-            Handlers = 1024
-        };
-
-        /**
-         * Set a bitmask of subsystems to activate.
-         *
-         * @param enabled   bitmask of component constants
-         */
-        void setFeatures(unsigned long enabled);
-
-
-        /**
-         * Gets the bitmask of subsystems being activated.
-         *
-         * @return bitmask of component constants
-         */
-        unsigned long getFeatures() const;
-
-        /**
-         * Test whether a subsystem is enabled.
-         *
-         * @param feature   subsystem/component to test
-         * @return true iff feature is enabled
-         */
-        bool isEnabled(components_t feature) const;
-
-        /**
-         * Initializes library
-         *
-         * Each process using the library MUST call this function exactly once
-         * before using any library classes.
-         *
-         * @param catalog_path  delimited set of schema catalog files to load
-         * @param inst_prefix   installation prefix for software
-         * @return true iff initialization was successful
-         */
-        virtual bool init(const char* catalog_path=nullptr, const char* inst_prefix=nullptr);
-
-        /**
-         * Shuts down library
-         *
-         * Each process using the library SHOULD call this function exactly once
-         * before terminating itself.
-         */
-        virtual void term();
-
-        /**
-         * Sets the global ServiceProvider instance.
-         * This method must be externally synchronized with any code that uses the object.
-         * Any previously set object is destroyed.
-         *
-         * @param serviceProvider   new ServiceProvider instance to store
-         */
-        void setServiceProvider(ServiceProvider* serviceProvider);
-
-        /**
-         * Returns the global ServiceProvider instance.
-         *
-         * @return  global ServiceProvider or nullptr
-         */
-        ServiceProvider* getServiceProvider() const;
-
-        /**
-         * Instantiates and installs a ServiceProvider instance based on an XML configuration string
-         * or a configuration pathname.
-         *
-         * @param config    a snippet of XML to parse (it <strong>MUST</strong> contain a type attribute) or a pathname
-         * @param rethrow   true iff caught exceptions should be rethrown instead of just returning the status
-         * @return true iff instantiation was successful
-         */
-        virtual bool instantiate(const char* config=nullptr, bool rethrow=false);
-
-        /**
-          * Separator for serialized values of multi-valued attributes.
-          *
-          * <p>This is deprecated, and was never actually read within the code.</p>
-          *
-          * @deprecated
-          */
-        char attribute_value_delimeter;
-
-        /**
-         * Manages factories for AccessControl plugins.
-         */
-        PluginManager<AccessControl,std::string,const xercesc::DOMElement*> AccessControlManager;
-
-        /**
-         * Manages factories for Handler plugins that implement AssertionConsumerService functionality.
-         */
-        PluginManager< Handler,std::string,std::pair<const xercesc::DOMElement*,const char*> > AssertionConsumerServiceManager;
-
-        /**
-         * Manages factories for Handler plugins that implement customized functionality.
-         */
-        PluginManager< Handler,std::string,std::pair<const xercesc::DOMElement*,const char*> > HandlerManager;
-
-        /**
-         * Manages factories for Handler plugins that implement LogoutInitiator functionality.
-         */
-        PluginManager< Handler,std::string,std::pair<const xercesc::DOMElement*,const char*> > LogoutInitiatorManager;
-
-        /**
-         * Manages factories for RequestMapper plugins.
-         */
-        PluginManager<RequestMapper,std::string,const xercesc::DOMElement*> RequestMapperManager;
-
-        /**
-         * Manages factories for ServiceProvider plugins.
-         */
-        PluginManager<ServiceProvider,std::string,const xercesc::DOMElement*> ServiceProviderManager;
-
-        /**
-         * Manages factories for SessionCache plugins.
-         */
-        PluginManager<SessionCache,std::string,const xercesc::DOMElement*> SessionCacheManager;
-
-        /**
-         * Manages factories for Handler plugins that implement SessionInitiator functionality.
-         */
-        PluginManager< SessionInitiator,std::string,std::pair<const xercesc::DOMElement*,const char*> > SessionInitiatorManager;
-
-        /**
-         * Manages factories for Handler plugins that implement SingleLogoutService functionality.
-         */
-        PluginManager< Handler,std::string,std::pair<const xercesc::DOMElement*,const char*> > SingleLogoutServiceManager;
-
-        /**
-         * Helper for deprecation warnings about an at-risk feature or setting.
-         */
-        Category& deprecation() const;
-
-    protected:
-        /** Global ServiceProvider instance. */
-        ServiceProvider* m_serviceProvider;
-
-    private:
-        unsigned long m_features;
-        xercesc::DOMDocument* m_configDoc;
-    };
-
-#if defined (_MSC_VER)
-    #pragma warning( pop )
-#endif
-
-};
-
-#endif /* __shibsp_config_h__ */
diff --git a/shibsp/ServiceProvider.cpp b/shibsp/ServiceProvider.cpp
deleted file mode 100644
index 2d041fd9..00000000
--- a/shibsp/ServiceProvider.cpp
+++ /dev/null
@@ -1,592 +0,0 @@
-/**
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * ServiceProvider.cpp
- *
- * Interface to a Shibboleth ServiceProvider instance.
- */
-
-#include "internal.h"
-#include "AgentConfig.h"
-#include "exceptions.h"
-#include "AccessControl.h"
-#include "Application.h"
-#include "ServiceProvider.h"
-#include "SessionCache.h"
-#include "SPRequest.h"
-#include "attribute/Attribute.h"
-#include "handler/SessionInitiator.h"
-#include "util/Date.h"
-#include "util/PathResolver.h"
-#include "util/URLEncoder.h"
-
-#include <fstream>
-#include <sstream>
-#ifdef HAVE_CXX14
-# include <shared_mutex>
-#endif
-#include <boost/algorithm/string.hpp>
-#include <boost/lexical_cast.hpp>
-
-#ifndef HAVE_STRCASECMP
-# define strcasecmp _stricmp
-#endif
-
-using namespace shibsp;
-using namespace xmltooling;
-using namespace std;
-
-namespace shibsp {
-    SHIBSP_DLLLOCAL PluginManager<ServiceProvider,string,const DOMElement*>::Factory XMLServiceProviderFactory;
-
-    long SHIBSP_DLLLOCAL handleError(
-        Category& log, SPRequest& request, const Session* session=nullptr, const exception* ex=nullptr, bool mayRedirect=true
-        )
-    {
-        // The properties we need can be set in the RequestMap, or the Errors element.
-        bool externalParameters = false;
-        const char* redirectErrors = nullptr;
-
-        const agent_exception* richEx = dynamic_cast<const agent_exception*>(ex);
-
-        // Now look for settings in the request map.
-        try {
-            RequestMapper::Settings settings = request.getRequestSettings();
-            externalParameters = settings.first->getBool("externalParameters", false);
-            if (mayRedirect)
-                redirectErrors = settings.first->getString("redirectErrors");
-        }
-        catch (const exception& ex) {
-            log.error(ex.what());
-        }
-
-        // Check for redirection on errors.
-        if (mayRedirect && redirectErrors) {
-            string loc(redirectErrors);
-            request.absolutize(loc);
-            const agent_exception* richEx = dynamic_cast<const agent_exception*>(ex);
-            if (richEx) {
-                // TODO: probably alter how this works or what's included.
-                loc = loc + '?' + richEx->toQueryString();
-            }
-            return request.sendRedirect(loc.c_str());
-        }
-
-        // TODO: this probably changes significantly, but ultimately we're trying to pass
-        // back a status code.
-
-        istringstream msg("Internal Server Error. Please contact the site administrator.");
-        return request.sendResponse(msg, richEx ? richEx->getStatusCode() : HTTPResponse::SHIBSP_HTTP_STATUS_ERROR);
-    }
-
-    void SHIBSP_DLLLOCAL clearHeaders(SPRequest& request) {
-        request.clearHeader("Shib-Cookie-Name", "HTTP_SHIB_COOKIE_NAME");
-        request.clearHeader("Shib-Session-ID", "HTTP_SHIB_SESSION_ID");
-        request.clearHeader("Shib-Session-Index", "HTTP_SHIB_SESSION_INDEX");
-        request.clearHeader("Shib-Session-Expires", "HTTP_SHIB_SESSION_EXPIRES");
-        request.clearHeader("Shib-Session-Inactivity", "HTTP_SHIB_SESSION_INACTIVITY");
-        request.clearHeader("Shib-Identity-Provider", "HTTP_SHIB_IDENTITY_PROVIDER");
-        request.clearHeader("Shib-Authentication-Method", "HTTP_SHIB_AUTHENTICATION_METHOD");
-        request.clearHeader("Shib-Authentication-Instant", "HTTP_SHIB_AUTHENTICATION_INSTANT");
-        request.clearHeader("Shib-AuthnContext-Class", "HTTP_SHIB_AUTHNCONTEXT_CLASS");
-        request.clearHeader("Shib-AuthnContext-Decl", "HTTP_SHIB_AUTHNCONTEXT_DECL");
-        request.clearHeader("Shib-Assertion-Count", "HTTP_SHIB_ASSERTION_COUNT");
-        request.clearHeader("Shib-Handler", "HTTP_SHIB_HANDLER");
-        request.clearHeader("REMOTE_USER", "HTTP_REMOTE_USER");
-        // TODO: Redo the handling of attribute headers in the code, likely supplanting all of the above...
-        //request.clearAttributeHeaders();
-    }
-
-    void SHIBSP_DLLLOCAL exportAttributes(SPRequest& request, const Session* session, RequestMapper::Settings settings) {
-
-        const char* enc = settings.first->getString("encoding");
-        if (enc && strcmp(enc, "URL"))
-            throw ConfigurationException(string("Unsupported value for 'encoding' content setting: ") + enc);
-
-        const URLEncoder& encoder = AgentConfig::getConfig().getURLEncoder();
-
-        // Default delimiter is semicolon but is now configurable.
-        const char* delim = settings.first->getString("attributeValueDelimiter", ";");
-        size_t delim_len = strlen(delim);
-
-        bool exportDups = settings.first->getBool("exportDuplicateValues", true);
-        const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
-
-        // Default export strategy will include duplicates.
-        if (exportDups) {
-            for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
-                if (a->second->isInternal())
-                    continue;
-                string header(request.getSecureHeader(a->first.c_str()));
-                const vector<string>& vals = a->second->getSerializedValues();
-                for (vector<string>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
-                    if (!header.empty())
-                        header += delim;
-                    if (enc) {
-                        // If URL-encoding, any semicolons will get escaped anyway.
-                        header += encoder.encode(v->c_str());
-                    }
-                    else {
-                        string::size_type pos = v->find(delim, string::size_type(0));
-                        if (pos != string::npos) {
-                            string value(*v);
-                            for (; pos != string::npos; pos = value.find(delim, pos)) {
-                                value.insert(pos, "\\");
-                                pos += delim_len + 1;
-                            }
-                            header += value;
-                        }
-                        else {
-                            header += (*v);
-                        }
-                    }
-                }
-                request.setHeader(a->first.c_str(), header.c_str());
-            }
-        }
-        else {
-            // Capture values in a map of sets to check for duplicates on the fly.
-            map< string,set<string> > valueMap;
-            for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
-                if (a->second->isInternal())
-                    continue;
-                const vector<string>& vals = a->second->getSerializedValues();
-                valueMap[a->first].insert(vals.begin(), vals.end());
-            }
-
-            // Export the mapped sets to the headers.
-            for (map< string,set<string> >::const_iterator deduped = valueMap.begin(); deduped != valueMap.end(); ++deduped) {
-                string header;
-                for (set<string>::const_iterator v = deduped->second.begin(); v != deduped->second.end(); ++v) {
-                    if (!header.empty())
-                        header += delim;
-                    if (enc) {
-                        // If URL-encoding, any semicolons will get escaped anyway.
-                        header += encoder.encode(v->c_str());
-                    }
-                    else {
-                        string::size_type pos = v->find(delim, string::size_type(0));
-                        if (pos != string::npos) {
-                            string value(*v);
-                            for (; pos != string::npos; pos = value.find(delim, pos)) {
-                                value.insert(pos, "\\");
-                                pos += delim_len + 1;
-                            }
-                            header += value;
-                        }
-                        else {
-                            header += (*v);
-                        }
-                    }
-                }
-                request.setHeader(deduped->first.c_str(), header.c_str());
-            }
-        }
-
-        // Check for REMOTE_USER.
-        bool remoteUserSet = false;
-        vector<string> dummy;
-        const vector<string>& rmids = dummy; // app.getRemoteUserAttributeIds(); TODO: re implement this elsewhere
-        for (vector<string>::const_iterator rmid = rmids.begin(); !remoteUserSet && rmid != rmids.end(); ++rmid) {
-            pair<multimap<string,const Attribute*>::const_iterator,multimap<string,const Attribute*>::const_iterator> matches =
-                attributes.equal_range(*rmid);
-            for (; matches.first != matches.second; ++matches.first) {
-                const vector<string>& vals = matches.first->second->getSerializedValues();
-                if (!vals.empty()) {
-                    if (enc)
-                        request.setRemoteUser(encoder.encode(vals.front().c_str()).c_str());
-                    else
-                        request.setRemoteUser(vals.front().c_str());
-                    remoteUserSet = true;
-                    break;
-                }
-            }
-        }
-    }
-};
-
-void SHIBSP_API shibsp::registerServiceProviders()
-{
-    SPConfig::getConfig().ServiceProviderManager.registerFactory(XML_SERVICE_PROVIDER, XMLServiceProviderFactory);
-}
-
-ServiceProvider::ServiceProvider()
-{
-    m_authTypes.insert("shibboleth");
-}
-
-ServiceProvider::~ServiceProvider()
-{
-}
-
-pair<bool,long> ServiceProvider::doAuthentication(SPRequest& request, bool handler) const
-{
-    Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
-
-    const Application* app = nullptr;
-    string targetURL = request.getRequestURL();
-
-    try {
-        RequestMapper::Settings settings = request.getRequestSettings();
-
-        // If not SSL, check to see if we should block or redirect it.
-        if (!request.isSecure()) {
-            const char* redirectToSSL = settings.first->getString("redirectToSSL");
-            if (redirectToSSL) {
-                if (!strcasecmp("GET",request.getMethod()) || !strcasecmp("HEAD",request.getMethod())) {
-                    // Compute the new target URL
-                    string redirectURL = string("https://") + request.getHostname();
-                    if (strcmp(redirectToSSL,"443")) {
-                        redirectURL = redirectURL + ':' + redirectToSSL;
-                    }
-                    redirectURL += request.getRequestURI();
-                    return make_pair(true, request.sendRedirect(redirectURL.c_str()));
-                }
-                else {
-                    agent_exception ex("Access via unencrypted HTTP was blocked.");
-                    return make_pair(true, handleError(log, request, nullptr, &ex, false));
-                }
-            }
-        }
-
-        const char* handlerURL=request.getHandlerURL(targetURL.c_str());
-        if (!handlerURL)
-            throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
-
-        // If the request URL contains the handler base URL for this application, either dispatch
-        // directly (mainly Apache 2.0) or just pass back control.
-        if (boost::contains(targetURL, handlerURL)) {
-            if (handler)
-                return doHandler(request);
-            else
-                return make_pair(true, request.returnOK());
-        }
-
-        // These settings dictate how to proceed.
-        const char* authType = settings.first->getString("authType");
-        bool requireSession = settings.first->getBool("requireSession", false);
-        const char* requireSessionWith = settings.first->getString("requireSessionWith");
-        const char* requireLogoutWith = settings.first->getString("requireLogoutWith");
-
-        // If no session is required AND the AuthType (an Apache-derived concept) isn't recognized,
-        // then we ignore this request and consider it unprotected. Apache might lie to us if
-        // ShibBasicHijack is on, but that's up to it.
-        if (!requireSession && !requireSessionWith &&
-            (!authType || m_authTypes.find(boost::to_lower_copy(string(authType))) == m_authTypes.end()))
-            return make_pair(true, request.returnDecline());
-
-        // Fix for secadv 20050901
-        clearHeaders(request);
-
-        Session* session = nullptr;
-        try {
-            session = request.getSession(true, false, false);   // don't cache it
-        }
-        catch (const exception& e) {
-            log.warn("error during session lookup: %s", e.what());
-            // If it's not a retryable session failure, we throw to the outer handler for reporting.
-            throw;
-        }
-
-        lock_guard<Session> slocker(*session, adopt_lock); // pop existing lock on exit
-        if (session) {
-            // Check for logout interception.
-            if (requireLogoutWith) {
-                // Check for a completion parameter on the query string.
-                const char* qstr = request.getQueryString();
-                if (!qstr || !strstr(qstr, "shiblogoutdone=1")) {
-                    // First leg of circuit, so we redirect to the logout endpoint specified with this URL as a return location.
-                    string selfurl = request.getRequestURL();
-                    if (qstr)
-                        selfurl += '&';
-                    else
-                        selfurl += '?';
-                    selfurl += "shiblogoutdone=1";
-                    string loc(requireLogoutWith);
-                    request.absolutize(loc);
-                    if (loc.find('?') != string::npos)
-                        loc += '&';
-                    else
-                        loc += '?';
-                    loc += "return=" + AgentConfig::getConfig().getURLEncoder().encode(selfurl.c_str());
-                    return make_pair(true, request.sendRedirect(loc.c_str()));
-                }
-            }
-            app->setHeader(request, "Shib-Handler", handlerURL);
-        }
-        else {
-            // No session.  Maybe that's acceptable?
-            if (!requireSession && !requireSessionWith) {
-                app->setHeader(request, "Shib-Handler", handlerURL);
-                return make_pair(true, request.returnOK());
-            }
-
-            // No session, but we require one. Initiate a new session using the indicated method.
-            const SessionInitiator* initiator=nullptr;
-            if (requireSessionWith) {
-                SPConfig::getConfig().deprecation().warn("requireSessionWith");
-                initiator=app->getSessionInitiatorById(requireSessionWith);
-                if (!initiator) {
-                    throw ConfigurationException(string("No session initiator found with id: ") + requireSessionWith);
-                }
-            }
-            else {
-                initiator=app->getDefaultSessionInitiator();
-                if (!initiator)
-                    throw ConfigurationException("No default session initiator found, check configuration.");
-            }
-
-            // Dispatch to SessionInitiator. This MUST handle the request, or we want to fail here.
-            // Used to fall through into doExport, but this is a cleaner exit path.
-            pair<bool, long> ret = initiator->run(request, false);
-            if (ret.first)
-                return ret;
-            throw ConfigurationException("Session initiator did not handle request for a new session, check configuration.");
-        }
-
-        request.setAuthType(authType);
-
-        // We're done.  Everything is okay.  Nothing to report.  Nothing to do..
-        // Let the caller decide how to proceed.
-        log.debug("doAuthentication succeeded");
-        return make_pair(false,0L);
-    }
-    catch (const exception& e) {
-        request.log(Priority::SHIB_ERROR, e.what());
-        return make_pair(true, handleError(log, request, nullptr, &e));
-    }
-}
-
-pair<bool,long> ServiceProvider::doAuthorization(SPRequest& request) const
-{
-    Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
-
-    const Application* app = nullptr;
-    Session* session = nullptr;
-    unique_lock<Session> slocker;
-    string targetURL = request.getRequestURL();
-
-    try {
-        RequestMapper::Settings settings = request.getRequestSettings();
-
-        // Three settings dictate how to proceed.
-        const char* authType = settings.first->getString("authType");
-        bool requireSession = settings.first->getBool("requireSession", false);
-        const char* requireSessionWith = settings.first->getString("requireSessionWith");
-
-        // If no session is required AND the AuthType (an Apache-derived concept) isn't recognized,
-        // then we ignore this request and consider it unprotected. Apache might lie to us if
-        // ShibBasicHijack is on, but that's up to it.
-        if (!requireSession && !requireSessionWith &&
-                (!authType || m_authTypes.find(boost::to_lower_copy(string(authType))) == m_authTypes.end()))
-            return make_pair(true, request.returnDecline());
-
-        // Do we have an access control plugin?
-        if (settings.second) {
-            try {
-                session = request.getSession(false, false, false);  // ignore timeout and do not cache
-                if (session) {
-                    unique_lock<Session> slocker2(*session, adopt_lock);
-                    slocker.swap(slocker2); // assign to lock popper
-                }
-            }
-            catch (const exception& e) {
-                log.warn("unable to obtain session to pass to access control provider: %s", e.what());
-            }
-
-#ifdef HAVE_CXX14
-            shared_lock<AccessControl> acllock(*settings.second);
-#endif
-            switch (settings.second->authorized(request, session)) {
-                case AccessControl::shib_acl_true:
-                    log.debug("access control provider granted access");
-                    return make_pair(true, request.returnOK());
-
-                case AccessControl::shib_acl_false:
-                {
-                    log.warn("access control provider denied access");
-                    agent_exception ex("Access to resource denied.");
-                    ex.setStatusCode(HTTPResponse::SHIBSP_HTTP_STATUS_FORBIDDEN);
-                    return make_pair(true, handleError(log, request, session, nullptr, false));
-                }
-
-                default:
-                    // Use the "DECLINE" interface to signal we don't know what to do.
-                    return make_pair(true, request.returnDecline());
-            }
-        }
-        else {
-            return make_pair(true, request.returnDecline());
-        }
-    }
-    catch (const exception& e) {
-        request.log(Priority::SHIB_ERROR, e.what());
-        return make_pair(true, handleError(log, request, nullptr, &e));
-    }
-}
-
-pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSession) const
-{
-    Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
-
-    const Application* app = nullptr;
-    Session* session = nullptr;
-    unique_lock<Session> slocker;
-    string targetURL = request.getRequestURL();
-
-    try {
-        RequestMapper::Settings settings = request.getRequestSettings();
-
-        try {
-            session = request.getSession(false, false, false);  // ignore timeout and do not cache
-            if (session) {
-                unique_lock<Session> slocker2(*session, adopt_lock);
-                slocker.swap(slocker2); // assign to lock popper
-            }
-        }
-        catch (const exception& e) {
-            log.warn("unable to obtain session to export to request: %s", e.what());
-        	// If we have to have a session, then this is a fatal error.
-        	if (requireSession)
-        		throw;
-        }
-
-		// Still no data?
-        if (!session) {
-        	if (requireSession)
-                throw SessionException("Unable to obtain session to export to request.");
-        	else
-        		return make_pair(false, 0L);	// just bail silently
-        }
-
-        app->setHeader(request, "Shib-Application-ID", app->getId());
-        app->setHeader(request, "Shib-Session-ID", session->getID());
-
-        const PropertySet* sessionProps = app->getPropertySet("Sessions");
-
-        // Check for export of "standard" variables.
-        // A 3.0 release would switch this default to false and rely solely on the
-        // Assertion extractor plugin and ship out of the box with the same defaults.
-        bool stdvars = settings.first->getBool("exportStdVars", true);
-        if (stdvars) {
-            const char* hval = session->getEntityID();
-            if (hval)
-                app->setHeader(request, "Shib-Identity-Provider", hval);
-            time_t ts = session->getAuthnInstant();
-            if (ts > 0) {
-                // TODO: Need to see what the output format of this really is.
-                ostringstream os;
-                os << date::format("%FT%TZ", chrono::system_clock::from_time_t(ts));
-                app->setHeader(request, "Shib-Authentication-Instant", os.str().c_str());
-            }
-            hval = session->getAuthnContextClassRef();
-            if (hval) {
-                app->setHeader(request, "Shib-Authentication-Method", hval);
-                app->setHeader(request, "Shib-AuthnContext-Class", hval);
-            }
-
-            app->setHeader(request, "Shib-Session-Expires", boost::lexical_cast<string>(session->getExpiration()).c_str());
-            pair<bool,unsigned int> timeout = sessionProps ? sessionProps->getUnsignedInt("timeout") : pair<bool,unsigned int>(false, 0);
-            if (timeout.first && timeout.second > 0) {
-                app->setHeader(request, "Shib-Session-Inactivity", boost::lexical_cast<string>(session->getLastAccess() + timeout.second).c_str());
-            }
-        }
-
-        // Check for export of algorithmically-derived portion of cookie names.
-        bool exportCookie = settings.first->getBool("exportCookie", false);
-        if (exportCookie) {
-            pair<string,const char*> cookieprops = app->getCookieNameProps(nullptr);
-            app->setHeader(request, "Shib-Cookie-Name", cookieprops.first.c_str());
-        }
-
-        // Export the attributes.
-        exportAttributes(request, session, settings);
-
-        return make_pair(false,0L);
-    }
-    catch (const exception& e) {
-        request.log(Priority::SHIB_ERROR, e.what());
-        return make_pair(true, handleError(log, request, session, &e));
-    }
-}
-
-pair<bool,long> ServiceProvider::doHandler(SPRequest& request) const
-{
-    Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
-
-    const Application* app = nullptr;
-    string targetURL = request.getRequestURL();
-
-    try {
-        RequestMapper::Settings settings = request.getRequestSettings();
-
-        // If not SSL, check to see if we should block or redirect it.
-        if (!request.isSecure()) {
-            const char* redirectToSSL = settings.first->getString("redirectToSSL");
-            if (redirectToSSL) {
-                if (!strcasecmp("GET",request.getMethod()) || !strcasecmp("HEAD",request.getMethod())) {
-                    // Compute the new target URL
-                    string redirectURL = string("https://") + request.getHostname();
-                    if (strcmp(redirectToSSL,"443")) {
-                        redirectURL = redirectURL + ':' + redirectToSSL;
-                    }
-                    redirectURL += request.getRequestURI();
-                    return make_pair(true, request.sendRedirect(redirectURL.c_str()));
-                }
-                else {
-                    throw IOException("Blocked non-SSL access to Shibboleth handler.");
-                }
-            }
-        }
-
-        const char* handlerURL = request.getHandlerURL(targetURL.c_str());
-        if (!handlerURL)
-            throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
-
-        // Make sure we only process handler requests.
-        if (!boost::contains(targetURL, handlerURL))
-            return make_pair(true, request.returnDecline());
-
-        const PropertySet* sessionProps = app->getPropertySet("Sessions");
-        if (!sessionProps)
-            throw ConfigurationException("Unable to map request to application session settings, check configuration.");
-
-        // Process incoming request.
-        pair<bool,bool> handlerSSL = sessionProps->getBool("handlerSSL");
-
-        // We dispatch based on our path info. We know the request URL begins with or equals the handler URL,
-        // so the path info is the next character (or null).
-        const Handler* handler = app->getHandler(targetURL.c_str() + strlen(handlerURL));
-        if (!handler)
-            throw ConfigurationException("Shibboleth handler invoked at an unconfigured location.");
-
-        pair<bool, long> hret = handler->run(request);
-        // Did the handler run successfully?
-        if (hret.first)
-            return hret;
-        throw ConfigurationException("Configured Shibboleth handler failed to process the request.");
-    }
-    catch (const exception& e) {
-        request.log(Priority::SHIB_ERROR, e.what());
-        Session* session = nullptr;
-        try {
-            session = request.getSession(false, true, false);   // do not cache
-        }
-        catch (const exception&) {
-        }
-        lock_guard<Session> slocker(*session, adopt_lock); // pop existing lock on exit
-        return make_pair(true, handleError(log, request, session, &e));
-    }
-}
diff --git a/shibsp/ServiceProvider.h b/shibsp/ServiceProvider.h
deleted file mode 100644
index d6017d68..00000000
--- a/shibsp/ServiceProvider.h
+++ /dev/null
@@ -1,173 +0,0 @@
-/**
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * @file shibsp/ServiceProvider.h
- * 
- * Interface to a Shibboleth ServiceProvider instance.
- */
-
-#ifndef __shibsp_sp_h__
-#define __shibsp_sp_h__
-
-#include <shibsp/util/PropertySet.h>
-
-#include <set>
-#include <vector>
-#include <xmltooling/Lockable.h>
-
-namespace shibsp {
-
-    class SHIBSP_API Handler;
-    class SHIBSP_API ListenerService;
-    class SHIBSP_API Remoted;
-    class SHIBSP_API RequestMapper;
-    class SHIBSP_API SessionCache;
-    class SHIBSP_API SPRequest;
-    class SHIBSP_API TemplateParameters;
-
-#if defined (_MSC_VER)
-    #pragma warning( push )
-    #pragma warning( disable : 4251 )
-#endif
-
-    /**
-     * Interface to a Shibboleth ServiceProvider instance.
-     * 
-     * <p>A ServiceProvider exposes configuration and infrastructure services required
-     * by the SP implementation, allowing a flexible configuration format.
-     */
-	class SHIBSP_API ServiceProvider : public virtual xmltooling::Lockable, public virtual PropertySet
-    {
-        MAKE_NONCOPYABLE(ServiceProvider);
-    protected:
-        ServiceProvider();
-    public:
-        virtual ~ServiceProvider();
-        
-        /**
-         * Loads a configuration and prepares the instance for use.
-         * 
-         * <p>Implemented as a separate method so that services can rely on
-         * other services while they initialize by accessing the ServiceProvider
-         * from the SPConfig singleton.
-         */
-        virtual void init()=0;
-
-        /**
-         * Returns a SessionCache instance.
-         * 
-         * @param required  true iff an exception should be thrown if no SessionCache is available
-         * @return  a SessionCache
-         */
-        virtual SessionCache* getSessionCache(bool required=true) const=0;
-        
-        /**
-         * Returns a RequestMapper instance.
-         * 
-         * @param required  true iff an exception should be thrown if no RequestMapper is available
-         * @return  a RequestMapper
-         */
-        virtual RequestMapper* getRequestMapper(bool required=true) const=0;
-        
-        /**
-         * Enforces requirements for an authenticated session.
-         * 
-         * <p>If the return value's first member is true, then request processing should terminate
-         * with the second member as a status value. If false, processing can continue. 
-         * 
-         * @param request   SP request interface
-         * @param handler   true iff a request to a registered Handler location can be directly executed
-         * @return a pair containing a "request completed" indicator and a server-specific response code
-         */
-        virtual std::pair<bool,long> doAuthentication(SPRequest& request, bool handler=false) const;
-        
-        /**
-         * Enforces authorization requirements based on the authenticated session.
-         * 
-         * <p>If the return value's first member is true, then request processing should terminate
-         * with the second member as a status value. If false, processing can continue. 
-         * 
-         * @param request   SP request interface
-         * @return a pair containing a "request completed" indicator and a server-specific response code
-         */
-        virtual std::pair<bool,long> doAuthorization(SPRequest& request) const;
-        
-        /**
-         * Publishes session contents to the request in the form of headers or environment variables.
-         * 
-         * <p>If the return value's first member is true, then request processing should terminate
-         * with the second member as a status value. If false, processing can continue. 
-         * 
-         * @param request   SP request interface
-         * @param requireSession    set to true iff an error should result if no session exists 
-         * @return a pair containing a "request completed" indicator and a server-specific response code
-         */
-        virtual std::pair<bool,long> doExport(SPRequest& request, bool requireSession=true) const;
-
-        /**
-         * Services requests for registered Handler locations. 
-         * 
-         * <p>If the return value's first member is true, then request processing should terminate
-         * with the second member as a status value. If false, processing can continue. 
-         * 
-         * @param request   SP request interface
-         * @return a pair containing a "request completed" indicator and a server-specific response code
-         */
-        virtual std::pair<bool,long> doHandler(SPRequest& request) const;
-
-        /**
-         * Register for a message.
-         *
-         * @param address       message address to register
-         * @param svc           pointer to remote service
-         */
-        virtual void regListener(const char* address, Remoted* svc)=0;
-
-        /**
-         * Unregisters service from an address, possibly restoring an original.
-         *
-         * @param address   message address to modify
-         * @param current   pointer to unregistering service
-         * @return  true iff the current service was still registered
-         */
-        virtual bool unregListener(const char* address, Remoted* current)=0;
-
-        /**
-         * Returns current service registered at an address, if any.
-         *
-         * @param address message address to access
-         * @return  registered service, or nullptr
-         */
-        virtual Remoted* lookupListener(const char* address) const=0;
-
-    protected:
-        /** The AuthTypes to "recognize" (defaults to "shibboleth"). */
-        std::set<std::string> m_authTypes;
-    };
-
-#if defined (_MSC_VER)
-    #pragma warning( pop )
-#endif
-
-    /**
-     * Registers ServiceProvider classes into the runtime.
-     */
-    void SHIBSP_API registerServiceProviders();
-
-    /** SP based on integrated XML and native server configuration. */
-    #define XML_SERVICE_PROVIDER "XML"
-};
-
-#endif /* __shibsp_sp_h__ */

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list