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
29 #include <shib/shib-threads.h>
31 #include <log4cpp/Category.hh>
37 #ifdef HAVE_LIBDMALLOCXX
42 using namespace log4cpp;
44 using namespace shibboleth;
45 using namespace shibtarget;
47 static const XMLCh cleanupInterval[] =
48 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
49 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
51 static const XMLCh cacheTimeout[] =
52 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
53 chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
55 static const XMLCh AAConnectTimeout[] =
56 { chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
57 chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
59 static const XMLCh AATimeout[] =
60 { chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
62 static const XMLCh defaultLifetime[] =
63 { chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
64 chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
66 static const XMLCh retryInterval[] =
67 { chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
68 chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
70 static const XMLCh strictValidity[] =
71 { chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
72 chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
74 static const XMLCh propagateErrors[] =
75 { chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
76 chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
81 * Stubbed out, inproc version of an ISessionCacheEntry
83 class StubCacheEntry : public virtual ISessionCacheEntry
86 StubCacheEntry(Category* log) : m_log(log), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
87 StubCacheEntry(DDF& obj, Category* log)
88 : m_log(log), m_obj(obj), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
89 ~StubCacheEntry() { m_obj.destroy(); delete m_pSubject; delete m_pUnfiltered; delete m_pFiltered; }
91 void unlock() { delete this; }
92 const char* getClientAddress() const { return m_obj["client_address"].string(); }
93 const char* getProviderId() const { return m_obj["provider_id"].string(); }
94 const char* getAuthnContext() const { return m_obj["authn_context"].string(); }
95 pair<const char*,const SAMLSubject*> getSubject(bool xml=true, bool obj=false) const;
96 pair<const char*,const SAMLResponse*> getTokens(bool xml=true, bool obj=false) const;
97 pair<const char*,const SAMLResponse*> getFilteredTokens(bool xml=true, bool obj=false) const;
102 mutable SAMLSubject* m_pSubject;
103 mutable SAMLResponse* m_pUnfiltered;
104 mutable SAMLResponse* m_pFiltered;
107 pair<const char*,const SAMLSubject*> StubCacheEntry::getSubject(bool xml, bool obj) const
109 const char* raw=m_obj["subject"].string();
110 pair<const char*,const SAMLSubject*> ret=pair<const char*,const SAMLSubject*>(NULL,NULL);
115 istringstream in(raw);
116 m_log->debugStream() << "decoding subject: " << (raw ? raw : "(none)") << CategoryStream::ENDLINE;
117 m_pSubject=raw ? new SAMLSubject(in) : NULL;
119 ret.second=m_pSubject;
124 pair<const char*,const SAMLResponse*> StubCacheEntry::getTokens(bool xml, bool obj) const
126 const char* unfiltered=m_obj["tokens.unfiltered"].string();
127 pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
129 ret.first=unfiltered;
131 if (!m_pUnfiltered) {
133 istringstream in(unfiltered);
134 m_log->debugStream() << "decoding unfiltered tokens: " << unfiltered << CategoryStream::ENDLINE;
135 m_pUnfiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
138 ret.second=m_pUnfiltered;
143 pair<const char*,const SAMLResponse*> StubCacheEntry::getFilteredTokens(bool xml, bool obj) const
145 const char* filtered=m_obj["tokens.filtered"].string();
147 return getTokens(xml,obj);
148 pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
153 istringstream in(filtered);
154 m_log->debugStream() << "decoding filtered tokens: " << filtered << CategoryStream::ENDLINE;
155 m_pFiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
157 ret.second=m_pFiltered;
163 * Remoting front-half of session cache, drops out in single process deployments.
164 * TODO: Add buffering of frequently-used entries.
166 class StubCache : public virtual ISessionCache
169 StubCache(const DOMElement* e);
172 const IApplication* application,
173 const IEntityDescriptor* source,
174 const char* client_addr,
175 const SAMLSubject* subject,
176 const char* authnContext,
177 const SAMLResponse* tokens
179 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
180 void remove(const char* key, const IApplication* application, const char* client_addr);
182 bool setBackingStore(ISessionCacheStore*) { return false; }
188 StubCache::StubCache(const DOMElement* e) : m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")) {}
191 * The public methods are remoted using the message passing system.
192 * In practice, insert is unlikely to be used remotely, but just in case...
195 string StubCache::insert(
196 const IApplication* application,
197 const IEntityDescriptor* source,
198 const char* client_addr,
199 const SAMLSubject* subject,
200 const char* authnContext,
201 const SAMLResponse* tokens
204 DDF in("SessionCache::insert"),out;
205 DDFJanitor jin(in),jout(out);
207 in.addmember("application_id").string(application->getId());
208 in.addmember("client_address").string(client_addr);
209 auto_ptr_char provid(source->getId());
210 in.addmember("provider_id").string(provid.get());
211 in.addmember("major_version").integer(1);
212 in.addmember("minor_version").integer(tokens->getMinorVersion());
213 in.addmember("authn_context").string(authnContext);
217 in.addmember("subject").string(os.str().c_str());
220 in.addmember("tokens.unfiltered").string(os.str().c_str());
222 out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
223 if (out["key"].isstring())
224 return out["key"].string();
225 throw InvalidSessionException("A remoted cache insertion operation did not return a usable session key.");
228 ISessionCacheEntry* StubCache::find(const char* key, const IApplication* application, const char* client_addr)
230 DDF in("SessionCache::find"),out;
233 in.addmember("key").string(key);
234 in.addmember("application_id").string(application->getId());
235 in.addmember("client_address").string(client_addr);
238 out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
239 if (!out.isstruct()) {
244 // Wrap the results in a stub entry and return it to the caller.
245 return new StubCacheEntry(out,m_log);
253 void StubCache::remove(const char* key, const IApplication* application, const char* client_addr)
255 DDF in("SessionCache::remove");
258 in.addmember("key").string(key);
259 in.addmember("application_id").string(application->getId());
260 in.addmember("client_address").string(client_addr);
262 ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
266 * Long-lived cache entries that store the actual sessions and
267 * wrap attribute query/refresh/filtering
269 class MemorySessionCache;
270 class MemorySessionCacheEntry : public virtual ISessionCacheEntry, public virtual StubCacheEntry
273 MemorySessionCacheEntry(
274 MemorySessionCache* cache,
276 const IApplication* application,
277 const IEntityDescriptor* source,
278 const char* client_addr,
279 const SAMLSubject* subject,
280 const char* authnContext,
281 const SAMLResponse* tokens
283 MemorySessionCacheEntry(
284 MemorySessionCache* cache,
286 const IApplication* application,
287 const IEntityDescriptor* source,
288 const char* client_addr,
290 const char* authnContext,
297 ~MemorySessionCacheEntry();
299 void lock() { m_lock->lock(); }
300 void unlock() { m_lock->unlock(); }
302 HRESULT isValid(const IApplication* application, const char* client_addr) const;
303 void populate() const;
304 bool checkApplication(const IApplication* application) { return (m_obj["application_id"]==application->getId()); }
305 time_t created() const { return m_sessionCreated; }
306 time_t lastAccess() const { return m_lastAccess; }
307 const DDF& getDDF() const { return m_obj; }
310 bool hasAttributes(const SAMLResponse& r) const;
311 time_t calculateExpiration(const SAMLResponse& r) const;
312 pair<SAMLResponse*,SAMLResponse*> getNewResponse() const; // wraps an actual query
313 SAMLResponse* filter(const SAMLResponse* r, const IApplication* application, const IEntityDescriptor* source) const;
315 time_t m_sessionCreated;
316 mutable time_t m_responseExpiration, m_lastAccess, m_lastRetry;
318 MemorySessionCache* m_cache;
323 * The actual in-memory session cache implementation.
325 class MemorySessionCache : public virtual ISessionCache, public virtual IRemoted
328 MemorySessionCache(const DOMElement* e);
329 virtual ~MemorySessionCache();
331 DDF receive(const DDF& in);
334 const IApplication* application,
335 const IEntityDescriptor* source,
336 const char* client_addr,
337 const SAMLSubject* subject,
338 const char* authnContext,
339 const SAMLResponse* tokens
341 ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
342 void remove(const char* key, const IApplication* application, const char* client_addr);
346 bool setBackingStore(ISessionCacheStore* store);
349 const DOMElement* m_root; // Only valid during initialization
351 map<string,MemorySessionCacheEntry*> m_hashtable;
354 IRemoted* restoreInsert;
355 IRemoted* restoreFind;
356 IRemoted* restoreRemove;
357 ISessionCacheStore* m_sink;
359 void dormant(const char* key);
360 static void* cleanup_fcn(void*);
362 CondWait* shutdown_wait;
363 Thread* cleanup_thread;
365 // extracted config settings
366 unsigned int m_AATimeout,m_AAConnectTimeout;
367 unsigned int m_defaultLifetime,m_retryInterval;
368 bool m_strictValidity,m_propagateErrors;
369 friend class MemorySessionCacheEntry;
372 MemorySessionCacheEntry::MemorySessionCacheEntry(
373 MemorySessionCache* cache,
375 const IApplication* application,
376 const IEntityDescriptor* source,
377 const char* client_addr,
378 const SAMLSubject* subject,
379 const char* authnContext,
380 const SAMLResponse* tokens
381 ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
383 m_sessionCreated = m_lastAccess = time(NULL);
385 // Store session properties in DDF.
386 m_obj=DDF(NULL).structure();
387 m_obj.addmember("key").string(key);
388 m_obj.addmember("client_address").string(client_addr);
389 m_obj.addmember("application_id").string(application->getId());
390 auto_ptr_char pid(source->getId());
391 m_obj.addmember("provider_id").string(pid.get());
392 m_obj.addmember("major_version").integer(1);
393 m_obj.addmember("minor_version").integer(tokens->getMinorVersion());
395 // Save the subject as XML.
398 m_obj.addmember("subject").string(os.str().c_str());
400 // Save the authn method.
401 m_obj.addmember("authn_context").string(authnContext);
403 // Serialize unfiltered assertions.
406 m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
408 if (hasAttributes(*tokens)) {
409 // Filter attributes in the response.
410 auto_ptr<SAMLResponse> filtered(filter(tokens, application, source));
412 // Calculate expiration.
413 m_responseExpiration=calculateExpiration(*(filtered.get()));
415 // Serialize filtered assertions (if changes were made).
417 os << *(filtered.get());
418 string fstr=os.str();
419 if (fstr.length() != m_obj["tokens.unfiltered"].strlen())
420 m_obj.addmember("tokens.filtered").string(fstr.c_str());
422 // Save actual objects only if we're running inprocess. The subject needs to be
423 // owned by the entry, so we'll defer creation of a cloned copy.
424 if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
425 if (m_obj["tokens.filtered"].isstring())
426 m_pFiltered=filtered.release();
430 m_lock = Mutex::create();
432 if (m_log->isDebugEnabled()) {
433 m_log->debug("new cache entry created: SessionID (%s) IdP (%s) Address (%s)", key, pid.get(), client_addr);
436 // Transaction Logging
437 auto_ptr_char hname(subject->getNameIdentifier()->getName());
438 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
439 stc.getTransactionLog().infoStream() <<
440 "New session (ID: " <<
442 ") with (applicationId: " <<
443 application->getId() <<
444 ") for principal from (IdP: " <<
446 ") at (ClientAddress: " <<
448 ") with (NameIdentifier: " <<
451 stc.releaseTransactionLog();
454 MemorySessionCacheEntry::MemorySessionCacheEntry(
455 MemorySessionCache* cache,
457 const IApplication* application,
458 const IEntityDescriptor* source,
459 const char* client_addr,
461 const char* authnContext,
467 ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
469 m_sessionCreated = created;
470 m_lastAccess = accessed;
472 // Reconstitute the tokens for filtering.
473 istringstream is(tokens);
474 auto_ptr<SAMLResponse> unfiltered(new SAMLResponse(is,minorVersion));
476 // Store session properties in DDF.
477 m_obj=DDF(NULL).structure();
478 m_obj.addmember("key").string(key);
479 m_obj.addmember("client_address").string(client_addr);
480 m_obj.addmember("application_id").string(application->getId());
481 auto_ptr_char pid(source->getId());
482 m_obj.addmember("provider_id").string(pid.get());
483 m_obj.addmember("subject").string(subject);
484 m_obj.addmember("authn_context").string(authnContext);
485 m_obj.addmember("tokens.unfiltered").string(tokens);
486 m_obj.addmember("major_version").integer(majorVersion);
487 m_obj.addmember("minor_version").integer(minorVersion);
489 if (hasAttributes(*(unfiltered.get()))) {
490 auto_ptr<SAMLResponse> filtered(filter(unfiltered.get(), application, source));
492 // Calculate expiration.
493 m_responseExpiration=calculateExpiration(*(filtered.get()));
495 // Serialize filtered assertions (if changes were made).
497 os << *(filtered.get());
498 string fstr=os.str();
499 if (fstr.length() != strlen(tokens))
500 m_obj.addmember("tokens.filtered").string(fstr.c_str());
502 // Save actual objects only if we're running inprocess.
503 if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
504 m_pUnfiltered=unfiltered.release();
505 if (m_obj["tokens.filtered"].isstring())
506 m_pFiltered=filtered.release();
510 m_lock = Mutex::create();
512 if (m_log->isDebugEnabled())
513 m_log->debug("session loaded from secondary cache (ID: %s)", key);
517 MemorySessionCacheEntry::~MemorySessionCacheEntry()
522 HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* client_addr) const
525 saml::NDC ndc("isValid");
528 // Obtain validation rules from application settings.
529 bool consistentIPAddress=true;
530 int lifetime=0,timeout=0;
531 const IPropertySet* props=app->getPropertySet("Sessions");
533 pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
536 p=props->getUnsignedInt("timeout");
539 pair<bool,bool> pcheck=props->getBool("consistentIPAddress");
541 consistentIPAddress = pcheck.second;
544 if (m_log->isDebugEnabled())
545 m_log->debug("checking validity of session (ID: %s)", m_obj["key"].string());
547 time_t now=time(NULL);
548 if (lifetime > 0 && now > m_sessionCreated+lifetime) {
549 if (m_log->isInfoEnabled())
550 m_log->info("session expired (ID: %s)", m_obj["key"].string());
551 return SESSION_E_EXPIRED;
554 if (timeout > 0 && now-m_lastAccess >= timeout) {
555 m_log->info("session timed out (ID: %s)", m_obj["key"].string());
556 return SESSION_E_EXPIRED;
559 if (consistentIPAddress) {
560 if (m_log->isDebugEnabled())
561 m_log->debug("comparing client address %s against %s", client_addr, getClientAddress());
562 if (strcmp(client_addr, getClientAddress())) {
563 m_log->debug("client address mismatch");
564 return SESSION_E_ADDRESSMISMATCH;
572 bool MemorySessionCacheEntry::hasAttributes(const SAMLResponse& r) const
574 Iterator<SAMLAssertion*> assertions=r.getAssertions();
575 while (assertions.hasNext()) {
576 Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
577 while (statements.hasNext()) {
578 if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
585 time_t MemorySessionCacheEntry::calculateExpiration(const SAMLResponse& r) const
588 Iterator<SAMLAssertion*> assertions = r.getAssertions();
589 while (assertions.hasNext()) {
590 SAMLAssertion* assertion = assertions.next();
592 // Only examine this assertion if it contains an attribute statement.
593 // We know at least one such statement exists, or this is a query response.
594 Iterator<SAMLStatement*> statements = assertion->getStatements();
595 while (statements.hasNext()) {
596 if (dynamic_cast<SAMLAttributeStatement*>(statements.next())) {
597 const SAMLDateTime* thistime = assertion->getNotOnOrAfter();
599 // If there is no time, then just continue and ignore this assertion.
601 // If this is a tighter expiration, cache it.
602 if (expiration == 0 || thistime->getEpoch() < expiration)
603 expiration = thistime->getEpoch();
606 // No need to continue with this assertion.
612 // If we didn't find any assertions with times, then use the default.
614 expiration = time(NULL) + m_cache->m_defaultLifetime;
619 void MemorySessionCacheEntry::populate() const
622 saml::NDC ndc("populate");
625 // Do we have any attribute data cached?
626 if (m_responseExpiration > 0) {
627 // Can we use what we have?
628 if (time(NULL) < m_responseExpiration)
631 // If we're being strict, dump what we have and reset timestamps.
632 if (m_cache->m_strictValidity) {
633 m_log->info("strictly enforcing attribute validity, dumping expired data");
634 m_obj["tokens"].destroy();
635 delete m_pUnfiltered;
637 m_pUnfiltered=m_pFiltered=NULL;
638 m_responseExpiration=0;
640 if (m_cache->m_sink) {
641 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),"")))
642 m_log->error("cache store returned failure while clearing tokens from entry");
648 pair<SAMLResponse*,SAMLResponse*> new_responses=getNewResponse();
649 auto_ptr<SAMLResponse> r1(new_responses.first),r2(new_responses.second);
650 if (new_responses.first) {
651 m_obj["tokens"].destroy();
652 delete m_pUnfiltered;
654 m_pUnfiltered=m_pFiltered=NULL;
655 m_responseExpiration=0;
657 // Serialize unfiltered assertions.
659 os << *new_responses.first;
660 m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
662 // Serialize filtered assertions (if changes were made).
664 os << *new_responses.second;
665 string fstr=os.str();
666 if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
667 m_obj.addmember("tokens.filtered").string(fstr.c_str());
669 // Update expiration.
670 m_responseExpiration=calculateExpiration(*new_responses.second);
672 // Save actual objects only if we're running inprocess.
673 if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::InProcess)) {
674 m_pUnfiltered=r1.release();
675 if (m_obj["tokens.filtered"].isstring())
676 m_pFiltered=r2.release();
679 // Update backing store.
680 if (m_cache->m_sink) {
681 if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),m_obj["tokens.unfiltered"].string())))
682 m_log->error("cache store returned failure while updating tokens in entry");
686 m_log->debug("fetched and stored new response");
687 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
688 stc.getTransactionLog().infoStream() << "Successful attribute query for session (ID: " << m_obj["key"].string() << ")";
689 stc.releaseTransactionLog();
692 catch (SAMLException&) {
693 if (m_cache->m_propagateErrors)
695 m_log->warn("suppressed SAML exception caught while trying to fetch attributes");
699 if (m_cache->m_propagateErrors)
701 m_log->warn("suppressed unknown exception caught while trying to fetch attributes");
706 pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse() const
709 saml::NDC ndc("getNewResponse");
712 // The retryInterval determines how often to poll an AA that might be down.
713 time_t now=time(NULL);
714 if ((now - m_lastRetry) < m_cache->m_retryInterval)
715 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
717 m_log->debug("retry interval exceeded, trying for attributes again");
720 m_log->info("trying to get new attributes for session (ID: %s)", m_obj["key"].string());
722 // Transaction Logging
723 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
724 stc.getTransactionLog().infoStream() <<
725 "Making attribute query for session (ID: " <<
726 m_obj["key"].string() <<
727 ") on (applicationId: " <<
728 m_obj["application_id"].string() <<
729 ") for principal from (IdP: " <<
730 m_obj["provider_id"].string() <<
732 stc.releaseTransactionLog();
735 // Caller must be holding the config lock.
736 // Lookup application for session to get providerId and attributes to request.
737 IConfig* conf=ShibTargetConfig::getConfig().getINI();
738 const IApplication* application=conf->getApplication(m_obj["application_id"].string());
740 m_log->crit("unable to locate application for session, deleted?");
741 throw SAMLException("Unable to locate application for session, deleted?");
743 pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
744 if (!providerID.first) {
745 m_log->crit("unable to determine ProviderID for application, not set?");
746 throw SAMLException("Unable to determine ProviderID for application, not set?");
750 Metadata m(application->getMetadataProviders());
751 const IEntityDescriptor* site=m.lookup(m_obj["provider_id"].string());
753 m_log->error("unable to locate identity provider's metadata for attribute query");
754 throw MetadataException("Unable to locate identity provider's metadata for attribute query.");
757 // Try to locate an AA role.
758 const IAttributeAuthorityDescriptor* AA=site->getAttributeAuthorityDescriptor(
759 m_obj["minor_version"].integer()==1 ? saml::XML::SAML11_PROTOCOL_ENUM : saml::XML::SAML10_PROTOCOL_ENUM
762 m_log->warn("unable to locate metadata for identity provider's Attribute Authority");
763 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
766 // Get protocol signing policy.
767 const IPropertySet* credUse=application->getCredentialUse(site);
768 pair<bool,bool> signRequest=credUse ? credUse->getBool("signRequest") : make_pair(false,false);
769 pair<bool,const char*> signatureAlg=credUse ? credUse->getString("signatureAlg") : pair<bool,const char*>(false,NULL);
770 if (!signatureAlg.first)
771 signatureAlg.second=URI_ID_RSA_SHA1;
772 pair<bool,const char*> digestAlg=credUse ? credUse->getString("digestAlg") : pair<bool,const char*>(false,NULL);
773 if (!digestAlg.first)
774 digestAlg.second=URI_ID_SHA1;
775 pair<bool,bool> signedResponse=credUse ? credUse->getBool("signedResponse") : make_pair(false,false);
776 pair<bool,const char*> signingCred=credUse ? credUse->getString("Signing") : pair<bool,const char*>(false,NULL);
778 SAMLResponse* response = NULL;
780 // Copy NameID from subject (may need to reconstitute it).
781 SAMLNameIdentifier* nameid=NULL;
783 nameid=static_cast<SAMLNameIdentifier*>(m_pSubject->getNameIdentifier()->clone());
785 istringstream instr(m_obj["subject"].string());
786 auto_ptr<SAMLSubject> sub(new SAMLSubject(instr));
787 nameid=static_cast<SAMLNameIdentifier*>(sub->getNameIdentifier()->clone());
790 // Build a SAML Request....
791 SAMLAttributeQuery* q=new SAMLAttributeQuery(
792 new SAMLSubject(nameid),
794 application->getAttributeDesignators().clone()
796 auto_ptr<SAMLRequest> req(new SAMLRequest(q));
797 req->setMinorVersion(m_obj["minor_version"].integer());
800 if (signRequest.first && signRequest.second && signingCred.first) {
801 if (req->getMinorVersion()==1) {
802 Credentials creds(conf->getCredentialsProviders());
803 const ICredResolver* cr=creds.lookup(signingCred.second);
805 req->sign(cr->getKey(),cr->getCertificates(),signatureAlg.second,digestAlg.second);
807 m_log->error("unable to sign attribute query, specified credential (%s) was not found",signingCred.second);
810 m_log->error("unable to sign SAML 1.0 attribute query, only SAML 1.1 defines signing adequately");
813 m_log->debug("trying to query an AA...");
815 // Call context object
816 ShibHTTPHook::ShibHTTPHookCallContext ctx(credUse,AA);
817 Trust t(application->getTrustProviders());
819 // Use metadata to locate endpoints.
820 Iterator<const IEndpoint*> endpoints=AA->getAttributeServiceManager()->getEndpoints();
821 while (!response && endpoints.hasNext()) {
822 const IEndpoint* ep=endpoints.next();
824 // Get a binding object for this protocol.
825 const SAMLBinding* binding = application->getBinding(ep->getBinding());
827 auto_ptr_char prot(ep->getBinding());
828 m_log->warn("skipping binding on unsupported protocol (%s)", prot.get());
831 static const XMLCh https[] = {chLatin_h, chLatin_t, chLatin_t, chLatin_p, chLatin_s, chColon, chNull};
832 auto_ptr<SAMLResponse> r(binding->send(ep->getLocation(), *(req.get()), &ctx));
834 if (!t.validate(*r,AA))
835 throw TrustException("Unable to verify signed response message.");
837 else if (!ctx.isAuthenticated() || XMLString::compareNString(ep->getLocation(),https,6))
838 throw TrustException("Response message was unauthenticated.");
839 response = r.release();
841 catch (SAMLException& e) {
842 m_log->error("caught SAML exception during SAML attribute query: %s", e.what());
843 // Check for shib:InvalidHandle error and propagate it out.
844 Iterator<saml::QName> codes=e.getCodes();
845 if (codes.size()>1) {
846 const saml::QName& code=codes[1];
847 if (!XMLString::compareString(code.getNamespaceURI(),shibboleth::Constants::SHIB_NS) &&
848 !XMLString::compareString(code.getLocalName(), shibboleth::Constants::InvalidHandle)) {
850 throw InvalidHandleException(e.what(),params(),codes);
857 if (signedResponse.first && signedResponse.second && !response->isSigned()) {
859 m_log->error("unsigned response obtained, but we were told it must be signed.");
860 throw TrustException("Unable to obtain a signed response message.");
863 // Iterate over the tokens and apply basic validation.
864 time_t now=time(NULL);
865 Iterator<SAMLAssertion*> assertions=response->getAssertions();
866 for (unsigned int a=0; a<assertions.size();) {
867 // Discard any assertions not issued by the right entity.
868 if (XMLString::compareString(site->getId(),assertions[a]->getIssuer())) {
869 auto_ptr_char bad(assertions[a]->getIssuer());
870 m_log->warn("discarding assertion not issued by (%s), instead by (%s)",m_obj["provider_id"].string(),bad.get());
871 response->removeAssertion(a);
875 // Validate the token.
877 application->validateToken(assertions[a],now,AA,application->getTrustProviders());
880 catch (SAMLException&) {
881 m_log->warn("assertion failed to validate, removing it from response");
882 response->removeAssertion(a);
886 // Run it through the filter.
887 return make_pair(response,filter(response,application,site));
890 catch (SAMLException& e) {
891 m_log->error("caught SAML exception during query to AA: %s", e.what());
892 annotateException(&e,AA);
895 m_log->error("no response obtained");
896 return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
899 SAMLResponse* MemorySessionCacheEntry::filter(
900 const SAMLResponse* r, const IApplication* application, const IEntityDescriptor* source
904 saml::NDC ndc("filter");
907 // Make a copy of the original and process that against the AAP.
908 auto_ptr<SAMLResponse> copy(static_cast<SAMLResponse*>(r->clone()));
911 Iterator<SAMLAssertion*> copies=copy->getAssertions();
912 for (unsigned long j=0; j < copies.size();) {
914 // Finally, filter the content.
915 AAP::apply(application->getAAPProviders(),*(copies[j]),source);
919 catch (SAMLException&) {
920 m_log->info("no statements remain after AAP, removing assertion");
921 copy->removeAssertion(j);
925 // Audit the results.
926 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
927 Category& tran=stc.getTransactionLog();
928 if (tran.isInfoEnabled()) {
930 "Caching the following attributes after AAP applied for session (ID: " <<
931 m_obj["key"].string() <<
932 ") on (applicationId: " <<
933 m_obj["application_id"].string() <<
934 ") for principal from (IdP: " <<
935 m_obj["provider_id"].string() <<
938 Iterator<SAMLAssertion*> loggies=copy->getAssertions();
939 while (loggies.hasNext()) {
940 SAMLAssertion* logit=loggies.next();
941 Iterator<SAMLStatement*> states=logit->getStatements();
942 while (states.hasNext()) {
943 SAMLAttributeStatement* state=dynamic_cast<SAMLAttributeStatement*>(states.next());
944 Iterator<SAMLAttribute*> attrs=state ? state->getAttributes() : EMPTY(SAMLAttribute*);
945 while (attrs.hasNext()) {
946 SAMLAttribute* attr=attrs.next();
947 auto_ptr_char attrname(attr->getName());
948 tran.infoStream() << "\t" << attrname.get() << " (" << attr->getValues().size() << " values)";
954 stc.releaseTransactionLog();
956 return copy.release();
959 MemorySessionCache::MemorySessionCache(const DOMElement* e)
960 : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
961 m_strictValidity(true), m_propagateErrors(false), m_lock(RWLock::create()),
962 m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")),
963 restoreInsert(NULL), restoreFind(NULL), restoreRemove(NULL), m_sink(NULL)
965 const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
967 m_AATimeout = XMLString::parseInt(tag);
971 SAMLConfig::getConfig().timeout = m_AATimeout;
973 tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
975 m_AAConnectTimeout = XMLString::parseInt(tag);
976 if (!m_AAConnectTimeout)
977 m_AAConnectTimeout=15;
979 SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
981 tag=m_root->getAttributeNS(NULL,defaultLifetime);
983 m_defaultLifetime = XMLString::parseInt(tag);
984 if (!m_defaultLifetime)
985 m_defaultLifetime=1800;
988 tag=m_root->getAttributeNS(NULL,retryInterval);
990 m_retryInterval = XMLString::parseInt(tag);
991 if (!m_retryInterval)
995 tag=m_root->getAttributeNS(NULL,strictValidity);
996 if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
997 m_strictValidity=false;
999 tag=m_root->getAttributeNS(NULL,propagateErrors);
1000 if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
1001 m_propagateErrors=true;
1003 // Register for remoted messages.
1004 IListener* listener=ShibTargetConfig::getConfig().getINI()->getListener();
1005 if (listener && ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess)) {
1006 restoreInsert=listener->regListener("SessionCache::insert",this);
1007 restoreFind=listener->regListener("SessionCache::find",this);
1008 restoreRemove=listener->regListener("SessionCache::remove",this);
1011 m_log->info("no listener interface available, cache remoting is disabled");
1013 shutdown_wait = CondWait::create();
1015 cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
1018 MemorySessionCache::~MemorySessionCache()
1020 // Shut down the cleanup thread and let it know...
1022 shutdown_wait->signal();
1023 cleanup_thread->join(NULL);
1025 // Unregister remoted messages.
1026 IListener* listener=ShibTargetConfig::getConfig().getINI()->getListener();
1027 if (listener && ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess)) {
1028 listener->unregListener("SessionCache::insert",this,restoreInsert);
1029 listener->unregListener("SessionCache::find",this,restoreFind);
1030 listener->unregListener("SessionCache::remove",this,restoreRemove);
1033 for_each(m_hashtable.begin(),m_hashtable.end(),shibtarget::cleanup<string,MemorySessionCacheEntry>);
1035 delete shutdown_wait;
1038 bool MemorySessionCache::setBackingStore(ISessionCacheStore* store)
1040 if (m_sink && store!=m_sink)
1047 * IPC message definitions:
1049 * SessionCache::insert
1064 * SessionCache::find
1081 * SessionCache::remove
1089 DDF MemorySessionCache::receive(const DDF& in)
1092 saml::NDC ndc("receive");
1095 // Find application.
1096 const char* aid=in["application_id"].string();
1097 const IApplication* app=aid ? ShibTargetConfig::getConfig().getINI()->getApplication(aid) : NULL;
1099 // Something's horribly wrong.
1100 m_log->error("couldn't find application (%s) for session", aid ? aid : "(missing)");
1101 throw SAMLException("Unable to locate application for session, deleted?");
1104 if (!strcmp(in.name(),"SessionCache::find")) {
1105 // Check required parameters.
1106 const char* key=in["key"].string();
1107 const char* client_address=in["client_address"].string();
1108 if (!key || !client_address)
1109 throw SAMLException("Required parameters missing in call to SessionCache::find");
1112 // Lookup the session and cast down to the internal type.
1113 MemorySessionCacheEntry* entry=dynamic_cast<MemorySessionCacheEntry*>(find(key,app,client_address));
1116 DDF dup=entry->getDDF().copy();
1120 catch (SAMLException&) {
1121 remove(key,app,client_address);
1125 else if (!strcmp(in.name(),"SessionCache::remove")) {
1126 // Check required parameters.
1127 const char* key=in["key"].string();
1128 const char* client_address=in["client_address"].string();
1129 if (!key || !client_address)
1130 throw SAMLException("Required parameters missing in call to SessionCache::remove");
1132 remove(key,app,client_address);
1135 else if (!strcmp(in.name(),"SessionCache::insert")) {
1136 // Check required parameters.
1137 const char* client_address=in["client_address"].string();
1138 const char* provider_id=in["provider_id"].string();
1139 const char* authn_context=in["authn_context"].string();
1140 const char* subject=in["subject"].string();
1141 const char* tokens=in["tokens.unfiltered"].string();
1142 if (!client_address || !provider_id || !authn_context || !subject || !tokens)
1143 throw SAMLException("Required parameters missing in call to SessionCache::insert");
1144 int minor=in["minor_version"].integer();
1146 // Locate role descriptor to use in filtering.
1147 Metadata m(app->getMetadataProviders());
1148 const IEntityDescriptor* site=m.lookup(provider_id);
1150 m_log->error("unable to locate issuing identity provider's metadata");
1151 throw MetadataException("Unable to locate identity provider's metadata.");
1153 // Deserialize XML for insert method.
1154 istringstream subis(subject);
1155 auto_ptr<SAMLSubject> pSubject(new SAMLSubject(subis));
1156 istringstream tokis(tokens);
1157 auto_ptr<SAMLResponse> pTokens=new SAMLResponse(tokis,minor);
1159 // Insert the data and return the cache key.
1160 string key=insert(app,site,client_address,pSubject.get(),authn_context,pTokens.get());
1164 out.addmember("key").string(key.c_str());
1167 throw ListenerException("Unsupported operation ($1)",params(1,in.name()));
1170 string MemorySessionCache::insert(
1171 const IApplication* application,
1172 const IEntityDescriptor* source,
1173 const char* client_addr,
1174 const SAMLSubject* subject,
1175 const char* authnContext,
1176 const SAMLResponse* tokens
1180 saml::NDC ndc("insert");
1184 auto_ptr_char key(id);
1186 if (m_log->isDebugEnabled())
1187 m_log->debug("creating new cache entry for application %s: \"%s\"", application->getId(), key.get());
1189 auto_ptr<MemorySessionCacheEntry> entry(
1190 new MemorySessionCacheEntry(
1204 HRESULT hr=m_sink->onCreate(key.get(),application,entry.get(),1,tokens->getMinorVersion(),entry->created());
1206 m_log->error("cache store returned failure while storing new entry");
1207 throw SAMLException(hr,"Unable to record new session in cache store.");
1212 m_hashtable[key.get()]=entry.release();
1218 ISessionCacheEntry* MemorySessionCache::find(const char* key, const IApplication* application, const char* client_addr)
1221 saml::NDC ndc("find");
1224 m_log->debug("searching memory cache for key (%s)", key);
1227 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1228 if (i==m_hashtable.end()) {
1230 m_log->debug("no match found");
1232 return NULL; // no backing store to search
1234 m_log->debug("searching backing store");
1235 string appid,addr,pid,sub,ac,tokens;
1237 time_t created,accessed;
1238 HRESULT hr=m_sink->onRead(key,appid,addr,pid,sub,ac,tokens,major,minor,created,accessed);
1241 else if (FAILED(hr)) {
1242 m_log->error("cache store returned failure during search");
1245 const IApplication* eapp=ShibTargetConfig::getConfig().getINI()->getApplication(appid.c_str());
1247 // Something's horribly wrong.
1248 m_log->error("couldn't find application (%s) for session", appid.c_str());
1249 if (FAILED(m_sink->onDelete(key)))
1250 m_log->error("cache store returned failure during delete");
1253 if (m_log->isDebugEnabled())
1254 m_log->debug("loading cache entry (ID: %s) back into memory for application (%s)", key, appid.c_str());
1256 // Locate role descriptor to use in filtering.
1257 Metadata m(eapp->getMetadataProviders());
1258 const IEntityDescriptor* site=m.lookup(pid.c_str());
1260 m_log->error("unable to locate issuing identity provider's metadata");
1261 if (FAILED(m_sink->onDelete(key)))
1262 m_log->error("cache store returned failure during delete");
1265 MemorySessionCacheEntry* entry = new MemorySessionCacheEntry(
1280 m_hashtable[key]=entry;
1283 // Downgrade to a read lock and repeat the initial search.
1285 i=m_hashtable.find(key);
1286 if (i==m_hashtable.end()) {
1288 m_log->warn("cache entry was loaded from backing store, but disappeared after lock downgrade");
1293 m_log->debug("match found");
1295 // Check for application mismatch (could also do this with partitioned caches by application ID)
1296 if (!i->second->checkApplication(application)) {
1298 m_log->crit("An application (%s) attempted to access another application's session!", application->getId());
1302 // Check for timeouts, expiration, address mismatch, etc (also updates last access)
1303 // Use the return code to assign specific error messages.
1305 HRESULT hr=i->second->isValid(application, client_addr);
1307 Metadata m(application->getMetadataProviders());
1309 case SESSION_E_EXPIRED: {
1310 InvalidSessionException ex(SESSION_E_EXPIRED, "Your session has expired, and you must re-authenticate.");
1311 annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1314 case SESSION_E_ADDRESSMISMATCH: {
1315 InvalidSessionException ex(
1316 SESSION_E_ADDRESSMISMATCH,
1317 "Your IP address (%1) does not match the address recorded at the time the session was established.",
1318 params(1,client_addr)
1320 annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1324 InvalidSessionException ex(hr, "Your session is invalid.");
1325 annotateException(&ex,m.lookup(i->second->getProviderId())); // throws it
1335 // Lock the cache entry for the caller -- they have to unlock it.
1341 void MemorySessionCache::remove(const char* key, const IApplication* application, const char* client_addr)
1344 saml::NDC ndc("remove");
1347 m_log->debug("removing cache entry with key (%s)", key);
1349 // lock the cache for writing, which means we know nobody is sitting in find()
1352 // grab the entry from the database.
1353 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1354 if (i==m_hashtable.end()) {
1359 // ok, remove the entry and lock it
1360 MemorySessionCacheEntry* entry=i->second;
1361 m_hashtable.erase(key);
1369 // Notify sink. Smart ptr will make sure entry gets deleted.
1370 auto_ptr<ISessionCacheEntry> entrywrap(entry);
1372 if (FAILED(m_sink->onDelete(key)))
1373 m_log->error("cache store failed to delete entry");
1376 // Transaction Logging
1377 STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
1378 stc.getTransactionLog().infoStream() << "Destroyed session (ID: " << key << ")";
1379 stc.releaseTransactionLog();
1382 void MemorySessionCache::dormant(const char* key)
1385 saml::NDC ndc("dormant");
1388 m_log->debug("purging old cache entry with key (%s)", key);
1390 // lock the cache for writing, which means we know nobody is sitting in find()
1393 // grab the entry from the database.
1394 map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
1395 if (i==m_hashtable.end()) {
1400 // ok, remove the entry and lock it
1401 MemorySessionCacheEntry* entry=i->second;
1402 m_hashtable.erase(key);
1408 // we can release the cache entry lock because we know we're not in the cache anymore
1411 // Update sink with last access data. Wrapper will make sure entry gets deleted.
1412 auto_ptr<ISessionCacheEntry> entrywrap(entry);
1414 if (FAILED(m_sink->onUpdate(key,NULL,entry->lastAccess())))
1415 m_log->error("cache store failed to update last access timestamp");
1419 void MemorySessionCache::cleanup()
1422 saml::NDC ndc("cleanup()");
1425 int rerun_timer = 0;
1426 int timeout_life = 0;
1427 Mutex* mutex = Mutex::create();
1429 // Load our configuration details...
1430 const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
1432 rerun_timer = XMLString::parseInt(tag);
1434 tag=m_root->getAttributeNS(NULL,cacheTimeout);
1436 timeout_life = XMLString::parseInt(tag);
1438 if (rerun_timer <= 0)
1439 rerun_timer = 300; // rerun every 5 minutes
1441 if (timeout_life <= 0)
1442 timeout_life = 28800; // timeout after 8 hours
1446 m_log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
1449 shutdown_wait->timedwait(mutex,rerun_timer);
1453 // Ok, let's run through the cleanup process and clean out
1454 // really old sessions. This is a two-pass process. The
1455 // first pass is done holding a read-lock while we iterate over
1456 // the cache. The second pass doesn't need a lock because
1457 // the 'deletes' will lock the cache.
1459 // Pass 1: iterate over the map and find all entries that have not been
1461 vector<string> stale_keys;
1462 time_t stale = time(NULL) - timeout_life;
1465 for (map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
1467 // If the last access was BEFORE the stale timeout...
1469 time_t last=i->second->lastAccess();
1470 i->second->unlock();
1472 stale_keys.push_back(i->first);
1476 m_log->info("purging %d old sessions", stale_keys.size());
1478 // Pass 2: walk through the list of stale entries and remove them from the cache
1479 for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); j++)
1480 dormant(j->c_str());
1483 m_log->info("cleanup thread finished.");
1490 void* MemorySessionCache::cleanup_fcn(void* cache_p)
1492 MemorySessionCache* cache = reinterpret_cast<MemorySessionCache*>(cache_p);
1494 // First, let's block all signals
1495 Thread::mask_all_signals();
1497 // Now run the cleanup process.
1502 IPlugIn* MemoryCacheFactory(const DOMElement* e)
1504 // If this is a long-lived process, we return the "real" cache.
1505 if (ShibTargetConfig::getConfig().isEnabled(ShibTargetConfig::OutOfProcess))
1506 return new MemorySessionCache(e);
1507 // Otherwise, we return a stubbed front-end that remotes calls to the real cache.
1508 return new StubCache(e);