#include "util/SPConstants.h"
#include <algorithm>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
#include <xmltooling/io/HTTPRequest.h>
#include <xmltooling/io/HTTPResponse.h>
#include <xmltooling/util/DateTime.h>
# include <saml/saml2/metadata/Metadata.h>
# include <xmltooling/XMLToolingConfig.h>
# include <xmltooling/util/StorageService.h>
+# include <xercesc/util/XMLStringTokenizer.hpp>
using namespace opensaml::saml2md;
#else
# include <ctime>
using namespace shibsp;
using namespace opensaml;
using namespace xmltooling;
+using namespace boost;
using namespace std;
namespace shibsp {
bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const;
LogoutEvent* newLogoutEvent(const Application& app) const;
- bool m_cacheAssertions;
+ bool m_cacheAssertions,m_reverseIndex;
+ set<xstring> m_excludedNames;
#endif
const DOMElement* m_root; // Only valid during initialization
unsigned long m_inprocTimeout,m_cacheTimeout,m_cacheAllowance;
string m_inboundHeader,m_outboundHeader;
// inproc means we buffer sessions in memory
- RWLock* m_lock;
+ scoped_ptr<RWLock> m_lock;
map<string,StoredSession*> m_hashtable;
// management of buffered sessions
- void dormant(const char* key);
- static void* cleanup_fn(void*);
+ void dormant(const char* key);
+ static void* cleanup_fn(void*);
bool shutdown;
- CondWait* shutdown_wait;
- Thread* cleanup_thread;
+ scoped_ptr<CondWait> shutdown_wait;
+ scoped_ptr<Thread> cleanup_thread;
};
class StoredSession : public virtual Session
{
public:
- StoredSession(SSCache* cache, DDF& obj) : m_obj(obj),
-#ifndef SHIBSP_LITE
- m_nameid(nullptr),
-#endif
- m_cache(cache), m_expires(0), m_lastAccess(time(nullptr)), m_lock(nullptr) {
+ StoredSession(SSCache* cache, DDF& obj) : m_obj(obj), m_cache(cache), m_expires(0), m_lastAccess(time(nullptr)) {
auto_ptr_XMLCh exp(m_obj["expires"].string());
if (exp.get()) {
DateTime iso(exp.get());
istringstream instr(nameid);
DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
XercesJanitor<DOMDocument> janitor(doc);
- auto_ptr<saml2::NameID> n(saml2::NameIDBuilder::buildNameID());
- n->unmarshall(doc->getDocumentElement(), true);
+ m_nameid.reset(saml2::NameIDBuilder::buildNameID());
+ m_nameid->unmarshall(doc->getDocumentElement(), true);
janitor.release();
- m_nameid = n.release();
}
#endif
if (cache->inproc)
- m_lock = Mutex::create();
+ m_lock.reset(Mutex::create());
}
~StoredSession() {
- delete m_lock;
m_obj.destroy();
for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
-#ifndef SHIBSP_LITE
- delete m_nameid;
- for_each(m_tokens.begin(), m_tokens.end(), cleanup_pair<string,Assertion>());
-#endif
}
Lockable* lock() {
- if (m_lock)
+ if (m_lock.get())
m_lock->lock();
return this;
}
void unlock() {
- if (m_lock)
+ if (m_lock.get())
m_lock->unlock();
else
delete this;
}
#ifndef SHIBSP_LITE
const saml2::NameID* getNameID() const {
- return m_nameid;
+ return m_nameid.get();
}
#endif
const char* getSessionIndex() const {
DDF m_obj;
#ifndef SHIBSP_LITE
- saml2::NameID* m_nameid;
- mutable map<string,Assertion*> m_tokens;
+ scoped_ptr<saml2::NameID> m_nameid;
+ mutable map< string,boost::shared_ptr<Assertion> > m_tokens;
#endif
mutable vector<Attribute*> m_attributes;
mutable multimap<string,const Attribute*> m_attributeIndex;
SSCache* m_cache;
time_t m_expires,m_lastAccess;
- Mutex* m_lock;
+ scoped_ptr<Mutex> m_lock;
};
SessionCache* SHIBSP_DLLLOCAL StorageServiceCacheFactory(const DOMElement* const & e)
try {
m_cache->m_storage->updateContext(getID(), now + cacheTimeout);
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_cache->m_log.error("failed to update session expiration: %s", ex.what());
}
try {
ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer()-1);
}
- catch (exception&) {
+ catch (std::exception&) {
// Roll back modification to record.
m_obj["version"].integer(m_obj["version"].integer()-1);
vector<Attribute*>::size_type count = attributes.size();
if (!m_cache->m_storage)
throw ConfigurationException("Assertion retrieval requires a StorageService.");
- map<string,Assertion*>::const_iterator i = m_tokens.find(id);
- if (i!=m_tokens.end())
- return i->second;
+ map< string,boost::shared_ptr<Assertion> >::const_iterator i = m_tokens.find(id);
+ if (i != m_tokens.end())
+ return i->second.get();
string tokenstr;
if (!m_cache->m_storage->readText(getID(), id, &tokenstr, nullptr))
istringstream instr(tokenstr);
DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
XercesJanitor<DOMDocument> janitor(doc);
- auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
+ boost::shared_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
janitor.release();
-
- Assertion* token = dynamic_cast<Assertion*>(xmlObject.get());
+
+ boost::shared_ptr<Assertion> token = dynamic_pointer_cast<Assertion,XMLObject>(xmlObject);
if (!token)
throw FatalProfileException("Request for cached assertion returned an unknown object type.");
- // Transfer ownership to us.
- xmlObject.release();
- m_tokens[id]=token;
- return token;
+ m_tokens[id] = token;
+ return token.get();
}
void StoredSession::addAssertion(Assertion* assertion)
try {
ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer()-1);
}
- catch (exception&) {
+ catch (std::exception&) {
token.destroy();
m_obj["version"].integer(m_obj["version"].integer()-1);
m_cache->m_storage->deleteText(getID(), id.get());
SSCache::SSCache(const DOMElement* e)
: m_log(Category::getInstance(SHIBSP_LOGCAT".SessionCache")), inproc(true),
#ifndef SHIBSP_LITE
- m_storage(nullptr), m_storage_lite(nullptr), m_cacheAssertions(true),
+ m_storage(nullptr), m_storage_lite(nullptr), m_cacheAssertions(true), m_reverseIndex(true),
#endif
- m_root(e), m_inprocTimeout(900), m_cacheTimeout(0), m_cacheAllowance(0),
- m_lock(nullptr), shutdown(false), shutdown_wait(nullptr), cleanup_thread(nullptr)
+ m_root(e), m_inprocTimeout(900), m_cacheTimeout(0), m_cacheAllowance(0), shutdown(false)
{
SPConfig& conf = SPConfig::getConfig();
inproc = conf.isEnabled(SPConfig::InProcess);
- static const XMLCh cacheAllowance[] = UNICODE_LITERAL_14(c,a,c,h,e,A,l,l,o,w,a,n,c,e);
- static const XMLCh cacheAssertions[] = UNICODE_LITERAL_15(c,a,c,h,e,A,s,s,e,r,t,i,o,n,s);
- static const XMLCh cacheTimeout[] = UNICODE_LITERAL_12(c,a,c,h,e,T,i,m,e,o,u,t);
- static const XMLCh inprocTimeout[] = UNICODE_LITERAL_13(i,n,p,r,o,c,T,i,m,e,o,u,t);
- static const XMLCh inboundHeader[] = UNICODE_LITERAL_13(i,n,b,o,u,n,d,H,e,a,d,e,r);
- static const XMLCh outboundHeader[] = UNICODE_LITERAL_14(o,u,t,b,o,u,n,d,H,e,a,d,e,r);
- static const XMLCh _StorageService[] = UNICODE_LITERAL_14(S,t,o,r,a,g,e,S,e,r,v,i,c,e);
- static const XMLCh _StorageServiceLite[] = UNICODE_LITERAL_18(S,t,o,r,a,g,e,S,e,r,v,i,c,e,L,i,t,e);
-
- m_cacheTimeout = XMLHelper::getAttrInt(e, 0, cacheTimeout);
+ static const XMLCh cacheAllowance[] = UNICODE_LITERAL_14(c,a,c,h,e,A,l,l,o,w,a,n,c,e);
+ static const XMLCh cacheAssertions[] = UNICODE_LITERAL_15(c,a,c,h,e,A,s,s,e,r,t,i,o,n,s);
+ static const XMLCh cacheTimeout[] = UNICODE_LITERAL_12(c,a,c,h,e,T,i,m,e,o,u,t);
+ static const XMLCh excludeReverseIndex[] = UNICODE_LITERAL_19(e,x,c,l,u,d,e,R,e,v,e,r,s,e,I,n,d,e,x);
+ static const XMLCh inprocTimeout[] = UNICODE_LITERAL_13(i,n,p,r,o,c,T,i,m,e,o,u,t);
+ static const XMLCh inboundHeader[] = UNICODE_LITERAL_13(i,n,b,o,u,n,d,H,e,a,d,e,r);
+ static const XMLCh maintainReverseIndex[] = UNICODE_LITERAL_20(m,a,i,n,t,a,i,n,R,e,v,e,r,s,e,I,n,d,e,x);
+ static const XMLCh outboundHeader[] = UNICODE_LITERAL_14(o,u,t,b,o,u,n,d,H,e,a,d,e,r);
+ static const XMLCh _StorageService[] = UNICODE_LITERAL_14(S,t,o,r,a,g,e,S,e,r,v,i,c,e);
+ static const XMLCh _StorageServiceLite[] = UNICODE_LITERAL_18(S,t,o,r,a,g,e,S,e,r,v,i,c,e,L,i,t,e);
+
+ if (e && e->hasAttributeNS(nullptr, cacheTimeout)) {
+ m_log.warn("cacheTimeout property is deprecated in favor of cacheAllowance (see documentation)");
+ m_cacheTimeout = XMLHelper::getAttrInt(e, 0, cacheTimeout);
+ }
m_cacheAllowance = XMLHelper::getAttrInt(e, 0, cacheAllowance);
if (inproc)
m_inprocTimeout = XMLHelper::getAttrInt(e, 900, inprocTimeout);
}
m_cacheAssertions = XMLHelper::getAttrBool(e, true, cacheAssertions);
+ m_reverseIndex = XMLHelper::getAttrBool(e, true, maintainReverseIndex);
+ const XMLCh* excludedNames = e ? e->getAttributeNS(nullptr, excludeReverseIndex) : nullptr;
+ if (excludedNames && *excludedNames) {
+ XMLStringTokenizer toks(excludedNames);
+ while (toks.hasMoreTokens())
+ m_excludedNames.insert(toks.nextToken());
+ }
}
#endif
if (inproc) {
if (!conf.isEnabled(SPConfig::OutOfProcess) && !listener)
throw ConfigurationException("SessionCache requires a ListenerService, but none available.");
- m_lock = RWLock::create();
- shutdown_wait = CondWait::create();
- cleanup_thread = Thread::create(&cleanup_fn, this);
+ m_lock.reset(RWLock::create());
+ shutdown_wait.reset(CondWait::create());
+ cleanup_thread.reset(Thread::create(&cleanup_fn, this));
}
#ifndef SHIBSP_LITE
else {
if (inproc) {
// Shut down the cleanup thread and let it know...
shutdown = true;
- shutdown_wait->signal();
- cleanup_thread->join(nullptr);
+ if (shutdown_wait.get())
+ shutdown_wait->signal();
+ if (cleanup_thread.get())
+ cleanup_thread->join(nullptr);
for_each(m_hashtable.begin(),m_hashtable.end(),cleanup_pair<string,StoredSession>());
- delete m_lock;
-
- delete cleanup_thread;
- delete shutdown_wait;
}
#ifndef SHIBSP_LITE
else {
void SSCache::test()
{
- auto_ptr_char temp(SAMLConfig::getConfig().generateIdentifier());
+ XMLCh* wide = SAMLConfig::getConfig().generateIdentifier();
+ auto_ptr_char temp(wide);
+ XMLString::release(&wide);
m_storage->createString("SessionCacheTest", temp.get(), "Test", time(nullptr) + 60);
m_storage->deleteString("SessionCacheTest", temp.get());
}
throw FatalProfileException("Attempted to create a session with a duplicate key.");
// Store the reverse mapping for logout.
- try {
- if (nameid)
+ if (nameid && m_reverseIndex && (m_excludedNames.size() == 0 || m_excludedNames.count(nameid->getName()) == 0)) {
+ try {
insert(key.get(), expires, name.get(), index.get());
- }
- catch (exception& ex) {
- m_log.error("error storing back mapping of NameID for logout: %s", ex.what());
+ }
+ catch (std::exception& ex) {
+ m_log.error("error storing back mapping of NameID for logout: %s", ex.what());
+ }
}
if (tokens && m_cacheAssertions) {
throw IOException("Duplicate assertion ID ($1)", params(1, tokenid.get()));
}
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("error storing assertion along with session: %s", ex.what());
}
}
}
}
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("error while matching session: %s", ex.what());
}
return false;
record.erase();
}
+ if (!m_reverseIndex) {
+ m_log.error("cannot support logout because maintainReverseIndex property is turned off");
+ throw ConfigurationException("Logout is unsupported by the session cache configuration.");
+ }
+
// Read in potentially matching sessions.
ver = m_storage_lite->readText("NameID", name.get(), &record);
if (ver == 0) {
try {
session = find(app, key.string());
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("error locating session (%s): %s", key.string(), ex.what());
}
}
}
else {
- // Session's gone, so...
- sessionsKilled.push_back(key.string());
- key.destroy();
+ // Session may already be gone, or it may be associated with a different application.
+ // To be conservative, we'll leave it alone. This isn't really increasing our security
+ // risk, because if we can't lookup the session, it's unlikely the calling logout code
+ // can either, so there's no chance of removing the session anyway.
+ m_log.warn("session (%s) not accessible for logout, may be gone, or associated with a different application", key.string());
}
key = sessions.next();
}
m_log.warn("logout mapping record changed behind us, leaving it alone");
}
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("error updating logout mapping record: %s", ex.what());
}
m_log.warn("unable to audit event, log event object was of an incorrect type");
}
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.warn("exception auditing event: %s", ex.what());
}
return nullptr;
if (timeout && *timeout > 0 && now - lastAccess >= *timeout) {
m_log.info("session timed out (ID: %s)", key);
- auto_ptr<LogoutEvent> logout_event(newLogoutEvent(app));
+ scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(app));
if (logout_event.get()) {
logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_INVALID;
logout_event->m_sessions.push_back(key);
try {
m_storage->updateContext(key, now + cacheTimeout);
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("failed to update session expiration: %s", ex.what());
}
}
}
if (!XMLString::equals(session->getApplicationID(), app.getId())) {
- m_log.error("an application (%s) tried to access another application's session", app.getId());
+ m_log.warn("an application (%s) tried to access another application's session", app.getId());
session->unlock();
return nullptr;
}
}
catch (...) {
#ifndef SHIBSP_LITE
- auto_ptr<LogoutEvent> logout_event(newLogoutEvent(app));
+ scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(app));
if (logout_event.get()) {
logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_INVALID;
logout_event->m_session = session;
response->setCookie(shib_cookie.first.c_str(), exp.c_str());
}
}
- catch (exception&) {
+ catch (std::exception&) {
HTTPResponse* response = dynamic_cast<HTTPResponse*>(&request);
if (response) {
if (!m_outboundHeader.empty())
Thread::mask_all_signals();
#endif
- auto_ptr<Mutex> mutex(Mutex::create());
+ scoped_ptr<Mutex> mutex(Mutex::create());
// Load our configuration details...
static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
pcache->m_log.debug("cleanup thread running");
pcache->m_lock->rdlock();
- for (map<string,StoredSession*>::const_iterator i=pcache->m_hashtable.begin(); i!=pcache->m_hashtable.end(); ++i) {
+ for (map<string,StoredSession*>::const_iterator i = pcache->m_hashtable.begin(); i != pcache->m_hashtable.end(); ++i) {
// If the last access was BEFORE the stale timeout...
i->second->lock();
time_t last=i->second->getLastAccess();
pcache->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)
- pcache->dormant(j->c_str());
+ for_each(stale_keys.begin(), stale_keys.end(), boost::bind(&SSCache::dormant, pcache, boost::bind(&string::c_str, _1)));
}
pcache->m_log.debug("cleanup thread completed");
string record;
time_t lastAccess;
if (!m_storage->readText(key, "session", &record, &lastAccess)) {
+ m_log.debug("session not found in cache (%s)", key);
DDF ret(nullptr);
DDFJanitor jan(ret);
out << ret;
if (timeout > 0 && now - lastAccess >= timeout) {
m_log.info("session timed out (ID: %s)", key);
- auto_ptr<LogoutEvent> logout_event(newLogoutEvent(*app));
+ scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(*app));
if (logout_event.get()) {
logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_INVALID;
logout_event->m_sessions.push_back(key);
try {
m_storage->updateContext(key, now + cacheTimeout);
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("failed to update session expiration: %s", ex.what());
}
}
try {
m_storage->updateContext(key, now + cacheTimeout);
}
- catch (exception& ex) {
+ catch (std::exception& ex) {
m_log.error("failed to update session expiration: %s", ex.what());
}