2 * Copyright 2001-2005 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * shib-ccache.cpp -- in-memory session cache plugin
33 #include <shibsp/SPConfig.h>
35 #ifdef HAVE_LIBDMALLOCXX
39 using namespace shibsp;
40 using namespace shibtarget;
42 using namespace opensaml::saml2md;
43 using namespace xmltooling;
44 using namespace log4cpp;
46 using xmlsignature::CredentialResolver;
48 static const XMLCh cleanupInterval[] =
49 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
50 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
52 static const XMLCh cacheTimeout[] =
53 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
54 chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
56 static const XMLCh AAConnectTimeout[] =
57 { chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
58 chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
60 static const XMLCh AATimeout[] =
61 { chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
63 static const XMLCh defaultLifetime[] =
64 { chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
65 chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
67 static const XMLCh retryInterval[] =
68 { chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
69 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
71 static const XMLCh strictValidity[] =
72 { chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
73 chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
75 static const XMLCh propagateErrors[] =
76 { chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
77 chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
79 static const XMLCh writeThrough[] =
80 { chLatin_w, chLatin_r, chLatin_i, chLatin_t, chLatin_e,
81 chLatin_T, chLatin_h, chLatin_r, chLatin_o, chLatin_u, chLatin_g, chLatin_h, chNull
86 * Stubbed out, inproc version of an ISessionCacheEntry
88 class StubCacheEntry : public virtual ISessionCacheEntry
91 StubCacheEntry(Category* log) : m_log(log), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
92 StubCacheEntry(DDF& obj, Category* log)
93 : m_log(log), m_obj(obj), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
94 ~StubCacheEntry() { m_obj.destroy(); delete m_pSubject; delete m_pUnfiltered; delete m_pFiltered; }
96 void unlock() { delete this; }
97 const char* getClientAddress() const { return m_obj["client_address"].string(); }
98 const char* getProviderId() const { return m_obj["provider_id"].string(); }
99 const char* getAuthnContext() const { return m_obj["authn_context"].string(); }
100 pair<const char*,const SAMLSubject*> getSubject(bool xml=true, bool obj=false) const;
101 pair<const char*,const SAMLResponse*> getTokens(bool xml=true, bool obj=false) const;
102 pair<const char*,const SAMLResponse*> getFilteredTokens(bool xml=true, bool obj=false) const;
107 mutable SAMLSubject* m_pSubject;
108 mutable SAMLResponse* m_pUnfiltered;
109 mutable SAMLResponse* m_pFiltered;
112 pair<const char*,const SAMLSubject*> StubCacheEntry::getSubject(bool xml, bool obj) const
114 const char* raw=m_obj["subject"].string();
115 pair<const char*,const SAMLSubject*> ret=pair<const char*,const SAMLSubject*>(NULL,NULL);
120 istringstream in(raw);
121 m_log->debugStream() << "decoding subject: " << (raw ? raw : "(none)") << CategoryStream::ENDLINE;
122 m_pSubject=raw ? new SAMLSubject(in) : NULL;
124 ret.second=m_pSubject;
129 pair<const char*,const SAMLResponse*> StubCacheEntry::getTokens(bool xml, bool obj) const
131 const char* unfiltered=m_obj["tokens.unfiltered"].string();
132 pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
134 ret.first=unfiltered;
136 if (!m_pUnfiltered) {
138 istringstream in(unfiltered);
139 m_log->debugStream() << "decoding unfiltered tokens: " << unfiltered << CategoryStream::ENDLINE;
140 m_pUnfiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
143 ret.second=m_pUnfiltered;
148 pair<const char*,const SAMLResponse*> StubCacheEntry::getFilteredTokens(bool xml, bool obj) const
150 const char* filtered=m_obj["tokens.filtered"].string();
152 return getTokens(xml,obj);
153 pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
158 istringstream in(filtered);
159 m_log->debugStream() << "decoding filtered tokens: " << filtered << CategoryStream::ENDLINE;
160 m_pFiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
162 ret.second=m_pFiltered;
168 * Remoting front-half of session cache, drops out in single process deployments.
169 * TODO: Add buffering of frequently-used entries.
171 class StubCache : public virtual ISessionCache
174 StubCache(const DOMElement* e);
177 const IApplication* application,
178 const RoleDescriptor* role,
179 const char* client_addr,
180 const SAMLSubject* subject,
181 const char* authnContext,
182 const SAMLResponse* tokens
184 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
185 void remove(const char* key, const IApplication* application, const char* client_addr);
187 bool setBackingStore(ISessionCacheStore*) { return false; }
193 StubCache::StubCache(const DOMElement* e) : m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")) {}
196 * The public methods are remoted using the message passing system.
197 * In practice, insert is unlikely to be used remotely, but just in case...
200 string StubCache::insert(
201 const IApplication* application,
202 const RoleDescriptor* role,
203 const char* client_addr,
204 const SAMLSubject* subject,
205 const char* authnContext,
206 const SAMLResponse* tokens
209 DDF in("SessionCache::insert"),out;
210 DDFJanitor jin(in),jout(out);
212 in.addmember("application_id").string(application->getId());
213 in.addmember("client_address").string(client_addr);
214 xmltooling::auto_ptr_char provid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
215 in.addmember("provider_id").string(provid.get());
216 in.addmember("major_version").integer(1);
217 in.addmember("minor_version").integer(tokens->getMinorVersion());
218 in.addmember("authn_context").string(authnContext);
222 in.addmember("subject").string(os.str().c_str());
225 in.addmember("tokens.unfiltered").string(os.str().c_str());
227 out=ShibTargetConfig::getConfig().getINI()->getListenerService()->send(in);
228 if (out["key"].isstring())
229 return out["key"].string();
230 throw opensaml::RetryableProfileException("A remoted cache insertion operation did not return a usable session key.");
233 ISessionCacheEntry* StubCache::find(const char* key, const IApplication* application, const char* client_addr)
235 DDF in("SessionCache::find"),out;
238 in.addmember("key").string(key);
239 in.addmember("application_id").string(application->getId());
240 in.addmember("client_address").string(client_addr);
243 out=ShibTargetConfig::getConfig().getINI()->getListenerService()->send(in);
244 if (!out.isstruct()) {
249 // Wrap the results in a stub entry and return it to the caller.
250 return new StubCacheEntry(out,m_log);
258 void StubCache::remove(const char* key, const IApplication* application, const char* client_addr)
260 DDF in("SessionCache::remove");
263 in.addmember("key").string(key);
264 in.addmember("application_id").string(application->getId());
265 in.addmember("client_address").string(client_addr);
267 ShibTargetConfig::getConfig().getINI()->getListenerService()->send(in);
271 * Long-lived cache entries that store the actual sessions and
272 * wrap attribute query/refresh/filtering
274 class MemorySessionCache;
275 class MemorySessionCacheEntry : public virtual ISessionCacheEntry, public virtual StubCacheEntry
278 MemorySessionCacheEntry(
279 MemorySessionCache* cache,
281 const IApplication* application,
282 const RoleDescriptor* role,
283 const char* client_addr,
284 const SAMLSubject* subject,
285 const char* authnContext,
286 const SAMLResponse* tokens
288 MemorySessionCacheEntry(
289 MemorySessionCache* cache,
291 const IApplication* application,
292 const RoleDescriptor* role,
293 const char* client_addr,
295 const char* authnContext,
302 ~MemorySessionCacheEntry();
304 void lock() { m_lock->lock(); }
305 void unlock() { m_lock->unlock(); }
307 HRESULT isValid(const IApplication* application, const char* client_addr) const;
308 void populate(const IApplication* application, const EntityDescriptor* source, bool initial=false) const;
309 bool checkApplication(const IApplication* application) { return (m_obj["application_id"]==application->getId()); }
310 time_t created() const { return m_sessionCreated; }
311 time_t lastAccess() const { return m_lastAccess; }
312 const DDF& getDDF() const { return m_obj; }
315 bool hasAttributes(const SAMLResponse& r) const;
316 time_t calculateExpiration(const SAMLResponse& r) const;
317 pair<SAMLResponse*,SAMLResponse*> getNewResponse(const IApplication* application, const EntityDescriptor* source) const;
318 SAMLResponse* filter(const SAMLResponse* r, const IApplication* application, const RoleDescriptor* role) const;
320 time_t m_sessionCreated;
321 mutable time_t m_responseExpiration, m_lastAccess, m_lastRetry;
323 MemorySessionCache* m_cache;
328 * The actual in-memory session cache implementation.
330 class MemorySessionCache : public virtual ISessionCache, public virtual Remoted
333 MemorySessionCache(const DOMElement* e);
334 virtual ~MemorySessionCache();
336 DDF receive(const DDF& in);
339 const IApplication* application,
340 const RoleDescriptor* role,
341 const char* client_addr,
342 const SAMLSubject* subject,
343 const char* authnContext,
344 const SAMLResponse* tokens
346 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
347 void remove(const char* key, const IApplication* application, const char* client_addr);
351 bool setBackingStore(ISessionCacheStore* store);
354 const DOMElement* m_root; // Only valid during initialization
356 map<string,MemorySessionCacheEntry*> m_hashtable;
359 Remoted* restoreInsert;
360 Remoted* restoreFind;
361 Remoted* restoreRemove;
362 ISessionCacheStore* m_sink;
364 void dormant(const char* key);
365 static void* cleanup_fcn(void*);
367 CondWait* shutdown_wait;
368 Thread* cleanup_thread;
370 // extracted config settings
371 unsigned int m_AATimeout,m_AAConnectTimeout;
372 unsigned int m_defaultLifetime,m_retryInterval;
373 bool m_strictValidity,m_propagateErrors,m_writeThrough;
374 friend class MemorySessionCacheEntry;
377 MemorySessionCacheEntry::MemorySessionCacheEntry(
378 MemorySessionCache* cache,
380 const IApplication* application,
381 const RoleDescriptor* role,
382 const char* client_addr,
383 const SAMLSubject* subject,
384 const char* authnContext,
385 const SAMLResponse* tokens
386 ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
388 m_sessionCreated = m_lastAccess = time(NULL);
390 // Store session properties in DDF.
391 m_obj=DDF(NULL).structure();
392 m_obj.addmember("key").string(key);
393 m_obj.addmember("client_address").string(client_addr);
394 m_obj.addmember("application_id").string(application->getId());
395 xmltooling::auto_ptr_char pid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
396 m_obj.addmember("provider_id").string(pid.get());
397 m_obj.addmember("major_version").integer(1);
398 m_obj.addmember("minor_version").integer(tokens->getMinorVersion());
400 // Save the subject as XML.
403 m_obj.addmember("subject").string(os.str().c_str());
405 // Save the authn method.
406 m_obj.addmember("authn_context").string(authnContext);
408 // Serialize unfiltered assertions.
411 m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
413 if (hasAttributes(*tokens)) {
414 // Filter attributes in the response.
415 auto_ptr<SAMLResponse> filtered(filter(tokens, application, role));
417 // Calculate expiration.
418 m_responseExpiration=calculateExpiration(*(filtered.get()));
420 // Serialize filtered assertions (if changes were made).
422 os << *(filtered.get());
423 string fstr=os.str();
424 if (fstr.length() != m_obj["tokens.unfiltered"].strlen())
425 m_obj.addmember("tokens.filtered").string(fstr.c_str());
427 // Save actual objects only if we're running inprocess. The subject needs to be
428 // owned by the entry, so we'll defer creation of a cloned copy.
429 if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
430 if (m_obj["tokens.filtered"].isstring())
431 m_pFiltered=filtered.release();
435 m_lock = Mutex::create();
437 if (m_log->isDebugEnabled()) {
438 m_log->debug("new cache entry created: SessionID (%s) IdP (%s) Address (%s)", key, pid.get(), client_addr);
441 // Transaction Logging
442 xmltooling::auto_ptr_char hname(subject->getNameIdentifier()->getName());
443 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
444 stc.getTransactionLog().infoStream() <<
445 "New session (ID: " <<
447 ") with (applicationId: " <<
448 application->getId() <<
449 ") for principal from (IdP: " <<
451 ") at (ClientAddress: " <<
453 ") with (NameIdentifier: " <<
456 stc.releaseTransactionLog();
459 MemorySessionCacheEntry::MemorySessionCacheEntry(
460 MemorySessionCache* cache,
462 const IApplication* application,
463 const RoleDescriptor* role,
464 const char* client_addr,
466 const char* authnContext,
472 ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
474 m_sessionCreated = created;
475 m_lastAccess = accessed;
477 // Reconstitute the tokens for filtering.
478 istringstream is(tokens);
479 auto_ptr<SAMLResponse> unfiltered(new SAMLResponse(is,minorVersion));
481 // Store session properties in DDF.
482 m_obj=DDF(NULL).structure();
483 m_obj.addmember("key").string(key);
484 m_obj.addmember("client_address").string(client_addr);
485 m_obj.addmember("application_id").string(application->getId());
486 xmltooling::auto_ptr_char pid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
487 m_obj.addmember("provider_id").string(pid.get());
488 m_obj.addmember("subject").string(subject);
489 m_obj.addmember("authn_context").string(authnContext);
490 m_obj.addmember("tokens.unfiltered").string(tokens);
491 m_obj.addmember("major_version").integer(majorVersion);
492 m_obj.addmember("minor_version").integer(minorVersion);
494 if (hasAttributes(*(unfiltered.get()))) {
495 auto_ptr<SAMLResponse> filtered(filter(unfiltered.get(), application, role));
497 // Calculate expiration.
498 m_responseExpiration=calculateExpiration(*(filtered.get()));
500 // Serialize filtered assertions (if changes were made).
502 os << *(filtered.get());
503 string fstr=os.str();
504 if (fstr.length() != strlen(tokens))
505 m_obj.addmember("tokens.filtered").string(fstr.c_str());
507 // Save actual objects only if we're running inprocess.
508 if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
509 m_pUnfiltered=unfiltered.release();
510 if (m_obj["tokens.filtered"].isstring())
511 m_pFiltered=filtered.release();
515 m_lock = Mutex::create();
517 if (m_log->isDebugEnabled())
518 m_log->debug("session loaded from secondary cache (ID: %s)", key);
522 MemorySessionCacheEntry::~MemorySessionCacheEntry()
527 HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* client_addr) const
530 xmltooling::NDC ndc("isValid");
533 // Obtain validation rules from application settings.
534 bool consistentIPAddress=true;
535 int lifetime=0,timeout=0;
536 const PropertySet* props=app->getPropertySet("Sessions");
538 pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
541 p=props->getUnsignedInt("timeout");
544 pair<bool,bool> pcheck=props->getBool("consistentIPAddress");
546 consistentIPAddress = pcheck.second;
549 if (m_log->isDebugEnabled())
550 m_log->debug("checking validity of session (ID: %s)", m_obj["key"].string());
552 time_t now=time(NULL);
553 if (lifetime > 0 && now > m_sessionCreated+lifetime) {
554 if (m_log->isInfoEnabled())
555 m_log->info("session expired (ID: %s)", m_obj["key"].string());
556 return SESSION_E_EXPIRED;
559 if (timeout > 0 && now-m_lastAccess >= timeout) {
560 // May need to query sink first to find out if another cluster member has been used.
561 if (m_cache->m_sink && m_cache->m_writeThrough) {
562 if (NOERROR!=m_cache->m_sink->onRead(m_obj["key"].string(),m_lastAccess))
563 m_log->error("cache store failed to return last access timestamp");
564 if (now-m_lastAccess >= timeout) {
565 m_log->info("session timed out (ID: %s)", m_obj["key"].string());
566 return SESSION_E_EXPIRED;
570 m_log->info("session timed out (ID: %s)", m_obj["key"].string());
571 return SESSION_E_EXPIRED;
575 if (consistentIPAddress) {
576 if (m_log->isDebugEnabled())
577 m_log->debug("comparing client address %s against %s", client_addr, getClientAddress());
578 if (strcmp(client_addr, getClientAddress())) {
579 m_log->debug("client address mismatch");
580 return SESSION_E_ADDRESSMISMATCH;
586 if (m_cache->m_sink && m_cache->m_writeThrough && timeout > 0) {
587 // Update sink with last access data, if possible.
588 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),NULL,m_lastAccess)))
589 m_log->error("cache store failed to update last access timestamp");
595 bool MemorySessionCacheEntry::hasAttributes(const SAMLResponse& r) const
597 Iterator<SAMLAssertion*> assertions=r.getAssertions();
598 while (assertions.hasNext()) {
599 Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
600 while (statements.hasNext()) {
601 if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
608 time_t MemorySessionCacheEntry::calculateExpiration(const SAMLResponse& r) const
611 Iterator<SAMLAssertion*> assertions = r.getAssertions();
612 while (assertions.hasNext()) {
613 SAMLAssertion* assertion = assertions.next();
615 // Only examine this assertion if it contains an attribute statement.
616 // We know at least one such statement exists, or this is a query response.
617 Iterator<SAMLStatement*> statements = assertion->getStatements();
618 while (statements.hasNext()) {
619 if (dynamic_cast<SAMLAttributeStatement*>(statements.next())) {
620 const SAMLDateTime* thistime = assertion->getNotOnOrAfter();
622 // If there is no time, then just continue and ignore this assertion.
624 // If this is a tighter expiration, cache it.
625 if (expiration == 0 || thistime->getEpoch() < expiration)
626 expiration = thistime->getEpoch();
629 // No need to continue with this assertion.
635 // If we didn't find any assertions with times, then use the default.
637 expiration = time(NULL) + m_cache->m_defaultLifetime;
642 void MemorySessionCacheEntry::populate(const IApplication* application, const EntityDescriptor* source, bool initial) const
645 xmltooling::NDC ndc("populate");
648 // Do we have any attribute data cached?
649 if (m_responseExpiration > 0) {
650 // Can we use what we have?
651 if (time(NULL) < m_responseExpiration)
654 // Possibly check the sink in case another cluster member already refreshed it.
655 if (m_cache->m_sink && m_cache->m_writeThrough) {
656 string tokensFromSink;
657 HRESULT hr=m_cache->m_sink->onRead(m_obj["key"].string(),tokensFromSink);
659 m_log->error("cache store failed to return updated tokens");
660 else if (hr==NOERROR && tokensFromSink!=m_obj["tokens.unfiltered"].string()) {
662 // Bah...find role again.
663 const RoleDescriptor* role=source->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
665 role=source->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
667 role=source->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
669 role=source->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
671 throw MetadataException("Unable to locate attribute-issuing role in metadata.");
674 // The tokens in the sink were different.
675 istringstream is(tokensFromSink);
676 auto_ptr<SAMLResponse> respFromSink(new SAMLResponse(is,m_obj["minor_version"].integer()));
677 auto_ptr<SAMLResponse> filteredFromSink(filter(respFromSink.get(),application,role));
678 time_t expFromSink=calculateExpiration(*(filteredFromSink.get()));
680 // Recheck to see if the new tokens are valid.
681 if (expFromSink < time(NULL)) {
682 m_log->info("loading replacement tokens into memory from cache store");
683 m_obj["tokens"].destroy();
684 delete m_pUnfiltered;
686 m_pUnfiltered=m_pFiltered=NULL;
687 m_obj.addmember("tokens.unfiltered").string(tokensFromSink.c_str());
689 // Serialize filtered assertions (if changes were made).
691 os << *(filteredFromSink.get());
692 string fstr=os.str();
693 if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
694 m_obj.addmember("tokens.filtered").string(fstr.c_str());
696 // Save actual objects only if we're running inprocess.
697 if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
698 m_pUnfiltered=respFromSink.release();
699 if (m_obj["tokens.filtered"].isstring())
700 m_pFiltered=filteredFromSink.release();
703 m_responseExpiration=expFromSink;
710 // If we're being strict, dump what we have and reset timestamps.
711 if (m_cache->m_strictValidity) {
712 m_log->info("strictly enforcing attribute validity, dumping expired data");
713 m_obj["tokens"].destroy();
714 delete m_pUnfiltered;
716 m_pUnfiltered=m_pFiltered=NULL;
717 m_responseExpiration=0;
719 if (m_cache->m_sink) {
720 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),"")))
721 m_log->error("cache store returned failure while clearing tokens from entry");
727 pair<SAMLResponse*,SAMLResponse*> new_responses=getNewResponse(application,source);
728 auto_ptr<SAMLResponse> r1(new_responses.first),r2(new_responses.second);
729 if (new_responses.first) {
730 m_obj["tokens"].destroy();
731 delete m_pUnfiltered;
733 m_pUnfiltered=m_pFiltered=NULL;
734 m_responseExpiration=0;
736 // Serialize unfiltered assertions.
738 os << *new_responses.first;
739 m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
741 // Serialize filtered assertions (if changes were made).
743 os << *new_responses.second;
744 string fstr=os.str();
745 if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
746 m_obj.addmember("tokens.filtered").string(fstr.c_str());
748 // Update expiration.
749 m_responseExpiration=calculateExpiration(*new_responses.second);
751 // Save actual objects only if we're running inprocess.
752 if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
753 m_pUnfiltered=r1.release();
754 if (m_obj["tokens.filtered"].isstring())
755 m_pFiltered=r2.release();
758 // Update backing store.
759 if (!initial && m_cache->m_sink) {
760 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),m_obj["tokens.unfiltered"].string())))
761 m_log->error("cache store returned failure while updating tokens in entry");
765 m_log->debug("fetched and stored new response");
766 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
767 stc.getTransactionLog().infoStream() << "Successful attribute query for session (ID: " << m_obj["key"].string() << ")";
768 stc.releaseTransactionLog();
772 if (m_cache->m_propagateErrors)
774 m_log->warn("suppressed exception caught while trying to fetch attributes");
778 if (m_cache->m_propagateErrors)
780 m_log->warn("suppressed unknown exception caught while trying to fetch attributes");
785 pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse(
786 const IApplication* application, const EntityDescriptor* source
790 xmltooling::NDC ndc("getNewResponse");
793 // The retryInterval determines how often to poll an AA that might be down.
794 time_t now=time(NULL);
795 if ((now - m_lastRetry) < m_cache->m_retryInterval)
796 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
798 m_log->debug("retry interval exceeded, trying for attributes again");
801 m_log->info("trying to get new attributes for session (ID: %s)", m_obj["key"].string());
803 // Transaction Logging
804 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
805 stc.getTransactionLog().infoStream() <<
806 "Making attribute query for session (ID: " <<
807 m_obj["key"].string() <<
808 ") on (applicationId: " <<
809 m_obj["application_id"].string() <<
810 ") for principal from (IdP: " <<
811 m_obj["provider_id"].string() <<
813 stc.releaseTransactionLog();
816 pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
817 if (!providerID.first) {
818 m_log->crit("unable to determine ProviderID for application, not set?");
819 throw ConfigurationException("Unable to determine ProviderID for application, not set?");
822 // Try to locate an AA role.
823 const AttributeAuthorityDescriptor* AA=source->getAttributeAuthorityDescriptor(
824 m_obj["minor_version"].integer()==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM
827 m_log->warn("unable to locate metadata for identity provider's Attribute Authority");
828 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
831 // Get protocol signing policy.
832 const PropertySet* credUse=application->getCredentialUse(source);
833 pair<bool,bool> signRequest=credUse ? credUse->getBool("signRequest") : make_pair(false,false);
834 pair<bool,const char*> signatureAlg=credUse ? credUse->getString("signatureAlg") : pair<bool,const char*>(false,NULL);
835 if (!signatureAlg.first)
836 signatureAlg.second=URI_ID_RSA_SHA1;
837 pair<bool,const char*> digestAlg=credUse ? credUse->getString("digestAlg") : pair<bool,const char*>(false,NULL);
838 if (!digestAlg.first)
839 digestAlg.second=URI_ID_SHA1;
840 pair<bool,bool> signedResponse=credUse ? credUse->getBool("signedResponse") : make_pair(false,false);
841 pair<bool,const char*> signingCred=credUse ? credUse->getString("Signing") : pair<bool,const char*>(false,NULL);
843 SAMLResponse* response = NULL;
845 // Copy NameID from subject (may need to reconstitute it).
846 SAMLNameIdentifier* nameid=NULL;
848 nameid=static_cast<SAMLNameIdentifier*>(m_pSubject->getNameIdentifier()->clone());
850 istringstream instr(m_obj["subject"].string());
851 auto_ptr<SAMLSubject> sub(new SAMLSubject(instr));
852 nameid=static_cast<SAMLNameIdentifier*>(sub->getNameIdentifier()->clone());
855 // Build a SAML Request....
856 SAMLAttributeQuery* q=new SAMLAttributeQuery(
857 new SAMLSubject(nameid),
859 application->getAttributeDesignators().clone()
861 auto_ptr<SAMLRequest> req(new SAMLRequest(q));
862 req->setMinorVersion(m_obj["minor_version"].integer());
865 if (signRequest.first && signRequest.second && signingCred.first) {
866 if (req->getMinorVersion()==1) {
867 CredentialResolver* cr=ShibTargetConfig::getConfig().getINI()->getCredentialResolver(signingCred.second);
869 xmltooling::Locker locker(cr);
870 req->sign(cr->getKey(),cr->getCertificates(),signatureAlg.second,digestAlg.second);
873 m_log->error("unable to sign attribute query, specified credential (%s) was not found",signingCred.second);
876 m_log->error("unable to sign SAML 1.0 attribute query, only SAML 1.1 defines signing adequately");
879 m_log->debug("trying to query an AA...");
881 // Call context object
882 ShibHTTPHook::ShibHTTPHookCallContext ctx(credUse,AA);
884 // Use metadata to locate endpoints.
885 const vector<AttributeService*>& endpoints=AA->getAttributeServices();
886 for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {
888 // Get a binding object for this protocol.
889 const SAMLBinding* binding = application->getBinding((*ep)->getBinding());
891 xmltooling::auto_ptr_char prot((*ep)->getBinding());
892 m_log->warn("skipping binding on unsupported protocol (%s)", prot.get());
895 static const XMLCh https[] = {chLatin_h, chLatin_t, chLatin_t, chLatin_p, chLatin_s, chColon, chNull};
896 auto_ptr<SAMLResponse> r(binding->send((*ep)->getLocation(), *(req.get()), &ctx));
898 // TODO: trust stuff will be changing anyway...
899 //if (!t.validate(*r,AA))
900 // throw TrustException("Unable to verify signed response message.");
902 else if (!ctx.isAuthenticated() || XMLString::compareNString((*ep)->getLocation(),https,6))
903 throw XMLSecurityException("Response message was unauthenticated.");
904 response = r.release();
906 catch (exception& e) {
907 m_log->error("caught exception during SAML attribute query: %s", e.what());
912 if (signedResponse.first && signedResponse.second && !response->isSigned()) {
914 m_log->error("unsigned response obtained, but we were told it must be signed.");
915 throw XMLSecurityException("Unable to obtain a signed response message.");
918 // Iterate over the tokens and apply basic validation.
919 time_t now=time(NULL);
920 Iterator<SAMLAssertion*> assertions=response->getAssertions();
921 for (unsigned int a=0; a<assertions.size();) {
922 // Discard any assertions not issued by the right entity.
923 if (XMLString::compareString(source->getEntityID(),assertions[a]->getIssuer())) {
924 xmltooling::auto_ptr_char bad(assertions[a]->getIssuer());
925 m_log->warn("discarding assertion not issued by (%s), instead by (%s)",m_obj["provider_id"].string(),bad.get());
926 response->removeAssertion(a);
930 // Validate the token.
932 application->validateToken(assertions[a],now,AA,application->getTrustEngine());
936 m_log->warn("assertion failed to validate, removing it from response");
937 response->removeAssertion(a);
941 // Run it through the filter.
942 return make_pair(response,filter(response,application,AA));
945 catch (exception& e) {
946 m_log->error("caught exception during query to AA: %s", e.what());
950 m_log->error("no response obtained");
951 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
954 SAMLResponse* MemorySessionCacheEntry::filter(
955 const SAMLResponse* r, const IApplication* application, const RoleDescriptor* role
959 xmltooling::NDC ndc("filter");
962 // Make a copy of the original and process that against the AAP.
963 auto_ptr<SAMLResponse> copy(static_cast<SAMLResponse*>(r->clone()));
966 Iterator<SAMLAssertion*> copies=copy->getAssertions();
967 for (unsigned long j=0; j < copies.size();) {
969 // Finally, filter the content.
970 shibboleth::AAP::apply(application->getAAPProviders(),*(copies[j]),role);
975 m_log->info("no statements remain after AAP, removing assertion");
976 copy->removeAssertion(j);
980 // Audit the results.
981 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
982 Category& tran=stc.getTransactionLog();
983 if (tran.isInfoEnabled()) {
985 "Caching the following attributes after AAP applied for session (ID: " <<
986 m_obj["key"].string() <<
987 ") on (applicationId: " <<
988 m_obj["application_id"].string() <<
989 ") for principal from (IdP: " <<
990 m_obj["provider_id"].string() <<
993 Iterator<SAMLAssertion*> loggies=copy->getAssertions();
994 while (loggies.hasNext()) {
995 SAMLAssertion* logit=loggies.next();
996 Iterator<SAMLStatement*> states=logit->getStatements();
997 while (states.hasNext()) {
998 SAMLAttributeStatement* state=dynamic_cast<SAMLAttributeStatement*>(states.next());
999 Iterator<SAMLAttribute*> attrs=state ? state->getAttributes() : EMPTY(SAMLAttribute*);
1000 while (attrs.hasNext()) {
1001 SAMLAttribute* attr=attrs.next();
1002 xmltooling::auto_ptr_char attrname(attr->getName());
1003 tran.infoStream() << "\t" << attrname.get() << " (" << attr->getValues().size() << " values)";
1009 stc.releaseTransactionLog();
1011 return copy.release();
1014 MemorySessionCache::MemorySessionCache(const DOMElement* e)
1015 : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
1016 m_strictValidity(true), m_propagateErrors(false), m_writeThrough(false), m_lock(RWLock::create()),
1017 m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")),
1018 restoreInsert(NULL), restoreFind(NULL), restoreRemove(NULL), m_sink(NULL)
1021 const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
1023 m_AATimeout = XMLString::parseInt(tag);
1028 tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
1030 m_AAConnectTimeout = XMLString::parseInt(tag);
1031 if (!m_AAConnectTimeout)
1032 m_AAConnectTimeout=15;
1035 tag=m_root->getAttributeNS(NULL,defaultLifetime);
1037 m_defaultLifetime = XMLString::parseInt(tag);
1038 if (!m_defaultLifetime)
1039 m_defaultLifetime=1800;
1042 tag=m_root->getAttributeNS(NULL,retryInterval);
1044 m_retryInterval = XMLString::parseInt(tag);
1045 if (!m_retryInterval)
1046 m_retryInterval=300;
1049 tag=m_root->getAttributeNS(NULL,strictValidity);
1050 if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
1051 m_strictValidity=false;
1053 tag=m_root->getAttributeNS(NULL,propagateErrors);
1054 if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
1055 m_propagateErrors=true;
1057 tag=m_root->getAttributeNS(NULL,writeThrough);
1058 if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
1059 m_writeThrough=true;
1062 SAMLConfig::getConfig().timeout = m_AATimeout;
1063 SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
1065 // Register for remoted messages.
1066 ListenerService* listener=ShibTargetConfig::getConfig().getINI()->getListenerService(false);
1067 if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
1068 restoreInsert=listener->regListener("SessionCache::insert",this);
1069 restoreFind=listener->regListener("SessionCache::find",this);
1070 restoreRemove=listener->regListener("SessionCache::remove",this);
1073 m_log->info("no listener interface available, cache remoting is disabled");
1075 shutdown_wait = CondWait::create();
1077 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
1080 MemorySessionCache::~MemorySessionCache()
1082 // Shut down the cleanup thread and let it know...
1084 shutdown_wait->signal();
1085 cleanup_thread->join(NULL);
1087 // Unregister remoted messages.
1088 ListenerService* listener=ShibTargetConfig::getConfig().getINI()->getListenerService(false);
1089 if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
1090 listener->unregListener("SessionCache::insert",this,restoreInsert);
1091 listener->unregListener("SessionCache::find",this,restoreFind);
1092 listener->unregListener("SessionCache::remove",this,restoreRemove);
1095 for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,MemorySessionCacheEntry>());
1097 delete shutdown_wait;
1100 bool MemorySessionCache::setBackingStore(ISessionCacheStore* store)
1102 if (m_sink && store!=m_sink)
1109 * IPC message definitions:
1111 * SessionCache::insert
1126 * SessionCache::find
1143 * SessionCache::remove
1151 DDF MemorySessionCache::receive(const DDF& in)
1154 xmltooling::NDC ndc("receive");
1157 // Find application.
1158 xmltooling::Locker confLocker(ShibTargetConfig::getConfig().getINI());
1159 const char* aid=in["application_id"].string();
1160 const IApplication* app=aid ? ShibTargetConfig::getConfig().getINI()->getApplication(aid) : NULL;
1162 // Something's horribly wrong.
1163 m_log->error("couldn't find application (%s) for session", aid ? aid : "(missing)");
1164 throw ConfigurationException("Unable to locate application for session, deleted?");
1167 if (!strcmp(in.name(),"SessionCache::find")) {
1168 // Check required parameters.
1169 const char* key=in["key"].string();
1170 const char* client_address=in["client_address"].string();
1171 if (!key || !client_address)
1172 throw SAMLException("Required parameters missing in call to SessionCache::find");
1175 // Lookup the session and cast down to the internal type.
1176 MemorySessionCacheEntry* entry=dynamic_cast<MemorySessionCacheEntry*>(find(key,app,client_address));
1179 DDF dup=entry->getDDF().copy();
1183 catch (exception&) {
1184 remove(key,app,client_address);
1188 else if (!strcmp(in.name(),"SessionCache::remove")) {
1189 // Check required parameters.
1190 const char* key=in["key"].string();
1191 const char* client_address=in["client_address"].string();
1192 if (!key || !client_address)
1193 throw SAMLException("Required parameters missing in call to SessionCache::remove");
1195 remove(key,app,client_address);
1198 else if (!strcmp(in.name(),"SessionCache::insert")) {
1199 // Check required parameters.
1200 const char* client_address=in["client_address"].string();
1201 const char* provider_id=in["provider_id"].string();
1202 const char* authn_context=in["authn_context"].string();
1203 const char* subject=in["subject"].string();
1204 const char* tokens=in["tokens.unfiltered"].string();
1205 if (!client_address || !provider_id || !authn_context || !subject || !tokens)
1206 throw SAMLException("Required parameters missing in call to SessionCache::insert");
1207 int minor=in["minor_version"].integer();
1209 // Locate entity descriptor to use in filtering.
1210 MetadataProvider* m=app->getMetadataProvider();
1211 xmltooling::Locker locker(m);
1212 const EntityDescriptor* site=m->getEntityDescriptor(provider_id);
1214 m_log->error("unable to locate issuing identity provider's metadata");
1215 throw MetadataException("Unable to locate identity provider's metadata.");
1217 const RoleDescriptor* role=site->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
1219 role=site->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
1221 role=site->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
1223 role=site->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
1225 m_log->error("unable to locate attribute-issuing role in identity provider's metadata");
1226 throw MetadataException("Unable to locate attribute-issuing role in identity provider's metadata.");
1229 // Deserialize XML for insert method.
1230 istringstream subis(subject);
1231 auto_ptr<SAMLSubject> pSubject(new SAMLSubject(subis));
1232 istringstream tokis(tokens);
1233 auto_ptr<SAMLResponse> pTokens(new SAMLResponse(tokis,minor));
1235 // Insert the data and return the cache key.
1236 string key=insert(app,role,client_address,pSubject.get(),authn_context,pTokens.get());
1240 out.addmember("key").string(key.c_str());
1243 throw ListenerException("Unsupported operation ($1)",xmltooling::params(1,in.name()));
1246 string MemorySessionCache::insert(
1247 const IApplication* application,
1248 const RoleDescriptor* role,
1249 const char* client_addr,
1250 const SAMLSubject* subject,
1251 const char* authnContext,
1252 const SAMLResponse* tokens
1256 xmltooling::NDC ndc("insert");
1260 xmltooling::auto_ptr_char key(id);
1262 if (m_log->isDebugEnabled())
1263 m_log->debug("creating new cache entry for application %s: \"%s\"", application->getId(), key.get());
1265 auto_ptr<MemorySessionCacheEntry> entry(
1266 new MemorySessionCacheEntry(
1277 entry->populate(application,dynamic_cast<EntityDescriptor*>(role->getParent()),true);
1280 HRESULT hr=m_sink->onCreate(key.get(),application,entry.get(),1,tokens->getMinorVersion(),entry->created());
1282 m_log->error("cache store returned failure while storing new entry");
1283 throw IOException("Unable to record new session in cache store.");
1288 m_hashtable[key.get()]=entry.release();
1294 ISessionCacheEntry* MemorySessionCache::find(const char* key, const IApplication* application, const char* client_addr)
1297 xmltooling::NDC ndc("find");
1300 m_log->debug("searching memory cache for key (%s)", key);
1303 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1304 if (i==m_hashtable.end()) {
1306 m_log->debug("no match found");
1308 return NULL; // no backing store to search
1310 m_log->debug("searching backing store");
1311 string appid,addr,pid,sub,ac,tokens;
1313 time_t created,accessed;
1314 HRESULT hr=m_sink->onRead(key,appid,addr,pid,sub,ac,tokens,major,minor,created,accessed);
1317 else if (FAILED(hr)) {
1318 m_log->error("cache store returned failure during search");
1321 const IApplication* eapp=ShibTargetConfig::getConfig().getINI()->getApplication(appid.c_str());
1323 // Something's horribly wrong.
1324 m_log->error("couldn't find application (%s) for session", appid.c_str());
1325 if (FAILED(m_sink->onDelete(key)))
1326 m_log->error("cache store returned failure during delete");
1329 if (m_log->isDebugEnabled())
1330 m_log->debug("loading cache entry (ID: %s) back into memory for application (%s)", key, appid.c_str());
1332 // Locate role to use in filtering.
1333 MetadataProvider* m=eapp->getMetadataProvider();
1334 xmltooling::Locker locker(m);
1335 const EntityDescriptor* site=m->getEntityDescriptor(pid.c_str());
1337 m_log->error("unable to locate issuing identity provider's metadata");
1338 if (FAILED(m_sink->onDelete(key)))
1339 m_log->error("cache store returned failure during delete");
1342 const RoleDescriptor* role=site->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
1344 role=site->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
1346 role=site->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
1348 role=site->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
1350 m_log->error("unable to locate attribute-issuing role in identity provider's metadata");
1351 if (FAILED(m_sink->onDelete(key)))
1352 m_log->error("cache store returned failure during delete");
1356 MemorySessionCacheEntry* entry = new MemorySessionCacheEntry(
1371 m_hashtable[key]=entry;
1374 // Downgrade to a read lock and repeat the initial search.
1376 i=m_hashtable.find(key);
1377 if (i==m_hashtable.end()) {
1379 m_log->warn("cache entry was loaded from backing store, but disappeared after lock downgrade");
1384 m_log->debug("match found");
1386 // Check for application mismatch (could also do this with partitioned caches by application ID)
1387 if (!i->second->checkApplication(application)) {
1389 m_log->crit("An application (%s) attempted to access another application's session!", application->getId());
1393 // Check for timeouts, expiration, address mismatch, etc (also updates last access)
1394 // Use the return code to assign specific error messages.
1396 HRESULT hr=i->second->isValid(application, client_addr);
1398 MetadataProvider* m=application->getMetadataProvider();
1399 xmltooling::Locker locker(m);
1401 case SESSION_E_EXPIRED: {
1402 opensaml::RetryableProfileException ex("Your session has expired, and you must re-authenticate.");
1403 annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
1406 case SESSION_E_ADDRESSMISMATCH: {
1407 opensaml::RetryableProfileException ex(
1408 "Your IP address ($1) does not match the address recorded at the time the session was established.",
1409 xmltooling::params(1,client_addr)
1411 annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
1415 opensaml::RetryableProfileException ex("Your session is invalid.");
1416 annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
1426 // Lock the cache entry for the caller -- they have to unlock it.
1431 // Make sure the entry has valid tokens.
1432 MetadataProvider* m=application->getMetadataProvider();
1433 xmltooling::Locker locker(m);
1434 i->second->populate(application,m->getEntityDescriptor(i->second->getProviderId()));
1437 i->second->unlock();
1444 void MemorySessionCache::remove(const char* key, const IApplication* application, const char* client_addr)
1447 xmltooling::NDC ndc("remove");
1450 m_log->debug("removing cache entry with key (%s)", key);
1452 // lock the cache for writing, which means we know nobody is sitting in find()
1455 // grab the entry from the database.
1456 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1457 if (i==m_hashtable.end()) {
1462 // ok, remove the entry and lock it
1463 MemorySessionCacheEntry* entry=i->second;
1464 m_hashtable.erase(key);
1472 // Notify sink. Smart ptr will make sure entry gets deleted.
1473 auto_ptr<ISessionCacheEntry> entrywrap(entry);
1475 if (FAILED(m_sink->onDelete(key)))
1476 m_log->error("cache store failed to delete entry");
1479 // Transaction Logging
1480 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
1481 stc.getTransactionLog().infoStream() << "Destroyed session (ID: " << key << ")";
1482 stc.releaseTransactionLog();
1485 void MemorySessionCache::dormant(const char* key)
1488 xmltooling::NDC ndc("dormant");
1491 m_log->debug("purging old cache entry with key (%s)", key);
1493 // lock the cache for writing, which means we know nobody is sitting in find()
1496 // grab the entry from the database.
1497 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1498 if (i==m_hashtable.end()) {
1503 // ok, remove the entry and lock it
1504 MemorySessionCacheEntry* entry=i->second;
1505 m_hashtable.erase(key);
1511 // we can release the cache entry lock because we know we're not in the cache anymore
1514 auto_ptr<ISessionCacheEntry> entrywrap(entry);
1515 if (m_sink && !m_writeThrough) {
1516 // Update sink with last access data. Wrapper will make sure entry gets deleted.
1517 if (FAILED(m_sink->onUpdate(key,NULL,entry->lastAccess())))
1518 m_log->error("cache store failed to update last access timestamp");
1522 void MemorySessionCache::cleanup()
1525 xmltooling::NDC ndc("cleanup()");
1528 int rerun_timer = 0;
1529 int timeout_life = 0;
1530 Mutex* mutex = Mutex::create();
1532 // Load our configuration details...
1533 const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
1535 rerun_timer = XMLString::parseInt(tag);
1537 tag=m_root->getAttributeNS(NULL,cacheTimeout);
1539 timeout_life = XMLString::parseInt(tag);
1541 if (rerun_timer <= 0)
1542 rerun_timer = 300; // rerun every 5 minutes
1544 if (timeout_life <= 0)
1545 timeout_life = 28800; // timeout after 8 hours
1549 m_log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
1552 shutdown_wait->timedwait(mutex,rerun_timer);
1556 // Ok, let's run through the cleanup process and clean out
1557 // really old sessions. This is a two-pass process. The
1558 // first pass is done holding a read-lock while we iterate over
1559 // the cache. The second pass doesn't need a lock because
1560 // the 'deletes' will lock the cache.
1562 // Pass 1: iterate over the map and find all entries that have not been
1564 vector<string> stale_keys;
1565 time_t stale = time(NULL) - timeout_life;
1568 for (map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
1570 // If the last access was BEFORE the stale timeout...
1572 time_t last=i->second->lastAccess();
1573 i->second->unlock();
1575 stale_keys.push_back(i->first);
1579 if (!stale_keys.empty()) {
1580 m_log->info("purging %d old sessions", stale_keys.size());
1582 // Pass 2: walk through the list of stale entries and remove them from the cache
1583 for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); j++)
1584 dormant(j->c_str());
1588 m_log->info("cleanup thread finished.");
1595 void* MemorySessionCache::cleanup_fcn(void* cache_p)
1597 MemorySessionCache* cache = reinterpret_cast<MemorySessionCache*>(cache_p);
1600 // First, let's block all signals
1601 Thread::mask_all_signals();
1604 // Now run the cleanup process.
1609 IPlugIn* MemoryCacheFactory(const DOMElement* e)
1611 // If this is a long-lived process, we return the "real" cache.
1612 if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
1613 return new MemorySessionCache(e);
1614 // Otherwise, we return a stubbed front-end that remotes calls to the real cache.
1615 return new StubCache(e);