[cpp-sp] branch main updated: Migrate components off of Application class.

Scott Cantor cantor.2 at osu.edu
Tue Jan 7 01:55:15 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=6853fdd9a008f00c49dc10ce206b33dc334046c2

The following commit(s) were added to refs/heads/main by this push:
     new 6853fdd9 Migrate components off of Application class.
6853fdd9 is described below

commit 6853fdd9a008f00c49dc10ce206b33dc334046c2
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Jan 6 20:55:07 2025 -0500

    Migrate components off of Application class.
---
 apache/mod_shib_24.cpp                           |   9 +-
 shibsp/AbstractSPRequest.cpp                     | 316 +++++++++++++++++------
 shibsp/AbstractSPRequest.h                       |   9 +-
 shibsp/Agent.cpp                                 |   8 +-
 shibsp/Agent.h                                   |  11 +-
 shibsp/SPRequest.h                               |  54 ++--
 shibsp/ServiceProvider.cpp                       |  41 ++-
 shibsp/ServiceProvider.h                         |  10 +-
 shibsp/SessionCache.h                            |  65 ++---
 shibsp/handler/AbstractHandler.h                 |  33 +--
 shibsp/handler/AssertionConsumerService.h        |  41 +--
 shibsp/handler/Handler.h                         |  48 +---
 shibsp/handler/LogoutHandler.h                   |  27 +-
 shibsp/handler/impl/AbstractHandler.cpp          | 152 +++++------
 shibsp/handler/impl/AdminLogoutInitiator.cpp     |  46 ++--
 shibsp/handler/impl/AssertionConsumerService.cpp |  63 ++---
 shibsp/handler/impl/AssertionLookup.cpp          |   9 +-
 shibsp/handler/impl/AttributeCheckerHandler.cpp  |  24 +-
 shibsp/handler/impl/DiscoveryFeed.cpp            |  31 +--
 shibsp/handler/impl/LocalLogoutInitiator.cpp     |  35 ++-
 shibsp/handler/impl/LogoutHandler.cpp            |  73 +-----
 shibsp/handler/impl/MetadataGenerator.cpp        |  23 +-
 shibsp/handler/impl/RemotedHandler.cpp           |   2 -
 shibsp/handler/impl/SAML2Logout.cpp              |  10 +-
 shibsp/handler/impl/SAML2LogoutInitiator.cpp     |  21 +-
 shibsp/handler/impl/SAML2SessionInitiator.cpp    |  29 +--
 shibsp/handler/impl/SAMLDSSessionInitiator.cpp   |  31 ++-
 shibsp/handler/impl/SessionHandler.cpp           |   1 -
 shibsp/handler/impl/SessionInitiator.cpp         |   8 +-
 shibsp/handler/impl/StatusHandler.cpp            |  14 +-
 shibsp/impl/StorageServiceSessionCache.cpp       | 251 +++++-------------
 shibsp/impl/StorageServiceSessionCache.h         |  53 ++--
 shibsp/impl/StoredSession.cpp                    | 213 +--------------
 shibsp/impl/StoredSession.h                      |   7 +-
 tests/impl/XMLAccessControlTests.cpp             |   2 +-
 tests/impl/XMLRequestMapperTests.cpp             |   2 +-
 36 files changed, 668 insertions(+), 1104 deletions(-)

diff --git a/apache/mod_shib_24.cpp b/apache/mod_shib_24.cpp
index 8edfe411..dd770805 100644
--- a/apache/mod_shib_24.cpp
+++ b/apache/mod_shib_24.cpp
@@ -39,6 +39,7 @@
 #include <shibsp/exceptions.h>
 #include <shibsp/AbstractSPRequest.h>
 #include <shibsp/AccessControl.h>
+#include <shibsp/Agent.h>
 #include <shibsp/AgentConfig.h>
 #include <shibsp/RequestMapper.h>
 #include <shibsp/SPConfig.h>
@@ -648,7 +649,7 @@ extern "C" int shib_check_user(request_rec* r)
         }
 
         // Check user authentication and export information, then set the handler bypass
-        pair<bool,long> res = psta->getServiceProvider().doAuthentication(*psta, true);
+        pair<bool,long> res = psta->getAgent().doAuthentication(*psta, true);
         apr_pool_userdata_setn((const void*)42,g_UserDataKey,nullptr,r->pool);
         // If directed, install a spoof key to recognize when we've already cleared headers.
         if (!g_spoofKey.empty() && (((shib_dir_config*)ap_get_module_config(r->per_dir_config, &shib_module))->bUseHeaders == 1))
@@ -664,7 +665,7 @@ extern "C" int shib_check_user(request_rec* r)
         }
 
         // user auth was okay -- export the session data now
-        res = psta->getServiceProvider().doExport(*psta);
+        res = psta->getAgent().doExport(*psta);
         if (res.first) {
             // See above for explanation of this hack.
             if (res.second == OK && !r->user)
@@ -722,7 +723,7 @@ extern "C" int shib_handler(request_rec* r)
             return HTTP_INTERNAL_SERVER_ERROR;
         }
 
-        pair<bool,long> res = psta->getServiceProvider().doHandler(*psta);
+        pair<bool,long> res = psta->getAgent().doHandler(*psta);
         if (res.first) return res.second;
 
         ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, "doHandler() did not handle the request");
@@ -769,7 +770,7 @@ extern "C" int shib_auth_checker(request_rec* r)
             return HTTP_INTERNAL_SERVER_ERROR;
         }
 
-        pair<bool,long> res = psta->getServiceProvider().doAuthorization(*psta);
+        pair<bool,long> res = psta->getAgent().doAuthorization(*psta);
         if (res.first) return res.second;
 
         // The SP method should always return true, so if we get this far, something unusual happened.
diff --git a/shibsp/AbstractSPRequest.cpp b/shibsp/AbstractSPRequest.cpp
index ea49d8fc..903457ef 100644
--- a/shibsp/AbstractSPRequest.cpp
+++ b/shibsp/AbstractSPRequest.cpp
@@ -23,13 +23,16 @@
 #include "AbstractSPRequest.h"
 #include "Agent.h"
 #include "AgentConfig.h"
-#include "Application.h"
-#include "ServiceProvider.h"
 #include "SessionCache.h"
 #include "logging/Category.h"
 #include "util/CGIParser.h"
 
 #include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+
+#ifndef HAVE_STRCASECMP
+# define strncasecmp _strnicmp
+#endif
 
 using namespace shibsp;
 using namespace std;
@@ -46,11 +49,8 @@ SPRequest::~SPRequest()
 AbstractSPRequest::AbstractSPRequest(const char* category)
     : m_log(Category::getInstance(category)),
         m_agent(AgentConfig::getConfig().getAgent()),
-        m_sp(SPConfig::getConfig().getServiceProvider()),
-        m_mapper(nullptr), m_app(nullptr), m_sessionTried(false), m_session(nullptr)
+        m_mapper(nullptr), m_sessionTried(false), m_session(nullptr)
 {
-    if (m_sp)
-        m_sp->lock();
 }
 
 AbstractSPRequest::~AbstractSPRequest()
@@ -59,8 +59,6 @@ AbstractSPRequest::~AbstractSPRequest()
         m_session->unlock();
     if (m_mapper)
         m_mapper->unlock_shared();
-    if (m_sp)
-        m_sp->unlock();
 }
 
 const Agent& AbstractSPRequest::getAgent() const
@@ -68,11 +66,6 @@ const Agent& AbstractSPRequest::getAgent() const
     return m_agent;
 }
 
-const ServiceProvider& AbstractSPRequest::getServiceProvider() const
-{
-    return *m_sp;
-}
-
 RequestMapper::Settings AbstractSPRequest::getRequestSettings() const
 {
     if (!m_mapper) {
@@ -88,17 +81,6 @@ RequestMapper::Settings AbstractSPRequest::getRequestSettings() const
     return m_settings;
 }
 
-const Application& AbstractSPRequest::getApplication() const
-{
-    if (!m_app) {
-        // Now find the application from the URL settings
-        m_app = m_sp->getApplication(getRequestSettings().first->getString("applicationId"));
-        if (!m_app)
-            throw ConfigurationException("Unable to map non-default applicationId to an ApplicationOverride, check configuration.");
-    }
-    return *m_app;
-}
-
 Session* AbstractSPRequest::getSession(bool checkTimeout, bool ignoreAddress, bool cache)
 {
     // Only attempt this once.
@@ -118,7 +100,7 @@ Session* AbstractSPRequest::getSession(bool checkTimeout, bool ignoreAddress, bo
 
     // The cache will either silently pass a session or nullptr back, or throw an exception out.
     Session* session = getAgent().getSessionCache()->find(
-        getApplication(), *this, (ignoreAddress ? nullptr : getRemoteAddr().c_str()), (checkTimeout ? &timeout : nullptr)
+        *this, (ignoreAddress ? nullptr : getRemoteAddr().c_str()), (checkTimeout ? &timeout : nullptr)
         );
     if (cache)
         m_session = session;
@@ -180,6 +162,52 @@ vector<const char*>::size_type AbstractSPRequest::getParameters(const char* name
     return values.size();
 }
 
+string AbstractSPRequest::getCookieName(const char* prefix, time_t* lifetime) const
+{
+    if (lifetime) {
+        *lifetime = getRequestSettings().first->getUnsignedInt("cookieLifetime", 0);
+    }
+
+    if (!prefix)
+        prefix = "";
+
+    const char* p = getRequestSettings().first->getString("cookieName");
+    if (p) {
+        return string(prefix) + p;
+    }
+
+    return string(prefix); // + getHash(); TODO: uniqueify the cookie name
+}
+
+pair<string,const char*> AbstractSPRequest::getCookieNameProps(const char* prefix, time_t* lifetime) const
+{
+    static const char* defProps="; path=/; HttpOnly";
+    static const char* sslProps="; path=/; secure; HttpOnly";
+
+    if (lifetime) {
+        *lifetime = getRequestSettings().first->getUnsignedInt("cookieLifetime", 0);
+    }
+
+    if (!prefix)
+        prefix = "";
+
+    const char* cookieProps = getRequestSettings().first->getString("cookieProps");
+    if (!cookieProps || !strcasecmp(cookieProps, "http")) {
+        cookieProps = defProps;
+    }
+    else if (!strcasecmp(cookieProps, "https")) {
+        cookieProps = sslProps;
+    }
+
+    const char* cookieName = getRequestSettings().first->getString("cookieName");
+    if (cookieName) {
+        return make_pair(string(prefix) + cookieName, cookieProps);
+    }
+
+    // TODO: uniqueify the cookie name
+    return make_pair(string(prefix) /* + getHash() */, cookieProps);
+}
+
 const char* AbstractSPRequest::getHandlerURL(const char* resource) const
 {
     if (!resource)
@@ -208,25 +236,11 @@ const char* AbstractSPRequest::getHandlerURL(const char* resource) const
 #endif
         throw ConfigurationException("Target resource was not an absolute URL.");
 
-    bool ssl_only = true;
-    const char* handler = nullptr;
-    const PropertySet* props = getApplication().getPropertySet("Sessions");
-    if (props) {
-        pair<bool,bool> p = props->getBool("handlerSSL");
-        if (p.first)
-            ssl_only = p.second;
-        pair<bool,const char*> p2 = props->getString("handlerURL");
-        if (p2.first)
-            handler = p2.second;
-    }
+    bool ssl_only = getRequestSettings().first->getBool("handlerSSL", true);
+    const char* handler = getRequestSettings().first->getString("handlerURL", "/Shibboleth.sso");
 
-    if (!handler) {
-        handler = "/Shibboleth.sso";
-    }
-    else if (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)) {
-        throw ConfigurationException(
-            string("Invalid handlerURL property in <Sessions> element for Application ") + m_app->getId()
-            );
+    if (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)) {
+        throw ConfigurationException(string("Invalid handlerURL property: ") + handler);
     }
 
     // The "handlerURL" property can be in one of three formats:
@@ -282,6 +296,170 @@ const char* AbstractSPRequest::getHandlerURL(const char* resource) const
     return m_handlerURL.c_str();
 }
 
+string AbstractSPRequest::getNotificationURL(bool front, unsigned int index) const
+{
+    // We have to process the underlying setting each call to this method unfortunately.
+    const char* rawlocs = getRequestSettings().first->getString(front ? "frontNotifyURLs" : "backNotifyURLs");
+    vector<string> locs;
+    boost::split(locs, rawlocs, boost::is_space(), boost::algorithm::token_compress_on);
+
+    if (index >= locs.size())
+        return string();
+
+    const char* resource = getRequestURL();
+    if (!resource || (strncasecmp(resource,"http://", 7) && strncasecmp(resource,"https://", 8))) {
+        throw ConfigurationException("Request URL was not absolute.");
+    }
+
+    const char* handler = locs[index].c_str();
+
+    // Should never happen...
+    if (!handler || (*handler!='/' && strncasecmp(handler, "http:", 5) && strncasecmp(handler, "https:", 6))) {
+        throw ConfigurationException("Invalid Location property in Notify element");
+    }
+
+    // The "Location" property can be in one of three formats:
+    //
+    // 1) a full URI:       http://host/foo/bar
+    // 2) a hostless URI:   http:///foo/bar
+    // 3) a relative path:  /foo/bar
+    //
+    // #  Protocol  Host        Path
+    // 1  handler   handler     handler
+    // 2  handler   resource    handler
+    // 3  resource  resource    handler
+
+    const char* path = nullptr;
+
+    // Decide whether to use the handler or the resource for the "protocol"
+    const char* prot;
+    if (*handler != '/') {
+        prot = handler;
+    }
+    else {
+        prot = resource;
+        path = handler;
+    }
+
+    // break apart the "protocol" string into protocol, host, and "the rest"
+    const char* colon=strchr(prot,':');
+    colon += 3;
+    const char* slash=strchr(colon,'/');
+    if (!path)
+        path = slash;
+
+    // Compute the actual protocol and store.
+    string notifyURL(prot, colon-prot);
+
+    // create the "host" from either the colon/slash or from the target string
+    // If prot == handler then we're in either #1 or #2, else #3.
+    // If slash == colon then we're in #2.
+    if (prot != handler || slash == colon) {
+        colon = strchr(resource, ':');
+        colon += 3;      // Get past the ://
+        slash = strchr(colon, '/');
+    }
+    string host(colon, (slash ? slash-colon : strlen(colon)));
+
+    // Build the URL
+    notifyURL += host + path;
+    return notifyURL;
+}
+
+void AbstractSPRequest::limitRedirect(const char* url) const
+{
+    if (!url || *url == '/')
+        return;
+
+    enum {
+        REDIRECT_LIMIT_NONE,
+        REDIRECT_LIMIT_EXACT,
+        REDIRECT_LIMIT_HOST,
+        REDIRECT_LIMIT_ALLOW,
+        REDIRECT_LIMIT_EXACT_ALLOW,
+        REDIRECT_LIMIT_HOST_ALLOW
+    } redirectLimit;
+
+    // Derive the active rule.
+    vector<string> redirectAllow;
+    const char* prop = getRequestSettings().first->getString("redirectLimit", "exact");
+    if (!strcmp(prop, "none")) {
+        redirectLimit = REDIRECT_LIMIT_NONE;
+    }
+    else if (!strcmp(prop, "exact")) {
+        redirectLimit = REDIRECT_LIMIT_EXACT;
+    }
+    else if (!strcmp(prop, "host")) {
+        redirectLimit = REDIRECT_LIMIT_HOST;
+    }
+    else {
+        if (!strcmp(prop, "exact+allow")) {
+            redirectLimit = REDIRECT_LIMIT_EXACT_ALLOW;
+        }
+        else if (!strcmp(prop, "host+allow")) {
+            redirectLimit = REDIRECT_LIMIT_HOST_ALLOW;
+        }
+        else if (!strcmp(prop, "allow")) {
+            redirectLimit = REDIRECT_LIMIT_ALLOW;
+        }
+        else {
+            m_log.error("unrecognized redirectLimit setting (%s), falling back to 'exact' ", prop);
+        }
+        prop = getRequestSettings().first->getString("redirectAllow");
+        if (prop) {
+            string dup(prop);
+            boost::trim(dup);
+            boost::split(redirectAllow, dup, boost::is_space(), boost::algorithm::token_compress_on);
+        }
+    }
+
+    if (redirectLimit != REDIRECT_LIMIT_NONE) {
+
+        // This is ugly, but the purpose is to prevent blocking legitimate redirects
+        // that lack a trailing slash after the hostname. If there are fewer than 3
+        // slashes, we assume the hostname wasn't terminated.
+        string urlcopy(url);
+        if (count(urlcopy.begin(), urlcopy.end(), '/') < 3) {
+            urlcopy += '/';
+        }
+
+        vector<string> allowlist;
+        if (redirectLimit == REDIRECT_LIMIT_EXACT || redirectLimit == REDIRECT_LIMIT_EXACT_ALLOW) {
+            // Scheme and hostname have to match.
+            if (isDefaultPort()) {
+                allowlist.push_back(string(getScheme()) + "://" + getHostname() + '/');
+            }
+            allowlist.push_back(string(getScheme()) + "://" + getHostname() + ':' + boost::lexical_cast<string>(getPort()) + '/');
+        }
+        else if (redirectLimit == REDIRECT_LIMIT_HOST || redirectLimit == REDIRECT_LIMIT_HOST_ALLOW) {
+            // Allow any scheme or port.
+            allowlist.push_back(string("https://") + getHostname() + '/');
+            allowlist.push_back(string("http://") + getHostname() + '/');
+            allowlist.push_back(string("https://") + getHostname() + ':');
+            allowlist.push_back(string("http://") + getHostname() + ':');
+        }
+
+        if (!allowlist.empty()) {
+            for (const string& s : allowlist) {
+                if (boost::istarts_with(urlcopy, s)) {
+                    return;
+                }
+            }
+        }
+
+        if (!redirectAllow.empty()) {
+            for (const string& s : redirectAllow) {
+                if (boost::istarts_with(urlcopy, s)) {
+                    return;
+                }
+            }
+        }
+
+        m_log.warn("redirectLimit policy enforced, blocked redirect to (%s)", url);
+        throw agent_exception("Blocked unacceptable redirect location.");
+    }
+}
+
 string AbstractSPRequest::getSecureHeader(const char* name) const
 {
     return getHeader(name);
@@ -294,50 +472,32 @@ void AbstractSPRequest::setAuthType(const char* authtype)
 
 const char* AbstractSPRequest::getCookie(const char* name) const
 {
-    pair<bool, bool> sameSiteFallback = pair<bool, bool>(false, false);
-    const PropertySet* props = getApplication().getPropertySet("Sessions");
-    if (props) {
-        sameSiteFallback = props->getBool("sameSiteFallback");
-    }
-    return HTTPRequest::getCookie(name, sameSiteFallback.first && sameSiteFallback.second);
+    bool sameSiteFallback = getRequestSettings().first->getBool("sameSiteFallback", false);
+    return HTTPRequest::getCookie(name, sameSiteFallback);
 }
 
 void AbstractSPRequest::setCookie(const char* name, const char* value, time_t expires, samesite_t sameSite)
 {
+    bool sameSiteFallback = false;
+    if (sameSite == SAMESITE_NONE) {
+        sameSiteFallback = getRequestSettings().first->getBool("sameSiteFallback", false);
+    }
+
     static const char* defProps="; path=/; HttpOnly";
     static const char* sslProps="; path=/; secure; HttpOnly";
 
-    const char* cookieProps = defProps;
-    pair<bool,bool> sameSiteFallback = pair<bool,bool>(false, false);
-
-    const PropertySet* props = getApplication().getPropertySet("Sessions");
-    if (props) {
-        if (sameSite == SAMESITE_NONE) {
-            sameSiteFallback = props->getBool("sameSiteFallback");
-        }
-
-        pair<bool, const char*> p = props->getString("cookieProps");
-        if (p.first) {
-            if (!strcmp(p.second, "https"))
-                cookieProps = sslProps;
-            else if (strcmp(p.second, "http"))
-                cookieProps = p.second;
-        }
-    }
+    const char* cookieProps = getRequestSettings().first->getString("cookieProps", defProps);
+    if (!strcmp(cookieProps, "https"))
+        cookieProps = sslProps;
+    else if (!strcmp(cookieProps, "http"))
+        cookieProps = defProps;
 
-    if (cookieProps) {
-        string decoratedValue(value ? value : "");
-        if (!value) {
-            decoratedValue += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
-        }
-        decoratedValue += cookieProps;
-        HTTPResponse::setCookie(name, decoratedValue.c_str(), expires, sameSite,
-            sameSiteFallback.first && sameSiteFallback.second);
-    }
-    else {
-        HTTPResponse::setCookie(name, value, expires, sameSite,
-            sameSiteFallback.first && sameSiteFallback.second);
+    string decoratedValue(value ? value : "");
+    if (!value) {
+        decoratedValue += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
     }
+    decoratedValue += cookieProps;
+    HTTPResponse::setCookie(name, decoratedValue.c_str(), expires, sameSite, sameSiteFallback);
 }
 
 void AbstractSPRequest::log(Priority::Value level, const std::string& msg) const
diff --git a/shibsp/AbstractSPRequest.h b/shibsp/AbstractSPRequest.h
index 09cfc494..58ba818c 100644
--- a/shibsp/AbstractSPRequest.h
+++ b/shibsp/AbstractSPRequest.h
@@ -61,16 +61,19 @@ namespace shibsp {
 
         // Virtual function overrides.
         const Agent& getAgent() const;
-        const ServiceProvider& getServiceProvider() const;
         RequestMapper::Settings getRequestSettings() const;
-        const Application& getApplication() const;
         Session* getSession(bool checkTimeout=true, bool ignoreAddress=false, bool cache=true);
         const char* getRequestURI() const;
         const char* getRequestURL() const;
         std::string getRemoteAddr() const;
         const char* getParameter(const char* name) const;
         std::vector<const char*>::size_type getParameters(const char* name, std::vector<const char*>& values) const;
+        std::string getCookieName(const char* prefix, time_t* lifetime) const;
+        std::pair<std::string,const char*> getCookieNameProps(const char* prefix, time_t* lifetime) const;
         const char* getHandlerURL(const char* resource=nullptr) const;
+        std::string getNotificationURL(bool front, unsigned int index) const;
+        void limitRedirect(const char* url) const;
+
         std::string getSecureHeader(const char* name) const;
         const char* getCookie(const char* name) const;
         void setAuthType(const char* authtype);
@@ -81,10 +84,8 @@ namespace shibsp {
     private:
         Category& m_log;
         Agent& m_agent;
-        ServiceProvider* m_sp; // TODO: remove
         mutable RequestMapper* m_mapper;
         mutable RequestMapper::Settings m_settings;
-        mutable const Application* m_app; // TODO: remove
         mutable bool m_sessionTried;
         mutable Session* m_session;
         std::string m_uri;
diff --git a/shibsp/Agent.cpp b/shibsp/Agent.cpp
index 5510d696..90422ca6 100644
--- a/shibsp/Agent.cpp
+++ b/shibsp/Agent.cpp
@@ -54,25 +54,25 @@ Agent::~Agent()
 // TODO: we'll eventually copy/port in substantially similar versions of the old ServiceProvider
 // method impls.
 
-pair<bool,long> Agent::doAuthentication(AgentRequest& request, bool handler) const
+pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
 {
     pair<bool,long> foo;
     return foo;
 }
 
-pair<bool,long> Agent::doAuthorization(AgentRequest& request) const
+pair<bool,long> Agent::doAuthorization(SPRequest& request) const
 {
     pair<bool, long> foo;
     return foo;
 }
 
-pair<bool,long> Agent::doExport(AgentRequest& request, bool requireSession) const
+pair<bool,long> Agent::doExport(SPRequest& request, bool requireSession) const
 {
     pair<bool, long> foo;
     return foo;
 }
 
-pair<bool,long> Agent::doHandler(AgentRequest& request) const
+pair<bool,long> Agent::doHandler(SPRequest& request) const
 {
     pair<bool, long> foo;
     return foo;
diff --git a/shibsp/Agent.h b/shibsp/Agent.h
index 408499cd..832a7178 100644
--- a/shibsp/Agent.h
+++ b/shibsp/Agent.h
@@ -32,7 +32,8 @@ namespace shibsp {
     class SHIBSP_API RemotingService;
     class SHIBSP_API RequestMapper;
     class SHIBSP_API SessionCache;
-    class SHIBSP_API AgentRequest;
+    //class SHIBSP_API AgentRequest;
+    class SHIBSP_API SPRequest;
 
 #if defined (_MSC_VER)
     #pragma warning( push )
@@ -96,7 +97,7 @@ namespace shibsp {
          * @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(AgentRequest& request, bool handler=false) const;
+        virtual std::pair<bool,long> doAuthentication(SPRequest& request, bool handler=false) const;
         
         /**
          * Enforces authorization requirements based on the authenticated session.
@@ -107,7 +108,7 @@ namespace shibsp {
          * @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(AgentRequest& request) const;
+        virtual std::pair<bool,long> doAuthorization(SPRequest& request) const;
         
         /**
          * Publishes session contents to the request in the form of headers or environment variables.
@@ -119,7 +120,7 @@ namespace shibsp {
          * @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(AgentRequest& request, bool requireSession=true) const;
+        virtual std::pair<bool,long> doExport(SPRequest& request, bool requireSession=true) const;
 
         /**
          * Services requests for registered Handler locations. 
@@ -130,7 +131,7 @@ namespace shibsp {
          * @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(AgentRequest& request) const;
+        virtual std::pair<bool,long> doHandler(SPRequest& request) const;
 
     protected:
         /** The AuthTypes to "recognize" (defaults to "shibboleth"). */
diff --git a/shibsp/SPRequest.h b/shibsp/SPRequest.h
index cead45fd..fd067243 100644
--- a/shibsp/SPRequest.h
+++ b/shibsp/SPRequest.h
@@ -56,15 +56,6 @@ namespace shibsp {
          */
         virtual const Agent& getAgent() const=0;
 
-        /**
-         * Returns the locked ServiceProvider processing the request.
-         * 
-         * TODO: remove
-         *
-         * @return reference to ServiceProvider
-         */
-        virtual const ServiceProvider& getServiceProvider() const=0;
-
         /**
          * Returns RequestMapper Settings associated with the request, guaranteed
          * to be valid for the request's duration.
@@ -73,15 +64,6 @@ namespace shibsp {
          */
         virtual RequestMapper::Settings getRequestSettings() const=0;
 
-        /**
-         * Returns the Application governing the request.
-         * 
-         * TODO: remove
-         *
-         * @return reference to Application
-         */
-        virtual const Application& getApplication() const=0;
-
         /**
          * Returns a locked Session associated with the request.
          *
@@ -92,6 +74,24 @@ namespace shibsp {
          */
         virtual Session* getSession(bool checkTimeout=true, bool ignoreAddress=false, bool cache=true)=0;
 
+        /**
+         * Returns the cookies name to use for this request.
+         *
+         * @param prefix    a value to prepend to the base cookie name
+         * @param lifetime  if non-null, will be populated with a suggested lifetime for the cookie, or 0 if session-bound
+         * @return  the assigned cookie name to use
+         */
+        virtual std::string getCookieName(const char* prefix, time_t* lifetime=nullptr) const=0;
+
+        /**
+         * Returns the name and cookie properties to use for this request.
+         *
+         * @param prefix    a value to prepend to the base cookie name
+         * @param lifetime  if non-null, will be populated with a suggested lifetime for the cookie, or 0 if session-bound
+         * @return  a pair containing the cookie name and the string to append to the cookie value
+         */
+        virtual std::pair<std::string,const char*> getCookieNameProps(const char* prefix, time_t* lifetime=nullptr) const=0;
+
         /**
          * Returns the effective base Handler URL for a resource,
          * or the current request URL.
@@ -101,6 +101,24 @@ namespace shibsp {
          */
         virtual const char* getHandlerURL(const char* resource=nullptr) const=0;
 
+        /**
+         * Returns the designated notification URL, or an empty string if no more locations are specified.
+         *
+         * @param front     true iff front channel notification is desired, false iff back channel is desired
+         * @param index     zero-based index of URL to return
+         * @return  the designated URL, or an empty string
+         */
+        virtual std::string getNotificationURL(bool front, unsigned int index) const=0;
+
+        /**
+         * Checks a proposed redirect URL against policy settings for legal redirects,
+         * such as same-host restrictions or allowed domains, and raises an exception
+         * in the event of a violation.
+         *
+         * @param url       an absolute URL to validate
+         */
+        virtual void limitRedirect(const char* url) const=0;
+
         /**
          * Returns a non-spoofable request header value, if possible.
          * Platforms that support environment export can redirect header
diff --git a/shibsp/ServiceProvider.cpp b/shibsp/ServiceProvider.cpp
index 99aa1f33..2d041fd9 100644
--- a/shibsp/ServiceProvider.cpp
+++ b/shibsp/ServiceProvider.cpp
@@ -92,21 +92,21 @@ namespace shibsp {
     }
 
     void SHIBSP_DLLLOCAL clearHeaders(SPRequest& request) {
-        const Application& app = request.getApplication();
-        app.clearHeader(request, "Shib-Cookie-Name", "HTTP_SHIB_COOKIE_NAME");
-        app.clearHeader(request, "Shib-Session-ID", "HTTP_SHIB_SESSION_ID");
-        app.clearHeader(request, "Shib-Session-Index", "HTTP_SHIB_SESSION_INDEX");
-        app.clearHeader(request, "Shib-Session-Expires", "HTTP_SHIB_SESSION_EXPIRES");
-        app.clearHeader(request, "Shib-Session-Inactivity", "HTTP_SHIB_SESSION_INACTIVITY");
-        app.clearHeader(request, "Shib-Identity-Provider", "HTTP_SHIB_IDENTITY_PROVIDER");
-        app.clearHeader(request, "Shib-Authentication-Method", "HTTP_SHIB_AUTHENTICATION_METHOD");
-        app.clearHeader(request, "Shib-Authentication-Instant", "HTTP_SHIB_AUTHENTICATION_INSTANT");
-        app.clearHeader(request, "Shib-AuthnContext-Class", "HTTP_SHIB_AUTHNCONTEXT_CLASS");
-        app.clearHeader(request, "Shib-AuthnContext-Decl", "HTTP_SHIB_AUTHNCONTEXT_DECL");
-        app.clearHeader(request, "Shib-Assertion-Count", "HTTP_SHIB_ASSERTION_COUNT");
-        app.clearHeader(request, "Shib-Handler", "HTTP_SHIB_HANDLER");
-        app.clearAttributeHeaders(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) {
@@ -129,7 +129,7 @@ namespace shibsp {
             for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
                 if (a->second->isInternal())
                     continue;
-                string header(request.getApplication().getSecureHeader(request, a->first.c_str()));
+                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())
@@ -153,7 +153,7 @@ namespace shibsp {
                         }
                     }
                 }
-                request.getApplication().setHeader(request, a->first.c_str(), header.c_str());
+                request.setHeader(a->first.c_str(), header.c_str());
             }
         }
         else {
@@ -191,13 +191,14 @@ namespace shibsp {
                         }
                     }
                 }
-                request.getApplication().setHeader(request, deduped->first.c_str(), header.c_str());
+                request.setHeader(deduped->first.c_str(), header.c_str());
             }
         }
 
         // Check for REMOTE_USER.
         bool remoteUserSet = false;
-        const vector<string>& rmids = request.getApplication().getRemoteUserAttributeIds();
+        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);
@@ -239,7 +240,6 @@ pair<bool,long> ServiceProvider::doAuthentication(SPRequest& request, bool handl
 
     try {
         RequestMapper::Settings settings = request.getRequestSettings();
-        app = &(request.getApplication());
 
         // If not SSL, check to see if we should block or redirect it.
         if (!request.isSecure()) {
@@ -380,7 +380,6 @@ pair<bool,long> ServiceProvider::doAuthorization(SPRequest& request) const
 
     try {
         RequestMapper::Settings settings = request.getRequestSettings();
-        app = &(request.getApplication());
 
         // Three settings dictate how to proceed.
         const char* authType = settings.first->getString("authType");
@@ -449,7 +448,6 @@ pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSessio
 
     try {
         RequestMapper::Settings settings = request.getRequestSettings();
-        app = &(request.getApplication());
 
         try {
             session = request.getSession(false, false, false);  // ignore timeout and do not cache
@@ -533,7 +531,6 @@ pair<bool,long> ServiceProvider::doHandler(SPRequest& request) const
 
     try {
         RequestMapper::Settings settings = request.getRequestSettings();
-        app = &(request.getApplication());
 
         // If not SSL, check to see if we should block or redirect it.
         if (!request.isSecure()) {
diff --git a/shibsp/ServiceProvider.h b/shibsp/ServiceProvider.h
index 1b0382f4..d6017d68 100644
--- a/shibsp/ServiceProvider.h
+++ b/shibsp/ServiceProvider.h
@@ -24,11 +24,11 @@
 #include <shibsp/util/PropertySet.h>
 
 #include <set>
+#include <vector>
 #include <xmltooling/Lockable.h>
 
 namespace shibsp {
 
-    class SHIBSP_API Application;
     class SHIBSP_API Handler;
     class SHIBSP_API ListenerService;
     class SHIBSP_API Remoted;
@@ -81,14 +81,6 @@ namespace shibsp {
          */
         virtual RequestMapper* getRequestMapper(bool required=true) const=0;
         
-        /**
-         * Returns an Application instance matching the specified ID.
-         * 
-         * @param applicationId the ID of the application, or nullptr for the default
-         * @return  pointer to the application, or nullptr
-         */
-        virtual const Application* getApplication(const char* applicationId) const=0;
-
         /**
          * Enforces requirements for an authenticated session.
          * 
diff --git a/shibsp/SessionCache.h b/shibsp/SessionCache.h
index b604e748..29afcae8 100644
--- a/shibsp/SessionCache.h
+++ b/shibsp/SessionCache.h
@@ -33,8 +33,7 @@ namespace shibsp {
 
     class SHIBSP_API Application;
     class SHIBSP_API Attribute;
-    class SHIBSP_API HTTPRequest;
-    class SHIBSP_API HTTPResponse;
+    class SHIBSP_API SPRequest;
 
     /**
      * Encapsulates access to a user's security session.
@@ -59,11 +58,12 @@ namespace shibsp {
         virtual const char* getID() const=0;
 
         /**
-         * Returns the session's application ID.
+         * Returns the session's "bucket" ID, i.e., a value separating sessions into
+         * specific buckets based on resources.
          *
-         * @return unique ID of application bound to session
+         * @return unique ID of session bucket
          */
-        virtual const char* getApplicationID() const=0;
+        virtual const char* getBucketID() const=0;
 
         /**
          * Returns the session expiration.
@@ -158,9 +158,7 @@ namespace shibsp {
          * <p>The SSO tokens and Attributes remain owned by the caller and are copied by the cache.</p>
          *
          * @param sessionID         reference to string to capture newly inserted session ID
-         * @param application       reference to Application that owns the Session
-         * @param httpRequest       request that initiated session
-         * @param httpResponse      current response to client
+         * @param request           request that initiated session
          * @param expires           expiration time of session
          * @param issuer            issuing metadata of assertion issuer, if known
          * @param protocol          protocol family used to initiate the session
@@ -174,9 +172,7 @@ namespace shibsp {
          */
         virtual void insert(
             std::string& sessionID,
-            const Application& application,
-            const xmltooling::HTTPRequest& httpRequest,
-            xmltooling::HTTPResponse& httpResponse,
+            const SPRequest& request,
             time_t expires,
             const opensaml::saml2md::EntityDescriptor* issuer=nullptr,
             const XMLCh* protocol=nullptr,
@@ -192,7 +188,6 @@ namespace shibsp {
         /**
          * Determines whether the Session bound to a client request matches a set of input criteria.
          *
-         * @param application   reference to Application that owns the Session
          * @param request       request in which to locate Session
          * @param issuer        required source of session(s)
          * @param nameid        required name identifier
@@ -200,8 +195,7 @@ namespace shibsp {
          * @return  true iff the Session exists and matches the input criteria
          */
         virtual bool matches(
-            const Application& application,
-            xmltooling::HTTPRequest& request,
+            const SPRequest& request,
             const opensaml::saml2md::EntityDescriptor* issuer,
             const opensaml::saml2::NameID& nameid,
             const std::set<std::string>* indexes
@@ -217,7 +211,7 @@ namespace shibsp {
         * <p>Until logout expiration, any attempt to create a session with the same parameters
         * will be blocked by the cache.
         *
-        * @param application   reference to Application that owns the session(s)
+        * @param bucketID      bucket for session
         * @param issuer        source of session(s)
         * @param nameid        name identifier associated with the session(s) to terminate
         * @param indexes       indexes of sessions, or nullptr for all sessions associated with other parameters
@@ -225,7 +219,7 @@ namespace shibsp {
         * @param sessions      on exit, contains the IDs of the matching sessions found
         */
         virtual std::vector<std::string>::size_type logout(
-            const Application& application,
+            const char* bucketID,
             const opensaml::saml2md::EntityDescriptor* issuer,
             const opensaml::saml2::NameID& nameid,
             const std::set<std::string>* indexes,
@@ -242,11 +236,10 @@ namespace shibsp {
         /**
          * Returns the ID of the session bound to the specified client request, if possible.
          *
-         * @param application   reference to Application that owns the Session
-         * @param request       request from client containing session, or a reference to it
+         * @param request   request from client containing session
          * @return  ID of session, if any known, or an empty string
          */
-        virtual std::string active(const Application& application, const HTTPRequest& request)=0;
+        virtual std::string active(const SPRequest& request)=0;
 
         /**
          * Locates an existing session bound to a request.
@@ -257,59 +250,49 @@ namespace shibsp {
          * <p>If a bound session is found to have expired, be invalid, etc., and if the request
          * can be used to "clear" the session from subsequent client requests, then it may be cleared.</p>
          *
-         * @param application   reference to Application that owns the Session
          * @param request       request from client bound to session
          * @param client_addr   network address of client (if known)
          * @param timeout       inactivity timeout to enforce (0 for none, nullptr to bypass check/update of last access)
          * @return  pointer to locked Session, or nullptr
          */
-        virtual Session* find(
-            const Application& application,
-            HTTPRequest& request,
-            const char* client_addr=nullptr,
-            time_t* timeout=nullptr
-            )=0;
+        virtual Session* find(SPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr)=0;
 
         /**
          * Deletes an existing session bound to a request.
          *
          * <p>Revocation may be supported by some implementations.</p>
          *
-         * @param application   reference to Application that owns the Session
-         * @param request       request from client containing session, or a reference to it
-         * @param response      optional response to client enabling removal of session or reference
+         * @param request       request from client containing session
          * @param revocationExp optional indicator for length of time to track revocation of this session
          */
-        virtual void remove(
-            const Application& application,
-            const HTTPRequest& request,
-            HTTPResponse* response=nullptr,
-            time_t revocationExp=0
-        )=0;
+        virtual void remove(SPRequest& request, time_t revocationExp=0)=0;
 
         /**
         * Locates an existing session by ID.
         *
-        * @param application   reference to Application that owns the Session
+        * @param bucketID      bucket for session
         * @param key           session key
         * @return  pointer to locked Session, or nullptr
         */
-        virtual Session* find(const Application& application, const char* key)=0;
+        virtual Session* find(const char* bucketID, const char* key)=0;
 
         /**
         * Deletes an existing session.
         *
         * <p>Revocation may be supported by some implementations.</p>
         *
-        * @param application   reference to Application that owns the Session
+        * @param bucketID      bucket for session
         * @param key           session key
         * @param revocationExp optional indicator for length of time to track revocation of this session
         */
-        virtual void remove(const Application& application, const char* key, time_t revocationExp=0)=0;
+        virtual void remove(const char* bucketID, const char* key, time_t revocationExp=0)=0;
     };
 
-    /** SessionCache implementation backed by a StorageService. */
-    #define STORAGESERVICE_SESSION_CACHE    "StorageService"
+    /** SessionCache implementation backed by the file system. */
+    #define FILESYSTEM_SESSION_CACHE    "filesystem"
+
+    /** SessionCache implementation backed by a hub-hosted StorageService. */
+    #define STORAGESERVICE_SESSION_CACHE    "storage"
 
     /**
      * Registers SessionCache classes into the runtime.
diff --git a/shibsp/handler/AbstractHandler.h b/shibsp/handler/AbstractHandler.h
index e7c8d7e8..113dd752 100644
--- a/shibsp/handler/AbstractHandler.h
+++ b/shibsp/handler/AbstractHandler.h
@@ -36,8 +36,6 @@ namespace xmltooling {
 
 namespace shibsp {
 
-    class SHIBSP_API Application;
-
 #if defined (_MSC_VER)
     #pragma warning( push )
     #pragma warning( disable : 4250 )
@@ -122,17 +120,10 @@ namespace shibsp {
         /**
          * Implements a mechanism to preserve form post data.
          *
-         * @param application   the associated Application
-         * @param request       incoming HTTP request
-         * @param response      outgoing HTTP response
+         * @param request       SP request
          * @param relayState    relay state information attached to current sequence, if any
          */
-        virtual void preservePostData(
-            const Application& application,
-            const HTTPRequest& request,
-            HTTPResponse& response,
-            const char* relayState
-            ) const;
+        virtual void preservePostData(SPRequest& request, const char* relayState) const;
 
         /**
          * Implements storage service and cookie mechanism to recover PostData.
@@ -140,33 +131,21 @@ namespace shibsp {
          * <p>If a supported mechanism can be identified, the return value will be
          * the recovered state information.
          *
-         * @param application   the associated Application
          * @param request       incoming HTTP request
          * @param response      outgoing HTTP response
          * @param relayState    relay state information attached to current sequence, if any
          * @return  recovered form post data associated with request as a DDF list of string members
          */
-        virtual DDF recoverPostData(
-            const Application& application,
-            const HTTPRequest& request,
-            HTTPResponse& response,
-            const char* relayState
-            ) const;
+        virtual DDF recoverPostData(SPRequest& request, const char* relayState) const;
 
         /**
          * Post a redirect response with post data.
          *
-         * @param application   the associated Application
          * @param response      outgoing HTTP response
          * @param url           action url for the form
          * @param postData      list of parameters to load into the form, as DDF string members
          */
-        virtual long sendPostResponse(
-            const Application& application,
-            HTTPResponse& response,
-            const char* url,
-            DDF& postData
-            ) const;
+        virtual long sendPostResponse(HTTPResponse& response, const char* url, DDF& postData) const;
 
         /**
          * Bitmask of property sources to read from
@@ -233,8 +212,8 @@ namespace shibsp {
         virtual ~AbstractHandler();
 
     private:
-        std::string getPostCookieName(const Application& app, const char* relayState) const;
-        DDF getPostData(const Application& application, const HTTPRequest& request) const;
+        std::string getPostCookieName(const SPRequest& request, const char* relayState) const;
+        DDF getPostData(const SPRequest& request) const;
     };
 
 #if defined (_MSC_VER)
diff --git a/shibsp/handler/AssertionConsumerService.h b/shibsp/handler/AssertionConsumerService.h
index a3c1255b..e52759ef 100644
--- a/shibsp/handler/AssertionConsumerService.h
+++ b/shibsp/handler/AssertionConsumerService.h
@@ -30,8 +30,6 @@
 #include <shibsp/handler/AbstractHandler.h>
 #include <shibsp/handler/RemotedHandler.h>
 
-#include <boost/scoped_ptr.hpp>
-
 namespace shibsp {
 
     class SHIBSP_API Attribute;
@@ -77,27 +75,19 @@ namespace shibsp {
         /**
          * Enforce address checking requirements.
          * 
-         * @param application   reference to application receiving message
-         * @param httpRequest   client request that initiated session
+         * @param request       client request that initiated session
          * @param issuedTo      address for which security assertion was issued
          */
-        void checkAddress(const Application& application, const HTTPRequest& httpRequest, const char* issuedTo) const;
+        void checkAddress(const SPRequest& request, const char* issuedTo) const;
 
 
         /**
          * Complete the client's transition back to the expected resource.
          * 
-         * @param application   reference to application receiving message
-         * @param httpRequest   client request that included message
-         * @param httpResponse  response to client
+         * @param request       client request that included message
          * @param relayState    relay state token
          */
-        virtual std::pair<bool,long> finalizeResponse(
-            const Application& application,
-            const HTTPRequest& httpRequest,
-            HTTPResponse& httpResponse,
-            std::string& relayState
-            ) const;
+        virtual std::pair<bool,long> finalizeResponse(SPRequest& httpRequest, std::string& relayState) const;
 
 #ifndef SHIBSP_LITE
         /**
@@ -115,17 +105,13 @@ namespace shibsp {
          * modifications to the request/response objects to reflect processing
          * of the message.</p>
          * 
-         * @param application   reference to application receiving message
-         * @param httpRequest   client request that included message
-         * @param httpResponse  response to client
+         * @param request       client request that included message
          * @param policy        the SecurityPolicy in effect, after having evaluated the message
          * @param reserved      ignore this parameter
          * @param xmlObject     a protocol-specific message object
          */
         virtual void implementProtocol(
-            const Application& application,
-            const xmltooling::HTTPRequest& httpRequest,
-            xmltooling::HTTPResponse& httpResponse,
+            SPRequest& httpRequest,
             opensaml::SecurityPolicy& policy,
             const PropertySet* reserved,
             const xmltooling::XMLObject& xmlObject
@@ -149,7 +135,6 @@ namespace shibsp {
          * 
          * <p>The caller must free the returned context handle.</p>
          * 
-         * @param application           reference to application receiving message
          * @param request               request delivering message, if any
          * @param issuer                source of SSO tokens
          * @param protocol              SSO protocol used
@@ -163,8 +148,7 @@ namespace shibsp {
          * @param tokens                available assertions, if any
          */
         ResolutionContext* resolveAttributes(
-            const Application& application,
-            const xmltooling::GenericRequest* request=nullptr,
+            const SPRequest* request=nullptr,
             const opensaml::saml2md::RoleDescriptor* issuer=nullptr,
             const XMLCh* protocol=nullptr,
             const xmltooling::XMLObject* protmsg=nullptr,
@@ -176,19 +160,12 @@ namespace shibsp {
             const XMLCh* authncontext_decl=nullptr,
             const std::vector<const opensaml::Assertion*>* tokens=nullptr
             ) const;
-
-    public:
-        const XMLCh* getProtocolFamily() const;
 #endif
     private:
-        std::pair<bool,long> processMessage(
-            const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
-            ) const;
+        std::pair<bool,long> processMessage(const SPRequest& request) const;
         
         std::pair<bool,long> sendRedirect(
-            const Application& application,
-            const HTTPRequest& request,
-            HTTPResponse& response,
+            SPRequest& request,
             const char* entityID,
             const char* relayState
             ) const;                
diff --git a/shibsp/handler/Handler.h b/shibsp/handler/Handler.h
index b9a25676..1e832770 100644
--- a/shibsp/handler/Handler.h
+++ b/shibsp/handler/Handler.h
@@ -1,21 +1,15 @@
 /**
- * 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.
+ * 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
  *
- * 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
  *
- * 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.
+ * 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.
  */
 
 /**
@@ -60,12 +54,9 @@ namespace shibsp {
          * step.
          *
          * @param application   the associated Application
-         * @param request       incoming HTTP request
-         * @param response      outgoing HTTP response
+         * @param request       SP request
          */
-        virtual void cleanRelayState(
-            const Application& application, const HTTPRequest& request, HTTPResponse& response
-            ) const;
+        virtual void cleanRelayState(SPRequest& request) const;
 
         /**
          * Implements various mechanisms to preserve RelayState,
@@ -74,13 +65,10 @@ namespace shibsp {
          * <p>If a supported mechanism can be identified, the input parameter will be
          * replaced with a suitable state key.
          *
-         * @param application   the associated Application
          * @param response      outgoing HTTP response
          * @param relayState    RelayState token to supply with message
          */
-        virtual void preserveRelayState(
-            const Application& application, HTTPResponse& response, std::string& relayState
-            ) const;
+        virtual void preserveRelayState(SPRequest& response, std::string& relayState) const;
 
         /**
          * Implements various mechanisms to recover RelayState,
@@ -89,19 +77,11 @@ namespace shibsp {
          * <p>If a supported mechanism can be identified, the input parameter will be
          * replaced with the recovered state information.
          *
-         * @param application   the associated Application
-         * @param request       incoming HTTP request
-         * @param response      outgoing HTTP response
+         * @param request       SP request
          * @param relayState    RelayState token supplied with message
          * @param clear         true iff the token state should be cleared
          */
-        virtual void recoverRelayState(
-            const Application& application,
-            const HTTPRequest& request,
-            HTTPResponse& response,
-            std::string& relayState,
-            bool clear=true
-            ) const;
+        virtual void recoverRelayState(SPRequest& request, std::string& relayState, bool clear=true) const;
 
     public:
         virtual ~Handler();
diff --git a/shibsp/handler/LogoutHandler.h b/shibsp/handler/LogoutHandler.h
index 5658f62b..892f9e0d 100644
--- a/shibsp/handler/LogoutHandler.h
+++ b/shibsp/handler/LogoutHandler.h
@@ -83,45 +83,24 @@ namespace shibsp {
         /**
          * Perform front-channel logout notifications for an Application.
          *
-         * @param application   the Application to notify
          * @param request       last request from browser
-         * @param response      response to use for next notification
          * @param params        map of query string parameters to preserve across this notification
          * @return  indicator of a completed response along with the status code to return from the handler
          */
         std::pair<bool,long> notifyFrontChannel(
-            const Application& application,
-            const HTTPRequest& request,
-            HTTPResponse& response,
-            const std::map<std::string,std::string>* params=nullptr
+            SPRequest& request, const std::map<std::string,std::string>* params=nullptr
             ) const;
 
         /**
          * Perform back-channel logout notifications for an Application.
          *
-         * @param application   the Application to notify
-         * @param requestURL    requestURL that resulted in method call
+         * @param request       request resulting in method call
          * @param sessions      array of session keys being logged out
          * @param local         true iff the logout operation is local to the SP, false iff global
          * @return  true iff all notifications succeeded
          */
         bool notifyBackChannel(
-            const Application& application, const char* requestURL, const std::vector<std::string>& sessions, bool local
-            ) const;
-
-        /**
-         * Sends a response template to the user agent informing it of the results of a logout attempt.
-         *
-         * @param application   the Application to use in determining the logout template
-         * @param request       the HTTP client request to supply to the template
-         * @param response      the HTTP response to use
-         * @param type          designates the prefix of logout template name to use
-         */
-        std::pair<bool,long> sendLogoutPage(
-            const Application& application,
-            const HTTPRequest& request,
-            HTTPResponse& response,
-            const char* type
+            const SPRequest& request, const std::vector<std::string>& sessions, bool local
             ) const;
     };
 
diff --git a/shibsp/handler/impl/AbstractHandler.cpp b/shibsp/handler/impl/AbstractHandler.cpp
index 3e240319..7347b588 100644
--- a/shibsp/handler/impl/AbstractHandler.cpp
+++ b/shibsp/handler/impl/AbstractHandler.cpp
@@ -21,12 +21,12 @@
 #include "internal.h"
 
 #include "exceptions.h"
+#include "Agent.h"
 #include "AgentConfig.h"
-#include "Application.h"
-#include "ServiceProvider.h"
 #include "SPRequest.h"
 #include "handler/AbstractHandler.h"
 #include "handler/LogoutHandler.h"
+#include "remoting/RemotingService.h"
 #include "util/CGIParser.h"
 #include "util/SPConstants.h"
 #include "util/PathResolver.h"
@@ -44,6 +44,10 @@ using namespace xercesc;
 using namespace boost;
 using namespace std;
 
+#ifndef HAVE_STRCASECMP
+# define strcasecmp _stricmp
+#endif
+
 namespace shibsp {
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2ConsumerFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2LogoutFactory;
@@ -118,26 +122,17 @@ void Handler::log(Priority::Value level, const string& msg) const
     Category::getInstance(SHIBSP_LOGCAT ".Handler").log(level, msg);
 }
 
-void Handler::cleanRelayState(
-    const Application& application, const HTTPRequest& request, HTTPResponse& response
-    ) const
+void Handler::cleanRelayState(SPRequest& request) const
 {
-    pair<bool,const char*> mech = getString("relayState");
-    if (!mech.first) {
-        // Check for setting on Sessions element.
-        const PropertySet* sessionprop = application.getPropertySet("Sessions");
-        if (sessionprop) {
-            mech = sessionprop->getString("relayState");
-        }
-    }
+    const char* mech = request.getRequestSettings().first->getString("relayState");
 
     int maxRSCookies = 20,purgedRSCookies = 0;
     int maxOSCookies = 20,purgedOSCookies = 0;
 
-    if (mech.first && !strncmp(mech.second, "cookie", 6)) {
-        mech.second += 6;
-        if (*mech.second == ':' && isdigit(*(++mech.second))) {
-            maxRSCookies = maxOSCookies = atoi(mech.second);
+    if (mech && !strncmp(mech, "cookie", 6)) {
+        mech += 6;
+        if (*mech == ':' && isdigit(*(++mech))) {
+            maxRSCookies = maxOSCookies = atoi(mech);
             if (maxRSCookies == 0) {
                 maxRSCookies = maxOSCookies = 20;
             }
@@ -154,7 +149,7 @@ void Handler::cleanRelayState(
             }
             else {
                 // We're over the limit, so everything here and older gets cleaned up.
-                response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
+                request.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
                 ++purgedRSCookies;
             }
         }
@@ -165,7 +160,7 @@ void Handler::cleanRelayState(
             }
             else {
                 // We're over the limit, so everything here and older gets cleaned up.
-                response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
+                request.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
                 ++purgedOSCookies;
             }
         }
@@ -177,7 +172,7 @@ void Handler::cleanRelayState(
         log(Priority::SHIB_DEBUG, string("purged ") + lexical_cast<string>(purgedOSCookies) + " stale request correlation cookie(s) from client");
 }
 
-void Handler::preserveRelayState(const Application& application, HTTPResponse& response, string& relayState) const
+void Handler::preserveRelayState(SPRequest& request, string& relayState) const
 {
     // The empty string implies no state to deal with but we need to generate a correlation handle.
     if (relayState.empty()) {
@@ -187,17 +182,12 @@ void Handler::preserveRelayState(const Application& application, HTTPResponse& r
     }
 
     // No setting means just pass state by value.
-    pair<bool,const char*> mech = getString("relayState");
-    if (!mech.first) {
-        // Check for setting on Sessions element.
-        const PropertySet* sessionprop = application.getPropertySet("Sessions");
-        if (sessionprop)
-            mech = sessionprop->getString("relayState");
-    }
-    if (!mech.first || !mech.second || !*mech.second)
+    const char* mech = request.getRequestSettings().first->getString("relayState");
+    if (!mech || !*mech) {
         return;
+    }
 
-    if (!strncmp(mech.second, "cookie", 6)) {
+    if (!strncmp(mech, "cookie", 6)) {
         // Here we store the state in a cookie and send a fixed
         // value so we can recognize it on the way back.
         if (relayState.find("cookie:") != 0 && relayState.find("ss:") != 0) {
@@ -206,16 +196,16 @@ void Handler::preserveRelayState(const Application& application, HTTPResponse& r
             generateRandomHex(rsKey, 4);
             rsKey = lexical_cast<string>(time(nullptr)) + '_' + rsKey;
             string shib_cookie_name = "_shibstate_" + rsKey;
-            response.setCookie(shib_cookie_name.c_str(),
+            request.setCookie(shib_cookie_name.c_str(),
                 AgentConfig::getConfig().getURLEncoder().encode(relayState.c_str()).c_str(),
                     0, HTTPResponse::SAMESITE_NONE);
             relayState = "cookie:" + rsKey;
         }
     }
-    else if (!strncmp(mech.second, "ss:", 3)) {
+    else if (!strncmp(mech, "ss:", 3)) {
         if (relayState.find("cookie:") != 0 && relayState.find("ss:") != 0) {
-            mech.second+=3;
-            if (*mech.second) {
+            mech+=3;
+            if (*mech) {
                 if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
 #ifndef SHIBSP_LITE
                     StorageService* storage = application.getServiceProvider().getStorageService(mech.second);
@@ -246,13 +236,13 @@ void Handler::preserveRelayState(const Application& application, HTTPResponse& r
                 }
                 else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
                     DDF out,in = DDF("set::RelayState").structure();
-                    in.addmember("id").string(mech.second);
+                    in.addmember("id").string(mech);
                     in.addmember("value").unsafe_string(relayState.c_str());
                     DDFJanitor jin(in),jout(out);
-                    //out = application.getServiceProvider().getListenerService()->send(in);
+                    out = request.getAgent().getRemotingService()->send(in);
                     if (!out.isstring())
                         throw IOException("StorageService-backed RelayState mechanism did not return a state key.");
-                    relayState = string(mech.second-3) + ':' + out.string();
+                    relayState = string(mech-3) + ':' + out.string();
                 }
             }
         }
@@ -262,12 +252,8 @@ void Handler::preserveRelayState(const Application& application, HTTPResponse& r
     }
 }
 
-void Handler::recoverRelayState(
-    const Application& application, const HTTPRequest& request, HTTPResponse& response, string& relayState, bool clear
-    ) const
+void Handler::recoverRelayState(SPRequest& request, string& relayState, bool clear) const
 {
-    SPConfig& conf = SPConfig::getConfig();
-
     // Sentry value that signifies it was only a correlation tool.
     if (starts_with(relayState, "corr:")) {
         relayState.clear();
@@ -283,7 +269,7 @@ void Handler::recoverRelayState(
             string ssid = relayState.substr(3, key - state);
             key++;
             if (!ssid.empty() && *key) {
-                if (conf.isEnabled(SPConfig::OutOfProcess)) {
+                if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
 #ifndef SHIBSP_LITE
                     StorageService* storage = conf.getServiceProvider()->getStorageService(ssid.c_str());
                     if (storage) {
@@ -312,13 +298,13 @@ void Handler::recoverRelayState(
                     }
 #endif
                 }
-                else if (conf.isEnabled(SPConfig::InProcess)) {
+                else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
                     DDF out,in = DDF("get::RelayState").structure();
                     in.addmember("id").string(ssid.c_str());
                     in.addmember("key").string(key);
                     in.addmember("clear").integer(clear ? 1 : 0);
                     DDFJanitor jin(in),jout(out);
-                    //out = application.getServiceProvider().getListenerService()->send(in);
+                    out = request.getAgent().getRemotingService()->send(in);
                     if (!out.isstring()) {
                         log(Priority::SHIB_ERROR, "StorageService-backed RelayState mechanism did not return a state value.");
                         relayState.erase();
@@ -348,7 +334,7 @@ void Handler::recoverRelayState(
                 relayState = rscopy;
                 free(rscopy);
                 if (clear) {
-                    response.setCookie(relay_cookie.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
+                    request.setCookie(relay_cookie.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
                 }
                 request.absolutize(relayState);
                 return;
@@ -360,11 +346,7 @@ void Handler::recoverRelayState(
 
     // Check for "default" value (or the old "cookie" value that might come from stale bookmarks).
     if (relayState.empty() || relayState == "default" || relayState == "cookie") {
-        pair<bool,const char*> homeURL=application.getString("homeURL");
-        if (homeURL.first)
-            relayState = homeURL.second;
-        else
-            relayState = '/';
+        relayState = request.getRequestSettings().first->getString("homeURL", "/");
     }
 
     request.absolutize(relayState);
@@ -530,31 +512,26 @@ long AbstractHandler::sendMessage(
 
 #endif
 
-void AbstractHandler::preservePostData(
-    const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState
-    ) const
+void AbstractHandler::preservePostData(SPRequest& request, const char* relayState) const
 {
-#ifdef HAVE_STRCASECMP
-    if (strcasecmp(request.getMethod(), "POST")) return;
-#else
-    if (stricmp(request.getMethod(), "POST")) return;
-#endif
+    if (strcasecmp(request.getMethod(), "POST")) {
+        return;
+    }
 
     // No specs mean no save.
-    const PropertySet* props = application.getPropertySet("Sessions");
-    pair<bool,const char*> mech = props ? props->getString("postData") : pair<bool,const char*>(false,nullptr);
-    if (!mech.first) {
+    const char* mech = request.getRequestSettings().first->getString("postData");
+    if (!mech) {
         m_log.info("postData property not supplied, form data will not be preserved across SSO");
         return;
     }
 
-    DDF postData = getPostData(application, request);
+    DDF postData = getPostData(request);
     if (postData.isnull())
         return;
 
-    if (strstr(mech.second,"ss:") == mech.second) {
-        mech.second+=3;
-        if (!*mech.second) {
+    if (strstr(mech, "ss:") == mech) {
+        mech+=3;
+        if (!*mech) {
             postData.destroy();
             throw ConfigurationException("Unsupported postData mechanism.");
         }
@@ -585,15 +562,15 @@ void AbstractHandler::preservePostData(
         else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
             DDF out,in = DDF("set::PostData").structure();
             DDFJanitor jin(in),jout(out);
-            in.addmember("id").string(mech.second);
+            in.addmember("id").string(mech);
             in.add(postData);
-            //out = application.getServiceProvider().getListenerService()->send(in);
+            out = request.getAgent().getRemotingService()->send(in);
             if (!out.isstring())
                 throw IOException("StorageService-backed PostData mechanism did not return a state key.");
-            postkey = string(mech.second-3) + ':' + out.string();
+            postkey = string(mech-3) + ':' + out.string();
         }
 
-        string shib_cookie = getPostCookieName(application, relayState);
+        string shib_cookie = getPostCookieName(request, relayState);
 
         // Purge any cookies in excess of 25.
         int maxCookies = 20,purgedCookies = 0;
@@ -609,7 +586,7 @@ void AbstractHandler::preservePostData(
                 }
                 else {
                     // We're over the limit, so everything here and older gets cleaned up.
-                    response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
+                    request.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
                     ++purgedCookies;
                 }
             }
@@ -619,7 +596,7 @@ void AbstractHandler::preservePostData(
             log(Priority::SHIB_DEBUG, string("purged ") + lexical_cast<string>(purgedCookies) + " stale POST preservation cookie(s) from client");
 
         // Set a cookie with key info.
-        response.setCookie(shib_cookie.c_str(), postkey.c_str(), 0, HTTPResponse::SAMESITE_NONE);
+        request.setCookie(shib_cookie.c_str(), postkey.c_str(), 0, HTTPResponse::SAMESITE_NONE);
     }
     else {
         postData.destroy();
@@ -627,11 +604,9 @@ void AbstractHandler::preservePostData(
     }
 }
 
-DDF AbstractHandler::recoverPostData(
-    const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState
-    ) const
+DDF AbstractHandler::recoverPostData(SPRequest& request, const char* relayState) const
 {
-    string shib_cookie = getPostCookieName(application, relayState);
+    string shib_cookie = getPostCookieName(request, relayState);
 
     // First we need the post recovery cookie.
     const char* cookie = request.getCookie(shib_cookie.c_str());
@@ -639,7 +614,7 @@ DDF AbstractHandler::recoverPostData(
         return DDF();
 
     // Clear the cookie.
-    response.setCookie(shib_cookie.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
+    request.setCookie(shib_cookie.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
 
     // Look for StorageService-backed state of the form "ss:SSID:key".
     const char* state = cookie;
@@ -676,10 +651,10 @@ DDF AbstractHandler::recoverPostData(
                     DDFJanitor jin(in);
                     in.addmember("id").string(ssid.c_str());
                     in.addmember("key").string(key);
-                    //DDF out = application.getServiceProvider().getListenerService()->send(in);
-                    //if (out.islist())
-                    //    return out;
-                    //out.destroy();
+                    DDF out = request.getAgent().getRemotingService()->send(in);
+                    if (out.islist())
+                        return out;
+                    out.destroy();
                     m_log.error("storageService-backed PostData mechanism did not return preserved data.");
                 }
             }
@@ -688,9 +663,7 @@ DDF AbstractHandler::recoverPostData(
     return DDF();
 }
 
-long AbstractHandler::sendPostResponse(
-    const Application& application, HTTPResponse& httpResponse, const char* url, DDF& postData
-    ) const
+long AbstractHandler::sendPostResponse(HTTPResponse& httpResponse, const char* url, DDF& postData) const
 {
     HTTPResponse::sanitizeURL(url);
 
@@ -733,7 +706,7 @@ long AbstractHandler::sendPostResponse(
    return 0;
 }
 
-string AbstractHandler::getPostCookieName(const Application& app, const char* relayState) const
+string AbstractHandler::getPostCookieName(const SPRequest& request, const char* relayState) const
 {
     // Decorates the name of the cookie with the relay state key, if any.
     // Doing so gives a better assurance that the recovered data really
@@ -746,18 +719,15 @@ string AbstractHandler::getPostCookieName(const Application& app, const char* re
         if (pch)
             return string("_shibpost_") + (pch + 1);
     }
-    return app.getCookieName("_shibpost_");
+    return request.getCookieName("_shibpost_");
 }
 
-DDF AbstractHandler::getPostData(const Application& application, const HTTPRequest& request) const
+DDF AbstractHandler::getPostData(const SPRequest& request) const
 {
     string contentType = request.getContentType();
     if (contentType.find("application/x-www-form-urlencoded") != string::npos) {
-        const PropertySet* props = application.getPropertySet("Sessions");
-        pair<bool,unsigned int> plimit = props ? props->getUnsignedInt("postLimit") : pair<bool,unsigned int>(false,0);
-        if (!plimit.first)
-            plimit.second = 1024 * 1024;
-        if (plimit.second == 0 || request.getContentLength() <= plimit.second) {
+        unsigned int plimit = request.getRequestSettings().first->getUnsignedInt("postLimit", 1024 * 1024);
+        if (plimit == 0 || request.getContentLength() <= plimit) {
             CGIParser cgi(request);
             pair<CGIParser::walker,CGIParser::walker> params = cgi.getParameters(nullptr);
             if (params.first == params.second)
diff --git a/shibsp/handler/impl/AdminLogoutInitiator.cpp b/shibsp/handler/impl/AdminLogoutInitiator.cpp
index dd977e47..adfa4066 100644
--- a/shibsp/handler/impl/AdminLogoutInitiator.cpp
+++ b/shibsp/handler/impl/AdminLogoutInitiator.cpp
@@ -26,7 +26,8 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "Application.h"
+#include "Agent.h"
+#include "AgentConfig.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
 #include "handler/SecuredHandler.h"
@@ -59,7 +60,7 @@ namespace shibsp {
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
 
     private:
-        pair<bool,long> doRequest(const Application& application, const HTTPRequest& request, HTTPResponse& httpResponse) const;
+        pair<bool,long> doRequest(SPRequest& request) const;
 
         string m_appId;
 #ifndef SHIBSP_LITE
@@ -126,7 +127,7 @@ pair<bool,long> AdminLogoutInitiator::run(SPRequest& request, bool isHandler) co
 
     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
         // When out of process, we run natively.
-        return doRequest(request.getApplication(), request, request);
+        return doRequest(request);
     }
     else {
         // When not out of process, we remote the request.
@@ -169,19 +170,19 @@ void AdminLogoutInitiator::receive(DDF& in, ostream& out)
 #endif
 }
 
-pair<bool,long> AdminLogoutInitiator::doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const
+pair<bool,long> AdminLogoutInitiator::doRequest(SPRequest& request) const
 {
-    const char* sessionId = httpRequest.getParameter("session");
+    const char* sessionId = request.getParameter("session");
     if (!sessionId || !*sessionId) {
         // Something's horribly wrong.
         m_log.error("no session parameter supplied for request");
         istringstream msg("NO SESSION PARAMETER");
-        return make_pair(true, httpResponse.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_BADREQUEST));
+        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_BADREQUEST));
     }
 
     Session* session = nullptr;
     try {
-        session = application.getServiceProvider().getSessionCache()->find(application, sessionId);
+        session = AgentConfig::getConfig().getAgent().getSessionCache()->find(request, sessionId);
     }
     catch (const std::exception& ex) {
         m_log.error("error accessing designated session: %s", ex.what());
@@ -189,9 +190,10 @@ pair<bool,long> AdminLogoutInitiator::doRequest(const Application& application,
 
     // With no session, we return a 404 after "revoking" the session just to be safe.
     if (!session) {
-        application.getServiceProvider().getSessionCache()->remove(application, sessionId);
+        AgentConfig::getConfig().getAgent().getSessionCache()->remove(
+            request.getRequestSettings().first->getString("sessionBucket", "default"), sessionId);
         istringstream msg("NOT FOUND");
-        return make_pair(true, httpResponse.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_NOTFOUND));
+        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_NOTFOUND));
     }
 
     time_t revocationExp = session->getExpiration();
@@ -200,38 +202,26 @@ pair<bool,long> AdminLogoutInitiator::doRequest(const Application& application,
 
     bool doSAML = false;
 
-#ifndef SHIBSP_LITE
-    if (XMLString::equals(session->getProtocol(), m_protocol.get())) {
-        if (!session->getEntityID() || !session->getNameID()) {
-            m_log.info("skipping SAML 2.0 logout attempt, no NameID or issuing entityID found in session");
-        }
-        else {
-            doSAML = true;
-        }
-    }
-    else {
-        m_log.info("skipping global logout for non-SAML2 session");
-    }
-#endif
-
     // Do back channel notification.
     vector<string> sessions(1, session->getID());
-    if (!notifyBackChannel(application, httpRequest.getRequestURL(), sessions, true)) {
+    if (!notifyBackChannel(request, sessions, true)) {
         sessionLocker.unlock();
         session = nullptr;
-        application.getServiceProvider().getSessionCache()->remove(application, sessionId, revocationExp);
+        AgentConfig::getConfig().getAgent().getSessionCache()->remove(
+            request.getRequestSettings().first->getString("sessionBucket", "default"), sessionId, revocationExp);
         
         istringstream msg("PARTIAL");
-        return make_pair(true, httpResponse.sendResponse(msg, 206)); // misuse of an HTTP code, but whatever
+        return make_pair(true, request.sendResponse(msg, 206)); // misuse of an HTTP code, but whatever
     }
 
     if (!doSAML) {
         sessionLocker.unlock();
         session = nullptr;
-        application.getServiceProvider().getSessionCache()->remove(application, sessionId, revocationExp);
+        AgentConfig::getConfig().getAgent().getSessionCache()->remove(
+            request.getRequestSettings().first->getString("sessionBucket", "default"), sessionId, revocationExp);
 
         istringstream msg("OK");
-        return make_pair(true, httpResponse.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_OK));
+        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_OK));
     }
 
 #ifndef SHIBSP_LITE
diff --git a/shibsp/handler/impl/AssertionConsumerService.cpp b/shibsp/handler/impl/AssertionConsumerService.cpp
index c45e6249..720a8d36 100644
--- a/shibsp/handler/impl/AssertionConsumerService.cpp
+++ b/shibsp/handler/impl/AssertionConsumerService.cpp
@@ -26,17 +26,13 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "Application.h"
 #include "ServiceProvider.h"
 #include "SPRequest.h"
 #include "handler/AssertionConsumerService.h"
 #include "util/CGIParser.h"
 #include "util/SPConstants.h"
 
-# include <ctime>
-
-#include <xmltooling/XMLToolingConfig.h>
-#include <xmltooling/util/URLEncoder.h>
+#include <ctime>
 
 using namespace shibspconstants;
 using namespace shibsp;
@@ -77,13 +73,13 @@ pair<bool,long> AssertionConsumerService::run(SPRequest& request, bool isHandler
             param = cgi.getParameters("target");
             if (param.first != param.second && param.first->second)
                 target = param.first->second;
-            return finalizeResponse(request.getApplication(), request, request, target);
+            return finalizeResponse(request, target);
         }
     }
 
     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
         // When out of process, we run natively and directly process the message.
-        return processMessage(request.getApplication(), request, request);
+        return processMessage(request);
     }
     else {
         // When not out of process, we remote all the message processing.
@@ -99,6 +95,8 @@ pair<bool,long> AssertionConsumerService::run(SPRequest& request, bool isHandler
 
 void AssertionConsumerService::receive(DDF& in, ostream& out)
 {
+    /*
+
     // Find application.
     const char* aid = in["application_id"].string();
     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
@@ -121,11 +119,11 @@ void AssertionConsumerService::receive(DDF& in, ostream& out)
     // which we capture in the facade and send back.
     processMessage(*app, *req, *resp);
     out << ret;
+
+    */
 }
 
-pair<bool,long> AssertionConsumerService::processMessage(
-    const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
-    ) const
+pair<bool,long> AssertionConsumerService::processMessage(const SPRequest& httpRequest) const
 {
 #ifndef SHIBSP_LITE
     // Locate policy key.
@@ -238,45 +236,38 @@ pair<bool,long> AssertionConsumerService::processMessage(
 #endif
 }
 
-pair<bool,long> AssertionConsumerService::finalizeResponse(
-    const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse, string& relayState
-    ) const
+pair<bool,long> AssertionConsumerService::finalizeResponse(SPRequest& request, string& relayState) const
 {
-    DDF postData = recoverPostData(application, httpRequest, httpResponse, relayState.c_str());
+    DDF postData = recoverPostData(request, relayState.c_str());
     DDFJanitor postjan(postData);
-    recoverRelayState(application, httpRequest, httpResponse, relayState);
-    application.limitRedirect(httpRequest, relayState.c_str());
+    recoverRelayState(request, relayState);
+    request.limitRedirect(relayState.c_str());
 
     // Now redirect to the state value. By now, it should be set to *something* usable.
     // First check for POST data.
     if (!postData.islist()) {
         m_log.debug("ACS returning via redirect to: %s", relayState.c_str());
-        return make_pair(true, httpResponse.sendRedirect(relayState.c_str()));
+        return make_pair(true, request.sendRedirect(relayState.c_str()));
     }
     else {
         m_log.debug("ACS returning via POST to: %s", relayState.c_str());
-        return make_pair(true, sendPostResponse(application, httpResponse, relayState.c_str(), postData));
+        return make_pair(true, sendPostResponse(request, relayState.c_str(), postData));
     }
 }
 
-void AssertionConsumerService::checkAddress(const Application& application, const HTTPRequest& httpRequest, const char* issuedTo) const
+void AssertionConsumerService::checkAddress(const SPRequest& request, const char* issuedTo) const
 {
     if (!issuedTo || !*issuedTo)
         return;
 
-    const PropertySet* props = application.getPropertySet("Sessions");
-    pair<bool,bool> checkAddress = props ? props->getBool("checkAddress") : make_pair(false,true);
-    if (!checkAddress.first)
-        checkAddress.second = true;
-
-    if (checkAddress.second) {
+    if (request.getRequestSettings().first->getBool("checkAddress", true)) {
         m_log.debug("checking client address");
-        if (httpRequest.getRemoteAddr() != issuedTo) {
-            throw XMLToolingException(
-               "Your client's current address ($client_addr) differs from the one used when you authenticated "
-                "to your identity provider. To correct this problem, you may need to bypass a proxy server. "
-                "Please contact your local support staff or help desk for assistance.",
-                namedparams(1, "client_addr", httpRequest.getRemoteAddr().c_str())
+        if (request.getRemoteAddr() != issuedTo) {
+            throw agent_exception(
+               string("Your client's current address (") + request.getRemoteAddr() +
+                ") differs from the one used when you authenticated to your home organization. "
+                "To correct this problem, you may need to bypass a proxy server. "
+                "Please contact your local support staff or help desk for assistance."
                 );
         }
     }
@@ -284,16 +275,6 @@ void AssertionConsumerService::checkAddress(const Application& application, cons
 
 #ifndef SHIBSP_LITE
 
-const char* AssertionConsumerService::getProfile() const
-{
-    return nullptr;
-}
-
-const XMLCh* AssertionConsumerService::getProtocolFamily() const
-{
-    return m_decoder ? m_decoder->getProtocolFamily() : nullptr;
-}
-
 namespace {
     class SHIBSP_DLLLOCAL DummyContext : public ResolutionContext
     {
diff --git a/shibsp/handler/impl/AssertionLookup.cpp b/shibsp/handler/impl/AssertionLookup.cpp
index 7fd58ef0..888377dd 100644
--- a/shibsp/handler/impl/AssertionLookup.cpp
+++ b/shibsp/handler/impl/AssertionLookup.cpp
@@ -26,7 +26,6 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "Application.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
 #include "SPRequest.h"
@@ -64,7 +63,7 @@ namespace shibsp {
         }
 
     private:
-        pair<bool,long> processMessage(const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
+        pair<bool,long> processMessage(SPRequest& request) const;
     };
 
 #if defined (_MSC_VER)
@@ -101,7 +100,7 @@ pair<bool,long> AssertionLookup::run(SPRequest& request, bool isHandler) const
     try {
         if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
             // When out of process, we run natively and directly process the message.
-            return processMessage(request.getApplication(), request, request);
+            return processMessage(request);
         }
         else {
             // When not out of process, we remote all the message processing.
@@ -121,6 +120,7 @@ pair<bool,long> AssertionLookup::run(SPRequest& request, bool isHandler) const
 
 void AssertionLookup::receive(DDF& in, ostream& out)
 {
+    /*
     // Find application.
     const char* aid = in["application_id"].string();
     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
@@ -144,9 +144,10 @@ void AssertionLookup::receive(DDF& in, ostream& out)
     // which we capture in the facade and send back.
     processMessage(*app, *req, *resp);
     out << ret;
+    */
 }
 
-pair<bool,long> AssertionLookup::processMessage(const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse) const
+pair<bool,long> AssertionLookup::processMessage(SPRequest& request) const
 {
 #ifndef SHIBSP_LITE
     const char* key = httpRequest.getParameter("key");
diff --git a/shibsp/handler/impl/AttributeCheckerHandler.cpp b/shibsp/handler/impl/AttributeCheckerHandler.cpp
index c1e5d5f0..d66d7070 100644
--- a/shibsp/handler/impl/AttributeCheckerHandler.cpp
+++ b/shibsp/handler/impl/AttributeCheckerHandler.cpp
@@ -26,8 +26,8 @@
 
 #include "internal.h"
 #include "AccessControl.h"
+#include "Agent.h"
 #include "AgentConfig.h"
-#include "Application.h"
 #include "exceptions.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
@@ -79,7 +79,7 @@ namespace shibsp {
     private:
         void flushSession(SPRequest& request, time_t exp) const {
             try {
-                request.getApplication().getServiceProvider().getSessionCache()->remove(request.getApplication(), request, &request, exp);
+                request.getAgent().getSessionCache()->remove(request, exp);
             }
             catch (const std::exception&) {
             }
@@ -137,14 +137,15 @@ pair<bool,long> AttributeCheckerHandler::run(SPRequest& request, bool isHandler)
     // If the checking passes, we route to the return URL, target URL, or homeURL in that order.
     const char* returnURL = request.getParameter("return");
     const char* target = request.getParameter("target");
-    if (!returnURL)
+    if (!returnURL) {
         returnURL = target;
-    if (returnURL)
-        request.getApplication().limitRedirect(request, returnURL);
-    else
-        returnURL = request.getApplication().getString("homeURL").second;
-    if (!returnURL)
-        returnURL = "/";
+    }
+    if (returnURL) {
+        request.limitRedirect(returnURL);
+    }
+    else {
+        returnURL = request.getRequestSettings().first->getString("homeURL", "/");
+    }
        
     Session* session = nullptr;
     try {
@@ -188,15 +189,14 @@ pair<bool,long> AttributeCheckerHandler::run(SPRequest& request, bool isHandler)
 
     ifstream infile(m_template.c_str());
     if (infile) {
-        const PropertySet* props = request.getApplication().getPropertySet("Errors");
-        //TemplateParameters tp(nullptr, props, session);
-
+        /*
         // If the externalParameters option isn't set, don't populate the request field.
         pair<bool,bool> externalParameters =
                 props ? props->getBool("externalParameters") : pair<bool,bool>(false,false);
         if (externalParameters.first && externalParameters.second) {
             //tp.m_request = &request;
         }
+        */
 
         stringstream str;
         //XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
diff --git a/shibsp/handler/impl/DiscoveryFeed.cpp b/shibsp/handler/impl/DiscoveryFeed.cpp
index 72934ee6..45c4c466 100644
--- a/shibsp/handler/impl/DiscoveryFeed.cpp
+++ b/shibsp/handler/impl/DiscoveryFeed.cpp
@@ -26,7 +26,6 @@
 
 #include "internal.h"
 #include "AgentConfig.h"
-#include "Application.h"
 #include "exceptions.h"
 #include "ServiceProvider.h"
 #include "SPRequest.h"
@@ -41,7 +40,6 @@
 #include <xmltooling/util/Threads.h>
 
 using namespace shibsp;
-using namespace xmltooling;
 using namespace std;
 
 namespace shibsp {
@@ -71,8 +69,8 @@ namespace shibsp {
         void receive(DDF& in, ostream& out);
 
     private:
-        void feedToFile(const Application& application, string& cacheTag) const;
-        void feedToStream(const Application& application, string& cacheTag, ostream& os) const;
+        void feedToFile(string& cacheTag) const;
+        void feedToStream(string& cacheTag, ostream& os) const;
 
         string m_dir;
         bool m_cacheToClient;
@@ -154,7 +152,7 @@ pair<bool,long> DiscoveryFeed::run(SPRequest& request, bool isHandler) const
             if (m_dir.empty()) {
                 // The feed is directly returned.
                 stringstream buf;
-                feedToStream(request.getApplication(), s, buf);
+                feedToStream(s, buf);
                 if (!s.empty()) {
                     if (m_cacheToClient) {
                         string etag = '"' + s + '"';
@@ -166,13 +164,12 @@ pair<bool,long> DiscoveryFeed::run(SPRequest& request, bool isHandler) const
             }
             else {
                 // Indirect the feed through a file.
-                feedToFile(request.getApplication(), s);
+                feedToFile(s);
             }
         }
         else {
             // When not out of process, we remote all the message processing.
             DDF out,in = DDF(m_address.c_str());
-            in.addmember("application_id").string(request.getApplication().getId());
             if (!s.empty())
                 in.addmember("cache_tag").string(s.c_str());
             DDFJanitor jin(in), jout(out);
@@ -204,7 +201,8 @@ pair<bool,long> DiscoveryFeed::run(SPRequest& request, bool isHandler) const
             return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_NOTMODIFIED));
         }
 
-        string fname = m_dir + '/' + request.getApplication().getHash() + '_' + s + ".json";
+        // TODO: uniqueify name
+        string fname = m_dir + '/' + /* request.getApplication().getHash() + '_' + */ s + ".json";
         ifstream feed(fname.c_str());
         if (!feed)
             throw ConfigurationException("Unable to access cached feed.");
@@ -224,15 +222,6 @@ pair<bool,long> DiscoveryFeed::run(SPRequest& request, bool isHandler) const
 
 void DiscoveryFeed::receive(DDF& in, ostream& out)
 {
-    // Find application.
-    const char* aid = in["application_id"].string();
-    const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
-    if (!app) {
-        // Something's horribly wrong.
-        m_log.error("couldn't find application (%s) for discovery feed request", aid ? aid : "(missing)");
-        throw ConfigurationException("Unable to locate application for discovery feed request, deleted?");
-    }
-
     string cacheTag;
     if (in["cache_tag"].string())
         cacheTag = in["cache_tag"].string();
@@ -242,14 +231,14 @@ void DiscoveryFeed::receive(DDF& in, ostream& out)
 
     if (!m_dir.empty()) {
         // We're relaying the feed through a file.
-        feedToFile(*app, cacheTag);
+        feedToFile(cacheTag);
         if (!cacheTag.empty())
             ret.string(cacheTag.c_str());
     }
     else {
         // We're relaying the feed directly.
         ostringstream os;
-        feedToStream(*app, cacheTag, os);
+        feedToStream(cacheTag, os);
         if (!cacheTag.empty())
             ret.addmember("cache_tag").string(cacheTag.c_str());
         string feed = os.str();
@@ -259,7 +248,7 @@ void DiscoveryFeed::receive(DDF& in, ostream& out)
     out << ret;
 }
 
-void DiscoveryFeed::feedToFile(const Application& application, string& cacheTag) const
+void DiscoveryFeed::feedToFile(string& cacheTag) const
 {
 #ifndef SHIBSP_LITE
     m_log.debug("processing discovery feed request");
@@ -313,7 +302,7 @@ void DiscoveryFeed::feedToFile(const Application& application, string& cacheTag)
 #endif
 }
 
-void DiscoveryFeed::feedToStream(const Application& application, string& cacheTag, ostream& os) const
+void DiscoveryFeed::feedToStream(string& cacheTag, ostream& os) const
 {
 #ifndef SHIBSP_LITE
     m_log.debug("processing discovery feed request");
diff --git a/shibsp/handler/impl/LocalLogoutInitiator.cpp b/shibsp/handler/impl/LocalLogoutInitiator.cpp
index 973de0a1..7ea1d017 100644
--- a/shibsp/handler/impl/LocalLogoutInitiator.cpp
+++ b/shibsp/handler/impl/LocalLogoutInitiator.cpp
@@ -26,7 +26,7 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "Application.h"
+#include "Agent.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
 #include "SPRequest.h"
@@ -35,7 +35,6 @@
 #include <mutex>
 
 using namespace shibsp;
-using namespace xmltooling;
 using namespace std;
 
 namespace shibsp {
@@ -56,9 +55,7 @@ namespace shibsp {
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
 
     private:
-        pair<bool,long> doRequest(
-            const Application& application, const HTTPRequest& request, HTTPResponse& httpResponse, Session* session
-            ) const;
+        pair<bool,long> doRequest(SPRequest& request, Session* session) const;
 
         string m_appId;
     };
@@ -112,7 +109,7 @@ pair<bool,long> LocalLogoutInitiator::run(SPRequest& request, bool isHandler) co
         catch (const std::exception& ex) {
             m_log.error("error accessing current session: %s", ex.what());
         }
-        return doRequest(request.getApplication(), request, request, session);
+        return doRequest(request, session);
     }
     else {
         // When not out of process, we remote the request.
@@ -166,9 +163,7 @@ void LocalLogoutInitiator::receive(DDF& in, ostream& out)
 #endif
 }
 
-pair<bool,long> LocalLogoutInitiator::doRequest(
-    const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse, Session* session
-    ) const
+pair<bool,long> LocalLogoutInitiator::doRequest(SPRequest& request, Session* session) const
 {
     if (session) {
         // Guard the session in case of exception.
@@ -177,25 +172,27 @@ pair<bool,long> LocalLogoutInitiator::doRequest(
         // Do back channel notification.
         bool result;
         vector<string> sessions(1, session->getID());
-        result = notifyBackChannel(application, httpRequest.getRequestURL(), sessions, true);
+        result = notifyBackChannel(request, sessions, true);
         time_t revocationExp = session->getExpiration();
         locker.unlock();    // unlock the session
-        application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
-        if (!result)
-            return sendLogoutPage(application, httpRequest, httpResponse, "partial");
+        request.getAgent().getSessionCache()->remove(request, revocationExp);
+        if (!result) {
+            //return sendLogoutPage(request, "partial");
+        }
     }
 
     // Route back to return location specified, or use the local template.
-    const char* dest = httpRequest.getParameter("return");
+    const char* dest = request.getParameter("return");
     if (dest) {
         // Relative URLs get promoted, absolutes get validated.
         if (*dest == '/') {
             string d(dest);
-            httpRequest.absolutize(d);
-            return make_pair(true, httpResponse.sendRedirect(d.c_str()));
+            request.absolutize(d);
+            return make_pair(true, request.sendRedirect(d.c_str()));
         }
-        application.limitRedirect(httpRequest, dest);
-        return make_pair(true, httpResponse.sendRedirect(dest));
+        request.limitRedirect(dest);
+        return make_pair(true, request.sendRedirect(dest));
     }
-    return sendLogoutPage(application, httpRequest, httpResponse, "local");
+
+    //return sendLogoutPage(application, httpRequest, httpResponse, "local");
 }
diff --git a/shibsp/handler/impl/LogoutHandler.cpp b/shibsp/handler/impl/LogoutHandler.cpp
index b65b4b40..6c5bf989 100644
--- a/shibsp/handler/impl/LogoutHandler.cpp
+++ b/shibsp/handler/impl/LogoutHandler.cpp
@@ -27,7 +27,6 @@
 #include "internal.h"
 #include "exceptions.h"
 #include "AgentConfig.h"
-#include "Application.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
 #include "SPRequest.h"
@@ -39,7 +38,6 @@
 #include <boost/lexical_cast.hpp>
 
 using namespace shibsp;
-using namespace boost;
 using namespace std;
 
 LogoutHandler::LogoutHandler() : m_initiator(true)
@@ -50,41 +48,6 @@ LogoutHandler::~LogoutHandler()
 {
 }
 
-pair<bool,long> LogoutHandler::sendLogoutPage(
-    const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* type
-    ) const
-{
-    string tname = string(type) + "Logout";
-    const PropertySet* props = application.getPropertySet("Errors");
-
-    pair<bool,const char*> prop = props ? props->getString(tname.c_str()) : pair<bool,const char*>(false,nullptr);
-    if (!prop.first) {
-        tname += ".html";
-        prop.second = tname.c_str();
-    }
-    response.setContentType("text/html");
-    response.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT");
-    response.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0");
-    string fname(prop.second);
-    ifstream infile(AgentConfig::getConfig().getPathResolver().resolve(fname, PathResolver::SHIBSP_CFG_FILE).c_str());
-    if (!infile)
-        throw ConfigurationException("Unable to access HTML template.");
-    //TemplateParameters tp;
-
-    // If the externalParameters option isn't set, don't populate the request field.
-    pair<bool,bool> externalParameters =
-            props ? props->getBool("externalParameters") : pair<bool,bool>(false,false);
-    if (externalParameters.first && externalParameters.second) {
-        //tp.m_request = &request;
-    }
-
-    //tp.setPropertySet(props);
-    //tp.m_map["logoutStatus"] = "Logout completed successfully.";  // Backward compatibility.
-    stringstream str;
-    //XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
-    return make_pair(true,response.sendResponse(str));
-}
-
 pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
 {
     // If we're inside a chain, do nothing.
@@ -96,7 +59,7 @@ pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
         return make_pair(false,0L);
 
     // Try another front-channel notification. No extra parameters and the session is implicit.
-    return notifyFrontChannel(request.getApplication(), request, request);
+    return notifyFrontChannel(request);
 }
 
 void LogoutHandler::receive(DDF& in, ostream& out)
@@ -106,32 +69,21 @@ void LogoutHandler::receive(DDF& in, ostream& out)
     if (in["notify"].integer() != 1)
         throw RemotintgException("Unsupported operation.");
 
-    // Find application.
-    const char* aid=in["application_id"].string();
-    const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
-    if (!app) {
-        // Something's horribly wrong.
-        Category::getInstance(SHIBSP_LOGCAT ".Logout").error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
-        throw ConfigurationException("Unable to locate application for logout, deleted?");
-    }
-
     vector<string> sessions;
     DDF s = in["sessions"];
     DDF temp = s.first();
     while (temp.isstring()) {
         sessions.push_back(temp.string());
         temp = s.next();
-        if (notifyBackChannel(*app, in["url"].string(), sessions, in["local"].integer()==1))
-            ret.integer(1);
+        //if (notifyBackChannel(*app, in["url"].string(), sessions, in["local"].integer()==1))
+            //ret.integer(1);
     }
 
     out << ret;
 }
 
 pair<bool,long> LogoutHandler::notifyFrontChannel(
-    const Application& application,
-    const HTTPRequest& request,
-    HTTPResponse& response,
+    SPRequest& request,
     const map<string,string>* params
     ) const
 {
@@ -145,7 +97,7 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(
     param = request.getParameter("return");
 
     // Fetch the next front notification URL and bump the index for the next round trip.
-    string loc = application.getNotificationURL(request.getRequestURL(), true, index++);
+    string loc = request.getNotificationURL(true, index++);
     if (loc.empty())
         return make_pair(false,0L);
 
@@ -160,7 +112,7 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(
     string locstr(start, end ? end - start : strlen(start));
 
     // Add a signal that we're coming back from notification and the next index.
-    locstr = locstr + "?notifying=1&index=" + lexical_cast<string>(index);
+    locstr = locstr + "?notifying=1&index=" + boost::lexical_cast<string>(index);
 
     // Add return if set.
     if (param)
@@ -182,7 +134,7 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(
     // Add the notifier's return parameter to the destination location and redirect.
     // This is NOT the same as the return parameter that might be embedded inside it ;-)
     loc = loc + "&return=" + encoder.encode(locstr.c_str());
-    return make_pair(true, response.sendRedirect(loc.c_str()));
+    return make_pair(true, request.sendRedirect(loc.c_str()));
 }
 
 #ifndef SHIBSP_LITE
@@ -217,9 +169,7 @@ namespace {
 };
 #endif
 
-bool LogoutHandler::notifyBackChannel(
-    const Application& application, const char* requestURL, const vector<string>& sessions, bool local
-    ) const
+bool LogoutHandler::notifyBackChannel(const SPRequest& request, const vector<string>& sessions, bool local) const
 {
     if (sessions.empty()) {
         Category::getInstance(SHIBSP_LOGCAT ".Logout").error("no sessions supplied to back channel notification method");
@@ -227,7 +177,7 @@ bool LogoutHandler::notifyBackChannel(
     }
 
     unsigned int index = 0;
-    string endpoint = application.getNotificationURL(requestURL, false, index++);
+    string endpoint = request.getNotificationURL(false, index++);
     if (endpoint.empty())
         return true;
 
@@ -267,11 +217,12 @@ bool LogoutHandler::notifyBackChannel(
     }
 
     // When not out of process, we remote the back channel work.
+    // TODO: remove anyway....
     DDF out,in(m_address.c_str());
     DDFJanitor jin(in), jout(out);
     in.addmember("notify").integer(1);
-    in.addmember("application_id").string(application.getId());
-    in.addmember("url").string(requestURL);
+    //in.addmember("application_id").string(application.getId());
+    in.addmember("url").string(request.getRequestURL());
     if (local)
         in.addmember("local").integer(1);
     DDF s = in.addmember("sessions").list();
diff --git a/shibsp/handler/impl/MetadataGenerator.cpp b/shibsp/handler/impl/MetadataGenerator.cpp
index 2687f5fd..9d493552 100644
--- a/shibsp/handler/impl/MetadataGenerator.cpp
+++ b/shibsp/handler/impl/MetadataGenerator.cpp
@@ -25,7 +25,6 @@
  */
 
 #include "internal.h"
-#include "Application.h"
 #include "exceptions.h"
 #include "ServiceProvider.h"
 #include "SPRequest.h"
@@ -58,7 +57,6 @@ namespace shibsp {
 
     private:
         pair<bool,long> processMessage(
-            const Application& application,
             const char* handlerURL,
             const char* entityID,
             HTTPResponse& httpResponse
@@ -348,12 +346,11 @@ pair<bool,long> MetadataGenerator::run(SPRequest& request, bool isHandler) const
     try {
         if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
             // When out of process, we run natively and directly process the message.
-            return processMessage(request.getApplication(), request.getHandlerURL(), request.getParameter("entityID"), request);
+            return processMessage(request.getHandlerURL(), request.getParameter("entityID"), request);
         }
         else {
             // When not out of process, we remote all the message processing.
             DDF out,in = DDF(m_address.c_str());
-            in.addmember("application_id").string(request.getApplication().getId());
             in.addmember("handler_url").string(request.getHandlerURL());
             if (request.getParameter("entityID"))
                 in.addmember("entity_id").string(request.getParameter("entityID"));
@@ -372,16 +369,9 @@ pair<bool,long> MetadataGenerator::run(SPRequest& request, bool isHandler) const
 
 void MetadataGenerator::receive(DDF& in, ostream& out)
 {
-    // Find application.
-    const char* aid = in["application_id"].string();
+    /*
     const char* hurl = in["handler_url"].string();
-    const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
-    if (!app) {
-        // Something's horribly wrong.
-        m_log.error("couldn't find application (%s) for metadata request", aid ? aid : "(missing)");
-        throw ConfigurationException("Unable to locate application for metadata request, deleted?");
-    }
-    else if (!hurl) {
+    if (!hurl) {
         throw ConfigurationException("Missing handler_url parameter in remoted method call.");
     }
 
@@ -393,13 +383,12 @@ void MetadataGenerator::receive(DDF& in, ostream& out)
     // Since we're remoted, the result should either be a throw, a false/0 return,
     // which we just return as an empty structure, or a response/redirect,
     // which we capture in the facade and send back.
-    processMessage(*app, hurl, in["entity_id"].string(), *resp);
+    processMessage(hurl, in["entity_id"].string(), *resp);
     out << ret;
+    */
 }
 
-pair<bool,long> MetadataGenerator::processMessage(
-    const Application& application, const char* handlerURL, const char* entityID, HTTPResponse& httpResponse
-    ) const
+pair<bool,long> MetadataGenerator::processMessage(const char* handlerURL, const char* entityID, HTTPResponse& httpResponse) const
 {
 #ifndef SHIBSP_LITE
     m_log.debug("processing metadata request");
diff --git a/shibsp/handler/impl/RemotedHandler.cpp b/shibsp/handler/impl/RemotedHandler.cpp
index cc129217..7327e7df 100644
--- a/shibsp/handler/impl/RemotedHandler.cpp
+++ b/shibsp/handler/impl/RemotedHandler.cpp
@@ -20,7 +20,6 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "Application.h"
 #include "ServiceProvider.h"
 #include "SPRequest.h"
 #include "handler/RemotedHandler.h"
@@ -314,7 +313,6 @@ DDF RemotedHandler::send(const SPRequest& request, DDF& in) const
 DDF RemotedHandler::wrap(const SPRequest& request, const vector<string>* headers, bool certs) const
 {
     DDF in = DDF(m_address.c_str()).structure();
-    in.addmember("application_id").string(request.getApplication().getId());
     in.addmember("scheme").string(request.getScheme());
     in.addmember("hostname").unsafe_string(request.getHostname());
     in.addmember("port").integer(request.getPort());
diff --git a/shibsp/handler/impl/SAML2Logout.cpp b/shibsp/handler/impl/SAML2Logout.cpp
index 3d6d058e..59f54b7d 100644
--- a/shibsp/handler/impl/SAML2Logout.cpp
+++ b/shibsp/handler/impl/SAML2Logout.cpp
@@ -26,7 +26,6 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "Application.h"
 #include "ServiceProvider.h"
 #include "SPRequest.h"
 #include "handler/AbstractHandler.h"
@@ -36,7 +35,6 @@
 #include <boost/scoped_ptr.hpp>
 
 using namespace shibsp;
-using namespace xmltooling;
 using namespace boost;
 using namespace std;
 
@@ -57,7 +55,7 @@ namespace shibsp {
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
 
     private:
-        pair<bool,long> doRequest(const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
+        pair<bool,long> doRequest(SPRequest& request) const;
     };
 
 #if defined (_MSC_VER)
@@ -146,7 +144,7 @@ pair<bool,long> SAML2Logout::run(SPRequest& request, bool isHandler) const
     SPConfig& conf = SPConfig::getConfig();
     if (conf.isEnabled(SPConfig::OutOfProcess)) {
         // When out of process, we run natively and directly process the message.
-        return doRequest(request.getApplication(), request, request);
+        return doRequest(request);
     }
     else {
         // When not out of process, we remote all the message processing.
@@ -161,6 +159,7 @@ pair<bool,long> SAML2Logout::run(SPRequest& request, bool isHandler) const
 
 void SAML2Logout::receive(DDF& in, ostream& out)
 {
+    /*
     // Find application.
     const char* aid = in["application_id"].string();
     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
@@ -183,9 +182,10 @@ void SAML2Logout::receive(DDF& in, ostream& out)
     // which we capture in the facade and send back.
     doRequest(*app, *req, *resp);
     out << ret;
+    */
 }
 
-pair<bool,long> SAML2Logout::doRequest(const Application& application, HTTPRequest& request, HTTPResponse& response) const
+pair<bool,long> SAML2Logout::doRequest(SPRequest& request) const
 {
 #ifndef SHIBSP_LITE
     // First capture the active session ID, if any.
diff --git a/shibsp/handler/impl/SAML2LogoutInitiator.cpp b/shibsp/handler/impl/SAML2LogoutInitiator.cpp
index b3975bdf..016ad037 100644
--- a/shibsp/handler/impl/SAML2LogoutInitiator.cpp
+++ b/shibsp/handler/impl/SAML2LogoutInitiator.cpp
@@ -26,16 +26,15 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "Application.h"
+#include "Agent.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
 #include "handler/AbstractHandler.h"
 #include "handler/LogoutInitiator.h"
+
 #include <mutex>
 
 using namespace shibsp;
-using namespace xmltooling;
-using namespace boost;
 using namespace std;
 
 namespace shibsp {
@@ -58,9 +57,7 @@ namespace shibsp {
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
 
     private:
-        pair<bool,long> doRequest(
-            const Application& application, const HTTPRequest& request, HTTPResponse& httpResponse, Session* session
-            ) const;
+        pair<bool,long> doRequest(SPRequest& request, Session* session) const;
 
         string m_appId;
         bool m_deprecationSupport;
@@ -170,7 +167,7 @@ pair<bool,long> SAML2LogoutInitiator::run(SPRequest& request, bool isHandler) co
 
     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
         // When out of process, we run natively.
-        return doRequest(request.getApplication(), request, request, session);
+        return doRequest(request, session);
     }
     else {
         // When not out of process, we remote the request.
@@ -236,20 +233,18 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out)
 #endif
 }
 
-pair<bool,long> SAML2LogoutInitiator::doRequest(
-    const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse, Session* session
-    ) const
+pair<bool,long> SAML2LogoutInitiator::doRequest(SPRequest& request, Session* session) const
 {
     unique_lock<Session> sessionLocker(*session, adopt_lock);
 
     // Do back channel notification.
     vector<string> sessions(1, session->getID());
-    if (!notifyBackChannel(application, httpRequest.getRequestURL(), sessions, false)) {
+    if (!notifyBackChannel(request, sessions, false)) {
         time_t revocationExp = session->getExpiration();
         sessionLocker.unlock();
         session = nullptr;
-        application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
-        return sendLogoutPage(application, httpRequest, httpResponse, "partial");
+        request.getAgent().getSessionCache()->remove(request, revocationExp);
+        //return sendLogoutPage(application, httpRequest, httpResponse, "partial");
     }
 
 #ifndef SHIBSP_LITE
diff --git a/shibsp/handler/impl/SAML2SessionInitiator.cpp b/shibsp/handler/impl/SAML2SessionInitiator.cpp
index 9135aba8..9bedb24f 100644
--- a/shibsp/handler/impl/SAML2SessionInitiator.cpp
+++ b/shibsp/handler/impl/SAML2SessionInitiator.cpp
@@ -25,7 +25,6 @@
  */
 
 #include "internal.h"
-#include "Application.h"
 #include "exceptions.h"
 #include "ServiceProvider.h"
 #include "handler/AbstractHandler.h"
@@ -33,8 +32,6 @@
 #include "handler/SessionInitiator.h"
 #include "util/SPConstants.h"
 
-#include <xercesc/util/XMLUniDefs.hpp>
-
 #include <boost/scoped_ptr.hpp>
 
 using namespace shibsp;
@@ -65,9 +62,7 @@ namespace shibsp {
 
     private:
         pair<bool,long> doRequest(
-            const Application& application,
-            const HTTPRequest* httpRequest,
-            HTTPResponse& httpResponse,
+            SPRequest& request,
             const char* entityID,
             const XMLCh* acsIndex,
             const char* attributeIndex,
@@ -201,7 +196,6 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
     const char* requestTemplate = nullptr;
     const char* outgoingBinding = nullptr;
     bool isPassive=false,forceAuthn=false;
-    const Application& app = request.getApplication();
 
     // ECP means the ACS will be by value no matter what.
     pair<bool,bool> acsByIndex = ECP ? make_pair(true,false) : getBool("acsByIndex");
@@ -210,7 +204,7 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
         prop.second = request.getParameter("acsIndex");
         if (prop.second && *prop.second) {
             SPConfig::getConfig().deprecation().warn("Use of acsIndex when specifying response endpoint");
-            ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second));
+            //ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second));
             if (!ACS)
                 request.log(Priority::SHIB_WARN, "invalid acsIndex specified in request, using acsIndex property");
             else if (ECP && !XMLString::equals(ACS->getString("Binding").second, nullptr)) {
@@ -224,8 +218,8 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
             target = prop.second;
 
         // Always need to recover target URL to compute handler below.
-        recoverRelayState(app, request, request, target, false);
-        app.limitRedirect(request, target.c_str());
+        recoverRelayState(request, target, false);
+        request.limitRedirect(target.c_str());
 
         // Default is to allow externally supplied settings.
         pair<bool,bool> externalInput = getBool("externalInput");
@@ -292,7 +286,7 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
             pair<bool,unsigned int> index = getUnsignedInt("acsIndex", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
             if (index.first) {
                 SPConfig::getConfig().deprecation().warn("Use of acsIndex when specifying response endpoint");
-                ACS = app.getAssertionConsumerServiceByIndex(index.second);
+                //ACS = app.getAssertionConsumerServiceByIndex(index.second);
             }
         }
     }
@@ -317,7 +311,7 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
             }
 
             return doRequest(
-                app, &request, request, entityID.c_str(),
+                request, entityID.c_str(),
                 nullptr,
                 attributeIndex.first ? attributeIndex.second : nullptr,
                 false,
@@ -349,7 +343,7 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
         }
 
         return doRequest(
-            app, &request, request, entityID.c_str(),
+            request, entityID.c_str(),
             nullptr,
             attributeIndex.first ? attributeIndex.second : nullptr,
             false,
@@ -368,7 +362,6 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
     // Remote the call.
     DDF out,in = DDF(m_address.c_str()).structure();
     DDFJanitor jin(in), jout(out);
-    in.addmember("application_id").string(app.getId());
     if (!entityID.empty())
         in.addmember("entity_id").string(entityID.c_str());
     if (isPassive)
@@ -438,13 +431,14 @@ pair<bool,long> SAML2SessionInitiator::unwrap(SPRequest& request, DDF& out) cons
     // See if there's any response to send back.
     if (!out["redirect"].isnull() || !out["response"].isnull()) {
         // If so, we're responsible for handling the POST data, probably by dropping a cookie.
-        preservePostData(request.getApplication(), request, request, out["RelayState"].string());
+        preservePostData(request, out["RelayState"].string());
     }
     return RemotedHandler::unwrap(request, out);
 }
 
 void SAML2SessionInitiator::receive(DDF& in, ostream& out)
 {
+    /*
     // Find application.
     const char* aid = in["application_id"].string();
     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
@@ -489,12 +483,11 @@ void SAML2SessionInitiator::receive(DDF& in, ostream& out)
         ret.structure();
     ret.addmember("RelayState").unsafe_string(relayState.c_str());
     out << ret;
+    */
 }
 
 pair<bool,long> SAML2SessionInitiator::doRequest(
-    const Application& app,
-    const HTTPRequest* httpRequest,
-    HTTPResponse& httpResponse,
+    SPRequest& request,
     const char* entityID,
     const XMLCh* acsIndex,
     const char* attributeIndex,
diff --git a/shibsp/handler/impl/SAMLDSSessionInitiator.cpp b/shibsp/handler/impl/SAMLDSSessionInitiator.cpp
index 132263df..2c2bf99f 100644
--- a/shibsp/handler/impl/SAMLDSSessionInitiator.cpp
+++ b/shibsp/handler/impl/SAMLDSSessionInitiator.cpp
@@ -19,17 +19,17 @@
  */
 
 #include "internal.h"
-#include "Application.h"
 #include "exceptions.h"
+#include "AgentConfig.h"
 #include "handler/AbstractHandler.h"
 #include "handler/SessionInitiator.h"
+#include "util/URLEncoder.h"
 
 #include <boost/algorithm/string.hpp>
 #include <xmltooling/XMLToolingConfig.h>
 #include <xmltooling/util/URLEncoder.h>
 
 using namespace shibsp;
-using namespace xmltooling;
 using namespace boost;
 using namespace std;
 
@@ -105,23 +105,22 @@ pair<bool,long> SAMLDSSessionInitiator::run(SPRequest& request, string& entityID
     string target;
     pair<bool,const char*> prop;
     bool isPassive = false;
-    const Application& app = request.getApplication();
     pair<bool,const char*> discoveryURL = pair<bool,const char*>(false, nullptr);
 
     if (isHandler) {
         prop.second = request.getParameter("SAMLDS");
         if (prop.second && !strcmp(prop.second,"1")) {
-            XMLToolingException ex("No identity provider was selected by user.");
+            SessionException ex("No identity provider was selected by user.");
             ex.addProperty("statusCode", "urn:oasis:names:tc:SAML:2.0:status:Requester");
             ex.addProperty("statusCode2", "urn:oasis:names:tc:SAML:2.0:status:NoAvailableIDP");
-            ex.raise();
+            throw ex;
         }
 
         prop = getString("target", request);
         if (prop.first)
             target = prop.second;
 
-        recoverRelayState(app, request, request, target, false);
+        recoverRelayState(request, target, false);
 
         pair<bool,bool> passopt = getBool("isPassive", request);
         isPassive = passopt.first && passopt.second;
@@ -163,11 +162,11 @@ pair<bool,long> SAMLDSSessionInitiator::run(SPRequest& request, string& entityID
         if (prop.second && *prop.second)
             target = prop.second;
     }
-    preserveRelayState(app, request, target);
+    preserveRelayState(request, target);
     if (!isHandler)
-        preservePostData(app, request, request, target.c_str());
+        preservePostData(request, target.c_str());
 
-    const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
+    const URLEncoder& urlenc = AgentConfig::getConfig().getURLEncoder();
     if (isHandler) {
         // Now the hard part. The base assumption is to append the entire query string, if any,
         // to the self-link. But we want to replace target with the RelayState-preserved value
@@ -203,17 +202,17 @@ pair<bool,long> SAMLDSSessionInitiator::run(SPRequest& request, string& entityID
 
         // Now append the sanitized target as needed.
         if (!target.empty())
-            returnURL = returnURL + "&target=" + urlenc->encode(target.c_str());
+            returnURL = returnURL + "&target=" + urlenc.encode(target.c_str());
     }
     else {
         // For a virtual handler, we append target to the return link.
          if (!target.empty())
-            returnURL = returnURL + "&target=" + urlenc->encode(target.c_str());
+            returnURL = returnURL + "&target=" + urlenc.encode(target.c_str());
          // Preserve designated request settings on the URL.
          for (vector<string>::const_iterator opt = m_preservedOptions.begin(); opt != m_preservedOptions.end(); ++ opt) {
              const char* optval = request.getRequestSettings().first->getString(opt->c_str());
              if (optval)
-                 returnURL = returnURL + '&' + (*opt) + '=' + urlenc->encode(optval);
+                 returnURL = returnURL + '&' + (*opt) + '=' + urlenc.encode(optval);
          }
     }
 
@@ -229,18 +228,18 @@ pair<bool,long> SAMLDSSessionInitiator::run(SPRequest& request, string& entityID
         }
     }
     else {
-        prop = app.getString("entityID");
+        prop.second = request.getRequestSettings().first->getString("entityID");
     }
 
-    string req=string(discoveryURL.second) + (strchr(discoveryURL.second,'?') ? '&' : '?') + "entityID=" + urlenc->encode(prop.second) +
-        "&return=" + urlenc->encode(returnURL.c_str());
+    string req=string(discoveryURL.second) + (strchr(discoveryURL.second,'?') ? '&' : '?') + "entityID=" + urlenc.encode(prop.second) +
+        "&return=" + urlenc.encode(returnURL.c_str());
     if (m_returnParam)
         req = req + "&returnIDParam=" + m_returnParam;
     if (isPassive)
         req += "&isPassive=true";
     prop = getString("discoveryPolicy");
     if (prop.first)
-        req += "&policy=" + urlenc->encode(prop.second);
+        req += "&policy=" + urlenc.encode(prop.second);
 
     return make_pair(true, request.sendRedirect(req.c_str()));
 }
diff --git a/shibsp/handler/impl/SessionHandler.cpp b/shibsp/handler/impl/SessionHandler.cpp
index 3e6591b0..41b92ef0 100644
--- a/shibsp/handler/impl/SessionHandler.cpp
+++ b/shibsp/handler/impl/SessionHandler.cpp
@@ -25,7 +25,6 @@
  */
 
 #include "internal.h"
-#include "Application.h"
 #include "exceptions.h"
 #include "ServiceProvider.h"
 #include "SessionCache.h"
diff --git a/shibsp/handler/impl/SessionInitiator.cpp b/shibsp/handler/impl/SessionInitiator.cpp
index b6ccde91..44dd7f02 100644
--- a/shibsp/handler/impl/SessionInitiator.cpp
+++ b/shibsp/handler/impl/SessionInitiator.cpp
@@ -26,12 +26,10 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "Application.h"
 #include "SPRequest.h"
 #include "handler/SessionInitiator.h"
 
 using namespace shibsp;
-using namespace xmltooling;
 using namespace std;
 
 SessionInitiator::SessionInitiator()
@@ -97,7 +95,7 @@ bool SessionInitiator::checkCompatibility(SPRequest& request, bool isHandler) co
 
 pair<bool,long> SessionInitiator::run(SPRequest& request, bool isHandler) const
 {
-    cleanRelayState(request.getApplication(), request, request);
+    cleanRelayState(request);
 
     const char* entityID = nullptr;
     pair<bool,const char*> param = getString("entityIDParam");
@@ -150,8 +148,8 @@ pair<bool,long> SessionInitiator::run(SPRequest& request, bool isHandler) const
                 log(Priority::SHIB_INFO, "trapping SessionInitiator error condition and returning to target location");
                 flag = request.getParameter("target");
                 string target(flag ? flag : "");
-                recoverRelayState(request.getApplication(), request, request, target, false);
-                request.getApplication().limitRedirect(request, target.c_str());
+                recoverRelayState(request, target, false);
+                request.limitRedirect(target.c_str());
                 return make_pair(true, request.sendRedirect(target.c_str()));
             }
         }
diff --git a/shibsp/handler/impl/StatusHandler.cpp b/shibsp/handler/impl/StatusHandler.cpp
index beb7280a..112e962b 100644
--- a/shibsp/handler/impl/StatusHandler.cpp
+++ b/shibsp/handler/impl/StatusHandler.cpp
@@ -25,8 +25,8 @@
  */
 
 #include "internal.h"
-#include "Application.h"
 #include "exceptions.h"
+#include "Agent.h"
 #include "ServiceProvider.h"
 #include "SPRequest.h"
 #include "handler/RemotedHandler.h"
@@ -66,7 +66,7 @@ namespace shibsp {
         void receive(DDF& in, ostream& out);
 
     private:
-        pair<bool,long> processMessage(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
+        pair<bool,long> processMessage(SPRequest& request) const;
         ostream& systemInfo(ostream& os) const;
     };
 
@@ -238,7 +238,7 @@ pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
     if (target) {
         // RequestMap query, so handle it inproc.
         DummyRequest dummy(target);
-        RequestMapper::Settings settings = request.getApplication().getServiceProvider().getRequestMapper()->getSettings(dummy);
+        RequestMapper::Settings settings = request.getAgent().getRequestMapper()->getSettings(dummy);
         XMLDateTime now(time(nullptr), false);
         now.parseDateTime();
         auto_ptr_char timestamp(now.getFormattedString());
@@ -268,7 +268,7 @@ pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
     try {
         if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
             // When out of process, we run natively and directly process the message.
-            return processMessage(request.getApplication(), request, request);
+            return processMessage(request);
         }
         else {
             // When not out of process, we remote all the message processing.
@@ -320,6 +320,7 @@ pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
 
 void StatusHandler::receive(DDF& in, ostream& out)
 {
+    /*
     // Find application.
     const char* aid = in["application_id"].string();
     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
@@ -340,11 +341,10 @@ void StatusHandler::receive(DDF& in, ostream& out)
     // which we capture in the facade and send back.
     processMessage(*app, *req, *resp);
     out << ret;
+    */
 }
 
-pair<bool,long> StatusHandler::processMessage(
-    const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
-    ) const
+pair<bool,long> StatusHandler::processMessage(SPRequest& request) const
 {
 #ifndef SHIBSP_LITE
     m_log.debug("processing status request");
diff --git a/shibsp/impl/StorageServiceSessionCache.cpp b/shibsp/impl/StorageServiceSessionCache.cpp
index 1a44782b..26618c0c 100644
--- a/shibsp/impl/StorageServiceSessionCache.cpp
+++ b/shibsp/impl/StorageServiceSessionCache.cpp
@@ -34,15 +34,12 @@
 
 #include "internal.h"
 
-#include "Application.h"
 #include "exceptions.h"
-#include "ServiceProvider.h"
+#include "SPRequest.h"
 #include "attribute/Attribute.h"
 #include "handler/RemotedHandler.h"
 #include "impl/StoredSession.h"
 #include "impl/StorageServiceSessionCache.h"
-#include "io/HTTPRequest.h"
-#include "io/HTTPResponse.h"
 #include "util/IPRange.h"
 #include "util/SPConstants.h"
 
@@ -51,7 +48,6 @@
 #include <boost/bind.hpp>
 #include <xmltooling/security/DataSealer.h>
 #include <xmltooling/util/Threads.h>
-#include <xmltooling/util/URLEncoder.h>
 #include <xmltooling/util/XMLHelper.h>
 #include <xercesc/util/XMLStringTokenizer.hpp>
 #include <xercesc/util/XMLUniDefs.hpp>
@@ -220,34 +216,24 @@ SSCache::~SSCache()
 #endif
 }
 
-unsigned long SSCache::getCacheTimeout(const Application& app) const
+unsigned long SSCache::getCacheTimeout(const SPRequest& request) const
 {
     // Computes offset for adjusting expiration of sessions.
     // This can either be static, or dynamic based on the per-app session timeout or lifetime.
     if (m_cacheTimeout)
         return m_cacheTimeout;
-    pair<bool, unsigned int> timeout = pair<bool, unsigned int>(false, 3600);
-    const PropertySet* props = app.getPropertySet("Sessions");
-    if (props) {
-        timeout = props->getUnsignedInt("timeout");
-        if (!timeout.first)
-            timeout.second = 3600;
-    }
+
+    unsigned int timeout = request.getRequestSettings().first->getUnsignedInt("timeout", 3600);
+
     // As long as one of the two factors is set, add them together.
-    if (timeout.second > 0 || m_cacheAllowance > 0)
-        return timeout.second + m_cacheAllowance;
+    if (timeout > 0 || m_cacheAllowance > 0)
+        return timeout + m_cacheAllowance;
 
     // If timeouts are off, and there's no cache slop set, then use the lifetime.
-    timeout = pair<bool, unsigned int>(false, 28800);
-    if (props) {
-        timeout = props->getUnsignedInt("lifetime");
-        if (!timeout.first || timeout.second == 0)
-            timeout.second = 28800;
-    }
-    return timeout.second;
+    return request.getRequestSettings().first->getUnsignedInt("lifetime", 28800);
 }
 
-string SSCache::active(const Application& app, const HTTPRequest& request)
+string SSCache::active(const SPRequest& request)
 {
     if (!m_inboundHeader.empty()) {
         string session_id = request.getHeader(m_inboundHeader.c_str());
@@ -255,7 +241,7 @@ string SSCache::active(const Application& app, const HTTPRequest& request)
             return session_id;
     }
 
-    const char* session_id = request.getCookie(app.getCookieName("_shibsession_").c_str());
+    const char* session_id = request.getCookie(getCookieName(request, "_shibsession_").c_str());
     return (session_id ? session_id : "");
 }
 
@@ -801,27 +787,42 @@ bool SSCache::stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::Na
 
 #endif
 
-HTTPResponse::samesite_t SSCache::getSameSitePolicy(const Application& app) const
+HTTPResponse::samesite_t SSCache::getSameSitePolicy(const SPRequest& request) const
 {
-    const PropertySet* props = app.getPropertySet("Sessions");
-    if (props) {
-        pair<bool,const char*> sameSiteSession = props->getString("sameSiteSession");
-        if (sameSiteSession.first) {
-            if (!strcmp(sameSiteSession.second, "None")) {
-                return HTTPResponse::SAMESITE_NONE;
-            }
-            else if (!strcmp(sameSiteSession.second, "Lax")) {
-                return HTTPResponse::SAMESITE_LAX;
-            }
-            else if (!strcmp(sameSiteSession.second, "Strict")) {
-                return HTTPResponse::SAMESITE_STRICT;
-            }
+    const char* sameSiteSession = request.getRequestSettings().first->getString("sameSiteSession");
+    if (sameSiteSession) {
+        if (!strcmp(sameSiteSession, "None")) {
+            return HTTPResponse::SAMESITE_NONE;
+        }
+        else if (!strcmp(sameSiteSession, "Lax")) {
+            return HTTPResponse::SAMESITE_LAX;
+        }
+        else if (!strcmp(sameSiteSession, "Strict")) {
+            return HTTPResponse::SAMESITE_STRICT;
         }
     }
     return HTTPResponse::SAMESITE_ABSENT;
 }
 
-Session* SSCache::_find(const Application& app, const char* key, const char* recovery, const char* client_addr, time_t* timeout)
+string SSCache::getCookieName(const SPRequest& request, const char* prefix, time_t* lifetime) const
+{
+    if (lifetime)
+        *lifetime = 0;
+    if (!prefix)
+        prefix = "";
+    if (lifetime) {
+        unsigned int lt = request.getRequestSettings().first->getUnsignedInt("cookieLifetime", 0);
+        if (lt > 0)
+            *lifetime = lt;
+    }
+    const char* p = request.getRequestSettings().first->getString("cookieName");
+    if (p)
+        return string(prefix) + p;
+
+    return string(prefix);  // TODO: implement some form of uniqueification for agent + getHash();
+}
+
+Session* SSCache::_find(const char* bucketID, const char* key, const char* recovery, const char* client_addr, time_t* timeout)
 {
     StoredSession* session=nullptr;
 
@@ -850,7 +851,7 @@ Session* SSCache::_find(const Application& app, const char* key, const char* rec
             in.structure();
             in.addmember("key").string(key);
             in.addmember("sealed").string(recovery);
-            in.addmember("application_id").string(app.getId());
+            in.addmember("bucket_id").string(bucketID);
             if (timeout && *timeout) {
                 // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
 #ifndef HAVE_GMTIME_R
@@ -969,36 +970,37 @@ Session* SSCache::_find(const Application& app, const char* key, const char* rec
         }
     }
 
-    if (!XMLString::equals(session->getApplicationID(), app.getId())) {
-        m_log.warn("an application (%s) tried to access another application's session", app.getId());
+    if (!XMLString::equals(session->getBucketID(), bucketID)) {
+        m_log.warn("session did not contain the expected bucket identifier(%s)", bucketID);
         session->unlock();
         return nullptr;
     }
 
     // Verify currency and update the timestamp if indicated by caller.
     try {
-        session->validate(app, client_addr, timeout);
+        session->validate(bucketID, client_addr, timeout);
     }
     catch (...) {
         session->unlock();
-        remove(app, key);
+        remove(bucketID, key);
         throw;
     }
 
     return session;
 }
 
-Session* SSCache::find(const Application& app, HTTPRequest& request, const char* client_addr, time_t* timeout)
+Session* SSCache::find(SPRequest& request, const char* client_addr, time_t* timeout)
 {
-    string id = active(app, request);
+    string id = active(request);
     if (id.empty())
         return nullptr;
 
-    HTTPResponse::samesite_t sameSitePolicy = getSameSitePolicy(app);
-    const char* c = request.getCookie(app.getCookieName("_shibsealed_").c_str());
+    HTTPResponse::samesite_t sameSitePolicy = getSameSitePolicy(request);
+
+    const char* bucketID = request.getRequestSettings().first->getString("sessionBucket", "default");
 
     try {
-        Session* session = _find(app, id.c_str(), c, client_addr, timeout);
+        Session* session = _find(bucketID, id.c_str(), nullptr, client_addr, timeout);
         if (session)
             return session;
 
@@ -1006,8 +1008,7 @@ Session* SSCache::find(const Application& app, HTTPRequest& request, const char*
         if (response) {
             if (!m_outboundHeader.empty())
                 response->setResponseHeader(m_outboundHeader.c_str(), nullptr);
-            response->setCookie(app.getCookieName("_shibsession_").c_str(), nullptr, 0, sameSitePolicy);
-            response->setCookie(app.getCookieName("_shibsealed_").c_str(), nullptr, 0, sameSitePolicy);
+            response->setCookie(getCookieName(request, "_shibsession_").c_str(), nullptr, 0, sameSitePolicy);
         }
     }
     catch (const std::exception&) {
@@ -1015,141 +1016,17 @@ Session* SSCache::find(const Application& app, HTTPRequest& request, const char*
         if (response) {
             if (!m_outboundHeader.empty())
                 response->setResponseHeader(m_outboundHeader.c_str(), nullptr);
-            response->setCookie(app.getCookieName("_shibsession_").c_str(), nullptr, 0, sameSitePolicy);
-            response->setCookie(app.getCookieName("_shibsealed_").c_str(), nullptr, 0, sameSitePolicy);
+            response->setCookie(getCookieName(request, "_shibsession_").c_str(), nullptr, 0, sameSitePolicy);
         }
         throw;
     }
     return nullptr;
 }
 
-bool SSCache::recover(const Application& app, const char* key, const char* data)
-{
-    if (!SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
-        m_log.debug("remoting recovery of session from sealed cookie");
-        // Remote the request.
-        DDF in("recover::" STORAGESERVICE_SESSION_CACHE "::SessionCache"), out;
-        DDFJanitor jin(in);
-        in.structure();
-        in.addmember("key").string(key);
-        in.addmember("application_id").string(app.getId());
-        in.addmember("sealed").string(data);
-
-        //out = app.getServiceProvider().getListenerService()->send(in);
-        if (!out.isint() || out.integer() != 1) {
-            out.destroy();
-            m_log.debug("recovery of session (%s) failed", key);
-            return false;
-        }
-
-        out.destroy();
-        m_log.debug("session (%s) recovered from sealed cookie", key);
-    }
-    else {
-        // We're out of process, so we can recover the session.
-#ifndef SHIBSP_LITE
-        const DataSealer* sealer = XMLToolingConfig::getConfig().getDataSealer();
-        if (!sealer) {
-            m_log.warn("can't attempt recovery of session (%s), no DataSealer configured", key);
-            return false;
-        }
-
-        m_log.debug("checking for revocation of session (%s)", key);
-        try {
-            if (m_storage_lite->readString("Revoked", key) > 0) {
-                m_log.warn("blocked recovery of revoked session (%s)", key);
-                return false;
-            }
-        }
-        catch (const std::exception& ex) {
-            if (m_softRevocation)
-                m_log.warn("ignoring failed check for revocation of session (%s): %s", ex.what());
-            else {
-                m_log.warn("check for revocation of session (%s) failed, treating as revoked: %s", ex.what());
-                return false;
-            }
-        }
-
-        m_log.debug("attempting recovery of session (%s)", key);
-
-        DDF obj;
-        DDFJanitor jobj(obj);
-        string unwrapped;
-
-        char* dup = nullptr;
-        try {
-            dup = strdup(data);
-            XMLToolingConfig::getConfig().getURLEncoder()->decode(dup);
-            unwrapped = sealer->unwrap(dup);
-            free(dup);
-
-            stringstream str(unwrapped);
-            str >> obj;
-        }
-        catch (const std::exception& e) {
-            if (dup)
-                free(dup);
-            m_log.error("failed to unwrap sealed session data with DataSealer: %s", e.what());
-            return false;
-        }
-
-        if (!obj.isstruct() || !obj.name() || strcmp(obj.name(), key)) {
-            m_log.info("recovered session data was invalid for session (%s)", key);
-            return false;
-        }
-
-        scoped_ptr<saml2::NameID> nameidObject;
-        const char* nameid = obj["nameid"].string();
-        if (nameid) {
-            // Parse and bind the document into an XMLObject.
-            istringstream instr(nameid);
-            DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
-            XercesJanitor<DOMDocument> janitor(doc);
-            nameidObject.reset(saml2::NameIDBuilder::buildNameID());
-            nameidObject->unmarshall(doc->getDocumentElement(), true);
-            janitor.release();
-        }
-
-        m_log.debug("storing recovered session (%s)...", key);
-        time_t now = time(nullptr);
-        if (!m_storage->createText(key, "session", unwrapped.c_str(), now + getCacheTimeout(app))) {
-            m_log.debug("recovered session (%s) matched existing record, likely a race condition");
-            return true;
-        }
-
-        // Store the reverse mapping for logout.
-        auto_ptr_char name(nameidObject ? nameidObject->getName() : nullptr);
-        if (name.get() && *name.get() && m_reverseIndex
-            && (m_excludedNames.size() == 0 || m_excludedNames.count(nameidObject->getName()) == 0)) {
-            try {
-                auto_ptr_XMLCh exp(obj["expires"].string());
-                if (exp.get()) {
-                    XMLDateTime iso(exp.get());
-                    iso.parseDateTime();
-                    insert(key, iso.getEpoch(), name.get(), obj["session_index"].string());
-                }
-            }
-            catch (const std::exception& ex) {
-                m_log.error("error storing back mapping of NameID for logout: %s", ex.what());
-            }
-        }
-
-        const char* pid = obj["entity_id"].string();
-        const char* prot = obj["protocol"].string();
-        m_log.info("session recovered: ID (%s) IdP (%s) Protocol(%s)",
-            key, pid ? pid : "none", prot ? prot : "none");
-#else
-        throw ConfigurationException("SessionCache recovery requires a DataSealer.");
-#endif
-    }
-
-    return true;
-}
-
-void SSCache::remove(const Application& app, const HTTPRequest& request, HTTPResponse* response, time_t revocationExp)
+void SSCache::remove(SPRequest& request, time_t revocationExp)
 {
     string session_id;
-    string shib_cookie = app.getCookieName("_shibsession_");
+    string shib_cookie = getCookieName(request, "_shibsession_");
 
     if (!m_inboundHeader.empty())
         session_id = request.getHeader(m_inboundHeader.c_str());
@@ -1160,18 +1037,16 @@ void SSCache::remove(const Application& app, const HTTPRequest& request, HTTPRes
     }
 
     if (!session_id.empty()) {
-        if (response) {
-            if (!m_outboundHeader.empty())
-                response->setResponseHeader(m_outboundHeader.c_str(), nullptr);
-            HTTPResponse::samesite_t sameSitePolicy = getSameSitePolicy(app);
-            response->setCookie(shib_cookie.c_str(), nullptr, 0, sameSitePolicy);
-            response->setCookie(app.getCookieName("_shibsealed_").c_str(), nullptr, 0, sameSitePolicy);
-        }
-        remove(app, session_id.c_str(), revocationExp);
+        if (!m_outboundHeader.empty())
+            request.setResponseHeader(m_outboundHeader.c_str(), nullptr);
+        HTTPResponse::samesite_t sameSitePolicy = getSameSitePolicy(request);
+        request.setCookie(shib_cookie.c_str(), nullptr, 0, sameSitePolicy);
+        request.setCookie(request.getCookieName("_shibsealed_").c_str(), nullptr, 0, sameSitePolicy);
+        remove(request.getRequestSettings().first->getString("sessionBucket", "default"), session_id.c_str(), revocationExp);
     }
 }
 
-void SSCache::remove(const Application& app, const char* key, time_t revocationExp)
+void SSCache::remove(const char* bucketID, const char* key, time_t revocationExp)
 {
     // Take care of local copy.
     if (inproc)
@@ -1210,7 +1085,7 @@ void SSCache::remove(const Application& app, const char* key, time_t revocationE
         DDFJanitor jin(in);
         in.structure();
         in.addmember("key").string(key);
-        in.addmember("application_id").string(app.getId());
+        in.addmember("bucket_id").string(bucketID);
 
         //DDF out = app.getServiceProvider().getListenerService()->send(in);
         //out.destroy();
diff --git a/shibsp/impl/StorageServiceSessionCache.h b/shibsp/impl/StorageServiceSessionCache.h
index f33f7daf..4c7745db 100644
--- a/shibsp/impl/StorageServiceSessionCache.h
+++ b/shibsp/impl/StorageServiceSessionCache.h
@@ -54,9 +54,7 @@ namespace shibsp {
 
         void insert(
             std::string& sessionID,
-            const Application& app,
-            const xmltooling::HTTPRequest& httpRequest,
-            xmltooling::HTTPResponse& httpResponse,
+            const SPRequest& request,
             time_t expires,
             const opensaml::saml2md::EntityDescriptor* issuer=nullptr,
             const XMLCh* protocol=nullptr,
@@ -69,52 +67,46 @@ namespace shibsp {
             const std::vector<Attribute*>* attributes=nullptr
             );
         std::vector<std::string>::size_type logout(
-            const Application& app,
+            const char* bucketID,
             const opensaml::saml2md::EntityDescriptor* issuer,
             const opensaml::saml2::NameID& nameid,
             const std::set<std::string>* indexes,
             time_t expires,
             std::vector<std::string>& sessions
             ) {
-            return _logout(app, issuer, nameid, indexes, expires, sessions, 0);
+            return _logout(bucketID, issuer, nameid, indexes, expires, sessions, 0);
         }
         bool matches(
-            const Application& app,
-            xmltooling::HTTPRequest& request,
+            const SPRequest& request,
             const opensaml::saml2md::EntityDescriptor* issuer,
             const opensaml::saml2::NameID& nameid,
             const std::set<std::string>* indexes
             );
 #endif
-        std::string active(const Application& app, const HTTPRequest& request);
-        Session* find(const Application& app, HTTPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr);
-
-        void remove(
-            const Application& app,
-            const HTTPRequest& request,
-            HTTPResponse* response=nullptr,
-            time_t revocationExp=0
-            );
+        std::string active(const SPRequest& request);
+        Session* find(SPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr);
+
+        void remove(SPRequest& request, time_t revocationExp=0);
 
-        Session* find(const Application& app, const char* key) {
-            return _find(app, key, nullptr, nullptr, nullptr);
+        Session* find(const char* bucketID, const char* key) {
+            return _find(bucketID, key, nullptr, nullptr, nullptr);
         }
-        void remove(const Application& app, const char* key, time_t revocationExp=0);
+        void remove(const char* bucketID, const char* key, time_t revocationExp=0);
         void test();
 
-        unsigned long getCacheTimeout(const Application& app) const;
+        unsigned long getCacheTimeout(const SPRequest& request) const;
 
     private:
         // internal delegates of external methods
         Session * _find(
-            const Application& app,
+            const char* bucketID,
             const char* key,
             const char* recovery,
             const char* client_addr,
             time_t* timeout);
 #ifndef SHIBSP_LITE
         std::vector<std::string>::size_type _logout(
-            const Application& app,
+            const char* bucketID,
             const opensaml::saml2md::EntityDescriptor* issuer,
             const opensaml::saml2::NameID& nameid,
             const std::set<std::string>* indexes,
@@ -126,7 +118,6 @@ namespace shibsp {
         // maintain back-mappings of NameID/SessionIndex -> session key
         void insert(const char* key, time_t expires, const char* name, const char* index, short attempts=0);
         bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const opensaml::saml2::NameID& n1, const opensaml::saml2::NameID& n2) const;
-        LogoutEvent* newLogoutEvent(const Application& app) const;
 
         xmltooling::StorageService* m_storage;
         xmltooling::StorageService* m_storage_lite;
@@ -147,24 +138,14 @@ namespace shibsp {
         // handle potentially inexact address comparisons
         bool compareAddresses(const char* client_addr, const char* session_addr) const;
 
-        HTTPResponse::samesite_t getSameSitePolicy(const Application& app) const;
+        HTTPResponse::samesite_t getSameSitePolicy(const SPRequest& request) const;
+        std::string getCookieName(const SPRequest& request, const char* prefix, time_t* lifetime=nullptr) const;
+
 
         // management of buffered sessions
         void dormant(const char* key);
         static void* cleanup_fn(void*);
 
-#ifndef SHIBSP_LITE
-        // persistence across nodes
-        void persist(
-            const Application& app,
-            xmltooling::HTTPResponse& httpResponse,
-            DDF& session,
-            time_t expires,
-            xmltooling::HTTPResponse::samesite_t sameSitePolicy
-            ) const;
-#endif
-        bool recover(const Application& app, const char* key, const char* data);
-
         Category& m_log;
         bool inproc;
         bool shutdown;
diff --git a/shibsp/impl/StoredSession.cpp b/shibsp/impl/StoredSession.cpp
index ca89ae49..ecb8f032 100644
--- a/shibsp/impl/StoredSession.cpp
+++ b/shibsp/impl/StoredSession.cpp
@@ -26,7 +26,6 @@
 
 #include "internal.h"
 #include "exceptions.h"
-#include "ServiceProvider.h"
 #include "attribute/Attribute.h"
 #include "impl/StoredSession.h"
 #include "impl/StorageServiceSessionCache.h"
@@ -118,7 +117,7 @@ void StoredSession::unmarshallAttributes() const
     }
 }
 
-void StoredSession::validate(const Application& app, const char* client_addr, time_t* timeout)
+void StoredSession::validate(const char* bucketID, const char* client_addr, time_t* timeout)
 {
     time_t now = time(nullptr);
 
@@ -156,7 +155,7 @@ void StoredSession::validate(const Application& app, const char* client_addr, ti
         in.structure();
         in.addmember("key").string(getID());
         in.addmember("version").integer(m_obj["version"].integer());
-        in.addmember("application_id").string(app.getId());
+        in.addmember("bucket_id").string(bucketID);
         if (client_addr)    // signals we need to bind an additional address to the session
             in.addmember("client_addr").string(client_addr);
         if (timeout && *timeout) {
@@ -317,211 +316,3 @@ void StoredSession::validate(const Application& app, const char* client_addr, ti
 
     m_lastAccess = now;
 }
-
-#ifndef SHIBSP_LITE
-
-void StoredSession::addAttributes(const vector<Attribute*>& attributes)
-{
-    if (!m_cache->m_storage)
-        throw ConfigurationException("Session modification requires a StorageService.");
-
-    m_cache->m_log.debug("adding attributes to session (%s)", getID());
-
-    int ver;
-    short attempts = 0;
-    do {
-        DDF attr;
-        DDF attrs = m_obj["attributes"];
-        if (!attrs.islist())
-            attrs = m_obj.addmember("attributes").list();
-        for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {
-            attr = (*a)->marshall();
-            attrs.add(attr);
-        }
-
-        // Tentatively increment the version.
-        m_obj["version"].integer(m_obj["version"].integer()+1);
-
-        ostringstream str;
-        str << m_obj;
-        string record(str.str());
-
-        try {
-            ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer()-1);
-        }
-        catch (std::exception&) {
-            // Roll back modification to record.
-            m_obj["version"].integer(m_obj["version"].integer()-1);
-            vector<Attribute*>::size_type count = attributes.size();
-            while (count--)
-                attrs.last().destroy();
-            throw;
-        }
-
-        if (ver <= 0) {
-            // Roll back modification to record.
-            m_obj["version"].integer(m_obj["version"].integer()-1);
-            vector<Attribute*>::size_type count = attributes.size();
-            while (count--)
-                attrs.last().destroy();
-        }
-        if (!ver) {
-            // Fatal problem with update.
-            throw IOException("Unable to update stored session.");
-        }
-        else if (ver < 0) {
-            // Out of sync.
-            if (++attempts > 10) {
-                m_cache->m_log.error("failed to update stored session, update attempts exceeded limit");
-                throw IOException("Unable to update stored session, exceeded retry limit.");
-            }
-            m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
-            ver = m_cache->m_storage->readText(getID(), "session", &record);
-            if (!ver) {
-                m_cache->m_log.error("readText failed on StorageService for session (%s)", getID());
-                throw IOException("Unable to read back stored session.");
-            }
-
-            // Reset object.
-            DDF newobj;
-            istringstream in(record);
-            in >> newobj;
-
-            m_ids.clear();
-            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
-            m_attributes.clear();
-            m_attributeIndex.clear();
-            newobj["version"].integer(ver);
-            m_obj.destroy();
-            m_obj = newobj;
-
-            ver = -1;
-        }
-    } while (ver < 0);  // negative indicates a sync issue so we retry
-
-    // We own them now, so clean them up.
-    for_each(attributes.begin(), attributes.end(), xmltooling::cleanup<Attribute>());
-}
-
-const Assertion* StoredSession::getAssertion(const char* id) const
-{
-    if (!m_cache->m_storage)
-        throw ConfigurationException("Assertion retrieval requires a StorageService.");
-
-    map< string,boost::shared_ptr<Assertion> >::const_iterator i = m_tokens.find(id);
-    if (i != m_tokens.end())
-        return i->second.get();
-
-    string tokenstr;
-    if (!m_cache->m_storage->readText(getID(), id, &tokenstr))
-        throw FatalProfileException("Assertion not found in cache.");
-
-    // Parse and bind the document into an XMLObject.
-    istringstream instr(tokenstr);
-    DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
-    XercesJanitor<DOMDocument> janitor(doc);
-    boost::shared_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
-    janitor.release();
-    
-    boost::shared_ptr<Assertion> token = dynamic_pointer_cast<Assertion,XMLObject>(xmlObject);
-    if (!token)
-        throw FatalProfileException("Request for cached assertion returned an unknown object type.");
-
-    m_tokens[id] = token;
-    return token.get();
-}
-
-void StoredSession::addAssertion(Assertion* assertion)
-{
-    if (!m_cache->m_storage)
-        throw ConfigurationException("Session modification requires a StorageService.");
-    else if (!assertion)
-        throw FatalProfileException("Unknown object type passed to session for storage.");
-
-    auto_ptr_char id(assertion->getID());
-    if (!id.get() || !*id.get())
-        throw IOException("Assertion did not carry an ID.");
-    else if (strlen(id.get()) > m_cache->m_storage->getCapabilities().getKeySize())
-        throw IOException("Assertion ID ($1) exceeds allowable storage key size.", params(1, id.get()));
-
-    m_cache->m_log.debug("adding assertion (%s) to session (%s)", id.get(), getID());
-
-    time_t exp = 0;
-    if (!m_cache->m_storage->readText(getID(), "session", nullptr, &exp) || exp == 0)
-        throw IOException("Unable to load expiration time for stored session.");
-
-    ostringstream tokenstr;
-    tokenstr << *assertion;
-    if (!m_cache->m_storage->createText(getID(), id.get(), tokenstr.str().c_str(), exp))
-        throw IOException("Attempted to insert duplicate assertion ID into session.");
-
-    int ver;
-    short attempts = 0;
-    do {
-        DDF token = DDF(nullptr).string(id.get());
-        m_obj["assertions"].add(token);
-
-        // Tentatively increment the version.
-        m_obj["version"].integer(m_obj["version"].integer() + 1);
-
-        ostringstream str;
-        str << m_obj;
-        string record(str.str());
-
-        try {
-            ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer()-1);
-        }
-        catch (std::exception&) {
-            token.destroy();
-            m_obj["version"].integer(m_obj["version"].integer() - 1);
-            m_cache->m_storage->deleteText(getID(), id.get());
-            throw;
-        }
-
-        if (ver <= 0) {
-            token.destroy();
-            m_obj["version"].integer(m_obj["version"].integer()-1);
-        }
-        if (!ver) {
-            // Fatal problem with update.
-            m_cache->m_log.error("updateText failed on StorageService for session (%s)", getID());
-            m_cache->m_storage->deleteText(getID(), id.get());
-            throw IOException("Unable to update stored session.");
-        }
-        else if (ver < 0) {
-            // Out of sync.
-            if (++attempts > 10) {
-                m_cache->m_log.error("failed to update stored session, update attempts exceeded limit");
-                throw IOException("Unable to update stored session, exceeded retry limit.");
-            }
-            m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
-            ver = m_cache->m_storage->readText(getID(), "session", &record);
-            if (!ver) {
-                m_cache->m_log.error("readText failed on StorageService for session (%s)", getID());
-                m_cache->m_storage->deleteText(getID(), id.get());
-                throw IOException("Unable to read back stored session.");
-            }
-
-            // Reset object.
-            DDF newobj;
-            istringstream in(record);
-            in >> newobj;
-
-            m_ids.clear();
-            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
-            m_attributes.clear();
-            m_attributeIndex.clear();
-            newobj["version"].integer(ver);
-            m_obj.destroy();
-            m_obj = newobj;
-
-            ver = -1;
-        }
-    } while (ver < 0); // negative indicates a sync issue so we retry
-
-    m_ids.clear();
-    delete assertion;
-}
-
-#endif
-
diff --git a/shibsp/impl/StoredSession.h b/shibsp/impl/StoredSession.h
index 44803b39..6637cf2e 100644
--- a/shibsp/impl/StoredSession.h
+++ b/shibsp/impl/StoredSession.h
@@ -27,7 +27,6 @@
 #ifndef __shibsp_storedsession_h__
 #define __shibsp_storedsession_h__
 
-#include "Application.h"
 #include "SessionCache.h"
 #include "remoting/ddf.h"
 
@@ -51,8 +50,8 @@ namespace shibsp {
         const char* getID() const {
             return m_obj.name();
         }
-        const char* getApplicationID() const {
-            return m_obj["application_id"].string();
+        const char* getBucketID() const {
+            return m_obj["bucket_id"].string();
         }
         const char* getClientAddress() const {
             return m_obj["client_addr"].first().string();
@@ -89,7 +88,7 @@ namespace shibsp {
         }
         const std::multimap<std::string, const Attribute*>& getIndexedAttributes() const;
 
-        void validate(const Application& application, const char* client_addr, time_t* timeout);
+        void validate(const char* bucketID, const char* client_addr, time_t* timeout);
 
         time_t getExpiration() const { return m_expires; }
         time_t getLastAccess() const { return m_lastAccess; }
diff --git a/tests/impl/XMLAccessControlTests.cpp b/tests/impl/XMLAccessControlTests.cpp
index 72f55e46..a5385cc4 100644
--- a/tests/impl/XMLAccessControlTests.cpp
+++ b/tests/impl/XMLAccessControlTests.cpp
@@ -52,7 +52,7 @@ public:
     const char* getID() const {
         return nullptr;
     }
-    const char* getApplicationID() const {
+    const char* getBucketID() const {
         return nullptr;
     }
     time_t getExpiration() const {
diff --git a/tests/impl/XMLRequestMapperTests.cpp b/tests/impl/XMLRequestMapperTests.cpp
index a57c313c..f2e4cd8f 100644
--- a/tests/impl/XMLRequestMapperTests.cpp
+++ b/tests/impl/XMLRequestMapperTests.cpp
@@ -53,7 +53,7 @@ public:
     const char* getID() const {
         return nullptr;
     }
-    const char* getApplicationID() const {
+    const char* getBucketID() const {
         return nullptr;
     }
     time_t getExpiration() const {

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


More information about the commits mailing list