Starting to refactor session cache, eliminated IConfig class.
[shibboleth/cpp-sp.git] / shib-target / shib-ccache.cpp
index f8934ec..bcbfa9d 100644 (file)
 # include <unistd.h>
 #endif
 
-#include <shib/shib-threads.h>
-
-#include <log4cpp/Category.hh>
-
+#include <ctime>
 #include <algorithm>
 #include <sstream>
 #include <stdexcept>
+#include <shibsp/SPConfig.h>
 
 #ifdef HAVE_LIBDMALLOCXX
 #include <dmalloc.h>
 #endif
 
-using namespace std;
-using namespace log4cpp;
-using namespace saml;
-using namespace shibboleth;
+using namespace shibsp;
 using namespace shibtarget;
+using namespace saml;
+using namespace opensaml::saml2md;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
+using xmlsignature::CredentialResolver;
 
 static const XMLCh cleanupInterval[] =
 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
@@ -75,6 +76,10 @@ static const XMLCh propagateErrors[] =
 { chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
   chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
 };
+static const XMLCh writeThrough[] =
+{ chLatin_w, chLatin_r, chLatin_i, chLatin_t, chLatin_e,
+  chLatin_T, chLatin_h, chLatin_r, chLatin_o, chLatin_u, chLatin_g, chLatin_h, chNull
+};
 
 
 /*
@@ -170,32 +175,18 @@ public:
 
     string insert(
         const IApplication* application,
-        const IRoleDescriptor* source,
+        const RoleDescriptor* role,
         const char* client_addr,
         const SAMLSubject* subject,
         const char* authnContext,
-        SAMLResponse* tokens
+        const SAMLResponse* tokens
     );
     ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
     void remove(const char* key, const IApplication* application, const char* client_addr);
 
+    bool setBackingStore(ISessionCacheStore*) { return false; }
+
 private:
-    // The front-end cache is never loaded from the "back door".
-    void load(
-        const char* key,
-        const IApplication* application,
-        const IRoleDescriptor* source,
-        const char* client_addr,
-        const char* providerId,
-        const char* subject,
-        const char* authnContext,
-        const char* tokens,
-        int majorVersion,
-        int minorVersion,
-        time_t created=0,
-        time_t accessed=0
-        ) { throw SAMLException("Unsupported operation."); }
-    
     Category* m_log;
 };
 
@@ -208,11 +199,11 @@ StubCache::StubCache(const DOMElement* e) : m_log(&Category::getInstance(SHIBT_L
 
 string StubCache::insert(
     const IApplication* application,
-    const IRoleDescriptor* source,
+    const RoleDescriptor* role,
     const char* client_addr,
     const SAMLSubject* subject,
     const char* authnContext,
-    SAMLResponse* tokens
+    const SAMLResponse* tokens
     )
 {
     DDF in("SessionCache::insert"),out;
@@ -220,10 +211,8 @@ string StubCache::insert(
     in.structure();
     in.addmember("application_id").string(application->getId());
     in.addmember("client_address").string(client_addr);
-    auto_ptr_char provid(source->getEntityDescriptor()->getId());
+    xmltooling::auto_ptr_char provid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
     in.addmember("provider_id").string(provid.get());
-    auto_ptr_char prot(source->getProtocolSupportEnumeration().next());
-    in.addmember("protocol").string(prot.get());
     in.addmember("major_version").integer(1);
     in.addmember("minor_version").integer(tokens->getMinorVersion());
     in.addmember("authn_context").string(authnContext);
@@ -235,10 +224,10 @@ string StubCache::insert(
     os << *tokens;
     in.addmember("tokens.unfiltered").string(os.str().c_str());
 
-    out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
+    out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);
     if (out["key"].isstring())
         return out["key"].string();
-    throw InvalidSessionException("A remoted cache insertion operation did not return a usable session key.");
+    throw opensaml::RetryableProfileException("A remoted cache insertion operation did not return a usable session key.");
 }
 
 ISessionCacheEntry* StubCache::find(const char* key, const IApplication* application, const char* client_addr)
@@ -251,7 +240,7 @@ ISessionCacheEntry* StubCache::find(const char* key, const IApplication* applica
     in.addmember("client_address").string(client_addr);
     
     try {
-        out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
+        out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);
         if (!out.isstruct()) {
             out.destroy();
             return NULL;
@@ -275,10 +264,9 @@ void StubCache::remove(const char* key, const IApplication* application, const c
     in.addmember("application_id").string(application->getId());
     in.addmember("client_address").string(client_addr);
     
-    ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
+    SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);
 }
 
-
 /*
  * Long-lived cache entries that store the actual sessions and
  * wrap attribute query/refresh/filtering
@@ -291,19 +279,18 @@ public:
         MemorySessionCache* cache,
         const char* key,
         const IApplication* application,
-        const IRoleDescriptor* source,
+        const RoleDescriptor* role,
         const char* client_addr,
         const SAMLSubject* subject,
         const char* authnContext,
-        SAMLResponse* tokens
+        const SAMLResponse* tokens
         );
     MemorySessionCacheEntry(
         MemorySessionCache* cache,
         const char* key,
         const IApplication* application,
-        const IRoleDescriptor* source,
+        const RoleDescriptor* role,
         const char* client_addr,
-        const char* providerId,
         const char* subject,
         const char* authnContext,
         const char* tokens,
@@ -316,24 +303,19 @@ public:
 
     void lock() { m_lock->lock(); }
     void unlock() { m_lock->unlock(); }
-
-    pair<const char*,const SAMLResponse*> getTokens(bool xml=true, bool obj=false) const
-    { populate(); return StubCacheEntry::getTokens(xml,obj); }
-    pair<const char*,const SAMLResponse*> getFilteredTokens(bool xml=true, bool obj=false) const
-    { return StubCacheEntry::getFilteredTokens(xml,obj); }
-
+    
     HRESULT isValid(const IApplication* application, const char* client_addr) const;
+    void populate(const IApplication* application, const EntityDescriptor* source, bool initial=false) const;
     bool checkApplication(const IApplication* application) { return (m_obj["application_id"]==application->getId()); }
+    time_t created() const { return m_sessionCreated; }
     time_t lastAccess() const { return m_lastAccess; }
     const DDF& getDDF() const { return m_obj; }
   
 private:
     bool hasAttributes(const SAMLResponse& r) const;
     time_t calculateExpiration(const SAMLResponse& r) const;
-    void populate() const;            // wraps process of checking cache, and repopulating if need be
-    bool responseValid() const;       // checks validity of existing response
-    pair<SAMLResponse*,SAMLResponse*> getNewResponse() const;   // wraps an actual query
-    SAMLResponse* filter(SAMLResponse* r, const IApplication* application, const IRoleDescriptor* source) const;
+    pair<SAMLResponse*,SAMLResponse*> getNewResponse(const IApplication* application, const EntityDescriptor* source) const;
+    SAMLResponse* filter(const SAMLResponse* r, const IApplication* application, const RoleDescriptor* role) const;
   
     time_t m_sessionCreated;
     mutable time_t m_responseExpiration, m_lastAccess, m_lastRetry;
@@ -345,7 +327,7 @@ private:
 /*
  * The actual in-memory session cache implementation.
  */
-class MemorySessionCache : public virtual ISessionCache, public virtual IRemoted
+class MemorySessionCache : public virtual ISessionCache, public virtual Remoted
 {
 public:
     MemorySessionCache(const DOMElement* e);
@@ -355,42 +337,31 @@ public:
 
     string insert(
         const IApplication* application,
-        const IRoleDescriptor* source,
+        const RoleDescriptor* role,
         const char* client_addr,
         const SAMLSubject* subject,
         const char* authnContext,
-        SAMLResponse* tokens
+        const SAMLResponse* tokens
     );
     ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
     void remove(const char* key, const IApplication* application, const char* client_addr);
 
     void cleanup();
 
-private:
-    void load(
-        const char* key,
-        const IApplication* application,
-        const IRoleDescriptor* source,
-        const char* client_addr,
-        const char* providerId,
-        const char* subject,
-        const char* authnContext,
-        const char* tokens,
-        int majorVersion,
-        int minorVersion,
-        time_t created,
-        time_t accessed
-        );
+    bool setBackingStore(ISessionCacheStore* store);
 
+private:
     const DOMElement* m_root;         // Only valid during initialization
     RWLock* m_lock;
     map<string,MemorySessionCacheEntry*> m_hashtable;
 
     Category* m_log;
-    IRemoted* restoreInsert;
-    IRemoted* restoreFind;
-    IRemoted* restoreRemove;
+    Remoted* restoreInsert;
+    Remoted* restoreFind;
+    Remoted* restoreRemove;
+    ISessionCacheStore* m_sink;
 
+    void dormant(const char* key);
     static void* cleanup_fcn(void*);
     bool shutdown;
     CondWait* shutdown_wait;
@@ -399,7 +370,7 @@ private:
     // extracted config settings
     unsigned int m_AATimeout,m_AAConnectTimeout;
     unsigned int m_defaultLifetime,m_retryInterval;
-    bool m_strictValidity,m_propagateErrors;
+    bool m_strictValidity,m_propagateErrors,m_writeThrough;
     friend class MemorySessionCacheEntry;
 };
 
@@ -407,14 +378,13 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
     MemorySessionCache* cache,
     const char* key,
     const IApplication* application,
-    const IRoleDescriptor* source,
+    const RoleDescriptor* role,
     const char* client_addr,
     const SAMLSubject* subject,
     const char* authnContext,
-    SAMLResponse* tokens
+    const SAMLResponse* tokens
     ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
 {
-    auto_ptr<SAMLResponse> unfiltered(tokens);
     m_sessionCreated = m_lastAccess = time(NULL);
 
     // Store session properties in DDF.
@@ -422,7 +392,7 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
     m_obj.addmember("key").string(key);
     m_obj.addmember("client_address").string(client_addr);
     m_obj.addmember("application_id").string(application->getId());
-    auto_ptr_char pid(source ? source->getEntityDescriptor()->getId() : tokens->getAssertions().next()->getIssuer());
+    xmltooling::auto_ptr_char pid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
     m_obj.addmember("provider_id").string(pid.get());
     m_obj.addmember("major_version").integer(1);
     m_obj.addmember("minor_version").integer(tokens->getMinorVersion());
@@ -442,7 +412,7 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
 
     if (hasAttributes(*tokens)) {
         // Filter attributes in the response.
-        auto_ptr<SAMLResponse> filtered(filter(tokens, application, source));
+        auto_ptr<SAMLResponse> filtered(filter(tokens, application, role));
         
         // Calculate expiration.
         m_responseExpiration=calculateExpiration(*(filtered.get()));
@@ -456,8 +426,7 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
 
         // Save actual objects only if we're running inprocess. The subject needs to be
         // owned by the entry, so we'll defer creation of a cloned copy.
-        if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
-            m_pUnfiltered=unfiltered.release();
+        if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
             if (m_obj["tokens.filtered"].isstring())
                 m_pFiltered=filtered.release();
         }
@@ -470,7 +439,7 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
     }
 
     // Transaction Logging
-    auto_ptr_char hname(subject->getNameIdentifier()->getName());
+    xmltooling::auto_ptr_char hname(subject->getNameIdentifier()->getName());
     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
     stc.getTransactionLog().infoStream() <<
         "New session (ID: " <<
@@ -491,9 +460,8 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
     MemorySessionCache* cache,
     const char* key,
     const IApplication* application,
-    const IRoleDescriptor* source,
+    const RoleDescriptor* role,
     const char* client_addr,
-    const char* providerId,
     const char* subject,
     const char* authnContext,
     const char* tokens,
@@ -515,7 +483,8 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
     m_obj.addmember("key").string(key);
     m_obj.addmember("client_address").string(client_addr);
     m_obj.addmember("application_id").string(application->getId());
-    m_obj.addmember("provider_id").string(providerId);
+    xmltooling::auto_ptr_char pid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
+    m_obj.addmember("provider_id").string(pid.get());
     m_obj.addmember("subject").string(subject);
     m_obj.addmember("authn_context").string(authnContext);
     m_obj.addmember("tokens.unfiltered").string(tokens);
@@ -523,7 +492,7 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
     m_obj.addmember("minor_version").integer(minorVersion);
 
     if (hasAttributes(*(unfiltered.get()))) {
-        auto_ptr<SAMLResponse> filtered(filter(unfiltered.get(), application, source));
+        auto_ptr<SAMLResponse> filtered(filter(unfiltered.get(), application, role));
     
         // Calculate expiration.
         m_responseExpiration=calculateExpiration(*(filtered.get()));
@@ -536,7 +505,7 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
             m_obj.addmember("tokens.filtered").string(fstr.c_str());
 
         // Save actual objects only if we're running inprocess.
-        if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
+        if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
             m_pUnfiltered=unfiltered.release();
             if (m_obj["tokens.filtered"].isstring())
                 m_pFiltered=filtered.release();
@@ -552,20 +521,19 @@ MemorySessionCacheEntry::MemorySessionCacheEntry(
 
 MemorySessionCacheEntry::~MemorySessionCacheEntry()
 {
-    if (m_log->isDebugEnabled())
-        m_log->debug("deleting cache entry (ID: %s)", m_obj["key"].string());
     delete m_lock;
 }
 
 HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* client_addr) const
 {
 #ifdef _DEBUG
-    saml::NDC ndc("isValid");
+    xmltooling::NDC ndc("isValid");
 #endif
+
     // Obtain validation rules from application settings.
-    bool checkIPAddress=true;
+    bool consistentIPAddress=true;
     int lifetime=0,timeout=0;
-    const IPropertySet* props=app->getPropertySet("Sessions");
+    const PropertySet* props=app->getPropertySet("Sessions");
     if (props) {
         pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
         if (p.first)
@@ -573,9 +541,9 @@ HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* cl
         p=props->getUnsignedInt("timeout");
         if (p.first)
             timeout = p.second;
-        pair<bool,bool> pcheck=props->getBool("checkAddress");
+        pair<bool,bool> pcheck=props->getBool("consistentIPAddress");
         if (pcheck.first)
-            checkIPAddress = pcheck.second;
+            consistentIPAddress = pcheck.second;
     }
     
     if (m_log->isDebugEnabled())
@@ -589,11 +557,22 @@ HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* cl
     }
 
     if (timeout > 0 && now-m_lastAccess >= timeout) {
-        m_log->info("session timed out (ID: %s)", m_obj["key"].string());
-        return SESSION_E_EXPIRED;
+        // May need to query sink first to find out if another cluster member has been used.
+        if (m_cache->m_sink && m_cache->m_writeThrough) {
+            if (NOERROR!=m_cache->m_sink->onRead(m_obj["key"].string(),m_lastAccess))
+                m_log->error("cache store failed to return last access timestamp");
+            if (now-m_lastAccess >= timeout) {
+                m_log->info("session timed out (ID: %s)", m_obj["key"].string());
+                return SESSION_E_EXPIRED;
+            }
+        }
+        else {
+            m_log->info("session timed out (ID: %s)", m_obj["key"].string());
+            return SESSION_E_EXPIRED;
+        }
     }
 
-    if (checkIPAddress) {
+    if (consistentIPAddress) {
         if (m_log->isDebugEnabled())
             m_log->debug("comparing client address %s against %s", client_addr, getClientAddress());
         if (strcmp(client_addr, getClientAddress())) {
@@ -603,7 +582,13 @@ HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* cl
     }
 
     m_lastAccess=now;
-    populate();
+
+    if (m_cache->m_sink && m_cache->m_writeThrough && timeout > 0) {
+        // Update sink with last access data, if possible.
+        if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),NULL,m_lastAccess)))
+            m_log->error("cache store failed to update last access timestamp");
+    }
+
     return NOERROR;
 }
 
@@ -654,10 +639,10 @@ time_t MemorySessionCacheEntry::calculateExpiration(const SAMLResponse& r) const
     return expiration;
 }
 
-void MemorySessionCacheEntry::populate() const
+void MemorySessionCacheEntry::populate(const IApplication* application, const EntityDescriptor* source, bool initial) const
 {
 #ifdef _DEBUG
-    saml::NDC ndc("populate");
+    xmltooling::NDC ndc("populate");
 #endif
 
     // Do we have any attribute data cached?
@@ -665,7 +650,63 @@ void MemorySessionCacheEntry::populate() const
         // Can we use what we have?
         if (time(NULL) < m_responseExpiration)
             return;
-      
+        
+        // Possibly check the sink in case another cluster member already refreshed it.
+        if (m_cache->m_sink && m_cache->m_writeThrough) {
+            string tokensFromSink;
+            HRESULT hr=m_cache->m_sink->onRead(m_obj["key"].string(),tokensFromSink);
+            if (FAILED(hr))
+                m_log->error("cache store failed to return updated tokens");
+            else if (hr==NOERROR && tokensFromSink!=m_obj["tokens.unfiltered"].string()) {
+
+                // Bah...find role again.
+                const RoleDescriptor* role=source->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+                if (!role)
+                    role=source->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+                if (!role)
+                    role=source->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+                if (!role)
+                    role=source->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+                if (!role) {
+                    throw MetadataException("Unable to locate attribute-issuing role in metadata.");
+                }
+
+                // The tokens in the sink were different.
+                istringstream is(tokensFromSink);
+                auto_ptr<SAMLResponse> respFromSink(new SAMLResponse(is,m_obj["minor_version"].integer()));
+                auto_ptr<SAMLResponse> filteredFromSink(filter(respFromSink.get(),application,role));
+                time_t expFromSink=calculateExpiration(*(filteredFromSink.get()));
+                
+                // Recheck to see if the new tokens are valid.
+                if (expFromSink < time(NULL)) {
+                    m_log->info("loading replacement tokens into memory from cache store");
+                    m_obj["tokens"].destroy();
+                    delete m_pUnfiltered;
+                    delete m_pFiltered;
+                    m_pUnfiltered=m_pFiltered=NULL;
+                    m_obj.addmember("tokens.unfiltered").string(tokensFromSink.c_str());
+
+                    // Serialize filtered assertions (if changes were made).
+                    ostringstream os;
+                    os << *(filteredFromSink.get());
+                    string fstr=os.str();
+                    if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
+                        m_obj.addmember("tokens.filtered").string(fstr.c_str());
+                    
+                    // Save actual objects only if we're running inprocess.
+                    if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
+                        m_pUnfiltered=respFromSink.release();
+                        if (m_obj["tokens.filtered"].isstring())
+                            m_pFiltered=filteredFromSink.release();
+                    }
+
+                    m_responseExpiration=expFromSink;
+                    m_lastRetry=0;
+                    return;
+                }
+            }
+        }
+
         // If we're being strict, dump what we have and reset timestamps.
         if (m_cache->m_strictValidity) {
             m_log->info("strictly enforcing attribute validity, dumping expired data");
@@ -674,12 +715,16 @@ void MemorySessionCacheEntry::populate() const
             delete m_pFiltered;
             m_pUnfiltered=m_pFiltered=NULL;
             m_responseExpiration=0;
-            m_lastRetry=0; 
+            m_lastRetry=0;
+            if (m_cache->m_sink) {
+                if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),"")))
+                    m_log->error("cache store returned failure while clearing tokens from entry");
+            }
         }
     }
 
     try {
-        pair<SAMLResponse*,SAMLResponse*> new_responses=getNewResponse();
+        pair<SAMLResponse*,SAMLResponse*> new_responses=getNewResponse(application,source);
         auto_ptr<SAMLResponse> r1(new_responses.first),r2(new_responses.second);
         if (new_responses.first) {
             m_obj["tokens"].destroy();
@@ -704,12 +749,18 @@ void MemorySessionCacheEntry::populate() const
             m_responseExpiration=calculateExpiration(*new_responses.second);
 
             // Save actual objects only if we're running inprocess.
-            if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
+            if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
                 m_pUnfiltered=r1.release();
                 if (m_obj["tokens.filtered"].isstring())
                     m_pFiltered=r2.release();
             }
 
+            // Update backing store.
+            if (!initial && m_cache->m_sink) {
+                if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),m_obj["tokens.unfiltered"].string())))
+                    m_log->error("cache store returned failure while updating tokens in entry");
+            }
+
             m_lastRetry=0;
             m_log->debug("fetched and stored new response");
             STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
@@ -717,10 +768,10 @@ void MemorySessionCacheEntry::populate() const
             stc.releaseTransactionLog();
         }
     }
-    catch (SAMLException&) {
+    catch (exception&) {
         if (m_cache->m_propagateErrors)
             throw;
-        m_log->warn("suppressed SAML exception caught while trying to fetch attributes");
+        m_log->warn("suppressed exception caught while trying to fetch attributes");
     }
 #ifndef _DEBUG
     catch (...) {
@@ -731,10 +782,12 @@ void MemorySessionCacheEntry::populate() const
 #endif
 }
 
-pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() const
+pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse(
+    const IApplication* application, const EntityDescriptor* source
+    ) const
 {
 #ifdef _DEBUG
-    saml::NDC ndc("getNewResponse");
+    xmltooling::NDC ndc("getNewResponse");
 #endif
 
     // The retryInterval determines how often to poll an AA that might be down.
@@ -760,31 +813,15 @@ pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() cons
     stc.releaseTransactionLog();
 
 
-    // Caller must be holding the config lock.
-    // Lookup application for session to get providerId and attributes to request.
-    IConfig* conf=ShibTargetConfig::getConfig().getINI();
-    const IApplication* application=conf->getApplication(m_obj["application_id"].string());
-    if (!application) {
-        m_log->crit("unable to locate application for session, deleted?");
-        throw SAMLException("Unable to locate application for session, deleted?");
-    }
     pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
     if (!providerID.first) {
         m_log->crit("unable to determine ProviderID for application, not set?");
-        throw SAMLException("Unable to determine ProviderID for application, not set?");
-    }
-
-    // Try this request.
-    Metadata m(application->getMetadataProviders());
-    const IEntityDescriptor* site=m.lookup(m_obj["provider_id"].string());
-    if (!site) {
-        m_log->error("unable to locate identity provider's metadata for attribute query");
-        throw MetadataException("Unable to locate identity provider's metadata for attribute query.");
+        throw ConfigurationException("Unable to determine ProviderID for application, not set?");
     }
 
     // Try to locate an AA role.
-    const IAttributeAuthorityDescriptor* AA=site->getAttributeAuthorityDescriptor(
-        m_obj["minor_version"].integer()==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
+    const AttributeAuthorityDescriptor* AA=source->getAttributeAuthorityDescriptor(
+        m_obj["minor_version"].integer()==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM
         );
     if (!AA) {
         m_log->warn("unable to locate metadata for identity provider's Attribute Authority");
@@ -792,7 +829,7 @@ pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() cons
     }
 
     // Get protocol signing policy.
-    const IPropertySet* credUse=application->getCredentialUse(site);
+    const PropertySet* credUse=application->getCredentialUse(source);
     pair<bool,bool> signRequest=credUse ? credUse->getBool("signRequest") : make_pair(false,false);
     pair<bool,const char*> signatureAlg=credUse ? credUse->getString("signatureAlg") : pair<bool,const char*>(false,NULL);
     if (!signatureAlg.first)
@@ -818,8 +855,7 @@ pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() cons
         // Build a SAML Request....
         SAMLAttributeQuery* q=new SAMLAttributeQuery(
             new SAMLSubject(nameid),
-            providerID.second,
-            application->getAttributeDesignators().clone()
+            providerID.second
             );
         auto_ptr<SAMLRequest> req(new SAMLRequest(q));
         req->setMinorVersion(m_obj["minor_version"].integer());
@@ -827,10 +863,11 @@ pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() cons
         // Sign it?
         if (signRequest.first && signRequest.second && signingCred.first) {
             if (req->getMinorVersion()==1) {
-                Credentials creds(conf->getCredentialsProviders());
-                const ICredResolver* cr=creds.lookup(signingCred.second);
-                if (cr)
+                CredentialResolver* cr=SPConfig::getConfig().getServiceProvider()->getCredentialResolver(signingCred.second);
+                if (cr) {
+                    xmltooling::Locker locker(cr);
                     req->sign(cr->getKey(),cr->getCertificates(),signatureAlg.second,digestAlg.second);
+                }
                 else
                     m_log->error("unable to sign attribute query, specified credential (%s) was not found",signingCred.second);
             }
@@ -842,42 +879,31 @@ pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() cons
 
         // Call context object
         ShibHTTPHook::ShibHTTPHookCallContext ctx(credUse,AA);
-        Trust t(application->getTrustProviders());
         
         // Use metadata to locate endpoints.
-        Iterator<const IEndpoint*> endpoints=AA->getAttributeServiceManager()->getEndpoints();
-        while (!response && endpoints.hasNext()) {
-            const IEndpoint* ep=endpoints.next();
+        const vector<AttributeService*>& endpoints=AA->getAttributeServices();
+        for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {
             try {
                 // Get a binding object for this protocol.
-                const SAMLBinding* binding = application->getBinding(ep->getBinding());
+                const SAMLBinding* binding = application->getBinding((*ep)->getBinding());
                 if (!binding) {
-                    auto_ptr_char prot(ep->getBinding());
+                    xmltooling::auto_ptr_char prot((*ep)->getBinding());
                     m_log->warn("skipping binding on unsupported protocol (%s)", prot.get());
                     continue;
                 }
                 static const XMLCh https[] = {chLatin_h, chLatin_t, chLatin_t, chLatin_p, chLatin_s, chColon, chNull};
-                auto_ptr<SAMLResponse> r(binding->send(ep->getLocation(), *(req.get()), &ctx));
+                auto_ptr<SAMLResponse> r(binding->send((*ep)->getLocation(), *(req.get()), &ctx));
                 if (r->isSigned()) {
-                    if (!t.validate(*r,AA))
-                        throw TrustException("Unable to verify signed response message.");
+                    // TODO: trust stuff will be changing anyway...
+                    //if (!t.validate(*r,AA))
+                    //    throw TrustException("Unable to verify signed response message.");
                 }
-                else if (!ctx.isAuthenticated() || XMLString::compareNString(ep->getLocation(),https,6))
-                    throw TrustException("Response message was unauthenticated.");
+                else if (!ctx.isAuthenticated() || XMLString::compareNString((*ep)->getLocation(),https,6))
+                    throw XMLSecurityException("Response message was unauthenticated.");
                 response = r.release();
             }
-            catch (SAMLException& e) {
-                m_log->error("caught SAML exception during SAML attribute query: %s", e.what());
-                // Check for shib:InvalidHandle error and propagate it out.
-                Iterator<saml::QName> codes=e.getCodes();
-                if (codes.size()>1) {
-                    const saml::QName& code=codes[1];
-                    if (!XMLString::compareString(code.getNamespaceURI(),shibboleth::Constants::SHIB_NS) &&
-                        !XMLString::compareString(code.getLocalName(), shibboleth::Constants::InvalidHandle)) {
-                        codes.reset();
-                        throw InvalidHandleException(e.what(),params(),codes);
-                    }
-                }
+            catch (exception& e) {
+                m_log->error("caught exception during SAML attribute query: %s", e.what());
             }
         }
 
@@ -885,16 +911,39 @@ pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() cons
             if (signedResponse.first && signedResponse.second && !response->isSigned()) {
                 delete response;
                 m_log->error("unsigned response obtained, but we were told it must be signed.");
-                throw TrustException("Unable to obtain a signed response message.");
+                throw XMLSecurityException("Unable to obtain a signed response message.");
             }
             
+            // Iterate over the tokens and apply basic validation.
+            time_t now=time(NULL);
+            Iterator<SAMLAssertion*> assertions=response->getAssertions();
+            for (unsigned int a=0; a<assertions.size();) {
+                // Discard any assertions not issued by the right entity.
+                if (XMLString::compareString(source->getEntityID(),assertions[a]->getIssuer())) {
+                    xmltooling::auto_ptr_char bad(assertions[a]->getIssuer());
+                    m_log->warn("discarding assertion not issued by (%s), instead by (%s)",m_obj["provider_id"].string(),bad.get());
+                    response->removeAssertion(a);
+                    continue;
+                }
+
+                // Validate the token.
+                try {
+                    application->validateToken(assertions[a],now,AA,application->getTrustEngine());
+                    a++;
+                }
+                catch (exception&) {
+                    m_log->warn("assertion failed to validate, removing it from response");
+                    response->removeAssertion(a);
+                }
+            }
+
             // Run it through the filter.
             return make_pair(response,filter(response,application,AA));
         }
     }
-    catch (SAMLException& e) {
-        m_log->error("caught SAML exception during query to AA: %s", e.what());
-        annotateException(&e,AA);
+    catch (exception& e) {
+        m_log->error("caught exception during query to AA: %s", e.what());
+        throw;
     }
     
     m_log->error("no response obtained");
@@ -902,48 +951,14 @@ pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() cons
 }
 
 SAMLResponse* MemorySessionCacheEntry::filter(
-    SAMLResponse* r, const IApplication* application, const IRoleDescriptor* source
+    const SAMLResponse* r, const IApplication* application, const RoleDescriptor* role
     ) const
 {
-    const IPropertySet* credUse=application->getCredentialUse(source->getEntityDescriptor());
-    pair<bool,bool> signedAssertions=credUse ? credUse->getBool("signedAssertions") : make_pair(false,false);
-    Trust t(application->getTrustProviders());
-
-    // Examine each original assertion...
-    Iterator<SAMLAssertion*> assertions=r->getAssertions();
-    for (unsigned long i=0; i < assertions.size();) {
-        // Check signing policy.
-        if (signedAssertions.first && signedAssertions.second && !(assertions[i]->isSigned())) {
-            m_log->warn("removing unsigned assertion from response, in accordance with signedAssertions policy");
-            r->removeAssertion(i);
-            continue;
-        }
-
-        // Check any conditions.
-        bool pruned=false;
-        Iterator<SAMLCondition*> conds=assertions[i]->getConditions();
-        while (conds.hasNext()) {
-            SAMLAudienceRestrictionCondition* cond=dynamic_cast<SAMLAudienceRestrictionCondition*>(conds.next());
-            if (!cond || !cond->eval(application->getAudiences())) {
-                m_log->warn("assertion condition invalid, removing it");
-                r->removeAssertion(i);
-                pruned=true;
-                break;
-            }
-        }
-        if (pruned)
-            continue;
-        
-        // Check token signature.
-        if (assertions[i]->isSigned() && !t.validate(*(assertions[i]),source)) {
-            m_log->warn("signed assertion failed to validate, removing it");
-            r->removeAssertion(i);
-            continue;
-        }
-        i++;
-    }
+#ifdef _DEBUG
+    xmltooling::NDC ndc("filter");
+#endif
 
-    // Make a copy of whatever's left and process that against the AAP.
+    // Make a copy of the original and process that against the AAP.
     auto_ptr<SAMLResponse> copy(static_cast<SAMLResponse*>(r->clone()));
     copy->toDOM();
 
@@ -951,11 +966,11 @@ SAMLResponse* MemorySessionCacheEntry::filter(
     for (unsigned long j=0; j < copies.size();) {
         try {
             // Finally, filter the content.
-            AAP::apply(application->getAAPProviders(),*(copies[j]),source);
+            shibboleth::AAP::apply(application->getAAPProviders(),*(copies[j]),role);
             j++;
 
         }
-        catch (SAMLException&) {
+        catch (exception&) {
             m_log->info("no statements remain after AAP, removing assertion");
             copy->removeAssertion(j);
         }
@@ -983,7 +998,7 @@ SAMLResponse* MemorySessionCacheEntry::filter(
                 Iterator<SAMLAttribute*> attrs=state ? state->getAttributes() : EMPTY(SAMLAttribute*);
                 while (attrs.hasNext()) {
                     SAMLAttribute* attr=attrs.next();
-                    auto_ptr_char attrname(attr->getName());
+                    xmltooling::auto_ptr_char attrname(attr->getName());
                     tran.infoStream() << "\t" << attrname.get() << " (" << attr->getValues().size() << " values)";
                 }
             }
@@ -997,51 +1012,58 @@ SAMLResponse* MemorySessionCacheEntry::filter(
 
 MemorySessionCache::MemorySessionCache(const DOMElement* e)
     : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
-        m_strictValidity(true), m_propagateErrors(false), m_lock(RWLock::create()),
+        m_strictValidity(true), m_propagateErrors(false), m_writeThrough(false), m_lock(RWLock::create()),
         m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")),
-        restoreInsert(NULL),restoreFind(NULL),restoreRemove(NULL)
+        restoreInsert(NULL), restoreFind(NULL), restoreRemove(NULL), m_sink(NULL)
 {
-    const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
-    if (tag && *tag) {
-        m_AATimeout = XMLString::parseInt(tag);
-        if (!m_AATimeout)
-            m_AATimeout=30;
-    }
-    SAMLConfig::getConfig().timeout = m_AATimeout;
+    if (m_root) {
+        const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
+        if (tag && *tag) {
+            m_AATimeout = XMLString::parseInt(tag);
+            if (!m_AATimeout)
+                m_AATimeout=30;
+        }
 
-    tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
-    if (tag && *tag) {
-        m_AAConnectTimeout = XMLString::parseInt(tag);
-        if (!m_AAConnectTimeout)
-            m_AAConnectTimeout=15;
-    }
-    SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
-    
-    tag=m_root->getAttributeNS(NULL,defaultLifetime);
-    if (tag && *tag) {
-        m_defaultLifetime = XMLString::parseInt(tag);
-        if (!m_defaultLifetime)
-            m_defaultLifetime=1800;
-    }
+        tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
+        if (tag && *tag) {
+            m_AAConnectTimeout = XMLString::parseInt(tag);
+            if (!m_AAConnectTimeout)
+                m_AAConnectTimeout=15;
+        }
+        
+        tag=m_root->getAttributeNS(NULL,defaultLifetime);
+        if (tag && *tag) {
+            m_defaultLifetime = XMLString::parseInt(tag);
+            if (!m_defaultLifetime)
+                m_defaultLifetime=1800;
+        }
 
-    tag=m_root->getAttributeNS(NULL,retryInterval);
-    if (tag && *tag) {
-        m_retryInterval = XMLString::parseInt(tag);
-        if (!m_retryInterval)
-            m_retryInterval=300;
-    }
-    
-    tag=m_root->getAttributeNS(NULL,strictValidity);
-    if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
-        m_strictValidity=false;
+        tag=m_root->getAttributeNS(NULL,retryInterval);
+        if (tag && *tag) {
+            m_retryInterval = XMLString::parseInt(tag);
+            if (!m_retryInterval)
+                m_retryInterval=300;
+        }
         
-    tag=m_root->getAttributeNS(NULL,propagateErrors);
-    if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
-        m_propagateErrors=true;
+        tag=m_root->getAttributeNS(NULL,strictValidity);
+        if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
+            m_strictValidity=false;
+            
+        tag=m_root->getAttributeNS(NULL,propagateErrors);
+        if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
+            m_propagateErrors=true;
+
+        tag=m_root->getAttributeNS(NULL,writeThrough);
+        if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
+            m_writeThrough=true;
+    }
+
+    SAMLConfig::getConfig().timeout = m_AATimeout;
+    SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
 
     // Register for remoted messages.
-    IListener* listener=ShibTargetConfig::getConfig().getINI()->getListener();
-    if (listener && ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess)) {
+    ListenerService* listener=SPConfig::getConfig().getServiceProvider()->getListenerService(false);
+    if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
         restoreInsert=listener->regListener("SessionCache::insert",this);
         restoreFind=listener->regListener("SessionCache::find",this);
         restoreRemove=listener->regListener("SessionCache::remove",this);
@@ -1062,18 +1084,26 @@ MemorySessionCache::~MemorySessionCache()
     cleanup_thread->join(NULL);
 
     // Unregister remoted messages.
-    IListener* listener=ShibTargetConfig::getConfig().getINI()->getListener();
-    if (listener && ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess)) {
+    ListenerService* listener=SPConfig::getConfig().getServiceProvider()->getListenerService(false);
+    if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
         listener->unregListener("SessionCache::insert",this,restoreInsert);
         listener->unregListener("SessionCache::find",this,restoreFind);
         listener->unregListener("SessionCache::remove",this,restoreRemove);
     }
 
-    for_each(m_hashtable.begin(),m_hashtable.end(),shibtarget::cleanup<string,MemorySessionCacheEntry>);
+    for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,MemorySessionCacheEntry>());
     delete m_lock;
     delete shutdown_wait;
 }
 
+bool MemorySessionCache::setBackingStore(ISessionCacheStore* store)
+{
+    if (m_sink && store!=m_sink)
+        return false;
+    m_sink=store;
+    return true;
+}
+
 /*
  * IPC message definitions:
  * 
@@ -1083,7 +1113,6 @@ MemorySessionCache::~MemorySessionCache()
  *      application_id
  *      client_address
  *      provider_id
- *      protocol
  *      major_version
  *      minor_version
  *      authn_context
@@ -1120,13 +1149,18 @@ MemorySessionCache::~MemorySessionCache()
 
 DDF MemorySessionCache::receive(const DDF& in)
 {
+#ifdef _DEBUG
+    xmltooling::NDC ndc("receive");
+#endif
+
     // Find application.
+    xmltooling::Locker confLocker(SPConfig::getConfig().getServiceProvider());
     const char* aid=in["application_id"].string();
-    const IApplication* app=aid ? ShibTargetConfig::getConfig().getINI()->getApplication(aid) : NULL;
+    const IApplication* app=aid ? dynamic_cast<const IApplication*>(SPConfig::getConfig().getServiceProvider()->getApplication(aid)) : NULL;
     if (!app) {
         // Something's horribly wrong.
         m_log->error("couldn't find application (%s) for session", aid ? aid : "(missing)");
-        throw SAMLException("Unable to locate application for session, deleted?");
+        throw ConfigurationException("Unable to locate application for session, deleted?");
     }
 
     if (!strcmp(in.name(),"SessionCache::find")) {
@@ -1145,7 +1179,7 @@ DDF MemorySessionCache::receive(const DDF& in)
             entry->unlock();
             return dup;
         }
-        catch (SAMLException&) {
+        catch (exception&) {
             remove(key,app,client_address);
             throw;
         }
@@ -1164,167 +1198,254 @@ DDF MemorySessionCache::receive(const DDF& in)
         // Check required parameters.
         const char* client_address=in["client_address"].string();
         const char* provider_id=in["provider_id"].string();
-        const char* protocol=in["protocol"].string();
         const char* authn_context=in["authn_context"].string();
         const char* subject=in["subject"].string();
         const char* tokens=in["tokens.unfiltered"].string();
-        if (!client_address || !provider_id || !protocol || !authn_context || !subject || !tokens)
+        if (!client_address || !provider_id || !authn_context || !subject || !tokens)
             throw SAMLException("Required parameters missing in call to SessionCache::insert");
         int minor=in["minor_version"].integer();
         
-        // Locate role descriptor to use in filtering.
-        Metadata m(app->getMetadataProviders());
-        const IEntityDescriptor* site=m.lookup(provider_id);
+        // Locate entity descriptor to use in filtering.
+        MetadataProvider* m=app->getMetadataProvider();
+        xmltooling::Locker locker(m);
+        const EntityDescriptor* site=m->getEntityDescriptor(provider_id);
         if (!site) {
             m_log->error("unable to locate issuing identity provider's metadata");
             throw MetadataException("Unable to locate identity provider's metadata.");
         }
-        auto_ptr_XMLCh prot(protocol);
-        const IIDPSSODescriptor* IDP=site->getIDPSSODescriptor(prot.get());
-        if (!IDP)
-            m_log->warn("unable to locate metadata for identity provider role, scoped attributes may be limited");
-        
+        const RoleDescriptor* role=site->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+        if (!role)
+            role=site->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+        if (!role)
+            role=site->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+        if (!role)
+            role=site->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+        if (!role) {
+            m_log->error("unable to locate attribute-issuing role in identity provider's metadata");
+            throw MetadataException("Unable to locate attribute-issuing role in identity provider's metadata.");
+        }
+
         // Deserialize XML for insert method.
         istringstream subis(subject);
         auto_ptr<SAMLSubject> pSubject(new SAMLSubject(subis));
         istringstream tokis(tokens);
-        SAMLResponse* pTokens=new SAMLResponse(tokis,minor);
+        auto_ptr<SAMLResponse> pTokens(new SAMLResponse(tokis,minor));
         
         // Insert the data and return the cache key.
-        string key=insert(app,IDP,client_address,pSubject.get(),authn_context,pTokens);
+        string key=insert(app,role,client_address,pSubject.get(),authn_context,pTokens.get());
         
         DDF out(NULL);
         out.structure();
         out.addmember("key").string(key.c_str());
         return out;
     }
-    throw ListenerException("unsupported operation ($1)",params(1,in.name()));
+    throw ListenerException("Unsupported operation ($1)",xmltooling::params(1,in.name()));
 }
 
 string MemorySessionCache::insert(
     const IApplication* application,
-    const IRoleDescriptor* source,
+    const RoleDescriptor* role,
     const char* client_addr,
     const SAMLSubject* subject,
     const char* authnContext,
-    SAMLResponse* tokens
+    const SAMLResponse* tokens
     )
 {
+#ifdef _DEBUG
+    xmltooling::NDC ndc("insert");
+#endif
+
     SAMLIdentifier id;
-    auto_ptr_char key(id);
+    xmltooling::auto_ptr_char key(id);
 
     if (m_log->isDebugEnabled())
         m_log->debug("creating new cache entry for application %s: \"%s\"", application->getId(), key.get());
 
-    MemorySessionCacheEntry* entry = new MemorySessionCacheEntry(
-        this,
-        key.get(),
-        application,
-        source,
-        client_addr,
-        subject,
-        authnContext,
-        tokens
+    auto_ptr<MemorySessionCacheEntry> entry(
+        new MemorySessionCacheEntry(
+            this,
+            key.get(),
+            application,
+            role,
+            client_addr,
+            subject,
+            authnContext,
+            tokens
+            )
         );
+    entry->populate(application,dynamic_cast<EntityDescriptor*>(role->getParent()),true);
+
+    if (m_sink) {
+        HRESULT hr=m_sink->onCreate(key.get(),application,entry.get(),1,tokens->getMinorVersion(),entry->created());
+        if (FAILED(hr)) {
+            m_log->error("cache store returned failure while storing new entry");
+            throw IOException("Unable to record new session in cache store.");
+        }
+    }
 
     m_lock->wrlock();
-    m_hashtable[key.get()]=entry;
+    m_hashtable[key.get()]=entry.release();
     m_lock->unlock();
 
     return key.get();
 }
 
-void MemorySessionCache::load(
-    const char* key,
-    const IApplication* application,
-    const IRoleDescriptor* source,
-    const char* client_addr,
-    const char* providerId,
-    const char* subject,
-    const char* authnContext,
-    const char* tokens,
-    int minorVersion,
-    int majorVersion,
-    time_t created,
-    time_t accessed
-    )
-{
-    if (m_log->isDebugEnabled())
-        m_log->debug("loading cache entry back into memory for application %s: \"%s\"", application->getId(), key);
-
-    MemorySessionCacheEntry* entry = new MemorySessionCacheEntry(
-        this,
-        key,
-        application,
-        source,
-        client_addr,
-        providerId,
-        subject,
-        authnContext,
-        tokens,
-        minorVersion,
-        majorVersion,
-        created,
-        accessed
-        );
-
-    m_lock->wrlock();
-    m_hashtable[key]=entry;
-    m_lock->unlock();
-}
-
 ISessionCacheEntry* MemorySessionCache::find(const char* key, const IApplication* application, const char* client_addr)
 {
+#ifdef _DEBUG
+    xmltooling::NDC ndc("find");
+#endif
+
     m_log->debug("searching memory cache for key (%s)", key);
-    ReadLock rwlock(m_lock);
+    m_lock->rdlock();
 
     map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
     if (i==m_hashtable.end()) {
+        m_lock->unlock();
         m_log->debug("no match found");
-        return NULL;
+        if (!m_sink)
+            return NULL;    // no backing store to search
+
+        m_log->debug("searching backing store");
+        string appid,addr,pid,sub,ac,tokens;
+        int major,minor;
+        time_t created,accessed;
+        HRESULT hr=m_sink->onRead(key,appid,addr,pid,sub,ac,tokens,major,minor,created,accessed);
+        if (hr==S_FALSE)
+            return NULL;
+        else if (FAILED(hr)) {
+            m_log->error("cache store returned failure during search");
+            return NULL;
+        }
+        const IApplication* eapp=dynamic_cast<const IApplication*>(SPConfig::getConfig().getServiceProvider()->getApplication(appid.c_str()));
+        if (!eapp) {
+            // Something's horribly wrong.
+            m_log->error("couldn't find application (%s) for session", appid.c_str());
+            if (FAILED(m_sink->onDelete(key)))
+                m_log->error("cache store returned failure during delete");
+            return NULL;
+        }
+        if (m_log->isDebugEnabled())
+            m_log->debug("loading cache entry (ID: %s) back into memory for application (%s)", key, appid.c_str());
+
+        // Locate role to use in filtering.
+        MetadataProvider* m=eapp->getMetadataProvider();
+        xmltooling::Locker locker(m);
+        const EntityDescriptor* site=m->getEntityDescriptor(pid.c_str());
+        if (!site) {
+            m_log->error("unable to locate issuing identity provider's metadata");
+            if (FAILED(m_sink->onDelete(key)))
+                m_log->error("cache store returned failure during delete");
+            return NULL;
+        }
+        const RoleDescriptor* role=site->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+        if (!role)
+            role=site->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+        if (!role)
+            role=site->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+        if (!role)
+            role=site->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+        if (!role) {
+            m_log->error("unable to locate attribute-issuing role in identity provider's metadata");
+            if (FAILED(m_sink->onDelete(key)))
+                m_log->error("cache store returned failure during delete");
+            return NULL;
+        }
+
+        MemorySessionCacheEntry* entry = new MemorySessionCacheEntry(
+            this,
+            key,
+            eapp,
+            role,
+            addr.c_str(),
+            sub.c_str(),
+            ac.c_str(),
+            tokens.c_str(),
+            major,
+            minor,
+            created,
+            accessed
+            );
+        m_lock->wrlock();
+        m_hashtable[key]=entry;
+        m_lock->unlock();
+
+        // Downgrade to a read lock and repeat the initial search.
+        m_lock->rdlock();
+        i=m_hashtable.find(key);
+        if (i==m_hashtable.end()) {
+            m_lock->unlock();
+            m_log->warn("cache entry was loaded from backing store, but disappeared after lock downgrade");
+            return NULL;
+        }
     }
-    m_log->debug("match found");
+    else
+        m_log->debug("match found");
 
     // Check for application mismatch (could also do this with partitioned caches by application ID)
     if (!i->second->checkApplication(application)) {
+        m_lock->unlock();
         m_log->crit("An application (%s) attempted to access another application's session!", application->getId());
         return NULL;
     }
     
     // Check for timeouts, expiration, address mismatch, etc (also updates last access)
     // Use the return code to assign specific error messages.
-    HRESULT hr=i->second->isValid(application, client_addr);
-    if (FAILED(hr)) {
-        Metadata m(application->getMetadataProviders());
-        switch (hr) {
-            case SESSION_E_EXPIRED: {
-                InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
-                annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
-            }
-            
-            case SESSION_E_ADDRESSMISMATCH: {
-                InvalidSessionException ex(
-                    SESSION_E_ADDRESSMISMATCH,
-                    "Your IP address (%1) does not match the address recorded at the time the session was established.",
-                    params(1,client_addr)
-                    );
-                annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
-            }
-            
-            default: {
-                InvalidSessionException ex(hr, "Your session is invalid.");
-                annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
+    try {
+        HRESULT hr=i->second->isValid(application, client_addr);
+        if (FAILED(hr)) {
+            MetadataProvider* m=application->getMetadataProvider();
+            xmltooling::Locker locker(m);
+            switch (hr) {
+                case SESSION_E_EXPIRED: {
+                    opensaml::RetryableProfileException ex("Your session has expired, and you must re-authenticate.");
+                    annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
+                }
+                
+                case SESSION_E_ADDRESSMISMATCH: {
+                    opensaml::RetryableProfileException ex(
+                        "Your IP address ($1) does not match the address recorded at the time the session was established.",
+                        xmltooling::params(1,client_addr)
+                        );
+                    annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
+                }
+                
+                default: {
+                    opensaml::RetryableProfileException ex("Your session is invalid.");
+                    annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
+                }
             }
         }
     }
+    catch (...) {
+        m_lock->unlock();
+        throw;
+    }
 
     // Lock the cache entry for the caller -- they have to unlock it.
     i->second->lock();
+    m_lock->unlock();
+
+    try {
+        // Make sure the entry has valid tokens.
+        MetadataProvider* m=application->getMetadataProvider();
+        xmltooling::Locker locker(m);
+        i->second->populate(application,m->getEntityDescriptor(i->second->getProviderId()));
+    }
+    catch (...) {
+        i->second->unlock();
+        throw;
+    }
+
     return i->second;
 }
 
 void MemorySessionCache::remove(const char* key, const IApplication* application, const char* client_addr)
 {
+#ifdef _DEBUG
+    xmltooling::NDC ndc("remove");
+#endif
+
     m_log->debug("removing cache entry with key (%s)", key);
 
     // lock the cache for writing, which means we know nobody is sitting in find()
@@ -1345,11 +1466,14 @@ void MemorySessionCache::remove(const char* key, const IApplication* application
     // unlock the cache
     m_lock->unlock();
 
-    // we can release the cache entry lock because we know we're not in the cache anymore
     entry->unlock();
 
-    // Now delete the entry
-    delete entry;
+    // Notify sink. Smart ptr will make sure entry gets deleted.
+    auto_ptr<ISessionCacheEntry> entrywrap(entry);
+    if (m_sink) {
+        if (FAILED(m_sink->onDelete(key)))
+            m_log->error("cache store failed to delete entry");
+    }
 
     // Transaction Logging
     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
@@ -1357,10 +1481,47 @@ void MemorySessionCache::remove(const char* key, const IApplication* application
     stc.releaseTransactionLog();
 }
 
+void MemorySessionCache::dormant(const char* key)
+{
+#ifdef _DEBUG
+    xmltooling::NDC ndc("dormant");
+#endif
+
+    m_log->debug("purging old cache entry with key (%s)", key);
+
+    // lock the cache for writing, which means we know nobody is sitting in find()
+    m_lock->wrlock();
+
+    // grab the entry from the database.
+    map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
+    if (i==m_hashtable.end()) {
+        m_lock->unlock();
+        return;
+    }
+
+    // ok, remove the entry and lock it
+    MemorySessionCacheEntry* entry=i->second;
+    m_hashtable.erase(key);
+    entry->lock();
+    
+    // unlock the cache
+    m_lock->unlock();
+
+    // we can release the cache entry lock because we know we're not in the cache anymore
+    entry->unlock();
+
+    auto_ptr<ISessionCacheEntry> entrywrap(entry);
+    if (m_sink && !m_writeThrough) {
+        // Update sink with last access data. Wrapper will make sure entry gets deleted.
+        if (FAILED(m_sink->onUpdate(key,NULL,entry->lastAccess())))
+            m_log->error("cache store failed to update last access timestamp");
+    }
+}
+
 void MemorySessionCache::cleanup()
 {
 #ifdef _DEBUG
-    saml::NDC ndc("cleanup()");
+    xmltooling::NDC ndc("cleanup()");
 #endif
 
     int rerun_timer = 0;
@@ -1384,15 +1545,13 @@ void MemorySessionCache::cleanup()
 
     mutex->lock();
 
-    m_log->info("Cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
+    m_log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
 
     while (!shutdown) {
         shutdown_wait->timedwait(mutex,rerun_timer);
         if (shutdown)
             break;
 
-        m_log->debug("Cleanup thread running...");
-
         // Ok, let's run through the cleanup process and clean out
         // really old sessions.  This is a two-pass process.  The
         // first pass is done holding a read-lock while we iterate over
@@ -1416,19 +1575,16 @@ void MemorySessionCache::cleanup()
         }
         m_lock->unlock();
     
-        m_log->info("deleting %d old items.", stale_keys.size());
+        if (!stale_keys.empty()) {
+            m_log->info("purging %d old sessions", stale_keys.size());
     
-        // Pass 2: walk through the list of stale entries and remove them from the cache
-        for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); j++) {
-            remove(j->c_str(),NULL,NULL);
-            // Transaction Logging
-            STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
-            stc.getTransactionLog().infoStream() << "Purged expired session from memory (ID: " << j->c_str() << ")";
-            stc.releaseTransactionLog();
+            // Pass 2: walk through the list of stale entries and remove them from the cache
+            for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); j++)
+                dormant(j->c_str());
         }
     }
 
-    m_log->info("Cleanup thread finished.");
+    m_log->info("cleanup thread finished.");
 
     mutex->unlock();
     delete mutex;
@@ -1439,18 +1595,20 @@ void* MemorySessionCache::cleanup_fcn(void* cache_p)
 {
     MemorySessionCache* cache = reinterpret_cast<MemorySessionCache*>(cache_p);
 
+#ifndef WIN32
     // First, let's block all signals 
     Thread::mask_all_signals();
+#endif
 
     // Now run the cleanup process.
     cache->cleanup();
     return NULL;
 }
 
-IPlugIn* MemoryCacheFactory(const DOMElement* e)
+SessionCache* MemoryCacheFactory(const DOMElement* const & e)
 {
     // If this is a long-lived process, we return the "real" cache.
-    if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess))
+    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
         return new MemorySessionCache(e);
     // Otherwise, we return a stubbed front-end that remotes calls to the real cache.
     return new StubCache(e);