/*\r
- * Copyright 2001-2005 Internet2\r
+ * Copyright 2001-2007 Internet2\r
* \r
* Licensed under the Apache License, Version 2.0 (the "License");\r
* you may not use this file except in compliance with the License.\r
#include "exceptions.h"\r
#include "ServiceProvider.h"\r
#include "SessionCache.h"\r
+#include "TransactionLog.h"\r
#include "attribute/Attribute.h"\r
#include "remoting/ListenerService.h"\r
#include "util/SPConstants.h"\r
\r
#include <sstream>\r
+#include <log4cpp/Category.hh>\r
#include <xmltooling/XMLToolingConfig.h>\r
+#include <xmltooling/util/NDC.h>\r
#include <xmltooling/util/XMLHelper.h>\r
\r
using namespace shibsp;\r
using namespace opensaml::saml2md;\r
using namespace opensaml;\r
using namespace xmltooling;\r
+using namespace log4cpp;\r
using namespace std;\r
\r
namespace shibsp {\r
\r
+ class RemotedCache;\r
class RemotedSession : public virtual Session\r
{\r
public:\r
- RemotedSession(const char* key, DDF& obj) : m_key(key), m_obj(obj), m_nameid(NULL) {\r
+ RemotedSession(RemotedCache* cache, DDF& obj) : m_version(obj["version"].integer()), m_obj(obj),\r
+ m_nameid(NULL), m_expires(0), m_lastAccess(time(NULL)), m_cache(cache), m_lock(NULL) {\r
const char* nameid = obj["nameid"].string();\r
if (!nameid)\r
throw FatalProfileException("NameID missing from remotely cached session.");\r
\r
- // Parse and bind the document into an XMLObject.\r
+ // Parse and bind the NameID into an XMLObject.\r
istringstream instr(nameid);\r
DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
XercesJanitor<DOMDocument> janitor(doc);\r
n->unmarshall(doc->getDocumentElement(), true);\r
janitor.release();\r
\r
- // TODO: Process attributes...\r
+ auto_ptr_XMLCh exp(m_obj["expires"].string());\r
+ if (exp.get()) {\r
+ DateTime iso(exp.get());\r
+ iso.parseDateTime();\r
+ m_expires = iso.getEpoch();\r
+ }\r
\r
+ m_lock = Mutex::create();\r
m_nameid = n.release();\r
}\r
\r
~RemotedSession() {\r
+ delete m_lock;\r
m_obj.destroy();\r
delete m_nameid;\r
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
- for_each(m_tokens.begin(), m_tokens.end(), xmltooling::cleanup_pair<string,RootObject>());\r
+ for_each(m_attributes.begin(), m_attributes.end(), cleanup_const_pair<string,Attribute>());\r
+ for_each(m_tokens.begin(), m_tokens.end(), cleanup_pair<string,Assertion>());\r
}\r
\r
Lockable* lock() {\r
+ m_lock->lock();\r
return this;\r
}\r
void unlock() {\r
- delete this;\r
+ m_lock->unlock();\r
+ }\r
+\r
+ const char* getApplicationID() const {\r
+ return m_obj["application_id"].string();\r
}\r
- \r
const char* getClientAddress() const {\r
- return m_obj["client_address"].string();\r
+ return m_obj["client_addr"].string();\r
}\r
const char* getEntityID() const {\r
return m_obj["entity_id"].string();\r
const char* getAuthnContextDeclRef() const {\r
return m_obj["authncontext_decl"].string();\r
}\r
- const vector<const Attribute*>& getAttributes() const {\r
+ const map<string,const Attribute*>& getAttributes() const {\r
+ if (m_attributes.empty())\r
+ unmarshallAttributes();\r
return m_attributes;\r
}\r
const vector<const char*>& getAssertionIDs() const {\r
if (m_ids.empty()) {\r
- DDF id = m_obj["assertion_ids"].first();\r
+ DDF id = m_obj["assertions"].first();\r
while (id.isstring()) {\r
m_ids.push_back(id.string());\r
id = id.next();\r
return m_ids;\r
}\r
\r
- void addAttributes(const vector<Attribute*>& attributes);\r
- const RootObject* getAssertion(const char* id) const;\r
- void addAssertion(RootObject* assertion);\r
+ const Assertion* getAssertion(const char* id) const;\r
+\r
+ void addAttributes(const vector<Attribute*>& attributes) {\r
+ throw ConfigurationException("addAttributes method not implemented by this session cache plugin.");\r
+ }\r
+ void addAssertion(Assertion* assertion) {\r
+ throw ConfigurationException("addAssertion method not implemented by this session cache plugin.");\r
+ }\r
+\r
+ time_t expires() const { return m_expires; }\r
+ time_t lastAccess() const { return m_lastAccess; }\r
+ void validate(const Application& application, const char* client_addr, time_t timeout, bool local=true);\r
\r
private:\r
- string m_key;\r
+ void unmarshallAttributes() const;\r
+\r
+ int m_version;\r
mutable DDF m_obj;\r
saml2::NameID* m_nameid;\r
- vector<const Attribute*> m_attributes;\r
+ mutable map<string,const Attribute*> m_attributes;\r
mutable vector<const char*> m_ids;\r
- mutable map<string,RootObject*> m_tokens;\r
+ mutable map<string,Assertion*> m_tokens;\r
+ time_t m_expires,m_lastAccess;\r
+ RemotedCache* m_cache;\r
+ Mutex* m_lock;\r
};\r
\r
class RemotedCache : public SessionCache\r
{\r
public:\r
RemotedCache(const DOMElement* e);\r
- ~RemotedCache() {}\r
+ ~RemotedCache();\r
\r
string insert(\r
time_t expires,\r
const char* session_index=NULL,\r
const char* authncontext_class=NULL,\r
const char* authncontext_decl=NULL,\r
- const RootObject* ssoToken=NULL,\r
+ const vector<const Assertion*>* tokens=NULL,\r
const vector<Attribute*>* attributes=NULL\r
);\r
Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t timeout=0);\r
void remove(const char* key, const Application& application, const char* client_addr);\r
+ \r
+ void cleanup();\r
+ \r
+ Category& m_log;\r
+ private:\r
+ const DOMElement* m_root; // Only valid during initialization\r
+ RWLock* m_lock;\r
+ map<string,RemotedSession*> m_hashtable;\r
+ \r
+ void dormant(const char* key);\r
+ static void* cleanup_fn(void*);\r
+ bool shutdown;\r
+ CondWait* shutdown_wait;\r
+ Thread* cleanup_thread;\r
};\r
\r
SessionCache* SHIBSP_DLLLOCAL RemotedCacheFactory(const DOMElement* const & e)\r
}\r
}\r
\r
-void RemotedSession::addAttributes(const vector<Attribute*>& attributes)\r
+void RemotedSession::unmarshallAttributes() const\r
{\r
- DDF in("addAttributes::"REMOTED_SESSION_CACHE);\r
- DDFJanitor jin(in);\r
- in.structure();\r
- in.addmember("key").string(m_key.c_str());\r
-\r
- DDF attr;\r
- DDF attrs = in.addmember("attributes").list();\r
- for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {\r
- attr = (*a)->marshall();\r
- attrs.add(attr);\r
+ Attribute* attribute;\r
+ DDF attr = m_obj["attributes"].first();\r
+ while (!attr.isnull()) {\r
+ try {\r
+ attribute = Attribute::unmarshall(attr);\r
+ m_attributes[attribute->getId()] = attribute;\r
+ if (m_cache->m_log.isDebugEnabled())\r
+ m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",\r
+ attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");\r
+ }\r
+ catch (AttributeException& ex) {\r
+ const char* id = attr.first().name();\r
+ m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());\r
+ }\r
+ attr = attr.next();\r
}\r
-\r
- attr=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
- DDFJanitor jout(attr);\r
- \r
- // Transfer ownership to us.\r
- m_attributes.insert(m_attributes.end(), attributes.begin(), attributes.end());\r
}\r
\r
-const RootObject* RemotedSession::getAssertion(const char* id) const\r
+const Assertion* RemotedSession::getAssertion(const char* id) const\r
{\r
- map<string,RootObject*>::const_iterator i = m_tokens.find(id);\r
+ map<string,Assertion*>::const_iterator i = m_tokens.find(id);\r
if (i!=m_tokens.end())\r
return i->second;\r
- \r
- DDF in("getAssertion::"REMOTED_SESSION_CACHE);\r
+\r
+ // Fetch from remoted cache.\r
+ DDF in("getAssertion::"REMOTED_SESSION_CACHE"::SessionCache");\r
DDFJanitor jin(in);\r
in.structure();\r
- in.addmember("key").string(m_key.c_str());\r
- in.addmember("assertion_id").string(id);\r
+ in.addmember("key").string(m_obj.name());\r
+ in.addmember("id").string(id);\r
\r
- DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
+ DDF out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
DDFJanitor jout(out);\r
\r
- const char* tokenstr = out["assertion"].string();\r
- if (!tokenstr)\r
- return NULL;\r
- \r
// Parse and bind the document into an XMLObject.\r
- istringstream instr(tokenstr);\r
+ istringstream instr(out.string());\r
DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
XercesJanitor<DOMDocument> janitor(doc);\r
auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));\r
janitor.release();\r
\r
- RootObject* token = dynamic_cast<RootObject*>(xmlObject.get());\r
- if (!token || !token->isAssertion())\r
- throw FatalProfileException("Remoted call for cached assertion returned an unknown object type.");\r
+ Assertion* token = dynamic_cast<Assertion*>(xmlObject.get());\r
+ if (!token)\r
+ throw FatalProfileException("Cached assertion was of an unknown object type.");\r
\r
// Transfer ownership to us.\r
xmlObject.release();\r
return token;\r
}\r
\r
-void RemotedSession::addAssertion(RootObject* assertion)\r
+void RemotedSession::validate(const Application& application, const char* client_addr, time_t timeout, bool local)\r
{\r
- if (!assertion || !assertion->isAssertion())\r
- throw FatalProfileException("Unknown object type passed to session cache for storage.");\r
+ // Basic expiration?\r
+ time_t now = time(NULL);\r
+ if (now > m_expires) {\r
+ m_cache->m_log.info("session expired (ID: %s)", m_obj.name());\r
+ RetryableProfileException ex("Your session has expired, and you must re-authenticate.");\r
+ if (!getEntityID())\r
+ throw ex;\r
+ MetadataProvider* m=application.getMetadataProvider();\r
+ Locker locker(m);\r
+ annotateException(&ex,m->getEntityDescriptor(getEntityID(),false)); // throws it\r
+ }\r
\r
- DDF in("addAssertion::"REMOTED_SESSION_CACHE);\r
+ // Address check?\r
+ if (client_addr) {\r
+ if (m_cache->m_log.isDebugEnabled())\r
+ m_cache->m_log.debug("comparing client address %s against %s", client_addr, getClientAddress());\r
+ if (strcmp(getClientAddress(),client_addr)) {\r
+ m_cache->m_log.warn("client address mismatch");\r
+ RetryableProfileException ex(\r
+ "Your IP address ($1) does not match the address recorded at the time the session was established.",\r
+ params(1,client_addr)\r
+ );\r
+ if (!getEntityID())\r
+ throw ex;\r
+ MetadataProvider* m=application.getMetadataProvider();\r
+ Locker locker(m);\r
+ annotateException(&ex,m->getEntityDescriptor(getEntityID(),false)); // throws it\r
+ }\r
+ }\r
+\r
+ if (local)\r
+ return;\r
+ \r
+ DDF in("touch::"REMOTED_SESSION_CACHE"::SessionCache"), out;\r
DDFJanitor jin(in);\r
in.structure();\r
- in.addmember("key").string(m_key.c_str());\r
- \r
- ostringstream os;\r
- os << *assertion;\r
- in.addmember("assertion").string(os.str().c_str());\r
+ in.addmember("key").string(m_obj.name());\r
+ in.addmember("version").integer(m_obj["version"].integer());\r
+ if (timeout) {\r
+ // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps. \r
+#ifndef HAVE_GMTIME_R\r
+ struct tm* ptime=gmtime(&timeout);\r
+#else\r
+ struct tm res;\r
+ struct tm* ptime=gmtime_r(&timeout,&res);\r
+#endif\r
+ char timebuf[32];\r
+ strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
+ in.addmember("timeout").string(timebuf);\r
+ }\r
\r
- DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
- out.destroy();\r
- delete assertion;\r
+ try {\r
+ out=application.getServiceProvider().getListenerService()->send(in);\r
+ }\r
+ catch (...) {\r
+ out.destroy();\r
+ throw;\r
+ }\r
+\r
+ if (out.isstruct()) {\r
+ // We got an updated record back.\r
+ m_ids.clear();\r
+ for_each(m_attributes.begin(), m_attributes.end(), cleanup_const_pair<string,Attribute>());\r
+ m_attributes.clear();\r
+ m_obj.destroy();\r
+ m_obj = out;\r
+ }\r
+\r
+ m_lastAccess = now;\r
}\r
\r
-RemotedCache::RemotedCache(const DOMElement* e) : SessionCache(e)\r
+RemotedCache::RemotedCache(const DOMElement* e)\r
+ : SessionCache(e), m_log(Category::getInstance(SHIBSP_LOGCAT".SessionCache")), m_root(e), m_lock(NULL), shutdown(false)\r
{\r
if (!SPConfig::getConfig().getServiceProvider()->getListenerService())\r
throw ConfigurationException("RemotedCacheService requires a ListenerService, but none available.");\r
+ \r
+ m_lock = RWLock::create();\r
+ shutdown_wait = CondWait::create();\r
+ cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\r
+}\r
+\r
+RemotedCache::~RemotedCache()\r
+{\r
+ // Shut down the cleanup thread and let it know...\r
+ shutdown = true;\r
+ shutdown_wait->signal();\r
+ cleanup_thread->join(NULL);\r
+\r
+ for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,RemotedSession>());\r
+ delete m_lock;\r
+ delete shutdown_wait;\r
}\r
\r
string RemotedCache::insert(\r
const char* session_index,\r
const char* authncontext_class,\r
const char* authncontext_decl,\r
- const RootObject* ssoToken,\r
+ const vector<const Assertion*>* tokens,\r
const vector<Attribute*>* attributes\r
)\r
{\r
- DDF in("insert::"REMOTED_SESSION_CACHE);\r
+ DDF in("insert::"REMOTED_SESSION_CACHE"::SessionCache");\r
DDFJanitor jin(in);\r
in.structure();\r
if (expires) {\r
in.addmember("expires").string(timebuf);\r
}\r
in.addmember("application_id").string(application.getId());\r
- in.addmember("client_address").string(client_addr);\r
+ in.addmember("client_addr").string(client_addr);\r
if (issuer) {\r
auto_ptr_char provid(issuer->getEntityID());\r
in.addmember("entity_id").string(provid.get());\r
namestr << nameid;\r
in.addmember("nameid").string(namestr.str().c_str());\r
\r
- if (ssoToken) {\r
- ostringstream tokenstr;\r
- tokenstr << *ssoToken;\r
- auto_ptr_char tokenid(ssoToken->getID());\r
- in.addmember("assertion_ids").list().add(DDF(NULL).string(tokenid.get()));\r
- in.addmember("assertions").list().add(DDF(NULL).string(tokenstr.str().c_str()));\r
+ if (tokens) {\r
+ in.addmember("assertions").list();\r
+ in.addmember("tokens").list();\r
+ for (vector<const Assertion*>::const_iterator t = tokens->begin(); t!=tokens->end(); ++t) {\r
+ ostringstream tokenstr;\r
+ tokenstr << *(*t);\r
+ auto_ptr_char tokenid((*t)->getID());\r
+ DDF tokid = DDF(NULL).string(tokenid.get());\r
+ in["assertions"].add(tokid);\r
+ DDF tok = DDF(tokenid.get()).string(tokenstr.str().c_str());\r
+ in["tokens"].add(tok);\r
+ }\r
}\r
\r
if (attributes) {\r
}\r
}\r
\r
- DDF out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
+ DDF out=application.getServiceProvider().getListenerService()->send(in);\r
DDFJanitor jout(out);\r
if (out["key"].isstring()) {\r
- for_each(attributes->begin(), attributes->end(), xmltooling::cleanup<Attribute>());\r
+ // Transaction Logging\r
+ auto_ptr_char name(nameid.getName());\r
+ const char* pid = in["entity_id"].string();\r
+ TransactionLog* xlog = application.getServiceProvider().getTransactionLog();\r
+ Locker locker(xlog);\r
+ xlog->log.infoStream() <<\r
+ "New session (ID: " <<\r
+ out["key"].string() <<\r
+ ") with (applicationId: " <<\r
+ application.getId() <<\r
+ ") for principal from (IdP: " <<\r
+ (pid ? pid : "none") <<\r
+ ") at (ClientAddress: " <<\r
+ client_addr <<\r
+ ") with (NameIdentifier: " <<\r
+ name.get() <<\r
+ ")";\r
+\r
+ if (attributes) {\r
+ xlog->log.infoStream() <<\r
+ "Cached the following attributes with session (ID: " <<\r
+ out["key"].string() <<\r
+ ") for (applicationId: " <<\r
+ application.getId() <<\r
+ ") {";\r
+ for (vector<Attribute*>::const_iterator a=attributes->begin(); a!=attributes->end(); ++a)\r
+ xlog->log.infoStream() << "\t" << (*a)->getId() << " (" << (*a)->valueCount() << " values)";\r
+ xlog->log.info("}");\r
+ for_each(attributes->begin(), attributes->end(), xmltooling::cleanup<Attribute>());\r
+ }\r
+\r
return out["key"].string();\r
}\r
throw RetryableProfileException("A remoted cache insertion operation did not return a usable session key.");\r
\r
Session* RemotedCache::find(const char* key, const Application& application, const char* client_addr, time_t timeout)\r
{\r
- DDF in("find::"REMOTED_SESSION_CACHE), out;\r
- DDFJanitor jin(in);\r
- in.structure();\r
- in.addmember("key").string(key);\r
- in.addmember("application_id").string(application.getId());\r
- in.addmember("client_address").string(client_addr);\r
- \r
- try {\r
- out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
- if (!out.isstruct()) {\r
+#ifdef _DEBUG\r
+ xmltooling::NDC ndc("find");\r
+#endif\r
+\r
+ bool localValidation = false;\r
+ RemotedSession* session=NULL;\r
+ m_log.debug("searching local cache for session (%s)", key);\r
+ m_lock->rdlock();\r
+ map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);\r
+ if (i==m_hashtable.end()) {\r
+ m_lock->unlock();\r
+ m_log.debug("session not found locally, searching remote cache");\r
+\r
+ DDF in("find::"REMOTED_SESSION_CACHE"::SessionCache"), out;\r
+ DDFJanitor jin(in);\r
+ in.structure();\r
+ in.addmember("key").string(key);\r
+ if (timeout) {\r
+ // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps. \r
+#ifndef HAVE_GMTIME_R\r
+ struct tm* ptime=gmtime(&timeout);\r
+#else\r
+ struct tm res;\r
+ struct tm* ptime=gmtime_r(&timeout,&res);\r
+#endif\r
+ char timebuf[32];\r
+ strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
+ in.addmember("timeout").string(timebuf);\r
+ }\r
+ \r
+ try {\r
+ out=application.getServiceProvider().getListenerService()->send(in);\r
+ if (!out.isstruct()) {\r
+ out.destroy();\r
+ m_log.debug("session not found in remote cache");\r
+ return NULL;\r
+ }\r
+ \r
+ // Wrap the results in a local entry and save it.\r
+ session = new RemotedSession(this, out);\r
+ // The remote end has handled timeout issues, we handle address and expiration checks.\r
+ localValidation = true;\r
+ }\r
+ catch (...) {\r
out.destroy();\r
- return NULL;\r
+ throw;\r
+ }\r
+\r
+ // Lock for writing and repeat the search to avoid duplication.\r
+ m_lock->wrlock();\r
+ SharedLock shared(m_lock, false);\r
+ if (m_hashtable.count(key)) {\r
+ delete session;\r
+ // We're using an existing session entry, so we have to switch back to full validation.\r
+ localValidation = false;\r
+ session = m_hashtable[key];\r
+ session->lock();\r
}\r
+ else {\r
+ m_hashtable[key]=session;\r
+ session->lock();\r
+ }\r
+ }\r
+ else {\r
+ // Save off and lock the session.\r
+ session = i->second;\r
+ session->lock();\r
+ m_lock->unlock();\r
\r
- // Wrap the results in a stub entry and return it to the caller.\r
- return new RemotedSession(key, out);\r
+ m_log.debug("session found locally, validating it for use");\r
+ }\r
+\r
+ if (!XMLString::equals(session->getApplicationID(), application.getId())) {\r
+ m_log.error("an application (%s) tried to access another application's session", application.getId());\r
+ session->unlock();\r
+ return NULL;\r
+ }\r
+\r
+ // Verify currency and update the timestamp.\r
+ // If the local switch is false, we also update the access time.\r
+ try {\r
+ session->validate(application, client_addr, timeout, localValidation);\r
}\r
catch (...) {\r
- out.destroy();\r
+ session->unlock();\r
+ remove(key, application, client_addr);\r
throw;\r
}\r
+ \r
+ return session;\r
}\r
\r
void RemotedCache::remove(const char* key, const Application& application, const char* client_addr)\r
{\r
- DDF in("remove::"REMOTED_SESSION_CACHE);\r
+ // Take care of local copy.\r
+ dormant(key);\r
+ \r
+ // Now remote...\r
+ DDF in("remove::"REMOTED_SESSION_CACHE"::SessionCache");\r
DDFJanitor jin(in);\r
in.structure();\r
in.addmember("key").string(key);\r
in.addmember("application_id").string(application.getId());\r
- in.addmember("client_address").string(client_addr);\r
+ in.addmember("client_addr").string(client_addr);\r
+ \r
+ DDF out = application.getServiceProvider().getListenerService()->send(in);\r
+ out.destroy();\r
+}\r
+\r
+void RemotedCache::dormant(const char* key)\r
+{\r
+#ifdef _DEBUG\r
+ xmltooling::NDC ndc("dormant");\r
+#endif\r
+\r
+ m_log.debug("deleting local copy of session (%s)", key);\r
+\r
+ // lock the cache for writing, which means we know nobody is sitting in find()\r
+ m_lock->wrlock();\r
+\r
+ // grab the entry from the table\r
+ map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);\r
+ if (i==m_hashtable.end()) {\r
+ m_lock->unlock();\r
+ return;\r
+ }\r
+\r
+ // ok, remove the entry and lock it\r
+ RemotedSession* entry=i->second;\r
+ m_hashtable.erase(key);\r
+ entry->lock();\r
\r
+ // unlock the cache\r
+ m_lock->unlock();\r
+\r
+ // we can release the cache entry lock because we know we're not in the cache anymore\r
+ entry->unlock();\r
+\r
+ delete entry;\r
+}\r
+\r
+void RemotedCache::cleanup()\r
+{\r
+#ifdef _DEBUG\r
+ xmltooling::NDC ndc("cleanup");\r
+#endif\r
+\r
+ Mutex* mutex = Mutex::create();\r
+ \r
+ // Load our configuration details...\r
+ static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
+ const XMLCh* tag=m_root ? m_root->getAttributeNS(NULL,cleanupInterval) : NULL;\r
+ int rerun_timer = 900;\r
+ if (tag && *tag)\r
+ rerun_timer = XMLString::parseInt(tag);\r
+ if (rerun_timer <= 0)\r
+ rerun_timer = 900;\r
+\r
+ mutex->lock();\r
+\r
+ m_log.info("cleanup thread started...run every %d secs; timeout after %d secs", rerun_timer, m_cacheTimeout);\r
+\r
+ while (!shutdown) {\r
+ shutdown_wait->timedwait(mutex,rerun_timer);\r
+ if (shutdown)\r
+ break;\r
+\r
+ // Ok, let's run through the cleanup process and clean out\r
+ // really old sessions. This is a two-pass process. The\r
+ // first pass is done holding a read-lock while we iterate over\r
+ // the cache. The second pass doesn't need a lock because\r
+ // the 'deletes' will lock the cache.\r
+ \r
+ // Pass 1: iterate over the map and find all entries that have not been\r
+ // used in X hours\r
+ vector<string> stale_keys;\r
+ time_t stale = time(NULL) - m_cacheTimeout;\r
+ \r
+ m_lock->rdlock();\r
+ for (map<string,RemotedSession*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); ++i) {\r
+ // If the last access was BEFORE the stale timeout...\r
+ i->second->lock();\r
+ time_t last=i->second->lastAccess();\r
+ i->second->unlock();\r
+ if (last < stale)\r
+ stale_keys.push_back(i->first);\r
+ }\r
+ m_lock->unlock();\r
+ \r
+ if (!stale_keys.empty()) {\r
+ m_log.info("purging %d old sessions", stale_keys.size());\r
+ \r
+ // Pass 2: walk through the list of stale entries and remove them from the cache\r
+ for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); ++j)\r
+ dormant(j->c_str());\r
+ }\r
+ }\r
+\r
+ m_log.info("cleanup thread exiting");\r
+\r
+ mutex->unlock();\r
+ delete mutex;\r
+ Thread::exit(NULL);\r
+}\r
+\r
+void* RemotedCache::cleanup_fn(void* cache_p)\r
+{\r
+ RemotedCache* cache = reinterpret_cast<RemotedCache*>(cache_p);\r
+\r
+#ifndef WIN32\r
+ // First, let's block all signals \r
+ Thread::mask_all_signals();\r
+#endif\r
+\r
+ // Now run the cleanup process.\r
+ cache->cleanup();\r
+ return NULL;\r
+}\r
+\r
+/* These are currently unimplemented.\r
+\r
+void RemotedSession::addAttributes(const vector<Attribute*>& attributes)\r
+{\r
+ DDF in("addAttributes::"REMOTED_SESSION_CACHE);\r
+ DDFJanitor jin(in);\r
+ in.structure();\r
+ in.addmember("key").string(m_key.c_str());\r
+ in.addmember("application_id").string(m_appId.c_str());\r
+\r
+ DDF attr;\r
+ DDF attrs = in.addmember("attributes").list();\r
+ for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {\r
+ attr = (*a)->marshall();\r
+ attrs.add(attr);\r
+ }\r
+\r
+ attr=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
+ DDFJanitor jout(attr);\r
+ \r
+ // Transfer ownership to us.\r
+ m_attributes.insert(m_attributes.end(), attributes.begin(), attributes.end());\r
+}\r
+\r
+void RemotedSession::addAssertion(Assertion* assertion)\r
+{\r
+ if (!assertion)\r
+ throw FatalProfileException("Unknown object type passed to session cache for storage.");\r
+\r
+ DDF in("addAssertion::"REMOTED_SESSION_CACHE);\r
+ DDFJanitor jin(in);\r
+ in.structure();\r
+ in.addmember("key").string(m_key.c_str());\r
+ in.addmember("application_id").string(m_appId.c_str());\r
+ \r
+ ostringstream os;\r
+ os << *assertion;\r
+ string token(os.str());\r
+ auto_ptr_char tokenid(assertion->getID());\r
+ in.addmember("assertion_id").string(tokenid.get());\r
+ in.addmember("assertion").string(token.c_str());\r
+\r
DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
out.destroy();\r
+ \r
+ // Add to local record and token map.\r
+ // Next attempt to find and lock session will refresh from remote store anyway.\r
+ m_obj["assertions"].addmember(tokenid.get()).string(token.c_str());\r
+ m_ids.clear();\r
+ m_tokens[tokenid.get()] = assertion;\r
}\r
+\r
+*/
\ No newline at end of file