[cpp-sp] branch master updated: Refactor session cache with separate source files.

Scott Cantor cantor.2 at osu.edu
Tue Feb 20 11:44:26 EST 2018


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

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

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

The following commit(s) were added to refs/heads/master by this push:
       new  5fe1fb4   Refactor session cache with separate source files.
5fe1fb4 is described below

commit 5fe1fb44ca7561566c0b929abf285dcf8ee36b77
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Feb 20 11:44:19 2018 -0500

    Refactor session cache with separate source files.
---
 Projects/vc15/shibsp/shibsp-lite.vcxproj         |   3 +
 Projects/vc15/shibsp/shibsp-lite.vcxproj.filters |   9 +
 Projects/vc15/shibsp/shibsp.vcxproj              |   3 +
 Projects/vc15/shibsp/shibsp.vcxproj.filters      |  15 +-
 shibsp/Makefile.am                               |   3 +
 shibsp/impl/StorageServiceSessionCache.cpp       | 832 ++---------------------
 shibsp/impl/StorageServiceSessionCache.h         | 160 +++++
 shibsp/impl/StoredSession.cpp                    | 594 ++++++++++++++++
 shibsp/impl/StoredSession.h                      | 153 +++++
 9 files changed, 996 insertions(+), 776 deletions(-)

diff --git a/Projects/vc15/shibsp/shibsp-lite.vcxproj b/Projects/vc15/shibsp/shibsp-lite.vcxproj
index 9f43c7d..c42df16 100644
--- a/Projects/vc15/shibsp/shibsp-lite.vcxproj
+++ b/Projects/vc15/shibsp/shibsp-lite.vcxproj
@@ -201,6 +201,7 @@
     <ClCompile Include="..\..\..\shibsp\handler\impl\ExternalAuthHandler.cpp" />
     <ClCompile Include="..\..\..\shibsp\handler\impl\LogoutInitiator.cpp" />
     <ClCompile Include="..\..\..\shibsp\handler\impl\SecuredHandler.cpp" />
+    <ClCompile Include="..\..\..\shibsp\impl\StoredSession.cpp" />
     <ClCompile Include="..\..\..\shibsp\ServiceProvider.cpp" />
     <ClCompile Include="..\..\..\shibsp\SPConfig.cpp" />
     <ClCompile Include="..\..\..\shibsp\util\CGIParser.cpp" />
@@ -258,6 +259,8 @@
     <ClInclude Include="..\..\..\shibsp\GSSRequest.h" />
     <ClInclude Include="..\..\..\shibsp\handler\LogoutInitiator.h" />
     <ClInclude Include="..\..\..\shibsp\handler\SecuredHandler.h" />
+    <ClInclude Include="..\..\..\shibsp\impl\StorageServiceSessionCache.h" />
+    <ClInclude Include="..\..\..\shibsp\impl\StoredSession.h" />
     <ClInclude Include="..\..\..\shibsp\remoting\impl\SocketListener.h" />
     <ClInclude Include="..\..\..\shibsp\AbstractSPRequest.h" />
     <ClInclude Include="..\..\..\shibsp\AccessControl.h" />
diff --git a/Projects/vc15/shibsp/shibsp-lite.vcxproj.filters b/Projects/vc15/shibsp/shibsp-lite.vcxproj.filters
index c1809ce..a1f9c34 100644
--- a/Projects/vc15/shibsp/shibsp-lite.vcxproj.filters
+++ b/Projects/vc15/shibsp/shibsp-lite.vcxproj.filters
@@ -240,6 +240,9 @@
     <ClCompile Include="..\..\..\shibsp\remoting\impl\TCPListener.cpp">
       <Filter>Source Files\remoting\impl</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\shibsp\impl\StoredSession.cpp">
+      <Filter>Source Files\impl</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\shibsp\GSSRequest.h">
@@ -371,6 +374,12 @@
     <ClInclude Include="..\..\..\shibsp\util\TemplateParameters.h">
       <Filter>Header Files\util</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\shibsp\impl\StorageServiceSessionCache.h">
+      <Filter>Source Files\impl</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\shibsp\impl\StoredSession.h">
+      <Filter>Source Files\impl</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\shibsp\shibsp.rc">
diff --git a/Projects/vc15/shibsp/shibsp.vcxproj b/Projects/vc15/shibsp/shibsp.vcxproj
index 3ae0fcd..fb24c93 100644
--- a/Projects/vc15/shibsp/shibsp.vcxproj
+++ b/Projects/vc15/shibsp/shibsp.vcxproj
@@ -210,6 +210,7 @@
     <ClCompile Include="..\..\..\shibsp\handler\impl\ExternalAuthHandler.cpp" />
     <ClCompile Include="..\..\..\shibsp\handler\impl\LogoutInitiator.cpp" />
     <ClCompile Include="..\..\..\shibsp\handler\impl\SecuredHandler.cpp" />
+    <ClCompile Include="..\..\..\shibsp\impl\StoredSession.cpp" />
     <ClCompile Include="..\..\..\shibsp\impl\XMLSecurityPolicyProvider.cpp" />
     <ClCompile Include="..\..\..\shibsp\ServiceProvider.cpp" />
     <ClCompile Include="..\..\..\shibsp\SPConfig.cpp" />
@@ -311,6 +312,8 @@
     <ClInclude Include="..\..\..\shibsp\GSSRequest.h" />
     <ClInclude Include="..\..\..\shibsp\handler\LogoutInitiator.h" />
     <ClInclude Include="..\..\..\shibsp\handler\SecuredHandler.h" />
+    <ClInclude Include="..\..\..\shibsp\impl\StorageServiceSessionCache.h" />
+    <ClInclude Include="..\..\..\shibsp\impl\StoredSession.h" />
     <ClInclude Include="..\..\..\shibsp\remoting\impl\SocketListener.h" />
     <ClInclude Include="..\..\..\shibsp\AbstractSPRequest.h" />
     <ClInclude Include="..\..\..\shibsp\AccessControl.h" />
diff --git a/Projects/vc15/shibsp/shibsp.vcxproj.filters b/Projects/vc15/shibsp/shibsp.vcxproj.filters
index 9f78f16..d9bb79d 100644
--- a/Projects/vc15/shibsp/shibsp.vcxproj.filters
+++ b/Projects/vc15/shibsp/shibsp.vcxproj.filters
@@ -429,6 +429,9 @@
     <ClCompile Include="..\..\..\shibsp\attribute\filtering\impl\RegistrationAuthorityFunctor.cpp">
       <Filter>Source Files\attribute\filtering\impl</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\..\shibsp\impl\StoredSession.cpp">
+      <Filter>Source Files\impl</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\..\shibsp\GSSRequest.h">
@@ -572,9 +575,6 @@
     <ClInclude Include="..\..\..\shibsp\remoting\ddf.h">
       <Filter>Header Files\remoting</Filter>
     </ClInclude>
-    <ClInclude Include="..\..\..\shibsp\remoting\ListenerService.h">
-      <Filter>Header Files\security</Filter>
-    </ClInclude>
     <ClInclude Include="..\..\..\shibsp\security\PKIXTrustEngine.h">
       <Filter>Header Files\security</Filter>
     </ClInclude>
@@ -602,6 +602,15 @@
     <ClInclude Include="..\..\..\shibsp\util\TemplateParameters.h">
       <Filter>Header Files\util</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\..\shibsp\impl\StoredSession.h">
+      <Filter>Source Files\impl</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\shibsp\remoting\ListenerService.h">
+      <Filter>Header Files\remoting</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\shibsp\impl\StorageServiceSessionCache.h">
+      <Filter>Source Files\impl</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\..\shibsp\shibsp.rc">
diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index 728742d..bc2187b 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -105,6 +105,8 @@ utilinclude_HEADERS = \
 
 noinst_HEADERS = \
 	internal.h \
+    impl/StoredSession.h \
+    impl/StorageServiceSessionCache.h \
 	remoting/impl/SocketListener.h
 
 common_sources = \
@@ -152,6 +154,7 @@ common_sources = \
 	handler/impl/TransformSessionInitiator.cpp \
 	handler/impl/WAYFSessionInitiator.cpp \
 	impl/ChainingAccessControl.cpp \
+    impl/StoredSession.cpp \
     impl/StorageServiceSessionCache.cpp \
 	impl/XMLAccessControl.cpp \
 	impl/XMLRequestMapper.cpp \
diff --git a/shibsp/impl/StorageServiceSessionCache.cpp b/shibsp/impl/StorageServiceSessionCache.cpp
index 494acee..037c593 100644
--- a/shibsp/impl/StorageServiceSessionCache.cpp
+++ b/shibsp/impl/StorageServiceSessionCache.cpp
@@ -33,23 +33,22 @@
  */
 
 #include "internal.h"
+
 #include "Application.h"
 #include "exceptions.h"
 #include "ServiceProvider.h"
-#include "SessionCache.h"
 #include "TransactionLog.h"
 #include "attribute/Attribute.h"
 #include "handler/RemotedHandler.h"
-#include "remoting/ListenerService.h"
+#include "impl/StoredSession.h"
+#include "impl/StorageServiceSessionCache.h"
 #include "util/SPConstants.h"
 
 #include <algorithm>
 #include <boost/bind.hpp>
-#include <boost/shared_ptr.hpp>
 #include <xmltooling/io/HTTPRequest.h>
 #include <xmltooling/io/HTTPResponse.h>
 #include <xmltooling/util/NDC.h>
-#include <xmltooling/util/ParserPool.h>
 #include <xmltooling/util/Threads.h>
 #include <xmltooling/util/XMLHelper.h>
 #include <xercesc/util/XMLUniDefs.hpp>
@@ -64,7 +63,6 @@
 # include <xercesc/util/XMLStringTokenizer.hpp>
 using namespace opensaml::saml2md;
 #else
-# include <ctime>
 # include <xercesc/util/XMLDateTime.hpp>
 #endif
 
@@ -74,322 +72,9 @@ using namespace xmltooling;
 using namespace boost;
 using namespace std;
 
-namespace {
-
-    // Allows the cache to bind sessions to multiple client address
-    // families based on whatever this function returns.
-    static const char* getAddressFamily(const char* addr) {
-        if (strchr(addr, ':'))
-            return "6";
-        else
-            return "4";
-    }
-
-    class StoredSession;
-    class SSCache : public SessionCache
-#ifndef SHIBSP_LITE
-        ,public virtual Remoted
-#endif
-    {
-    public:
-        SSCache(const DOMElement* e);
-        virtual ~SSCache();
-
-#ifndef SHIBSP_LITE
-        void receive(DDF& in, ostream& out);
-
-        void insert(
-            string& sessionID,
-            const Application& app,
-            const HTTPRequest& httpRequest,
-            HTTPResponse& httpResponse,
-            time_t expires,
-            const EntityDescriptor* issuer=nullptr,
-            const XMLCh* protocol=nullptr,
-            const saml2::NameID* nameid=nullptr,
-            const XMLCh* authn_instant=nullptr,
-            const XMLCh* session_index=nullptr,
-            const XMLCh* authncontext_class=nullptr,
-            const XMLCh* authncontext_decl=nullptr,
-            const vector<const Assertion*>* tokens=nullptr,
-            const vector<Attribute*>* attributes=nullptr
-            );
-        vector<string>::size_type logout(
-            const Application& app,
-            const EntityDescriptor* issuer,
-            const saml2::NameID& nameid,
-            const set<string>* indexes,
-            time_t expires,
-            vector<string>& sessions
-            ) {
-            return _logout(app, issuer, nameid, indexes, expires, sessions, 0);
-        }
-        bool matches(
-            const Application& app,
-            const HTTPRequest& request,
-            const EntityDescriptor* issuer,
-            const saml2::NameID& nameid,
-            const set<string>* indexes
-            );
-#endif
-        Session* find(const Application& app, const char* key, const char* client_addr=nullptr, time_t* timeout=nullptr);
-        void remove(const Application& app, const char* key);
-        void test();
-
-        string active(const Application& app, const HTTPRequest& request) {
-            if (!m_inboundHeader.empty()) {
-                string session_id = request.getHeader(m_inboundHeader.c_str());
-                if (!session_id.empty())
-                    return session_id;
-            }
-            pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
-            const char* session_id = request.getCookie(shib_cookie.first.c_str());
-            return (session_id ? session_id : "");
-        }
-
-        Session* find(const Application& app, const HTTPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr) {
-            string id = active(app, request);
-            if (!id.empty())
-                return find(app, id.c_str(), client_addr, timeout);
-            return nullptr;
-        }
-
-        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);
-
-        unsigned long getCacheTimeout(const Application& app) {
-            // 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;
-            }
-            // 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 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;
-        }
-
-        Category& m_log;
-        bool inproc;
-#ifndef SHIBSP_LITE
-        StorageService* m_storage;
-        StorageService* m_storage_lite;
-#endif
-
-    private:
-#ifndef SHIBSP_LITE
-        // 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);
-        vector<string>::size_type _logout(
-            const Application& app,
-            const EntityDescriptor* issuer,
-            const saml2::NameID& nameid,
-            const set<string>* indexes,
-            time_t expires,
-            vector<string>& sessions,
-            short attempts
-            );
-        bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const;
-        LogoutEvent* newLogoutEvent(const Application& app) const;
-
-        bool m_cacheAssertions,m_reverseIndex;
-        set<xstring> m_excludedNames;
-#endif
-        const DOMElement* m_root;         // Only valid during initialization
-        unsigned long m_inprocTimeout,m_cacheTimeout,m_cacheAllowance;
-        string m_inboundHeader,m_outboundHeader;
-
-        // inproc means we buffer sessions in memory
-        scoped_ptr<RWLock> m_lock;
-        map<string,StoredSession*> m_hashtable;
-
-        // management of buffered sessions
-        void dormant(const char* key);
-        static void* cleanup_fn(void*);
-
-        bool shutdown;
-        scoped_ptr<CondWait> shutdown_wait;
-        scoped_ptr<Thread> cleanup_thread;
-    };
-
-    class StoredSession : public virtual Session
-    {
-    public:
-        StoredSession(SSCache* cache, DDF& obj) : m_obj(obj), m_cache(cache), m_expires(0), m_lastAccess(time(nullptr)) {
-            // Check for old address format.
-            if (m_obj["client_addr"].isstring()) {
-                const char* saddr = m_obj["client_addr"].string();
-                DDF addrobj = m_obj["client_addr"].structure();
-                if (saddr && *saddr) {
-                    addrobj.addmember(getAddressFamily(saddr)).string(saddr);
-                }
-            }
-
-            auto_ptr_XMLCh exp(m_obj["expires"].string());
-            if (exp.get()) {
-                XMLDateTime iso(exp.get());
-                iso.parseDateTime();
-                m_expires = iso.getEpoch();
-            }
-
-#ifndef SHIBSP_LITE
-            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);
-                m_nameid.reset(saml2::NameIDBuilder::buildNameID());
-                m_nameid->unmarshall(doc->getDocumentElement(), true);
-                janitor.release();
-            }
-#endif
-            if (cache->inproc)
-                m_lock.reset(Mutex::create());
-        }
-
-        ~StoredSession() {
-            m_obj.destroy();
-            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
-        }
-
-        Lockable* lock() {
-            if (m_lock.get())
-                m_lock->lock();
-            return this;
-        }
-        void unlock() {
-            if (m_lock.get())
-                m_lock->unlock();
-            else
-                delete this;
-        }
-
-        const char* getID() const {
-            return m_obj.name();
-        }
-        const char* getApplicationID() const {
-            return m_obj["application_id"].string();
-        }
-        const char* getClientAddress() const {
-            return m_obj["client_addr"].first().string();
-        }
-
-        const char* getClientAddress(const char* family) const {
-            if (family)
-                return m_obj["client_addr"][family].string();
-            return nullptr;
-        }
-        void setClientAddress(const char* client_addr) {
-            DDF obj = m_obj["client_addr"];
-            if (!obj.isstruct())
-                obj = m_obj.addmember("client_addr").structure();
-            obj.addmember(getAddressFamily(client_addr)).string(client_addr);
-        }
-
-        const char* getEntityID() const {
-            return m_obj["entity_id"].string();
-        }
-        const char* getProtocol() const {
-            return m_obj["protocol"].string();
-        }
-        const char* getAuthnInstant() const {
-            return m_obj["authn_instant"].string();
-        }
-#ifndef SHIBSP_LITE
-        const saml2::NameID* getNameID() const {
-            return m_nameid.get();
-        }
-#endif
-        const char* getSessionIndex() const {
-            return m_obj["session_index"].string();
-        }
-        const char* getAuthnContextClassRef() const {
-            return m_obj["authncontext_class"].string();
-        }
-        const char* getAuthnContextDeclRef() const {
-            return m_obj["authncontext_decl"].string();
-        }
-        const vector<Attribute*>& getAttributes() const {
-            if (m_attributes.empty())
-                unmarshallAttributes();
-            return m_attributes;
-        }
-        const multimap<string,const Attribute*>& getIndexedAttributes() const {
-            if (m_attributeIndex.empty()) {
-                if (m_attributes.empty())
-                    unmarshallAttributes();
-                for (vector<Attribute*>::const_iterator a = m_attributes.begin(); a != m_attributes.end(); ++a) {
-                    const vector<string>& aliases = (*a)->getAliases();
-                    for (vector<string>::const_iterator alias = aliases.begin(); alias != aliases.end(); ++alias)
-                        m_attributeIndex.insert(multimap<string,const Attribute*>::value_type(*alias, *a));
-                }
-            }
-            return m_attributeIndex;
-        }
-        const vector<const char*>& getAssertionIDs() const {
-            if (m_ids.empty()) {
-                DDF ids = m_obj["assertions"];
-                DDF id = ids.first();
-                while (id.isstring()) {
-                    m_ids.push_back(id.string());
-                    id = ids.next();
-                }
-            }
-            return m_ids;
-        }
-
-        void validate(const Application& application, const char* client_addr, time_t* timeout);
-
-#ifndef SHIBSP_LITE
-        void addAttributes(const vector<Attribute*>& attributes);
-        const Assertion* getAssertion(const char* id) const;
-        void addAssertion(Assertion* assertion);
-#endif
-
-        time_t getExpiration() const { return m_expires; }
-        time_t getLastAccess() const { return m_lastAccess; }
-
-    private:
-        void unmarshallAttributes() const;
-
-        DDF m_obj;
-#ifndef SHIBSP_LITE
-        scoped_ptr<saml2::NameID> m_nameid;
-        mutable map< string,boost::shared_ptr<Assertion> > m_tokens;
-#endif
-        mutable vector<Attribute*> m_attributes;
-        mutable multimap<string,const Attribute*> m_attributeIndex;
-        mutable vector<const char*> m_ids;
-
-        SSCache* m_cache;
-        time_t m_expires,m_lastAccess;
-        scoped_ptr<Mutex> m_lock;
-    };
-
-    SessionCache* SHIBSP_DLLLOCAL StorageServiceCacheFactory(const DOMElement* const & e)
-    {
-        return new SSCache(e);
-    }
-}
-
-Session* SessionCache::find(const Application& application, HTTPRequest& request, const char* client_addr, time_t* timeout)
+SessionCache* SHIBSP_DLLLOCAL StorageServiceCacheFactory(const DOMElement* const & e)
 {
-    return find(application, const_cast<const HTTPRequest&>(request), client_addr, timeout);
+    return new SSCache(e);
 }
 
 void SHIBSP_API shibsp::registerSessionCaches()
@@ -397,463 +82,17 @@ void SHIBSP_API shibsp::registerSessionCaches()
     SPConfig::getConfig().SessionCacheManager.registerFactory(STORAGESERVICE_SESSION_CACHE, StorageServiceCacheFactory);
 }
 
-Session::Session()
-{
-}
-
-Session::~Session()
-{
-}
-
-void StoredSession::unmarshallAttributes() const
-{
-    Attribute* attribute;
-    DDF attrs = m_obj["attributes"];
-    DDF attr = attrs.first();
-    while (!attr.isnull()) {
-        try {
-            attribute = Attribute::unmarshall(attr);
-            m_attributes.push_back(attribute);
-            if (m_cache->m_log.isDebugEnabled())
-                m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",
-                    attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
-        }
-        catch (AttributeException& ex) {
-            const char* id = attr.first().name();
-            m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
-        }
-        attr = attrs.next();
-    }
-}
-
-void StoredSession::validate(const Application& app, const char* client_addr, time_t* timeout)
-{
-    time_t now = time(nullptr);
-
-    // Basic expiration?
-    if (m_expires > 0) {
-        if (now > m_expires) {
-            m_cache->m_log.info("session expired (ID: %s)", getID());
-            throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
-        }
-    }
-
-    // Address check?
-    if (client_addr) {
-        const char* saddr = getClientAddress(getAddressFamily(client_addr));
-        if (saddr && *saddr) {
-            if (!XMLString::equals(saddr, client_addr)) {
-                m_cache->m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
-                throw RetryableProfileException(
-                    "Your IP address ($1) does not match the address recorded at the time the session was established.",
-                    params(1, client_addr)
-                    );
-            }
-            client_addr = nullptr;  // clear out parameter as signal that session need not be updated below
-        }
-        else {
-            m_cache->m_log.info("session (%s) not yet bound to client address type, binding it to (%s)", getID(), client_addr);
-        }
-    }
-
-    if (!timeout && !client_addr)
-        return;
-
-    if (!SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
-        DDF in("touch::" STORAGESERVICE_SESSION_CACHE "::SessionCache"), out;
-        DDFJanitor jin(in);
-        in.structure();
-        in.addmember("key").string(getID());
-        in.addmember("version").integer(m_obj["version"].integer());
-        in.addmember("application_id").string(app.getId());
-        if (client_addr)    // signals we need to bind an additional address to the session
-            in.addmember("client_addr").string(client_addr);
-        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
-            struct tm* ptime = gmtime(timeout);
-#else
-            struct tm res;
-            struct tm* ptime = gmtime_r(timeout,&res);
-#endif
-            char timebuf[32];
-            strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
-            in.addmember("timeout").string(timebuf);
-        }
-
-        try {
-            out=app.getServiceProvider().getListenerService()->send(in);
-        }
-        catch (...) {
-            out.destroy();
-            throw;
-        }
-
-        if (out.isstruct()) {
-            // We got an updated record back.
-            m_cache->m_log.debug("session updated, reconstituting it");
-            m_ids.clear();
-            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
-            m_attributes.clear();
-            m_attributeIndex.clear();
-            m_obj.destroy();
-            m_obj = out;
-        }
-    }
-    else {
-#ifndef SHIBSP_LITE
-        if (!m_cache->m_storage)
-            throw ConfigurationException("Session touch requires a StorageService.");
-
-        // Versioned read, since we already have the data in hand if it's current.
-        string record;
-        time_t lastAccess = 0;
-        int curver = m_obj["version"].integer();
-        int ver = m_cache->m_storage->readText(getID(), "session", &record, &lastAccess, curver);
-        if (ver == 0) {
-            m_cache->m_log.info("session (ID: %s) no longer in storage", getID());
-            throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
-        }
-
-        if (timeout) {
-            if (lastAccess == 0) {
-                m_cache->m_log.error("session (ID: %s) did not report time of last access", getID());
-                throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
-            }
-            // Adjust for expiration to recover last access time and check timeout.
-            unsigned long cacheTimeout = m_cache->getCacheTimeout(app);
-            lastAccess -= cacheTimeout;
-            if (*timeout > 0 && now - lastAccess >= *timeout) {
-                m_cache->m_log.info("session timed out (ID: %s)", getID());
-                throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
-            }
-
-            // Update storage expiration, if possible.
-            try {
-                m_cache->m_storage->updateContext(getID(), now + cacheTimeout);
-            }
-            catch (std::exception& ex) {
-                m_cache->m_log.error("failed to update session expiration: %s", ex.what());
-            }
-        }
-
-        if (ver > curver) {
-            // We got an updated record back.
-            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();
-            m_obj.destroy();
-            m_obj = newobj;
-        }
-
-        // We may need to write back a new address into the session using a versioned update loop.
-        if (client_addr) {
-            short attempts = 0;
-            do {
-                const char* saddr = getClientAddress(getAddressFamily(client_addr));
-                if (saddr) {
-                    // Something snuck in and bound the session to this address type, so it better match what we have.
-                    if (!XMLString::equals(saddr, client_addr)) {
-                        m_cache->m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
-                        throw RetryableProfileException(
-                            "Your IP address ($1) does not match the address recorded at the time the session was established.",
-                            params(1, client_addr)
-                            );
-                    }
-                    break;  // No need to update.
-                }
-                else {
-                    // Bind it into the session.
-                    setClientAddress(client_addr);
-                }
-
-                // Tentatively increment the version.
-                m_obj["version"].integer(m_obj["version"].integer() + 1);
-
-                ostringstream str;
-                str << m_obj;
-                record = str.str();
-
-                try {
-                    ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer() - 1);
-                }
-                catch (std::exception&) {
-                    m_obj["version"].integer(m_obj["version"].integer() - 1);
-                    throw;
-                }
-
-                if (ver <= 0) {
-                    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());
-                    throw IOException("Unable to update stored session.");
-                }
-                else if (ver < 0) {
-                    // Out of sync.
-                    if (++attempts > 10) {
-                        m_cache->m_log.error("failed to bind client address, 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
-        }
-#else
-        throw ConfigurationException("Session touch requires a StorageService.");
-#endif
-    }
-
-    m_lastAccess = now;
-}
-
-#ifndef SHIBSP_LITE
-
-void StoredSession::addAttributes(const vector<Attribute*>& attributes)
-{
-#ifdef _DEBUG
-    xmltooling::NDC ndc("addAttributes");
-#endif
-
-    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)
+SessionCache::SessionCache()
 {
-#ifdef _DEBUG
-    xmltooling::NDC ndc("addAssertion");
-#endif
-
-    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
-
-SessionCache::SessionCache()
+SessionCache::~SessionCache()
 {
 }
 
-SessionCache::~SessionCache()
+Session* SessionCache::find(const Application& application, HTTPRequest& request, const char* client_addr, time_t* timeout)
 {
+    return find(application, const_cast<const HTTPRequest&>(request), client_addr, timeout);
 }
 
 SSCache::SSCache(const DOMElement* e)
@@ -978,6 +217,53 @@ SSCache::~SSCache()
 #endif
 }
 
+unsigned long SSCache::getCacheTimeout(const Application& app) 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;
+    }
+    // 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 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;
+}
+
+string SSCache::active(const Application& app, const HTTPRequest& request)
+{
+    if (!m_inboundHeader.empty()) {
+        string session_id = request.getHeader(m_inboundHeader.c_str());
+        if (!session_id.empty())
+            return session_id;
+    }
+    pair<string, const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
+    const char* session_id = request.getCookie(shib_cookie.first.c_str());
+    return (session_id ? session_id : "");
+}
+
+Session* SSCache::find(const Application& app, const HTTPRequest& request, const char* client_addr, time_t* timeout)
+{
+    string id = active(app, request);
+    if (!id.empty())
+        return find(app, id.c_str(), client_addr, timeout);
+    return nullptr;
+}
+
 #ifndef SHIBSP_LITE
 
 void SSCache::test()
@@ -1133,7 +419,7 @@ void SSCache::insert(
     string caddr(httpRequest.getRemoteAddr());
     if (!caddr.empty()) {
         DDF addrobj = obj.addmember("client_addr").structure();
-        addrobj.addmember(getAddressFamily(caddr.c_str())).string(caddr.c_str());
+        addrobj.addmember(StoredSession::getAddressFamily(caddr.c_str())).string(caddr.c_str());
     }
 
     if (issuer)
@@ -2005,7 +1291,7 @@ void SSCache::receive(DDF& in, ostream& out)
                 istringstream src(record);
                 src >> sessionobj;
                 ver = sessionobj["version"].integer();
-                const char* saddr = sessionobj["client_addr"][getAddressFamily(client_addr)].string();
+                const char* saddr = sessionobj["client_addr"][StoredSession::getAddressFamily(client_addr)].string();
                 if (saddr) {
                     // Something snuck in and bound the session to this address type, so it better match what we have.
                     if (!XMLString::equals(saddr, client_addr)) {
@@ -2019,7 +1305,7 @@ void SSCache::receive(DDF& in, ostream& out)
                 }
                 else {
                     // Bind it into the session.
-                    sessionobj["client_addr"].addmember(getAddressFamily(client_addr)).string(client_addr);
+                    sessionobj["client_addr"].addmember(StoredSession::getAddressFamily(client_addr)).string(client_addr);
                 }
 
                 // Tentatively increment the version.
diff --git a/shibsp/impl/StorageServiceSessionCache.h b/shibsp/impl/StorageServiceSessionCache.h
new file mode 100644
index 0000000..1a00060
--- /dev/null
+++ b/shibsp/impl/StorageServiceSessionCache.h
@@ -0,0 +1,160 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * StorageServiceSessionCache.h
+ *
+ * StorageService-based SessionCache implementation header.
+ */
+
+#ifndef __shibsp_sscache_h__
+#define __shibsp_sscache_h__
+
+#include <shibsp/SessionCache.h>
+#include <shibsp/remoting/ListenerService.h>
+
+#include <ctime>
+#include <boost/shared_ptr.hpp>
+
+namespace xmltooling {
+    class CondWait;
+    class RWLock;
+    class Thread;
+}
+
+#ifndef SHIBSP_LITE
+namespace opensaml {
+    class Assertion;
+
+    namespace saml2 {
+        class NameID;
+    };
+
+    namespace saml2md {
+        class EntityDescriptor;
+    };
+};
+#endif
+
+namespace shibsp {
+
+    class StoredSession;
+    class SSCache : public shibsp::SessionCache
+#ifndef SHIBSP_LITE
+        ,public virtual shibsp::Remoted
+#endif
+    {
+    public:
+        SSCache(const xercesc::DOMElement* e);
+        virtual ~SSCache();
+
+#ifndef SHIBSP_LITE
+        void receive(shibsp::DDF& in, std::ostream& out);
+
+        void insert(
+            std::string& sessionID,
+            const shibsp::Application& app,
+            const xmltooling::HTTPRequest& httpRequest,
+            xmltooling::HTTPResponse& httpResponse,
+            time_t expires,
+            const opensaml::saml2md::EntityDescriptor* issuer=nullptr,
+            const XMLCh* protocol=nullptr,
+            const opensaml::saml2::NameID* nameid=nullptr,
+            const XMLCh* authn_instant=nullptr,
+            const XMLCh* session_index=nullptr,
+            const XMLCh* authncontext_class=nullptr,
+            const XMLCh* authncontext_decl=nullptr,
+            const std::vector<const opensaml::Assertion*>* tokens=nullptr,
+            const std::vector<shibsp::Attribute*>* attributes=nullptr
+            );
+        std::vector<std::string>::size_type logout(
+            const shibsp::Application& app,
+            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);
+        }
+        bool matches(
+            const shibsp::Application& app,
+            const xmltooling::HTTPRequest& request,
+            const opensaml::saml2md::EntityDescriptor* issuer,
+            const opensaml::saml2::NameID& nameid,
+            const std::set<std::string>* indexes
+            );
+#endif
+        shibsp::Session* find(const shibsp::Application& app, const char* key, const char* client_addr=nullptr, time_t* timeout=nullptr);
+        void remove(const shibsp::Application& app, const char* key);
+        void test();
+
+        std::string active(const shibsp::Application& app, const xmltooling::HTTPRequest& request);
+        shibsp::Session* find(const shibsp::Application& app, const xmltooling::HTTPRequest& request, const char* client_addr = nullptr, time_t* timeout = nullptr);
+        shibsp::Session* find(const shibsp::Application& app, xmltooling::HTTPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr);
+        void remove(const shibsp::Application& app, const xmltooling::HTTPRequest& request, xmltooling::HTTPResponse* response=nullptr);
+
+        unsigned long getCacheTimeout(const shibsp::Application& app) const;
+
+        xmltooling::logging::Category& m_log;
+        bool inproc;
+#ifndef SHIBSP_LITE
+        xmltooling::StorageService* m_storage;
+        xmltooling::StorageService* m_storage_lite;
+#endif
+
+    private:
+#ifndef SHIBSP_LITE
+        // 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);
+        std::vector<std::string>::size_type _logout(
+            const shibsp::Application& app,
+            const opensaml::saml2md::EntityDescriptor* issuer,
+            const opensaml::saml2::NameID& nameid,
+            const std::set<std::string>* indexes,
+            time_t expires,
+            std::vector<std::string>& sessions,
+            short attempts
+            );
+        bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const opensaml::saml2::NameID& n1, const opensaml::saml2::NameID& n2) const;
+        shibsp::LogoutEvent* newLogoutEvent(const shibsp::Application& app) const;
+
+        bool m_cacheAssertions,m_reverseIndex;
+        std::set<xmltooling::xstring> m_excludedNames;
+#endif
+        const xercesc::DOMElement* m_root;         // Only valid during initialization
+        unsigned long m_inprocTimeout,m_cacheTimeout,m_cacheAllowance;
+        std::string m_inboundHeader,m_outboundHeader;
+
+        // inproc means we buffer sessions in memory
+        boost::scoped_ptr<xmltooling::RWLock> m_lock;
+        std::map<std::string,StoredSession*> m_hashtable;
+
+        // management of buffered sessions
+        void dormant(const char* key);
+        static void* cleanup_fn(void*);
+
+        bool shutdown;
+        boost::scoped_ptr<xmltooling::CondWait> shutdown_wait;
+        boost::scoped_ptr<xmltooling::Thread> cleanup_thread;
+    };
+
+}
+#endif /* __shibsp_sscache_h__ */
diff --git a/shibsp/impl/StoredSession.cpp b/shibsp/impl/StoredSession.cpp
new file mode 100644
index 0000000..94fbcf6
--- /dev/null
+++ b/shibsp/impl/StoredSession.cpp
@@ -0,0 +1,594 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * StoredSession.cpp
+ *
+ * Implementation of Session subclass used by StorageService-backed SessionCache.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "ServiceProvider.h"
+#include "attribute/Attribute.h"
+#include "impl/StoredSession.h"
+#include "impl/StorageServiceSessionCache.h"
+
+#include <xmltooling/util/NDC.h>
+#include <xmltooling/util/Threads.h>
+
+#ifndef SHIBSP_LITE
+# include <saml/exceptions.h>
+# include <saml/saml2/core/Assertions.h>
+# include <xmltooling/XMLToolingConfig.h>
+# include <xmltooling/util/ParserPool.h>
+# include <xmltooling/util/StorageService.h>
+# include <xercesc/util/XMLStringTokenizer.hpp>
+using namespace opensaml::saml2md;
+#else
+# include <xercesc/util/XMLDateTime.hpp>
+#endif
+
+using namespace shibsp;
+using namespace opensaml;
+using namespace xmltooling;
+using namespace boost;
+using namespace std;
+
+Session::Session()
+{
+}
+
+Session::~Session()
+{
+}
+
+const char* StoredSession::getAddressFamily(const char* addr) {
+    if (strchr(addr, ':'))
+        return "6";
+    else
+        return "4";
+}
+
+StoredSession::StoredSession(SSCache* cache, DDF& obj)
+    : m_obj(obj), m_cache(cache), m_expires(0), m_lastAccess(time(nullptr))
+{
+    // Check for old address format.
+    if (m_obj["client_addr"].isstring()) {
+        const char* saddr = m_obj["client_addr"].string();
+        DDF addrobj = m_obj["client_addr"].structure();
+        if (saddr && *saddr) {
+            addrobj.addmember(getAddressFamily(saddr)).string(saddr);
+        }
+    }
+
+    auto_ptr_XMLCh exp(m_obj["expires"].string());
+    if (exp.get()) {
+        XMLDateTime iso(exp.get());
+        iso.parseDateTime();
+        m_expires = iso.getEpoch();
+    }
+
+#ifndef SHIBSP_LITE
+    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);
+        m_nameid.reset(saml2::NameIDBuilder::buildNameID());
+        m_nameid->unmarshall(doc->getDocumentElement(), true);
+        janitor.release();
+    }
+#endif
+    if (cache->inproc)
+        m_lock.reset(Mutex::create());
+}
+
+StoredSession::~StoredSession()
+{
+    m_obj.destroy();
+    for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+}
+
+Lockable* StoredSession::lock()
+{
+    if (m_lock.get())
+        m_lock->lock();
+    return this;
+}
+void StoredSession::unlock()
+{
+    if (m_lock.get())
+        m_lock->unlock();
+    else
+        delete this;
+}
+
+const multimap<string, const Attribute*>& StoredSession::getIndexedAttributes() const
+{
+    if (m_attributeIndex.empty()) {
+        if (m_attributes.empty())
+            unmarshallAttributes();
+        for (vector<Attribute*>::const_iterator a = m_attributes.begin(); a != m_attributes.end(); ++a) {
+            const vector<string>& aliases = (*a)->getAliases();
+            for (vector<string>::const_iterator alias = aliases.begin(); alias != aliases.end(); ++alias)
+                m_attributeIndex.insert(multimap<string, const Attribute*>::value_type(*alias, *a));
+        }
+    }
+    return m_attributeIndex;
+}
+
+const vector<const char*>& StoredSession::getAssertionIDs() const
+{
+    if (m_ids.empty()) {
+        DDF ids = m_obj["assertions"];
+        DDF id = ids.first();
+        while (id.isstring()) {
+            m_ids.push_back(id.string());
+            id = ids.next();
+        }
+    }
+    return m_ids;
+}
+
+void StoredSession::unmarshallAttributes() const
+{
+    Attribute* attribute;
+    DDF attrs = m_obj["attributes"];
+    DDF attr = attrs.first();
+    while (!attr.isnull()) {
+        try {
+            attribute = Attribute::unmarshall(attr);
+            m_attributes.push_back(attribute);
+            if (m_cache->m_log.isDebugEnabled())
+                m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",
+                    attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
+        }
+        catch (AttributeException& ex) {
+            const char* id = attr.first().name();
+            m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
+        }
+        attr = attrs.next();
+    }
+}
+
+void StoredSession::validate(const Application& app, const char* client_addr, time_t* timeout)
+{
+    time_t now = time(nullptr);
+
+    // Basic expiration?
+    if (m_expires > 0) {
+        if (now > m_expires) {
+            m_cache->m_log.info("session expired (ID: %s)", getID());
+            throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
+        }
+    }
+
+    // Address check?
+    if (client_addr) {
+        const char* saddr = getClientAddress(getAddressFamily(client_addr));
+        if (saddr && *saddr) {
+            if (!XMLString::equals(saddr, client_addr)) {
+                m_cache->m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
+                throw RetryableProfileException(
+                    "Your IP address ($1) does not match the address recorded at the time the session was established.",
+                    params(1, client_addr)
+                    );
+            }
+            client_addr = nullptr;  // clear out parameter as signal that session need not be updated below
+        }
+        else {
+            m_cache->m_log.info("session (%s) not yet bound to client address type, binding it to (%s)", getID(), client_addr);
+        }
+    }
+
+    if (!timeout && !client_addr)
+        return;
+
+    if (!SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+        DDF in("touch::" STORAGESERVICE_SESSION_CACHE "::SessionCache"), out;
+        DDFJanitor jin(in);
+        in.structure();
+        in.addmember("key").string(getID());
+        in.addmember("version").integer(m_obj["version"].integer());
+        in.addmember("application_id").string(app.getId());
+        if (client_addr)    // signals we need to bind an additional address to the session
+            in.addmember("client_addr").string(client_addr);
+        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
+            struct tm* ptime = gmtime(timeout);
+#else
+            struct tm res;
+            struct tm* ptime = gmtime_r(timeout,&res);
+#endif
+            char timebuf[32];
+            strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+            in.addmember("timeout").string(timebuf);
+        }
+
+        try {
+            out=app.getServiceProvider().getListenerService()->send(in);
+        }
+        catch (...) {
+            out.destroy();
+            throw;
+        }
+
+        if (out.isstruct()) {
+            // We got an updated record back.
+            m_cache->m_log.debug("session updated, reconstituting it");
+            m_ids.clear();
+            for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+            m_attributes.clear();
+            m_attributeIndex.clear();
+            m_obj.destroy();
+            m_obj = out;
+        }
+    }
+    else {
+#ifndef SHIBSP_LITE
+        if (!m_cache->m_storage)
+            throw ConfigurationException("Session touch requires a StorageService.");
+
+        // Versioned read, since we already have the data in hand if it's current.
+        string record;
+        time_t lastAccess = 0;
+        int curver = m_obj["version"].integer();
+        int ver = m_cache->m_storage->readText(getID(), "session", &record, &lastAccess, curver);
+        if (ver == 0) {
+            m_cache->m_log.info("session (ID: %s) no longer in storage", getID());
+            throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
+        }
+
+        if (timeout) {
+            if (lastAccess == 0) {
+                m_cache->m_log.error("session (ID: %s) did not report time of last access", getID());
+                throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
+            }
+            // Adjust for expiration to recover last access time and check timeout.
+            unsigned long cacheTimeout = m_cache->getCacheTimeout(app);
+            lastAccess -= cacheTimeout;
+            if (*timeout > 0 && now - lastAccess >= *timeout) {
+                m_cache->m_log.info("session timed out (ID: %s)", getID());
+                throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
+            }
+
+            // Update storage expiration, if possible.
+            try {
+                m_cache->m_storage->updateContext(getID(), now + cacheTimeout);
+            }
+            catch (std::exception& ex) {
+                m_cache->m_log.error("failed to update session expiration: %s", ex.what());
+            }
+        }
+
+        if (ver > curver) {
+            // We got an updated record back.
+            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();
+            m_obj.destroy();
+            m_obj = newobj;
+        }
+
+        // We may need to write back a new address into the session using a versioned update loop.
+        if (client_addr) {
+            short attempts = 0;
+            do {
+                const char* saddr = getClientAddress(getAddressFamily(client_addr));
+                if (saddr) {
+                    // Something snuck in and bound the session to this address type, so it better match what we have.
+                    if (!XMLString::equals(saddr, client_addr)) {
+                        m_cache->m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
+                        throw RetryableProfileException(
+                            "Your IP address ($1) does not match the address recorded at the time the session was established.",
+                            params(1, client_addr)
+                            );
+                    }
+                    break;  // No need to update.
+                }
+                else {
+                    // Bind it into the session.
+                    setClientAddress(client_addr);
+                }
+
+                // Tentatively increment the version.
+                m_obj["version"].integer(m_obj["version"].integer() + 1);
+
+                ostringstream str;
+                str << m_obj;
+                record = str.str();
+
+                try {
+                    ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer() - 1);
+                }
+                catch (std::exception&) {
+                    m_obj["version"].integer(m_obj["version"].integer() - 1);
+                    throw;
+                }
+
+                if (ver <= 0) {
+                    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());
+                    throw IOException("Unable to update stored session.");
+                }
+                else if (ver < 0) {
+                    // Out of sync.
+                    if (++attempts > 10) {
+                        m_cache->m_log.error("failed to bind client address, 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
+        }
+#else
+        throw ConfigurationException("Session touch requires a StorageService.");
+#endif
+    }
+
+    m_lastAccess = now;
+}
+
+#ifndef SHIBSP_LITE
+
+void StoredSession::addAttributes(const vector<Attribute*>& attributes)
+{
+#ifdef _DEBUG
+    xmltooling::NDC ndc("addAttributes");
+#endif
+
+    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)
+{
+#ifdef _DEBUG
+    xmltooling::NDC ndc("addAssertion");
+#endif
+
+    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
new file mode 100644
index 0000000..d5ac966
--- /dev/null
+++ b/shibsp/impl/StoredSession.h
@@ -0,0 +1,153 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
+ */
+
+/**
+ * StoredSession.h
+ *
+ * Internal declaration of Session subclass used by StorageService-backed SessionCache.
+ */
+
+#ifndef __shibsp_storedsession_h__
+#define __shibsp_storedsession_h__
+
+#include <shibsp/SessionCache.h>
+#include <shibsp/remoting/ddf.h>
+
+#include <ctime>
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace xmltooling {
+    class Mutex;
+};
+
+#ifndef SHIBSP_LITE
+namespace opensaml {
+    class Assertion;
+
+    namespace saml2 {
+        class NameID;
+    };
+};
+#endif
+
+namespace shibsp {
+
+    class SSCache;
+
+    class StoredSession : public virtual shibsp::Session
+    {
+    public:
+        StoredSession(SSCache* cache, shibsp::DDF& obj);
+
+        virtual ~StoredSession();
+
+        xmltooling::Lockable* lock();
+        void unlock();
+
+        const char* getID() const {
+            return m_obj.name();
+        }
+        const char* getApplicationID() const {
+            return m_obj["application_id"].string();
+        }
+        const char* getClientAddress() const {
+            return m_obj["client_addr"].first().string();
+        }
+
+        const char* getClientAddress(const char* family) const {
+            if (family)
+                return m_obj["client_addr"][family].string();
+            return nullptr;
+        }
+        void setClientAddress(const char* client_addr) {
+            shibsp::DDF obj = m_obj["client_addr"];
+            if (!obj.isstruct())
+                obj = m_obj.addmember("client_addr").structure();
+            obj.addmember(getAddressFamily(client_addr)).string(client_addr);
+        }
+
+        const char* getEntityID() const {
+            return m_obj["entity_id"].string();
+        }
+        const char* getProtocol() const {
+            return m_obj["protocol"].string();
+        }
+        const char* getAuthnInstant() const {
+            return m_obj["authn_instant"].string();
+        }
+#ifndef SHIBSP_LITE
+        const opensaml::saml2::NameID* getNameID() const {
+            return m_nameid.get();
+        }
+#endif
+        const char* getSessionIndex() const {
+            return m_obj["session_index"].string();
+        }
+        const char* getAuthnContextClassRef() const {
+            return m_obj["authncontext_class"].string();
+        }
+        const char* getAuthnContextDeclRef() const {
+            return m_obj["authncontext_decl"].string();
+        }
+        const std::vector<shibsp::Attribute*>& getAttributes() const {
+            if (m_attributes.empty())
+                unmarshallAttributes();
+            return m_attributes;
+        }
+        const std::multimap<std::string, const shibsp::Attribute*>& getIndexedAttributes() const;
+
+        const std::vector<const char*>& getAssertionIDs() const;
+
+        void validate(const shibsp::Application& application, const char* client_addr, time_t* timeout);
+
+#ifndef SHIBSP_LITE
+        void addAttributes(const std::vector<shibsp::Attribute*>& attributes);
+        const opensaml::Assertion* getAssertion(const char* id) const;
+        void addAssertion(opensaml::Assertion* assertion);
+#endif
+
+        time_t getExpiration() const { return m_expires; }
+        time_t getLastAccess() const { return m_lastAccess; }
+
+        // Allows the cache to bind sessions to multiple client address
+        // families based on whatever this function returns.
+        static const char* getAddressFamily(const char* addr);
+
+    private:
+        void unmarshallAttributes() const;
+
+        shibsp::DDF m_obj;
+#ifndef SHIBSP_LITE
+        boost::scoped_ptr<opensaml::saml2::NameID> m_nameid;
+        mutable std::map< std::string,boost::shared_ptr<opensaml::Assertion> > m_tokens;
+#endif
+        mutable std::vector<shibsp::Attribute*> m_attributes;
+        mutable std::multimap<std::string,const shibsp::Attribute*> m_attributeIndex;
+        mutable std::vector<const char*> m_ids;
+
+        SSCache* m_cache;
+        time_t m_expires,m_lastAccess;
+        boost::scoped_ptr<xmltooling::Mutex> m_lock;
+    };
+
+}
+
+#endif /* __shibsp_storedsession_h__ */

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


More information about the commits mailing list