/*
- * The Shibboleth License, Version 1.
- * Copyright (c) 2002
- * University Corporation for Advanced Internet Development, Inc.
- * All rights reserved
+ * Copyright 2001-2005 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
*
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution, if any, must include
- * the following acknowledgment: "This product includes software developed by
- * the University Corporation for Advanced Internet Development
- * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
- * may appear in the software itself, if and wherever such third-party
- * acknowledgments normally appear.
- *
- * Neither the name of Shibboleth nor the names of its contributors, nor
- * Internet2, nor the University Corporation for Advanced Internet Development,
- * Inc., nor UCAID may be used to endorse or promote products derived from this
- * software without specific prior written permission. For written permission,
- * please contact shibboleth@shibboleth.org
- *
- * Products derived from this software may not be called Shibboleth, Internet2,
- * UCAID, or the University Corporation for Advanced Internet Development, nor
- * may Shibboleth appear in their name, without prior written permission of the
- * University Corporation for Advanced Internet Development.
- *
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
- * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
- * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
- * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
-
/*
- * shib-ccache.cpp -- SHAR Credential Cache
- *
- * Originally from mod_shib
- * Modified by: Derek Atkins <derek@ihtfp.com>
+ * shib-ccache.cpp -- in-memory session cache plugin
*
* $Id$
*/
-#ifndef WIN32
+#include "internal.h"
+
+#if HAVE_UNISTD_H
# include <unistd.h>
#endif
-#include "shib-target.h"
-#include <shib/shib-threads.h>
-
-#include <log4cpp/Category.hh>
-
+#include <ctime>
+#include <algorithm>
#include <sstream>
#include <stdexcept>
+#include <log4cpp/Category.hh>
+#include <shibsp/SPConfig.h>
+#include <xmltooling/util/NDC.h>
#ifdef HAVE_LIBDMALLOCXX
#include <dmalloc.h>
#endif
-using namespace std;
-using namespace saml;
-using namespace shibboleth;
+using namespace shibsp;
using namespace shibtarget;
+using namespace saml;
+using namespace opensaml::saml2md;
+using namespace xmltooling;
+using namespace log4cpp;
+using namespace std;
-class ResourceEntry
-{
-public:
- ResourceEntry(SAMLResponse* response);
- ~ResourceEntry();
-
- bool isValid(int slop);
- Iterator<SAMLAssertion*> getAssertions();
-
- static vector<SAMLAssertion*> g_emptyVector;
-
-private:
- SAMLResponse* m_response;
+static const XMLCh cleanupInterval[] =
+{ chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
+ chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
+};
+static const XMLCh cacheTimeout[] =
+{ chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
+ chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
+};
+static const XMLCh AAConnectTimeout[] =
+{ chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
+ chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
+};
+static const XMLCh AATimeout[] =
+{ chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
- log4cpp::Category* log;
+static const XMLCh defaultLifetime[] =
+{ chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
+ chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
+};
+static const XMLCh retryInterval[] =
+{ chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
+ chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
+};
+static const XMLCh strictValidity[] =
+{ chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
+ chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
};
+static const XMLCh propagateErrors[] =
+{ chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
+ chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
+};
+static const XMLCh writeThrough[] =
+{ chLatin_w, chLatin_r, chLatin_i, chLatin_t, chLatin_e,
+ chLatin_T, chLatin_h, chLatin_r, chLatin_o, chLatin_u, chLatin_g, chLatin_h, chNull
+};
+
-class InternalCCache;
-class InternalCCacheEntry : public CCacheEntry
+/*
+ * Stubbed out, inproc version of an ISessionCacheEntry
+ */
+class StubCacheEntry : public virtual ISessionCacheEntry
{
public:
- InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
- ~InternalCCacheEntry();
+ StubCacheEntry(Category* log) : m_log(log), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
+ StubCacheEntry(DDF& obj, Category* log)
+ : m_log(log), m_obj(obj), m_pSubject(NULL), m_pUnfiltered(NULL), m_pFiltered(NULL) {}
+ ~StubCacheEntry() { m_obj.destroy(); delete m_pSubject; delete m_pUnfiltered; delete m_pFiltered; }
+ void lock() {}
+ void unlock() { delete this; }
+ const char* getClientAddress() const { return m_obj["client_address"].string(); }
+ const char* getProviderId() const { return m_obj["provider_id"].string(); }
+ const char* getAuthnContext() const { return m_obj["authn_context"].string(); }
+ pair<const char*,const SAMLSubject*> getSubject(bool xml=true, bool obj=false) const;
+ pair<const char*,const SAMLResponse*> getTokens(bool xml=true, bool obj=false) const;
+ pair<const char*,const SAMLResponse*> getFilteredTokens(bool xml=true, bool obj=false) const;
+
+protected:
+ Category* m_log;
+ mutable DDF m_obj;
+ mutable SAMLSubject* m_pSubject;
+ mutable SAMLResponse* m_pUnfiltered;
+ mutable SAMLResponse* m_pFiltered;
+};
- virtual Iterator<SAMLAssertion*> getAssertions(Resource& resource);
- virtual void preFetch(Resource& resource, int prefetch_window);
- virtual bool isSessionValid(time_t lifetime, time_t timeout);
- virtual const char* getClientAddress() { return m_clientAddress.c_str(); }
- virtual void release() { cacheitem_lock->unlock(); }
+pair<const char*,const SAMLSubject*> StubCacheEntry::getSubject(bool xml, bool obj) const
+{
+ const char* raw=m_obj["subject"].string();
+ pair<const char*,const SAMLSubject*> ret=pair<const char*,const SAMLSubject*>(NULL,NULL);
+ if (xml)
+ ret.first=raw;
+ if (obj) {
+ if (!m_pSubject) {
+ istringstream in(raw);
+ m_log->debugStream() << "decoding subject: " << (raw ? raw : "(none)") << CategoryStream::ENDLINE;
+ m_pSubject=raw ? new SAMLSubject(in) : NULL;
+ }
+ ret.second=m_pSubject;
+ }
+ return ret;
+}
- void setCache(InternalCCache *cache) { m_cache = cache; }
- time_t lastAccess() { Lock lock(access_lock); return m_lastAccess; }
- void rdlock() { cacheitem_lock->rdlock(); }
- void wrlock() { cacheitem_lock->wrlock(); }
+pair<const char*,const SAMLResponse*> StubCacheEntry::getTokens(bool xml, bool obj) const
+{
+ const char* unfiltered=m_obj["tokens.unfiltered"].string();
+ pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
+ if (xml)
+ ret.first=unfiltered;
+ if (obj) {
+ if (!m_pUnfiltered) {
+ if (unfiltered) {
+ istringstream in(unfiltered);
+ m_log->debugStream() << "decoding unfiltered tokens: " << unfiltered << CategoryStream::ENDLINE;
+ m_pUnfiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
+ }
+ }
+ ret.second=m_pUnfiltered;
+ }
+ return ret;
+}
-private:
- ResourceEntry* populate(Resource& resource, int slop);
- ResourceEntry* find(const char* resource);
- void insert(const char* resource, ResourceEntry* entry);
- void remove(const char* resource);
-
- string m_originSite;
- string m_handle;
- string m_clientAddress;
- time_t m_sessionCreated;
- time_t m_lastAccess;
- bool m_hasbinding;
-
- const SAMLSubject* m_subject;
- SAMLAuthenticationStatement* p_auth;
- InternalCCache *m_cache;
-
- map<string,ResourceEntry*> m_resources;
-
- static saml::QName g_authorityKind;
- static saml::QName g_respondWith;
-
- log4cpp::Category* log;
-
- // This is used to keep track of in-process "populate()" calls,
- // to make sure that we don't try to populate the same resource
- // in multiple threads.
- map<string,Mutex*> populate_locks;
- Mutex* pop_locks_lock;
-
- Mutex* access_lock;
- RWLock* resource_lock;
- RWLock* cacheitem_lock;
-
- class ResourceLock
- {
- public:
- ResourceLock(InternalCCacheEntry* entry, string resource);
- ~ResourceLock();
-
- private:
- Mutex* find(string& resource);
- InternalCCacheEntry* entry;
- string resource;
- };
-};
+pair<const char*,const SAMLResponse*> StubCacheEntry::getFilteredTokens(bool xml, bool obj) const
+{
+ const char* filtered=m_obj["tokens.filtered"].string();
+ if (!filtered)
+ return getTokens(xml,obj);
+ pair<const char*,const SAMLResponse*> ret = pair<const char*,const SAMLResponse*>(NULL,NULL);
+ if (xml)
+ ret.first=filtered;
+ if (obj) {
+ if (!m_pFiltered) {
+ istringstream in(filtered);
+ m_log->debugStream() << "decoding filtered tokens: " << filtered << CategoryStream::ENDLINE;
+ m_pFiltered=new SAMLResponse(in,m_obj["minor_version"].integer());
+ }
+ ret.second=m_pFiltered;
+ }
+ return ret;
+}
-class InternalCCache : public CCache
+/*
+ * Remoting front-half of session cache, drops out in single process deployments.
+ * TODO: Add buffering of frequently-used entries.
+ */
+class StubCache : public virtual ISessionCache
{
public:
- InternalCCache();
- virtual ~InternalCCache();
+ StubCache(const DOMElement* e);
- virtual SAMLBinding* getBinding(const XMLCh* bindingProt);
- virtual CCacheEntry* find(const char* key);
- virtual void insert(const char* key, SAMLAuthenticationStatement *s,
- const char *client_addr);
- virtual void remove(const char* key);
+ string insert(
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const SAMLSubject* subject,
+ const char* authnContext,
+ const SAMLResponse* tokens
+ );
+ ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
+ void remove(const char* key, const IApplication* application, const char* client_addr);
- InternalCCacheEntry* findi(const char* key);
- void cleanup();
+ bool setBackingStore(ISessionCacheStore*) { return false; }
private:
- RWLock *lock;
-
- SAMLBinding* m_SAMLBinding;
- map<string,InternalCCacheEntry*> m_hashtable;
-
- log4cpp::Category* log;
-
- static void* cleanup_fcn(void*); // XXX Assumed an InternalCCache
- bool shutdown;
- CondWait* shutdown_wait;
- Thread* cleanup_thread;
+ Category* m_log;
};
-// Global Constructors & Destructors
-CCache::~CCache() { }
-
-CCache* CCache::getInstance(const char* type)
-{
- return (CCache*) new InternalCCache();
-}
+StubCache::StubCache(const DOMElement* e) : m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")) {}
-// static members
-saml::QName InternalCCacheEntry::g_authorityKind(saml::XML::SAMLP_NS,L(AttributeQuery));
-saml::QName InternalCCacheEntry::g_respondWith(saml::XML::SAML_NS,L(AttributeStatement));
-vector<SAMLAssertion*> ResourceEntry::g_emptyVector;
-
-
-/******************************************************************************/
-/* InternalCCache: A Credential Cache */
-/******************************************************************************/
+/*
+ * The public methods are remoted using the message passing system.
+ * In practice, insert is unlikely to be used remotely, but just in case...
+ */
-InternalCCache::InternalCCache()
+string StubCache::insert(
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const SAMLSubject* subject,
+ const char* authnContext,
+ const SAMLResponse* tokens
+ )
{
- m_SAMLBinding=SAMLBindingFactory::getInstance();
- string ctx="shibtarget.InternalCCache";
- log = &(log4cpp::Category::getInstance(ctx));
- lock = RWLock::create();
-
- shutdown_wait = CondWait::create();
- shutdown = false;
- cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
+ DDF in("SessionCache::insert"),out;
+ DDFJanitor jin(in),jout(out);
+ in.structure();
+ in.addmember("application_id").string(application->getId());
+ in.addmember("client_address").string(client_addr);
+ xmltooling::auto_ptr_char provid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
+ in.addmember("provider_id").string(provid.get());
+ in.addmember("major_version").integer(1);
+ in.addmember("minor_version").integer(tokens->getMinorVersion());
+ in.addmember("authn_context").string(authnContext);
+
+ ostringstream os;
+ os << *subject;
+ in.addmember("subject").string(os.str().c_str());
+ os.str("");
+ os << *tokens;
+ in.addmember("tokens.unfiltered").string(os.str().c_str());
+
+ out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
+ if (out["key"].isstring())
+ return out["key"].string();
+ throw opensaml::RetryableProfileException("A remoted cache insertion operation did not return a usable session key.");
}
-InternalCCache::~InternalCCache()
+ISessionCacheEntry* StubCache::find(const char* key, const IApplication* application, const char* client_addr)
{
- // Shut down the cleanup thread and let it know...
- shutdown = true;
- shutdown_wait->signal();
- cleanup_thread->join(NULL);
-
- delete m_SAMLBinding;
- for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
- delete i->second;
- delete lock;
- delete shutdown_wait;
+ DDF in("SessionCache::find"),out;
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(key);
+ in.addmember("application_id").string(application->getId());
+ in.addmember("client_address").string(client_addr);
+
+ try {
+ out=ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
+ if (!out.isstruct()) {
+ out.destroy();
+ return NULL;
+ }
+
+ // Wrap the results in a stub entry and return it to the caller.
+ return new StubCacheEntry(out,m_log);
+ }
+ catch (...) {
+ out.destroy();
+ throw;
+ }
}
-SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
+void StubCache::remove(const char* key, const IApplication* application, const char* client_addr)
{
- log->debug("looking for binding...");
- if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) {
- log->debug("https binding found");
- return m_SAMLBinding;
- }
- return NULL;
+ DDF in("SessionCache::remove");
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(key);
+ in.addmember("application_id").string(application->getId());
+ in.addmember("client_address").string(client_addr);
+
+ ShibTargetConfig::getConfig().getINI()->getListener()->send(in);
}
-// assumed a lock is held..
-InternalCCacheEntry* InternalCCache::findi(const char* key)
+/*
+ * Long-lived cache entries that store the actual sessions and
+ * wrap attribute query/refresh/filtering
+ */
+class MemorySessionCache;
+class MemorySessionCacheEntry : public virtual ISessionCacheEntry, public virtual StubCacheEntry
{
- log->debug("FindI: \"%s\"", key);
-
- map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
- if (i==m_hashtable.end()) {
- log->debug("No Match found");
- return NULL;
- }
- log->debug("Match Found.");
+public:
+ MemorySessionCacheEntry(
+ MemorySessionCache* cache,
+ const char* key,
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const SAMLSubject* subject,
+ const char* authnContext,
+ const SAMLResponse* tokens
+ );
+ MemorySessionCacheEntry(
+ MemorySessionCache* cache,
+ const char* key,
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const char* subject,
+ const char* authnContext,
+ const char* tokens,
+ int majorVersion,
+ int minorVersion,
+ time_t created,
+ time_t accessed
+ );
+ ~MemorySessionCacheEntry();
+
+ void lock() { m_lock->lock(); }
+ void unlock() { m_lock->unlock(); }
+
+ HRESULT isValid(const IApplication* application, const char* client_addr) const;
+ void populate(const IApplication* application, const EntityDescriptor* source, bool initial=false) const;
+ bool checkApplication(const IApplication* application) { return (m_obj["application_id"]==application->getId()); }
+ time_t created() const { return m_sessionCreated; }
+ time_t lastAccess() const { return m_lastAccess; }
+ const DDF& getDDF() const { return m_obj; }
+
+private:
+ bool hasAttributes(const SAMLResponse& r) const;
+ time_t calculateExpiration(const SAMLResponse& r) const;
+ pair<SAMLResponse*,SAMLResponse*> getNewResponse(const IApplication* application, const EntityDescriptor* source) const;
+ SAMLResponse* filter(const SAMLResponse* r, const IApplication* application, const RoleDescriptor* role) const;
+
+ time_t m_sessionCreated;
+ mutable time_t m_responseExpiration, m_lastAccess, m_lastRetry;
- return i->second;
-}
+ MemorySessionCache* m_cache;
+ Mutex* m_lock;
+};
-CCacheEntry* InternalCCache::find(const char* key)
+/*
+ * The actual in-memory session cache implementation.
+ */
+class MemorySessionCache : public virtual ISessionCache, public virtual Remoted
{
- log->debug("Find: \"%s\"", key);
- ReadLock rwlock(lock);
+public:
+ MemorySessionCache(const DOMElement* e);
+ virtual ~MemorySessionCache();
- InternalCCacheEntry* entry = findi(key);
- if (!entry) return NULL;
+ DDF receive(const DDF& in);
- // Lock the database for the caller -- they have to release the item.
- entry->rdlock();
- return dynamic_cast<CCacheEntry*>(entry);
-}
+ string insert(
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const SAMLSubject* subject,
+ const char* authnContext,
+ const SAMLResponse* tokens
+ );
+ ISessionCacheEntry* find(const char* key, const IApplication* application, const char* client_addr);
+ void remove(const char* key, const IApplication* application, const char* client_addr);
-void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
- const char *client_addr)
-{
- log->debug("caching new entry for \"%s\"", key);
+ void cleanup();
- InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
- entry->setCache(this);
+ bool setBackingStore(ISessionCacheStore* store);
- lock->wrlock();
- m_hashtable[key]=entry;
- lock->unlock();
-}
+private:
+ const DOMElement* m_root; // Only valid during initialization
+ RWLock* m_lock;
+ map<string,MemorySessionCacheEntry*> m_hashtable;
+
+ Category* m_log;
+ Remoted* restoreInsert;
+ Remoted* restoreFind;
+ Remoted* restoreRemove;
+ ISessionCacheStore* m_sink;
+
+ void dormant(const char* key);
+ static void* cleanup_fcn(void*);
+ bool shutdown;
+ CondWait* shutdown_wait;
+ Thread* cleanup_thread;
+
+ // extracted config settings
+ unsigned int m_AATimeout,m_AAConnectTimeout;
+ unsigned int m_defaultLifetime,m_retryInterval;
+ bool m_strictValidity,m_propagateErrors,m_writeThrough;
+ friend class MemorySessionCacheEntry;
+};
-// remove the entry from the database and then destroy the cacheentry
-void InternalCCache::remove(const char* key)
+MemorySessionCacheEntry::MemorySessionCacheEntry(
+ MemorySessionCache* cache,
+ const char* key,
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const SAMLSubject* subject,
+ const char* authnContext,
+ const SAMLResponse* tokens
+ ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
{
- log->debug("removing cache entry \"key\"", key);
-
- // grab the entry from the database. We'll have a readlock on it.
- CCacheEntry* entry = findi(key);
-
- if (!entry)
- return;
-
- // grab the cache write lock
- lock->wrlock();
-
- // verify we've still got the same entry.
- if (entry != findi(key)) {
- // Nope -- must've already been removed.
- lock->unlock();
- return;
- }
-
- // ok, remove the entry.
- m_hashtable.erase(key);
- lock->unlock();
-
- // now grab the write lock on the cacheitem.
- // This will make sure all other threads have released this item.
- InternalCCacheEntry* ientry = dynamic_cast<InternalCCacheEntry*>(entry);
- ientry->wrlock();
+ m_sessionCreated = m_lastAccess = time(NULL);
+
+ // Store session properties in DDF.
+ m_obj=DDF(NULL).structure();
+ m_obj.addmember("key").string(key);
+ m_obj.addmember("client_address").string(client_addr);
+ m_obj.addmember("application_id").string(application->getId());
+ xmltooling::auto_ptr_char pid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
+ m_obj.addmember("provider_id").string(pid.get());
+ m_obj.addmember("major_version").integer(1);
+ m_obj.addmember("minor_version").integer(tokens->getMinorVersion());
+
+ // Save the subject as XML.
+ ostringstream os;
+ os << *subject;
+ m_obj.addmember("subject").string(os.str().c_str());
+
+ // Save the authn method.
+ m_obj.addmember("authn_context").string(authnContext);
+
+ // Serialize unfiltered assertions.
+ os.str("");
+ os << *tokens;
+ m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
+
+ if (hasAttributes(*tokens)) {
+ // Filter attributes in the response.
+ auto_ptr<SAMLResponse> filtered(filter(tokens, application, role));
+
+ // Calculate expiration.
+ m_responseExpiration=calculateExpiration(*(filtered.get()));
+
+ // Serialize filtered assertions (if changes were made).
+ os.str("");
+ os << *(filtered.get());
+ string fstr=os.str();
+ if (fstr.length() != m_obj["tokens.unfiltered"].strlen())
+ m_obj.addmember("tokens.filtered").string(fstr.c_str());
+
+ // Save actual objects only if we're running inprocess. The subject needs to be
+ // owned by the entry, so we'll defer creation of a cloned copy.
+ if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
+ if (m_obj["tokens.filtered"].isstring())
+ m_pFiltered=filtered.release();
+ }
+ }
+
+ m_lock = Mutex::create();
- // we can release immediately because we know we're not in the database!
- ientry->release();
+ if (m_log->isDebugEnabled()) {
+ m_log->debug("new cache entry created: SessionID (%s) IdP (%s) Address (%s)", key, pid.get(), client_addr);
+ }
- // Now delete the entry
- delete ientry;
+ // Transaction Logging
+ xmltooling::auto_ptr_char hname(subject->getNameIdentifier()->getName());
+ STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
+ stc.getTransactionLog().infoStream() <<
+ "New session (ID: " <<
+ key <<
+ ") with (applicationId: " <<
+ application->getId() <<
+ ") for principal from (IdP: " <<
+ pid.get() <<
+ ") at (ClientAddress: " <<
+ client_addr <<
+ ") with (NameIdentifier: " <<
+ hname.get() <<
+ ")";
+ stc.releaseTransactionLog();
}
-void InternalCCache::cleanup()
+MemorySessionCacheEntry::MemorySessionCacheEntry(
+ MemorySessionCache* cache,
+ const char* key,
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const char* subject,
+ const char* authnContext,
+ const char* tokens,
+ int majorVersion,
+ int minorVersion,
+ time_t created,
+ time_t accessed
+ ) : StubCacheEntry(cache->m_log), m_cache(cache), m_responseExpiration(0), m_lastRetry(0)
{
- Mutex* mutex = Mutex::create();
- saml::NDC ndc("InternalCCache::cleanup()");
+ m_sessionCreated = created;
+ m_lastAccess = accessed;
+
+ // Reconstitute the tokens for filtering.
+ istringstream is(tokens);
+ auto_ptr<SAMLResponse> unfiltered(new SAMLResponse(is,minorVersion));
+
+ // Store session properties in DDF.
+ m_obj=DDF(NULL).structure();
+ m_obj.addmember("key").string(key);
+ m_obj.addmember("client_address").string(client_addr);
+ m_obj.addmember("application_id").string(application->getId());
+ xmltooling::auto_ptr_char pid(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
+ m_obj.addmember("provider_id").string(pid.get());
+ m_obj.addmember("subject").string(subject);
+ m_obj.addmember("authn_context").string(authnContext);
+ m_obj.addmember("tokens.unfiltered").string(tokens);
+ m_obj.addmember("major_version").integer(majorVersion);
+ m_obj.addmember("minor_version").integer(minorVersion);
+
+ if (hasAttributes(*(unfiltered.get()))) {
+ auto_ptr<SAMLResponse> filtered(filter(unfiltered.get(), application, role));
+
+ // Calculate expiration.
+ m_responseExpiration=calculateExpiration(*(filtered.get()));
+
+ // Serialize filtered assertions (if changes were made).
+ ostringstream os;
+ os << *(filtered.get());
+ string fstr=os.str();
+ if (fstr.length() != strlen(tokens))
+ m_obj.addmember("tokens.filtered").string(fstr.c_str());
+
+ // Save actual objects only if we're running inprocess.
+ if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
+ m_pUnfiltered=unfiltered.release();
+ if (m_obj["tokens.filtered"].isstring())
+ m_pFiltered=filtered.release();
+ }
+ }
+
+ m_lock = Mutex::create();
- mutex->lock();
+ if (m_log->isDebugEnabled())
+ m_log->debug("session loaded from secondary cache (ID: %s)", key);
+}
- log->debug("Cleanup thread started...");
- while (shutdown == false) {
- struct timespec ts;
- memset (&ts, 0, sizeof(ts));
- ts.tv_sec = time(NULL) + 3600; // run every hour
+MemorySessionCacheEntry::~MemorySessionCacheEntry()
+{
+ delete m_lock;
+}
- shutdown_wait->timedwait(mutex, &ts);
+HRESULT MemorySessionCacheEntry::isValid(const IApplication* app, const char* client_addr) const
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("isValid");
+#endif
- if (shutdown == true)
- break;
+ // Obtain validation rules from application settings.
+ bool consistentIPAddress=true;
+ int lifetime=0,timeout=0;
+ const PropertySet* props=app->getPropertySet("Sessions");
+ if (props) {
+ pair<bool,unsigned int> p=props->getUnsignedInt("lifetime");
+ if (p.first)
+ lifetime = p.second;
+ p=props->getUnsignedInt("timeout");
+ if (p.first)
+ timeout = p.second;
+ pair<bool,bool> pcheck=props->getBool("consistentIPAddress");
+ if (pcheck.first)
+ consistentIPAddress = pcheck.second;
+ }
+
+ if (m_log->isDebugEnabled())
+ m_log->debug("checking validity of session (ID: %s)", m_obj["key"].string());
+
+ time_t now=time(NULL);
+ if (lifetime > 0 && now > m_sessionCreated+lifetime) {
+ if (m_log->isInfoEnabled())
+ m_log->info("session expired (ID: %s)", m_obj["key"].string());
+ return SESSION_E_EXPIRED;
+ }
- log->info("Cleanup thread running...");
+ if (timeout > 0 && now-m_lastAccess >= timeout) {
+ // May need to query sink first to find out if another cluster member has been used.
+ if (m_cache->m_sink && m_cache->m_writeThrough) {
+ if (NOERROR!=m_cache->m_sink->onRead(m_obj["key"].string(),m_lastAccess))
+ m_log->error("cache store failed to return last access timestamp");
+ if (now-m_lastAccess >= timeout) {
+ m_log->info("session timed out (ID: %s)", m_obj["key"].string());
+ return SESSION_E_EXPIRED;
+ }
+ }
+ else {
+ m_log->info("session timed out (ID: %s)", m_obj["key"].string());
+ return SESSION_E_EXPIRED;
+ }
+ }
- // Ok, let's run through the cleanup process and clean out
- // really old sessions. This is a two-pass process. The
- // first pass is done holding a read-lock while we iterate over
- // the database. The second pass doesn't need a lock because
- // the 'deletes' will lock the database.
+ if (consistentIPAddress) {
+ if (m_log->isDebugEnabled())
+ m_log->debug("comparing client address %s against %s", client_addr, getClientAddress());
+ if (strcmp(client_addr, getClientAddress())) {
+ m_log->debug("client address mismatch");
+ return SESSION_E_ADDRESSMISMATCH;
+ }
+ }
- // Pass 1: iterate over the map and find all entries that have not been
- // used in X hours
- vector<string> stale_keys;
- time_t stale = time(NULL) - 8 * 3600; // XXX: 8 hour timeout.
+ m_lastAccess=now;
- lock->rdlock();
- for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
- i != m_hashtable.end(); i++)
- {
- // If the last access was BEFORE the stale timeout...
- if (i->second->lastAccess() < stale)
- stale_keys.push_back(i->first);
+ if (m_cache->m_sink && m_cache->m_writeThrough && timeout > 0) {
+ // Update sink with last access data, if possible.
+ if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),NULL,m_lastAccess)))
+ m_log->error("cache store failed to update last access timestamp");
}
- lock->unlock();
- log->info("deleting %d old items.", stale_keys.size());
+ return NOERROR;
+}
- // Pass 2: walk through the list of stale entries and remove them from
- // the database
- for (vector<string>::iterator i = stale_keys.begin();
- i != stale_keys.end(); i++)
- {
- remove (i->c_str());
+bool MemorySessionCacheEntry::hasAttributes(const SAMLResponse& r) const
+{
+ Iterator<SAMLAssertion*> assertions=r.getAssertions();
+ while (assertions.hasNext()) {
+ Iterator<SAMLStatement*> statements=assertions.next()->getStatements();
+ while (statements.hasNext()) {
+ if (dynamic_cast<SAMLAttributeStatement*>(statements.next()))
+ return true;
+ }
}
+ return false;
+}
- }
+time_t MemorySessionCacheEntry::calculateExpiration(const SAMLResponse& r) const
+{
+ time_t expiration=0;
+ Iterator<SAMLAssertion*> assertions = r.getAssertions();
+ while (assertions.hasNext()) {
+ SAMLAssertion* assertion = assertions.next();
+
+ // Only examine this assertion if it contains an attribute statement.
+ // We know at least one such statement exists, or this is a query response.
+ Iterator<SAMLStatement*> statements = assertion->getStatements();
+ while (statements.hasNext()) {
+ if (dynamic_cast<SAMLAttributeStatement*>(statements.next())) {
+ const SAMLDateTime* thistime = assertion->getNotOnOrAfter();
+
+ // If there is no time, then just continue and ignore this assertion.
+ if (thistime) {
+ // If this is a tighter expiration, cache it.
+ if (expiration == 0 || thistime->getEpoch() < expiration)
+ expiration = thistime->getEpoch();
+ }
+
+ // No need to continue with this assertion.
+ break;
+ }
+ }
+ }
- mutex->unlock();
- delete mutex;
- Thread::exit(NULL);
+ // If we didn't find any assertions with times, then use the default.
+ if (expiration == 0)
+ expiration = time(NULL) + m_cache->m_defaultLifetime;
+
+ return expiration;
}
-void* InternalCCache::cleanup_fcn(void* cache_p)
+void MemorySessionCacheEntry::populate(const IApplication* application, const EntityDescriptor* source, bool initial) const
{
- InternalCCache* cache = (InternalCCache*)cache_p;
+#ifdef _DEBUG
+ xmltooling::NDC ndc("populate");
+#endif
- // First, let's block all signals
- sigset_t sigmask;
- sigfillset(&sigmask);
- Thread::mask_signals(SIG_BLOCK, &sigmask, NULL);
+ // Do we have any attribute data cached?
+ if (m_responseExpiration > 0) {
+ // Can we use what we have?
+ if (time(NULL) < m_responseExpiration)
+ return;
+
+ // Possibly check the sink in case another cluster member already refreshed it.
+ if (m_cache->m_sink && m_cache->m_writeThrough) {
+ string tokensFromSink;
+ HRESULT hr=m_cache->m_sink->onRead(m_obj["key"].string(),tokensFromSink);
+ if (FAILED(hr))
+ m_log->error("cache store failed to return updated tokens");
+ else if (hr==NOERROR && tokensFromSink!=m_obj["tokens.unfiltered"].string()) {
+
+ // Bah...find role again.
+ const RoleDescriptor* role=source->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+ if (!role)
+ role=source->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+ if (!role)
+ role=source->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+ if (!role)
+ role=source->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+ if (!role) {
+ throw MetadataException("Unable to locate attribute-issuing role in metadata.");
+ }
+
+ // The tokens in the sink were different.
+ istringstream is(tokensFromSink);
+ auto_ptr<SAMLResponse> respFromSink(new SAMLResponse(is,m_obj["minor_version"].integer()));
+ auto_ptr<SAMLResponse> filteredFromSink(filter(respFromSink.get(),application,role));
+ time_t expFromSink=calculateExpiration(*(filteredFromSink.get()));
+
+ // Recheck to see if the new tokens are valid.
+ if (expFromSink < time(NULL)) {
+ m_log->info("loading replacement tokens into memory from cache store");
+ m_obj["tokens"].destroy();
+ delete m_pUnfiltered;
+ delete m_pFiltered;
+ m_pUnfiltered=m_pFiltered=NULL;
+ m_obj.addmember("tokens.unfiltered").string(tokensFromSink.c_str());
+
+ // Serialize filtered assertions (if changes were made).
+ ostringstream os;
+ os << *(filteredFromSink.get());
+ string fstr=os.str();
+ if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
+ m_obj.addmember("tokens.filtered").string(fstr.c_str());
+
+ // Save actual objects only if we're running inprocess.
+ if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
+ m_pUnfiltered=respFromSink.release();
+ if (m_obj["tokens.filtered"].isstring())
+ m_pFiltered=filteredFromSink.release();
+ }
+
+ m_responseExpiration=expFromSink;
+ m_lastRetry=0;
+ return;
+ }
+ }
+ }
+
+ // If we're being strict, dump what we have and reset timestamps.
+ if (m_cache->m_strictValidity) {
+ m_log->info("strictly enforcing attribute validity, dumping expired data");
+ m_obj["tokens"].destroy();
+ delete m_pUnfiltered;
+ delete m_pFiltered;
+ m_pUnfiltered=m_pFiltered=NULL;
+ m_responseExpiration=0;
+ m_lastRetry=0;
+ if (m_cache->m_sink) {
+ if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),"")))
+ m_log->error("cache store returned failure while clearing tokens from entry");
+ }
+ }
+ }
- // Now run the cleanup process.
- cache->cleanup();
+ try {
+ pair<SAMLResponse*,SAMLResponse*> new_responses=getNewResponse(application,source);
+ auto_ptr<SAMLResponse> r1(new_responses.first),r2(new_responses.second);
+ if (new_responses.first) {
+ m_obj["tokens"].destroy();
+ delete m_pUnfiltered;
+ delete m_pFiltered;
+ m_pUnfiltered=m_pFiltered=NULL;
+ m_responseExpiration=0;
+
+ // Serialize unfiltered assertions.
+ ostringstream os;
+ os << *new_responses.first;
+ m_obj.addmember("tokens.unfiltered").string(os.str().c_str());
+
+ // Serialize filtered assertions (if changes were made).
+ os.str("");
+ os << *new_responses.second;
+ string fstr=os.str();
+ if (fstr.length() != m_obj.getmember("tokens.unfiltered").strlen())
+ m_obj.addmember("tokens.filtered").string(fstr.c_str());
+
+ // Update expiration.
+ m_responseExpiration=calculateExpiration(*new_responses.second);
+
+ // Save actual objects only if we're running inprocess.
+ if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
+ m_pUnfiltered=r1.release();
+ if (m_obj["tokens.filtered"].isstring())
+ m_pFiltered=r2.release();
+ }
+
+ // Update backing store.
+ if (!initial && m_cache->m_sink) {
+ if (FAILED(m_cache->m_sink->onUpdate(m_obj["key"].string(),m_obj["tokens.unfiltered"].string())))
+ m_log->error("cache store returned failure while updating tokens in entry");
+ }
+
+ m_lastRetry=0;
+ m_log->debug("fetched and stored new response");
+ STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
+ stc.getTransactionLog().infoStream() << "Successful attribute query for session (ID: " << m_obj["key"].string() << ")";
+ stc.releaseTransactionLog();
+ }
+ }
+ catch (exception&) {
+ if (m_cache->m_propagateErrors)
+ throw;
+ m_log->warn("suppressed exception caught while trying to fetch attributes");
+ }
+#ifndef _DEBUG
+ catch (...) {
+ if (m_cache->m_propagateErrors)
+ throw;
+ m_log->warn("suppressed unknown exception caught while trying to fetch attributes");
+ }
+#endif
}
-/******************************************************************************/
-/* InternalCCacheEntry: A Credential Cache Entry */
-/******************************************************************************/
-
-InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
- : m_hasbinding(false)
+pair<SAMLResponse*,SAMLResponse*> MemorySessionCacheEntry::getNewResponse(
+ const IApplication* application, const EntityDescriptor* source
+ ) const
{
- string ctx = "shibtarget::InternalCCacheEntry";
- log = &(log4cpp::Category::getInstance(ctx));
- pop_locks_lock = Mutex::create();
- access_lock = Mutex::create();
- resource_lock = RWLock::create();
- cacheitem_lock = RWLock::create();
+#ifdef _DEBUG
+ xmltooling::NDC ndc("getNewResponse");
+#endif
- if (s == NULL) {
- log->error("NULL auth statement");
- throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
- }
+ // The retryInterval determines how often to poll an AA that might be down.
+ time_t now=time(NULL);
+ if ((now - m_lastRetry) < m_cache->m_retryInterval)
+ return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
+ if (m_lastRetry)
+ m_log->debug("retry interval exceeded, trying for attributes again");
+ m_lastRetry=now;
+
+ m_log->info("trying to get new attributes for session (ID: %s)", m_obj["key"].string());
+
+ // Transaction Logging
+ STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
+ stc.getTransactionLog().infoStream() <<
+ "Making attribute query for session (ID: " <<
+ m_obj["key"].string() <<
+ ") on (applicationId: " <<
+ m_obj["application_id"].string() <<
+ ") for principal from (IdP: " <<
+ m_obj["provider_id"].string() <<
+ ")";
+ stc.releaseTransactionLog();
+
+
+ pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
+ if (!providerID.first) {
+ m_log->crit("unable to determine ProviderID for application, not set?");
+ throw ConfigurationException("Unable to determine ProviderID for application, not set?");
+ }
- m_subject = s->getSubject();
+ // Try to locate an AA role.
+ const AttributeAuthorityDescriptor* AA=source->getAttributeAuthorityDescriptor(
+ m_obj["minor_version"].integer()==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM
+ );
+ if (!AA) {
+ m_log->warn("unable to locate metadata for identity provider's Attribute Authority");
+ return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
+ }
- xstring name = m_subject->getName();
- xstring qual = m_subject->getNameQualifier();
+ // Get protocol signing policy.
+ const PropertySet* credUse=application->getCredentialUse(source);
+ pair<bool,bool> signRequest=credUse ? credUse->getBool("signRequest") : make_pair(false,false);
+ pair<bool,const char*> signatureAlg=credUse ? credUse->getString("signatureAlg") : pair<bool,const char*>(false,NULL);
+ if (!signatureAlg.first)
+ signatureAlg.second=URI_ID_RSA_SHA1;
+ pair<bool,const char*> digestAlg=credUse ? credUse->getString("digestAlg") : pair<bool,const char*>(false,NULL);
+ if (!digestAlg.first)
+ digestAlg.second=URI_ID_SHA1;
+ pair<bool,bool> signedResponse=credUse ? credUse->getBool("signedResponse") : make_pair(false,false);
+ pair<bool,const char*> signingCred=credUse ? credUse->getString("Signing") : pair<bool,const char*>(false,NULL);
+
+ SAMLResponse* response = NULL;
+ try {
+ // Copy NameID from subject (may need to reconstitute it).
+ SAMLNameIdentifier* nameid=NULL;
+ if (m_pSubject)
+ nameid=static_cast<SAMLNameIdentifier*>(m_pSubject->getNameIdentifier()->clone());
+ else {
+ istringstream instr(m_obj["subject"].string());
+ auto_ptr<SAMLSubject> sub(new SAMLSubject(instr));
+ nameid=static_cast<SAMLNameIdentifier*>(sub->getNameIdentifier()->clone());
+ }
+
+ // Build a SAML Request....
+ SAMLAttributeQuery* q=new SAMLAttributeQuery(
+ new SAMLSubject(nameid),
+ providerID.second,
+ application->getAttributeDesignators().clone()
+ );
+ auto_ptr<SAMLRequest> req(new SAMLRequest(q));
+ req->setMinorVersion(m_obj["minor_version"].integer());
+
+ // Sign it?
+ if (signRequest.first && signRequest.second && signingCred.first) {
+ if (req->getMinorVersion()==1) {
+ shibboleth::Credentials creds(ShibTargetConfig::getConfig().getINI()->getCredentialsProviders());
+ const shibboleth::ICredResolver* cr=creds.lookup(signingCred.second);
+ if (cr)
+ req->sign(cr->getKey(),cr->getCertificates(),signatureAlg.second,digestAlg.second);
+ else
+ m_log->error("unable to sign attribute query, specified credential (%s) was not found",signingCred.second);
+ }
+ else
+ m_log->error("unable to sign SAML 1.0 attribute query, only SAML 1.1 defines signing adequately");
+ }
+
+ m_log->debug("trying to query an AA...");
+
+ // Call context object
+ ShibHTTPHook::ShibHTTPHookCallContext ctx(credUse,AA);
+
+ // Use metadata to locate endpoints.
+ const vector<AttributeService*>& endpoints=AA->getAttributeServices();
+ for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {
+ try {
+ // Get a binding object for this protocol.
+ const SAMLBinding* binding = application->getBinding((*ep)->getBinding());
+ if (!binding) {
+ xmltooling::auto_ptr_char prot((*ep)->getBinding());
+ m_log->warn("skipping binding on unsupported protocol (%s)", prot.get());
+ continue;
+ }
+ static const XMLCh https[] = {chLatin_h, chLatin_t, chLatin_t, chLatin_p, chLatin_s, chColon, chNull};
+ auto_ptr<SAMLResponse> r(binding->send((*ep)->getLocation(), *(req.get()), &ctx));
+ if (r->isSigned()) {
+ // TODO: trust stuff will be changing anyway...
+ //if (!t.validate(*r,AA))
+ // throw TrustException("Unable to verify signed response message.");
+ }
+ else if (!ctx.isAuthenticated() || XMLString::compareNString((*ep)->getLocation(),https,6))
+ throw XMLSecurityException("Response message was unauthenticated.");
+ response = r.release();
+ }
+ catch (exception& e) {
+ m_log->error("caught exception during SAML attribute query: %s", e.what());
+ }
+ }
+
+ if (response) {
+ if (signedResponse.first && signedResponse.second && !response->isSigned()) {
+ delete response;
+ m_log->error("unsigned response obtained, but we were told it must be signed.");
+ throw XMLSecurityException("Unable to obtain a signed response message.");
+ }
+
+ // Iterate over the tokens and apply basic validation.
+ time_t now=time(NULL);
+ Iterator<SAMLAssertion*> assertions=response->getAssertions();
+ for (unsigned int a=0; a<assertions.size();) {
+ // Discard any assertions not issued by the right entity.
+ if (XMLString::compareString(source->getEntityID(),assertions[a]->getIssuer())) {
+ xmltooling::auto_ptr_char bad(assertions[a]->getIssuer());
+ m_log->warn("discarding assertion not issued by (%s), instead by (%s)",m_obj["provider_id"].string(),bad.get());
+ response->removeAssertion(a);
+ continue;
+ }
+
+ // Validate the token.
+ try {
+ application->validateToken(assertions[a],now,AA,application->getTrustEngine());
+ a++;
+ }
+ catch (exception&) {
+ m_log->warn("assertion failed to validate, removing it from response");
+ response->removeAssertion(a);
+ }
+ }
+
+ // Run it through the filter.
+ return make_pair(response,filter(response,application,AA));
+ }
+ }
+ catch (exception& e) {
+ m_log->error("caught exception during query to AA: %s", e.what());
+ throw;
+ }
+
+ m_log->error("no response obtained");
+ return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
+}
- auto_ptr<char> h(XMLString::transcode(name.c_str()));
- auto_ptr<char> d(XMLString::transcode(qual.c_str()));
+SAMLResponse* MemorySessionCacheEntry::filter(
+ const SAMLResponse* r, const IApplication* application, const RoleDescriptor* role
+ ) const
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("filter");
+#endif
- m_handle = h.get();
- m_originSite = d.get();
+ // Make a copy of the original and process that against the AAP.
+ auto_ptr<SAMLResponse> copy(static_cast<SAMLResponse*>(r->clone()));
+ copy->toDOM();
+
+ Iterator<SAMLAssertion*> copies=copy->getAssertions();
+ for (unsigned long j=0; j < copies.size();) {
+ try {
+ // Finally, filter the content.
+ shibboleth::AAP::apply(application->getAAPProviders(),*(copies[j]),role);
+ j++;
+
+ }
+ catch (exception&) {
+ m_log->info("no statements remain after AAP, removing assertion");
+ copy->removeAssertion(j);
+ }
+ }
- Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
- if (bindings.hasNext())
- m_hasbinding = true;
+ // Audit the results.
+ STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
+ Category& tran=stc.getTransactionLog();
+ if (tran.isInfoEnabled()) {
+ tran.infoStream() <<
+ "Caching the following attributes after AAP applied for session (ID: " <<
+ m_obj["key"].string() <<
+ ") on (applicationId: " <<
+ m_obj["application_id"].string() <<
+ ") for principal from (IdP: " <<
+ m_obj["provider_id"].string() <<
+ ") {";
+
+ Iterator<SAMLAssertion*> loggies=copy->getAssertions();
+ while (loggies.hasNext()) {
+ SAMLAssertion* logit=loggies.next();
+ Iterator<SAMLStatement*> states=logit->getStatements();
+ while (states.hasNext()) {
+ SAMLAttributeStatement* state=dynamic_cast<SAMLAttributeStatement*>(states.next());
+ Iterator<SAMLAttribute*> attrs=state ? state->getAttributes() : EMPTY(SAMLAttribute*);
+ while (attrs.hasNext()) {
+ SAMLAttribute* attr=attrs.next();
+ xmltooling::auto_ptr_char attrname(attr->getName());
+ tran.infoStream() << "\t" << attrname.get() << " (" << attr->getValues().size() << " values)";
+ }
+ }
+ }
+ tran.info("}");
+ }
+ stc.releaseTransactionLog();
+
+ return copy.release();
+}
- m_clientAddress = client_addr;
- m_sessionCreated = m_lastAccess = time(NULL);
+MemorySessionCache::MemorySessionCache(const DOMElement* e)
+ : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
+ m_strictValidity(true), m_propagateErrors(false), m_writeThrough(false), m_lock(RWLock::create()),
+ m_log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")),
+ restoreInsert(NULL), restoreFind(NULL), restoreRemove(NULL), m_sink(NULL)
+{
+ if (m_root) {
+ const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
+ if (tag && *tag) {
+ m_AATimeout = XMLString::parseInt(tag);
+ if (!m_AATimeout)
+ m_AATimeout=30;
+ }
+
+ tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
+ if (tag && *tag) {
+ m_AAConnectTimeout = XMLString::parseInt(tag);
+ if (!m_AAConnectTimeout)
+ m_AAConnectTimeout=15;
+ }
+
+ tag=m_root->getAttributeNS(NULL,defaultLifetime);
+ if (tag && *tag) {
+ m_defaultLifetime = XMLString::parseInt(tag);
+ if (!m_defaultLifetime)
+ m_defaultLifetime=1800;
+ }
+
+ tag=m_root->getAttributeNS(NULL,retryInterval);
+ if (tag && *tag) {
+ m_retryInterval = XMLString::parseInt(tag);
+ if (!m_retryInterval)
+ m_retryInterval=300;
+ }
+
+ tag=m_root->getAttributeNS(NULL,strictValidity);
+ if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
+ m_strictValidity=false;
+
+ tag=m_root->getAttributeNS(NULL,propagateErrors);
+ if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
+ m_propagateErrors=true;
+
+ tag=m_root->getAttributeNS(NULL,writeThrough);
+ if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
+ m_writeThrough=true;
+ }
- // Save for later.
- p_auth = s;
+ SAMLConfig::getConfig().timeout = m_AATimeout;
+ SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
- log->info("New Session Created...");
- log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
- client_addr);
-}
+ // Register for remoted messages.
+ ListenerService* listener=ShibTargetConfig::getConfig().getINI()->getListener();
+ if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+ restoreInsert=listener->regListener("SessionCache::insert",this);
+ restoreFind=listener->regListener("SessionCache::find",this);
+ restoreRemove=listener->regListener("SessionCache::remove",this);
+ }
+ else
+ m_log->info("no listener interface available, cache remoting is disabled");
-InternalCCacheEntry::~InternalCCacheEntry()
-{
- log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
- delete p_auth;
- for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
- i!=m_resources.end(); i++)
- delete i->second;
-
- for (map<string,Mutex*>::iterator i=populate_locks.begin();
- i!=populate_locks.end(); i++)
- delete i->second;
-
- delete pop_locks_lock;
- delete cacheitem_lock;
- delete resource_lock;
- delete access_lock;
+ shutdown_wait = CondWait::create();
+ shutdown = false;
+ cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
}
-bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
+MemorySessionCache::~MemorySessionCache()
{
- saml::NDC ndc("isSessionValid");
- log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
- m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
- time_t now=time(NULL);
- if (lifetime > 0 && now > m_sessionCreated+lifetime) {
- log->debug("session beyond lifetime");
- return false;
- }
+ // Shut down the cleanup thread and let it know...
+ shutdown = true;
+ shutdown_wait->signal();
+ cleanup_thread->join(NULL);
+
+ // Unregister remoted messages.
+ ListenerService* listener=ShibTargetConfig::getConfig().getINI()->getListener();
+ if (listener && SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+ listener->unregListener("SessionCache::insert",this,restoreInsert);
+ listener->unregListener("SessionCache::find",this,restoreFind);
+ listener->unregListener("SessionCache::remove",this,restoreRemove);
+ }
- // Lock the access-time from here until we return
- Lock lock(access_lock);
- if (timeout > 0 && now-m_lastAccess >= timeout) {
- log->debug("session timed out");
- return false;
- }
- m_lastAccess=now;
- return true;
+ for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,MemorySessionCacheEntry>());
+ delete m_lock;
+ delete shutdown_wait;
}
-Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions(Resource& resource)
+bool MemorySessionCache::setBackingStore(ISessionCacheStore* store)
{
- saml::NDC ndc("getAssertions");
- ResourceEntry* entry = populate(resource, 0);
- if (entry)
- return entry->getAssertions();
- return Iterator<SAMLAssertion*>(ResourceEntry::g_emptyVector);
+ if (m_sink && store!=m_sink)
+ return false;
+ m_sink=store;
+ return true;
}
-void InternalCCacheEntry::preFetch(Resource& resource, int prefetch_window)
-{
- saml::NDC ndc("preFetch");
- ResourceEntry* entry = populate(resource, prefetch_window);
-}
+/*
+ * IPC message definitions:
+ *
+ * SessionCache::insert
+ *
+ * IN
+ * application_id
+ * client_address
+ * provider_id
+ * major_version
+ * minor_version
+ * authn_context
+ * subject
+ * tokens.unfiltered
+ *
+ * OUT
+ * key
+ *
+ * SessionCache::find
+ *
+ * IN
+ * key
+ * application_id
+ * client_address
+ *
+ * OUT
+ * client_address
+ * provider_id
+ * major_version
+ * minor_version
+ * authn_context
+ * subject
+ * tokens.unfiltered
+ * tokens.filtered
+ *
+ * SessionCache::remove
+ *
+ * IN
+ * key
+ * application_id
+ * client_address
+ */
-ResourceEntry* InternalCCacheEntry::populate(Resource& resource, int slop)
+DDF MemorySessionCache::receive(const DDF& in)
{
- saml::NDC ndc("populate");
- log->debug("populating entry for %s (%s)",
- resource.getResource(), resource.getURL());
-
- // Lock the resource within this entry...
- InternalCCacheEntry::ResourceLock lock(this, resource.getResource());
-
- // Can we use what we have?
- ResourceEntry *entry = find(resource.getResource());
- if (entry) {
- log->debug("found resource");
- if (entry->isValid(slop))
- return entry;
-
- // entry is invalid (expired) -- go fetch a new one.
- log->debug("removing resource cache; assertion is invalid");
- remove (resource.getResource());
- delete entry;
- }
-
- // Nope, no entry.. Create a new resource entry
-
- if (!m_hasbinding) {
- log->error("No binding!");
- return NULL;
- }
-
- log->info("trying to request attributes for %s@%s -> %s",
- m_handle.c_str(), m_originSite.c_str(), resource.getURL());
-
- auto_ptr<XMLCh> resourceURL(XMLString::transcode(resource.getURL()));
- Iterator<saml::QName> respond_withs = ArrayIterator<saml::QName>(&g_respondWith);
-
- // Clone the subject...
- // 1) I know the static_cast is safe from clone()
- // 2) the AttributeQuery will destroy this new subject.
- SAMLSubject* subject=static_cast<SAMLSubject*>(m_subject->clone());
-
- // Build a SAML Request....
- SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resourceURL.get(),
- resource.getDesignators());
- SAMLRequest* req=new SAMLRequest(respond_withs,q);
-
- // Try this request against all the bindings in the AuthenticationStatement
- // (i.e. send it to each AA in the list of bindings)
- Iterator<SAMLAuthorityBinding*> bindings = p_auth->getBindings();
- SAMLResponse* response = NULL;
-
- while (!response && bindings.hasNext()) {
- SAMLAuthorityBinding* binding = bindings.next();
-
- log->debug("Trying binding...");
- SAMLBinding* pBinding=m_cache->getBinding(binding->getBinding());
- log->debug("Sending request");
- response=pBinding->send(*binding,*req);
- }
-
- // ok, we can delete the request now.
- delete req;
-
- // Make sure we got a response
- if (!response) {
- log->info ("No Response");
- return NULL;
- }
+#ifdef _DEBUG
+ xmltooling::NDC ndc("receive");
+#endif
- entry = new ResourceEntry(response);
- insert (resource.getResource(), entry);
+ // Find application.
+ saml::Locker confLocker(ShibTargetConfig::getConfig().getINI());
+ const char* aid=in["application_id"].string();
+ const IApplication* app=aid ? ShibTargetConfig::getConfig().getINI()->getApplication(aid) : NULL;
+ if (!app) {
+ // Something's horribly wrong.
+ m_log->error("couldn't find application (%s) for session", aid ? aid : "(missing)");
+ throw ConfigurationException("Unable to locate application for session, deleted?");
+ }
- log->info("fetched and stored SAML response");
- return entry;
+ if (!strcmp(in.name(),"SessionCache::find")) {
+ // Check required parameters.
+ const char* key=in["key"].string();
+ const char* client_address=in["client_address"].string();
+ if (!key || !client_address)
+ throw SAMLException("Required parameters missing in call to SessionCache::find");
+
+ try {
+ // Lookup the session and cast down to the internal type.
+ MemorySessionCacheEntry* entry=dynamic_cast<MemorySessionCacheEntry*>(find(key,app,client_address));
+ if (!entry)
+ return DDF();
+ DDF dup=entry->getDDF().copy();
+ entry->unlock();
+ return dup;
+ }
+ catch (exception&) {
+ remove(key,app,client_address);
+ throw;
+ }
+ }
+ else if (!strcmp(in.name(),"SessionCache::remove")) {
+ // Check required parameters.
+ const char* key=in["key"].string();
+ const char* client_address=in["client_address"].string();
+ if (!key || !client_address)
+ throw SAMLException("Required parameters missing in call to SessionCache::remove");
+
+ remove(key,app,client_address);
+ return DDF();
+ }
+ else if (!strcmp(in.name(),"SessionCache::insert")) {
+ // Check required parameters.
+ const char* client_address=in["client_address"].string();
+ const char* provider_id=in["provider_id"].string();
+ const char* authn_context=in["authn_context"].string();
+ const char* subject=in["subject"].string();
+ const char* tokens=in["tokens.unfiltered"].string();
+ if (!client_address || !provider_id || !authn_context || !subject || !tokens)
+ throw SAMLException("Required parameters missing in call to SessionCache::insert");
+ int minor=in["minor_version"].integer();
+
+ // Locate entity descriptor to use in filtering.
+ MetadataProvider* m=app->getMetadataProvider();
+ xmltooling::Locker locker(m);
+ const EntityDescriptor* site=m->getEntityDescriptor(provider_id);
+ if (!site) {
+ m_log->error("unable to locate issuing identity provider's metadata");
+ throw MetadataException("Unable to locate identity provider's metadata.");
+ }
+ const RoleDescriptor* role=site->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+ if (!role)
+ role=site->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+ if (!role)
+ role=site->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+ if (!role)
+ role=site->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+ if (!role) {
+ m_log->error("unable to locate attribute-issuing role in identity provider's metadata");
+ throw MetadataException("Unable to locate attribute-issuing role in identity provider's metadata.");
+ }
+
+ // Deserialize XML for insert method.
+ istringstream subis(subject);
+ auto_ptr<SAMLSubject> pSubject(new SAMLSubject(subis));
+ istringstream tokis(tokens);
+ auto_ptr<SAMLResponse> pTokens(new SAMLResponse(tokis,minor));
+
+ // Insert the data and return the cache key.
+ string key=insert(app,role,client_address,pSubject.get(),authn_context,pTokens.get());
+
+ DDF out(NULL);
+ out.structure();
+ out.addmember("key").string(key.c_str());
+ return out;
+ }
+ throw ListenerException("Unsupported operation ($1)",xmltooling::params(1,in.name()));
}
-ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
+string MemorySessionCache::insert(
+ const IApplication* application,
+ const RoleDescriptor* role,
+ const char* client_addr,
+ const SAMLSubject* subject,
+ const char* authnContext,
+ const SAMLResponse* tokens
+ )
{
- ReadLock rwlock(resource_lock);
+#ifdef _DEBUG
+ xmltooling::NDC ndc("insert");
+#endif
- log->debug("find: %s", resource_url);
- map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
- if (i==m_resources.end()) {
- log->debug("no match found");
- return NULL;
- }
- log->debug("match found");
- return i->second;
-}
+ SAMLIdentifier id;
+ xmltooling::auto_ptr_char key(id);
+
+ if (m_log->isDebugEnabled())
+ m_log->debug("creating new cache entry for application %s: \"%s\"", application->getId(), key.get());
+
+ auto_ptr<MemorySessionCacheEntry> entry(
+ new MemorySessionCacheEntry(
+ this,
+ key.get(),
+ application,
+ role,
+ client_addr,
+ subject,
+ authnContext,
+ tokens
+ )
+ );
+ entry->populate(application,dynamic_cast<EntityDescriptor*>(role->getParent()),true);
+
+ if (m_sink) {
+ HRESULT hr=m_sink->onCreate(key.get(),application,entry.get(),1,tokens->getMinorVersion(),entry->created());
+ if (FAILED(hr)) {
+ m_log->error("cache store returned failure while storing new entry");
+ throw IOException("Unable to record new session in cache store.");
+ }
+ }
-void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
-{
- log->debug("inserting %s", resource);
+ m_lock->wrlock();
+ m_hashtable[key.get()]=entry.release();
+ m_lock->unlock();
- resource_lock->wrlock();
- m_resources[resource]=entry;
- resource_lock->unlock();
+ return key.get();
}
-// caller will delete the entry.. don't worry about that here.
-void InternalCCacheEntry::remove(const char* resource)
+ISessionCacheEntry* MemorySessionCache::find(const char* key, const IApplication* application, const char* client_addr)
{
- log->debug("removing %s", resource);
-
- resource_lock->wrlock();
- m_resources.erase(resource);
- resource_lock->unlock();
-}
+#ifdef _DEBUG
+ xmltooling::NDC ndc("find");
+#endif
+ m_log->debug("searching memory cache for key (%s)", key);
+ m_lock->rdlock();
+
+ map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
+ if (i==m_hashtable.end()) {
+ m_lock->unlock();
+ m_log->debug("no match found");
+ if (!m_sink)
+ return NULL; // no backing store to search
+
+ m_log->debug("searching backing store");
+ string appid,addr,pid,sub,ac,tokens;
+ int major,minor;
+ time_t created,accessed;
+ HRESULT hr=m_sink->onRead(key,appid,addr,pid,sub,ac,tokens,major,minor,created,accessed);
+ if (hr==S_FALSE)
+ return NULL;
+ else if (FAILED(hr)) {
+ m_log->error("cache store returned failure during search");
+ return NULL;
+ }
+ const IApplication* eapp=ShibTargetConfig::getConfig().getINI()->getApplication(appid.c_str());
+ if (!eapp) {
+ // Something's horribly wrong.
+ m_log->error("couldn't find application (%s) for session", appid.c_str());
+ if (FAILED(m_sink->onDelete(key)))
+ m_log->error("cache store returned failure during delete");
+ return NULL;
+ }
+ if (m_log->isDebugEnabled())
+ m_log->debug("loading cache entry (ID: %s) back into memory for application (%s)", key, appid.c_str());
+
+ // Locate role to use in filtering.
+ MetadataProvider* m=eapp->getMetadataProvider();
+ xmltooling::Locker locker(m);
+ const EntityDescriptor* site=m->getEntityDescriptor(pid.c_str());
+ if (!site) {
+ m_log->error("unable to locate issuing identity provider's metadata");
+ if (FAILED(m_sink->onDelete(key)))
+ m_log->error("cache store returned failure during delete");
+ return NULL;
+ }
+ const RoleDescriptor* role=site->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+ if (!role)
+ role=site->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+ if (!role)
+ role=site->getIDPSSODescriptor(samlconstants::SAML11_PROTOCOL_ENUM);
+ if (!role)
+ role=site->getIDPSSODescriptor(samlconstants::SAML10_PROTOCOL_ENUM);
+ if (!role) {
+ m_log->error("unable to locate attribute-issuing role in identity provider's metadata");
+ if (FAILED(m_sink->onDelete(key)))
+ m_log->error("cache store returned failure during delete");
+ return NULL;
+ }
+
+ MemorySessionCacheEntry* entry = new MemorySessionCacheEntry(
+ this,
+ key,
+ eapp,
+ role,
+ addr.c_str(),
+ sub.c_str(),
+ ac.c_str(),
+ tokens.c_str(),
+ major,
+ minor,
+ created,
+ accessed
+ );
+ m_lock->wrlock();
+ m_hashtable[key]=entry;
+ m_lock->unlock();
+
+ // Downgrade to a read lock and repeat the initial search.
+ m_lock->rdlock();
+ i=m_hashtable.find(key);
+ if (i==m_hashtable.end()) {
+ m_lock->unlock();
+ m_log->warn("cache entry was loaded from backing store, but disappeared after lock downgrade");
+ return NULL;
+ }
+ }
+ else
+ m_log->debug("match found");
+
+ // Check for application mismatch (could also do this with partitioned caches by application ID)
+ if (!i->second->checkApplication(application)) {
+ m_lock->unlock();
+ m_log->crit("An application (%s) attempted to access another application's session!", application->getId());
+ return NULL;
+ }
+
+ // Check for timeouts, expiration, address mismatch, etc (also updates last access)
+ // Use the return code to assign specific error messages.
+ try {
+ HRESULT hr=i->second->isValid(application, client_addr);
+ if (FAILED(hr)) {
+ MetadataProvider* m=application->getMetadataProvider();
+ xmltooling::Locker locker(m);
+ switch (hr) {
+ case SESSION_E_EXPIRED: {
+ opensaml::RetryableProfileException ex("Your session has expired, and you must re-authenticate.");
+ annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
+ }
+
+ case SESSION_E_ADDRESSMISMATCH: {
+ opensaml::RetryableProfileException ex(
+ "Your IP address ($1) does not match the address recorded at the time the session was established.",
+ xmltooling::params(1,client_addr)
+ );
+ annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
+ }
+
+ default: {
+ opensaml::RetryableProfileException ex("Your session is invalid.");
+ annotateException(&ex,m->getEntityDescriptor(i->second->getProviderId(),false)); // throws it
+ }
+ }
+ }
+ }
+ catch (...) {
+ m_lock->unlock();
+ throw;
+ }
-// a lock on a resource. This is a specific "table of locks" that
-// will provide a mutex on a particular resource within a Cache Entry.
-// Just instantiate a ResourceLock within scope of the function and it
-// will obtain and hold the proper lock until it goes out of scope and
-// deconstructs.
+ // Lock the cache entry for the caller -- they have to unlock it.
+ i->second->lock();
+ m_lock->unlock();
-InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry,
- string resource) :
- entry(entry), resource(resource)
-{
- Mutex *mutex = find(resource);
- mutex->lock();
-}
+ try {
+ // Make sure the entry has valid tokens.
+ MetadataProvider* m=application->getMetadataProvider();
+ xmltooling::Locker locker(m);
+ i->second->populate(application,m->getEntityDescriptor(i->second->getProviderId()));
+ }
+ catch (...) {
+ i->second->unlock();
+ throw;
+ }
-InternalCCacheEntry::ResourceLock::~ResourceLock()
-{
- Mutex *mutex = find(resource);
- mutex->unlock();
+ return i->second;
}
-Mutex* InternalCCacheEntry::ResourceLock::find(string& resource)
+void MemorySessionCache::remove(const char* key, const IApplication* application, const char* client_addr)
{
- Lock(entry->pop_locks_lock);
-
- map<string,Mutex*>::const_iterator i=entry->populate_locks.find(resource);
- if (i==entry->populate_locks.end()) {
- Mutex* mutex = Mutex::create();
- entry->populate_locks[resource] = mutex;
- return mutex;
- }
- return i->second;
-}
+#ifdef _DEBUG
+ xmltooling::NDC ndc("remove");
+#endif
-/******************************************************************************/
-/* ResourceEntry: A Credential Cache Entry for a particular Resource URL */
-/******************************************************************************/
+ m_log->debug("removing cache entry with key (%s)", key);
-ResourceEntry::ResourceEntry(SAMLResponse* response)
-{
- string ctx = "shibtarget::ResourceEntry";
- log = &(log4cpp::Category::getInstance(ctx));
+ // lock the cache for writing, which means we know nobody is sitting in find()
+ m_lock->wrlock();
- log->info("caching resource entry");
+ // grab the entry from the database.
+ map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
+ if (i==m_hashtable.end()) {
+ m_lock->unlock();
+ return;
+ }
- m_response = response;
-}
+ // ok, remove the entry and lock it
+ MemorySessionCacheEntry* entry=i->second;
+ m_hashtable.erase(key);
+ entry->lock();
+
+ // unlock the cache
+ m_lock->unlock();
+
+ entry->unlock();
+
+ // Notify sink. Smart ptr will make sure entry gets deleted.
+ auto_ptr<ISessionCacheEntry> entrywrap(entry);
+ if (m_sink) {
+ if (FAILED(m_sink->onDelete(key)))
+ m_log->error("cache store failed to delete entry");
+ }
-ResourceEntry::~ResourceEntry()
-{
- delete m_response;
+ // Transaction Logging
+ STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
+ stc.getTransactionLog().infoStream() << "Destroyed session (ID: " << key << ")";
+ stc.releaseTransactionLog();
}
-Iterator<SAMLAssertion*> ResourceEntry::getAssertions()
+void MemorySessionCache::dormant(const char* key)
{
- saml::NDC ndc("getAssertions");
- return m_response->getAssertions();
-}
+#ifdef _DEBUG
+ xmltooling::NDC ndc("dormant");
+#endif
-bool ResourceEntry::isValid(int slop)
-{
- saml::NDC ndc("isValid");
+ m_log->debug("purging old cache entry with key (%s)", key);
- log->info("checking validity");
+ // lock the cache for writing, which means we know nobody is sitting in find()
+ m_lock->wrlock();
- // This is awful, but the XMLDateTime class is truly horrible.
- time_t now=time(NULL)+slop;
-#ifdef WIN32
- struct tm* ptime=gmtime(&now);
-#else
- struct tm res;
- struct tm* ptime=gmtime_r(&now,&res);
-#endif
- char timebuf[32];
- strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
- auto_ptr<XMLCh> timeptr(XMLString::transcode(timebuf));
- XMLDateTime curDateTime(timeptr.get());
- curDateTime.parseDateTime();
+ // grab the entry from the database.
+ map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.find(key);
+ if (i==m_hashtable.end()) {
+ m_lock->unlock();
+ return;
+ }
- Iterator<SAMLAssertion*> iter = getAssertions();
+ // ok, remove the entry and lock it
+ MemorySessionCacheEntry* entry=i->second;
+ m_hashtable.erase(key);
+ entry->lock();
+
+ // unlock the cache
+ m_lock->unlock();
+
+ // we can release the cache entry lock because we know we're not in the cache anymore
+ entry->unlock();
+
+ auto_ptr<ISessionCacheEntry> entrywrap(entry);
+ if (m_sink && !m_writeThrough) {
+ // Update sink with last access data. Wrapper will make sure entry gets deleted.
+ if (FAILED(m_sink->onUpdate(key,NULL,entry->lastAccess())))
+ m_log->error("cache store failed to update last access timestamp");
+ }
+}
- while (iter.hasNext()) {
- SAMLAssertion* assertion = iter.next();
+void MemorySessionCache::cleanup()
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("cleanup()");
+#endif
- log->debug ("testing assertion...");
+ int rerun_timer = 0;
+ int timeout_life = 0;
+ Mutex* mutex = Mutex::create();
+
+ // Load our configuration details...
+ const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
+ if (tag && *tag)
+ rerun_timer = XMLString::parseInt(tag);
+
+ tag=m_root->getAttributeNS(NULL,cacheTimeout);
+ if (tag && *tag)
+ timeout_life = XMLString::parseInt(tag);
+
+ if (rerun_timer <= 0)
+ rerun_timer = 300; // rerun every 5 minutes
+
+ if (timeout_life <= 0)
+ timeout_life = 28800; // timeout after 8 hours
+
+ mutex->lock();
+
+ m_log->info("cleanup thread started...Run every %d secs; timeout after %d secs", rerun_timer, timeout_life);
+
+ while (!shutdown) {
+ shutdown_wait->timedwait(mutex,rerun_timer);
+ if (shutdown)
+ break;
+
+ // Ok, let's run through the cleanup process and clean out
+ // really old sessions. This is a two-pass process. The
+ // first pass is done holding a read-lock while we iterate over
+ // the cache. The second pass doesn't need a lock because
+ // the 'deletes' will lock the cache.
+
+ // Pass 1: iterate over the map and find all entries that have not been
+ // used in X hours
+ vector<string> stale_keys;
+ time_t stale = time(NULL) - timeout_life;
+
+ m_lock->rdlock();
+ for (map<string,MemorySessionCacheEntry*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
+ {
+ // If the last access was BEFORE the stale timeout...
+ i->second->lock();
+ time_t last=i->second->lastAccess();
+ i->second->unlock();
+ if (last < stale)
+ stale_keys.push_back(i->first);
+ }
+ m_lock->unlock();
+
+ if (!stale_keys.empty()) {
+ m_log->info("purging %d old sessions", stale_keys.size());
+
+ // Pass 2: walk through the list of stale entries and remove them from the cache
+ for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); j++)
+ dormant(j->c_str());
+ }
+ }
- const XMLDateTime* thistime = assertion->getNotOnOrAfter();
+ m_log->info("cleanup thread finished.");
- if (! thistime) {
- log->debug ("getNotOnOrAfter failed.");
- return false;
- }
+ mutex->unlock();
+ delete mutex;
+ Thread::exit(NULL);
+}
- auto_ptr<char> nowptr(XMLString::transcode(curDateTime.toString()));
- auto_ptr<char> assnptr(XMLString::transcode(thistime->toString()));
+void* MemorySessionCache::cleanup_fcn(void* cache_p)
+{
+ MemorySessionCache* cache = reinterpret_cast<MemorySessionCache*>(cache_p);
- log->debug ("comparing now (%s) to %s", nowptr.get(), assnptr.get());
- int result=XMLDateTime::compareOrder(&curDateTime, thistime);
+#ifndef WIN32
+ // First, let's block all signals
+ Thread::mask_all_signals();
+#endif
- if (result != XMLDateTime::LESS_THAN) {
- log->debug("nope, not still valid");
- return false;
- }
- } // while
+ // Now run the cleanup process.
+ cache->cleanup();
+ return NULL;
+}
- log->debug("yep, all still valid");
- return true;
+IPlugIn* MemoryCacheFactory(const DOMElement* e)
+{
+ // If this is a long-lived process, we return the "real" cache.
+ if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
+ return new MemorySessionCache(e);
+ // Otherwise, we return a stubbed front-end that remotes calls to the real cache.
+ return new StubCache(e);
}