[cpp-sp] branch master updated: Refactor session cache with separate source files.
Scott Cantor
cantor.2 at osu.edu
Tue Feb 20 11:44:26 EST 2018
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch master
in repository cpp-sp.
View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=5fe1fb44ca7561566c0b929abf285dcf8ee36b77
The following commit(s) were added to refs/heads/master by this push:
new 5fe1fb4 Refactor session cache with separate source files.
5fe1fb4 is described below
commit 5fe1fb44ca7561566c0b929abf285dcf8ee36b77
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Feb 20 11:44:19 2018 -0500
Refactor session cache with separate source files.
---
Projects/vc15/shibsp/shibsp-lite.vcxproj | 3 +
Projects/vc15/shibsp/shibsp-lite.vcxproj.filters | 9 +
Projects/vc15/shibsp/shibsp.vcxproj | 3 +
Projects/vc15/shibsp/shibsp.vcxproj.filters | 15 +-
shibsp/Makefile.am | 3 +
shibsp/impl/StorageServiceSessionCache.cpp | 832 ++---------------------
shibsp/impl/StorageServiceSessionCache.h | 160 +++++
shibsp/impl/StoredSession.cpp | 594 ++++++++++++++++
shibsp/impl/StoredSession.h | 153 +++++
9 files changed, 996 insertions(+), 776 deletions(-)
diff --git a/Projects/vc15/shibsp/shibsp-lite.vcxproj b/Projects/vc15/shibsp/shibsp-lite.vcxproj
index 9f43c7d..c42df16 100644
--- a/Projects/vc15/shibsp/shibsp-lite.vcxproj
+++ b/Projects/vc15/shibsp/shibsp-lite.vcxproj
@@ -201,6 +201,7 @@
<ClCompile Include="..\..\..\shibsp\handler\impl\ExternalAuthHandler.cpp" />
<ClCompile Include="..\..\..\shibsp\handler\impl\LogoutInitiator.cpp" />
<ClCompile Include="..\..\..\shibsp\handler\impl\SecuredHandler.cpp" />
+ <ClCompile Include="..\..\..\shibsp\impl\StoredSession.cpp" />
<ClCompile Include="..\..\..\shibsp\ServiceProvider.cpp" />
<ClCompile Include="..\..\..\shibsp\SPConfig.cpp" />
<ClCompile Include="..\..\..\shibsp\util\CGIParser.cpp" />
@@ -258,6 +259,8 @@
<ClInclude Include="..\..\..\shibsp\GSSRequest.h" />
<ClInclude Include="..\..\..\shibsp\handler\LogoutInitiator.h" />
<ClInclude Include="..\..\..\shibsp\handler\SecuredHandler.h" />
+ <ClInclude Include="..\..\..\shibsp\impl\StorageServiceSessionCache.h" />
+ <ClInclude Include="..\..\..\shibsp\impl\StoredSession.h" />
<ClInclude Include="..\..\..\shibsp\remoting\impl\SocketListener.h" />
<ClInclude Include="..\..\..\shibsp\AbstractSPRequest.h" />
<ClInclude Include="..\..\..\shibsp\AccessControl.h" />
diff --git a/Projects/vc15/shibsp/shibsp-lite.vcxproj.filters b/Projects/vc15/shibsp/shibsp-lite.vcxproj.filters
index c1809ce..a1f9c34 100644
--- a/Projects/vc15/shibsp/shibsp-lite.vcxproj.filters
+++ b/Projects/vc15/shibsp/shibsp-lite.vcxproj.filters
@@ -240,6 +240,9 @@
<ClCompile Include="..\..\..\shibsp\remoting\impl\TCPListener.cpp">
<Filter>Source Files\remoting\impl</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\shibsp\impl\StoredSession.cpp">
+ <Filter>Source Files\impl</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\shibsp\GSSRequest.h">
@@ -371,6 +374,12 @@
<ClInclude Include="..\..\..\shibsp\util\TemplateParameters.h">
<Filter>Header Files\util</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\shibsp\impl\StorageServiceSessionCache.h">
+ <Filter>Source Files\impl</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\shibsp\impl\StoredSession.h">
+ <Filter>Source Files\impl</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\..\..\shibsp\shibsp.rc">
diff --git a/Projects/vc15/shibsp/shibsp.vcxproj b/Projects/vc15/shibsp/shibsp.vcxproj
index 3ae0fcd..fb24c93 100644
--- a/Projects/vc15/shibsp/shibsp.vcxproj
+++ b/Projects/vc15/shibsp/shibsp.vcxproj
@@ -210,6 +210,7 @@
<ClCompile Include="..\..\..\shibsp\handler\impl\ExternalAuthHandler.cpp" />
<ClCompile Include="..\..\..\shibsp\handler\impl\LogoutInitiator.cpp" />
<ClCompile Include="..\..\..\shibsp\handler\impl\SecuredHandler.cpp" />
+ <ClCompile Include="..\..\..\shibsp\impl\StoredSession.cpp" />
<ClCompile Include="..\..\..\shibsp\impl\XMLSecurityPolicyProvider.cpp" />
<ClCompile Include="..\..\..\shibsp\ServiceProvider.cpp" />
<ClCompile Include="..\..\..\shibsp\SPConfig.cpp" />
@@ -311,6 +312,8 @@
<ClInclude Include="..\..\..\shibsp\GSSRequest.h" />
<ClInclude Include="..\..\..\shibsp\handler\LogoutInitiator.h" />
<ClInclude Include="..\..\..\shibsp\handler\SecuredHandler.h" />
+ <ClInclude Include="..\..\..\shibsp\impl\StorageServiceSessionCache.h" />
+ <ClInclude Include="..\..\..\shibsp\impl\StoredSession.h" />
<ClInclude Include="..\..\..\shibsp\remoting\impl\SocketListener.h" />
<ClInclude Include="..\..\..\shibsp\AbstractSPRequest.h" />
<ClInclude Include="..\..\..\shibsp\AccessControl.h" />
diff --git a/Projects/vc15/shibsp/shibsp.vcxproj.filters b/Projects/vc15/shibsp/shibsp.vcxproj.filters
index 9f78f16..d9bb79d 100644
--- a/Projects/vc15/shibsp/shibsp.vcxproj.filters
+++ b/Projects/vc15/shibsp/shibsp.vcxproj.filters
@@ -429,6 +429,9 @@
<ClCompile Include="..\..\..\shibsp\attribute\filtering\impl\RegistrationAuthorityFunctor.cpp">
<Filter>Source Files\attribute\filtering\impl</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\shibsp\impl\StoredSession.cpp">
+ <Filter>Source Files\impl</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\shibsp\GSSRequest.h">
@@ -572,9 +575,6 @@
<ClInclude Include="..\..\..\shibsp\remoting\ddf.h">
<Filter>Header Files\remoting</Filter>
</ClInclude>
- <ClInclude Include="..\..\..\shibsp\remoting\ListenerService.h">
- <Filter>Header Files\security</Filter>
- </ClInclude>
<ClInclude Include="..\..\..\shibsp\security\PKIXTrustEngine.h">
<Filter>Header Files\security</Filter>
</ClInclude>
@@ -602,6 +602,15 @@
<ClInclude Include="..\..\..\shibsp\util\TemplateParameters.h">
<Filter>Header Files\util</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\shibsp\impl\StoredSession.h">
+ <Filter>Source Files\impl</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\shibsp\remoting\ListenerService.h">
+ <Filter>Header Files\remoting</Filter>
+ </ClInclude>
+ <ClInclude Include="..\..\..\shibsp\impl\StorageServiceSessionCache.h">
+ <Filter>Source Files\impl</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\..\..\shibsp\shibsp.rc">
diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index 728742d..bc2187b 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -105,6 +105,8 @@ utilinclude_HEADERS = \
noinst_HEADERS = \
internal.h \
+ impl/StoredSession.h \
+ impl/StorageServiceSessionCache.h \
remoting/impl/SocketListener.h
common_sources = \
@@ -152,6 +154,7 @@ common_sources = \
handler/impl/TransformSessionInitiator.cpp \
handler/impl/WAYFSessionInitiator.cpp \
impl/ChainingAccessControl.cpp \
+ impl/StoredSession.cpp \
impl/StorageServiceSessionCache.cpp \
impl/XMLAccessControl.cpp \
impl/XMLRequestMapper.cpp \
diff --git a/shibsp/impl/StorageServiceSessionCache.cpp b/shibsp/impl/StorageServiceSessionCache.cpp
index 494acee..037c593 100644
--- a/shibsp/impl/StorageServiceSessionCache.cpp
+++ b/shibsp/impl/StorageServiceSessionCache.cpp
@@ -33,23 +33,22 @@
*/
#include "internal.h"
+
#include "Application.h"
#include "exceptions.h"
#include "ServiceProvider.h"
-#include "SessionCache.h"
#include "TransactionLog.h"
#include "attribute/Attribute.h"
#include "handler/RemotedHandler.h"
-#include "remoting/ListenerService.h"
+#include "impl/StoredSession.h"
+#include "impl/StorageServiceSessionCache.h"
#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/NDC.h>
-#include <xmltooling/util/ParserPool.h>
#include <xmltooling/util/Threads.h>
#include <xmltooling/util/XMLHelper.h>
#include <xercesc/util/XMLUniDefs.hpp>
@@ -64,7 +63,6 @@
# include <xercesc/util/XMLStringTokenizer.hpp>
using namespace opensaml::saml2md;
#else
-# include <ctime>
# include <xercesc/util/XMLDateTime.hpp>
#endif
@@ -74,322 +72,9 @@ using namespace xmltooling;
using namespace boost;
using namespace std;
-namespace {
-
- // Allows the cache to bind sessions to multiple client address
- // families based on whatever this function returns.
- static const char* getAddressFamily(const char* addr) {
- if (strchr(addr, ':'))
- return "6";
- else
- return "4";
- }
-
- class StoredSession;
- class SSCache : public SessionCache
-#ifndef SHIBSP_LITE
- ,public virtual Remoted
-#endif
- {
- public:
- SSCache(const DOMElement* e);
- virtual ~SSCache();
-
-#ifndef SHIBSP_LITE
- void receive(DDF& in, ostream& out);
-
- void insert(
- string& sessionID,
- const Application& app,
- const HTTPRequest& httpRequest,
- HTTPResponse& httpResponse,
- time_t expires,
- const EntityDescriptor* issuer=nullptr,
- const XMLCh* protocol=nullptr,
- const saml2::NameID* nameid=nullptr,
- const XMLCh* authn_instant=nullptr,
- const XMLCh* session_index=nullptr,
- const XMLCh* authncontext_class=nullptr,
- const XMLCh* authncontext_decl=nullptr,
- const vector<const Assertion*>* tokens=nullptr,
- const vector<Attribute*>* attributes=nullptr
- );
- vector<string>::size_type logout(
- const Application& app,
- const EntityDescriptor* issuer,
- const saml2::NameID& nameid,
- const set<string>* indexes,
- time_t expires,
- vector<string>& sessions
- ) {
- return _logout(app, issuer, nameid, indexes, expires, sessions, 0);
- }
- bool matches(
- const Application& app,
- const HTTPRequest& request,
- const EntityDescriptor* issuer,
- const saml2::NameID& nameid,
- const set<string>* indexes
- );
-#endif
- Session* find(const Application& app, const char* key, const char* client_addr=nullptr, time_t* timeout=nullptr);
- void remove(const Application& app, const char* key);
- void test();
-
- string active(const Application& app, const HTTPRequest& request) {
- if (!m_inboundHeader.empty()) {
- string session_id = request.getHeader(m_inboundHeader.c_str());
- if (!session_id.empty())
- return session_id;
- }
- pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
- const char* session_id = request.getCookie(shib_cookie.first.c_str());
- return (session_id ? session_id : "");
- }
-
- Session* find(const Application& app, const HTTPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr) {
- string id = active(app, request);
- if (!id.empty())
- return find(app, id.c_str(), client_addr, timeout);
- return nullptr;
- }
-
- Session* find(const Application& app, HTTPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr);
- void remove(const Application& app, const HTTPRequest& request, HTTPResponse* response=nullptr);
-
- unsigned long getCacheTimeout(const Application& app) {
- // Computes offset for adjusting expiration of sessions.
- // This can either be static, or dynamic based on the per-app session timeout or lifetime.
- if (m_cacheTimeout)
- return m_cacheTimeout;
- pair<bool,unsigned int> timeout = pair<bool,unsigned int>(false, 3600);
- const PropertySet* props = app.getPropertySet("Sessions");
- if (props) {
- timeout = props->getUnsignedInt("timeout");
- if (!timeout.first)
- timeout.second = 3600;
- }
- // As long as one of the two factors is set, add them together.
- if (timeout.second > 0 || m_cacheAllowance > 0)
- return timeout.second + m_cacheAllowance;
-
- // If timeouts are off, and there's no cache slop set, then use the lifetime.
- timeout = pair<bool,unsigned int>(false, 28800);
- if (props) {
- timeout = props->getUnsignedInt("lifetime");
- if (!timeout.first || timeout.second == 0)
- timeout.second = 28800;
- }
- return timeout.second;
- }
-
- Category& m_log;
- bool inproc;
-#ifndef SHIBSP_LITE
- StorageService* m_storage;
- StorageService* m_storage_lite;
-#endif
-
- private:
-#ifndef SHIBSP_LITE
- // maintain back-mappings of NameID/SessionIndex -> session key
- void insert(const char* key, time_t expires, const char* name, const char* index, short attempts=0);
- vector<string>::size_type _logout(
- const Application& app,
- const EntityDescriptor* issuer,
- const saml2::NameID& nameid,
- const set<string>* indexes,
- time_t expires,
- vector<string>& sessions,
- short attempts
- );
- 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,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
- scoped_ptr<RWLock> m_lock;
- map<string,StoredSession*> m_hashtable;
-
- // management of buffered sessions
- void dormant(const char* key);
- static void* cleanup_fn(void*);
-
- bool shutdown;
- scoped_ptr<CondWait> shutdown_wait;
- scoped_ptr<Thread> cleanup_thread;
- };
-
- class StoredSession : public virtual Session
- {
- public:
- StoredSession(SSCache* cache, DDF& obj) : m_obj(obj), m_cache(cache), m_expires(0), m_lastAccess(time(nullptr)) {
- // Check for old address format.
- if (m_obj["client_addr"].isstring()) {
- const char* saddr = m_obj["client_addr"].string();
- DDF addrobj = m_obj["client_addr"].structure();
- if (saddr && *saddr) {
- addrobj.addmember(getAddressFamily(saddr)).string(saddr);
- }
- }
-
- auto_ptr_XMLCh exp(m_obj["expires"].string());
- if (exp.get()) {
- XMLDateTime iso(exp.get());
- iso.parseDateTime();
- m_expires = iso.getEpoch();
- }
-
-#ifndef SHIBSP_LITE
- const char* nameid = obj["nameid"].string();
- if (nameid) {
- // Parse and bind the document into an XMLObject.
- istringstream instr(nameid);
- DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
- XercesJanitor<DOMDocument> janitor(doc);
- m_nameid.reset(saml2::NameIDBuilder::buildNameID());
- m_nameid->unmarshall(doc->getDocumentElement(), true);
- janitor.release();
- }
-#endif
- if (cache->inproc)
- m_lock.reset(Mutex::create());
- }
-
- ~StoredSession() {
- m_obj.destroy();
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
- }
-
- Lockable* lock() {
- if (m_lock.get())
- m_lock->lock();
- return this;
- }
- void unlock() {
- if (m_lock.get())
- m_lock->unlock();
- else
- delete this;
- }
-
- const char* getID() const {
- return m_obj.name();
- }
- const char* getApplicationID() const {
- return m_obj["application_id"].string();
- }
- const char* getClientAddress() const {
- return m_obj["client_addr"].first().string();
- }
-
- const char* getClientAddress(const char* family) const {
- if (family)
- return m_obj["client_addr"][family].string();
- return nullptr;
- }
- void setClientAddress(const char* client_addr) {
- DDF obj = m_obj["client_addr"];
- if (!obj.isstruct())
- obj = m_obj.addmember("client_addr").structure();
- obj.addmember(getAddressFamily(client_addr)).string(client_addr);
- }
-
- const char* getEntityID() const {
- return m_obj["entity_id"].string();
- }
- const char* getProtocol() const {
- return m_obj["protocol"].string();
- }
- const char* getAuthnInstant() const {
- return m_obj["authn_instant"].string();
- }
-#ifndef SHIBSP_LITE
- const saml2::NameID* getNameID() const {
- return m_nameid.get();
- }
-#endif
- const char* getSessionIndex() const {
- return m_obj["session_index"].string();
- }
- const char* getAuthnContextClassRef() const {
- return m_obj["authncontext_class"].string();
- }
- const char* getAuthnContextDeclRef() const {
- return m_obj["authncontext_decl"].string();
- }
- const vector<Attribute*>& getAttributes() const {
- if (m_attributes.empty())
- unmarshallAttributes();
- return m_attributes;
- }
- const multimap<string,const Attribute*>& getIndexedAttributes() const {
- if (m_attributeIndex.empty()) {
- if (m_attributes.empty())
- unmarshallAttributes();
- for (vector<Attribute*>::const_iterator a = m_attributes.begin(); a != m_attributes.end(); ++a) {
- const vector<string>& aliases = (*a)->getAliases();
- for (vector<string>::const_iterator alias = aliases.begin(); alias != aliases.end(); ++alias)
- m_attributeIndex.insert(multimap<string,const Attribute*>::value_type(*alias, *a));
- }
- }
- return m_attributeIndex;
- }
- const vector<const char*>& getAssertionIDs() const {
- if (m_ids.empty()) {
- DDF ids = m_obj["assertions"];
- DDF id = ids.first();
- while (id.isstring()) {
- m_ids.push_back(id.string());
- id = ids.next();
- }
- }
- return m_ids;
- }
-
- void validate(const Application& application, const char* client_addr, time_t* timeout);
-
-#ifndef SHIBSP_LITE
- void addAttributes(const vector<Attribute*>& attributes);
- const Assertion* getAssertion(const char* id) const;
- void addAssertion(Assertion* assertion);
-#endif
-
- time_t getExpiration() const { return m_expires; }
- time_t getLastAccess() const { return m_lastAccess; }
-
- private:
- void unmarshallAttributes() const;
-
- DDF m_obj;
-#ifndef SHIBSP_LITE
- 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;
- mutable vector<const char*> m_ids;
-
- SSCache* m_cache;
- time_t m_expires,m_lastAccess;
- scoped_ptr<Mutex> m_lock;
- };
-
- SessionCache* SHIBSP_DLLLOCAL StorageServiceCacheFactory(const DOMElement* const & e)
- {
- return new SSCache(e);
- }
-}
-
-Session* SessionCache::find(const Application& application, HTTPRequest& request, const char* client_addr, time_t* timeout)
+SessionCache* SHIBSP_DLLLOCAL StorageServiceCacheFactory(const DOMElement* const & e)
{
- return find(application, const_cast<const HTTPRequest&>(request), client_addr, timeout);
+ return new SSCache(e);
}
void SHIBSP_API shibsp::registerSessionCaches()
@@ -397,463 +82,17 @@ void SHIBSP_API shibsp::registerSessionCaches()
SPConfig::getConfig().SessionCacheManager.registerFactory(STORAGESERVICE_SESSION_CACHE, StorageServiceCacheFactory);
}
-Session::Session()
-{
-}
-
-Session::~Session()
-{
-}
-
-void StoredSession::unmarshallAttributes() const
-{
- Attribute* attribute;
- DDF attrs = m_obj["attributes"];
- DDF attr = attrs.first();
- while (!attr.isnull()) {
- try {
- attribute = Attribute::unmarshall(attr);
- m_attributes.push_back(attribute);
- if (m_cache->m_log.isDebugEnabled())
- m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",
- attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
- }
- catch (AttributeException& ex) {
- const char* id = attr.first().name();
- m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
- }
- attr = attrs.next();
- }
-}
-
-void StoredSession::validate(const Application& app, const char* client_addr, time_t* timeout)
-{
- time_t now = time(nullptr);
-
- // Basic expiration?
- if (m_expires > 0) {
- if (now > m_expires) {
- m_cache->m_log.info("session expired (ID: %s)", getID());
- throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
- }
- }
-
- // Address check?
- if (client_addr) {
- const char* saddr = getClientAddress(getAddressFamily(client_addr));
- if (saddr && *saddr) {
- if (!XMLString::equals(saddr, client_addr)) {
- m_cache->m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
- throw RetryableProfileException(
- "Your IP address ($1) does not match the address recorded at the time the session was established.",
- params(1, client_addr)
- );
- }
- client_addr = nullptr; // clear out parameter as signal that session need not be updated below
- }
- else {
- m_cache->m_log.info("session (%s) not yet bound to client address type, binding it to (%s)", getID(), client_addr);
- }
- }
-
- if (!timeout && !client_addr)
- return;
-
- if (!SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
- DDF in("touch::" STORAGESERVICE_SESSION_CACHE "::SessionCache"), out;
- DDFJanitor jin(in);
- in.structure();
- in.addmember("key").string(getID());
- in.addmember("version").integer(m_obj["version"].integer());
- in.addmember("application_id").string(app.getId());
- if (client_addr) // signals we need to bind an additional address to the session
- in.addmember("client_addr").string(client_addr);
- if (timeout && *timeout) {
- // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
-#ifndef HAVE_GMTIME_R
- struct tm* ptime = gmtime(timeout);
-#else
- struct tm res;
- struct tm* ptime = gmtime_r(timeout,&res);
-#endif
- char timebuf[32];
- strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
- in.addmember("timeout").string(timebuf);
- }
-
- try {
- out=app.getServiceProvider().getListenerService()->send(in);
- }
- catch (...) {
- out.destroy();
- throw;
- }
-
- if (out.isstruct()) {
- // We got an updated record back.
- m_cache->m_log.debug("session updated, reconstituting it");
- m_ids.clear();
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
- m_attributes.clear();
- m_attributeIndex.clear();
- m_obj.destroy();
- m_obj = out;
- }
- }
- else {
-#ifndef SHIBSP_LITE
- if (!m_cache->m_storage)
- throw ConfigurationException("Session touch requires a StorageService.");
-
- // Versioned read, since we already have the data in hand if it's current.
- string record;
- time_t lastAccess = 0;
- int curver = m_obj["version"].integer();
- int ver = m_cache->m_storage->readText(getID(), "session", &record, &lastAccess, curver);
- if (ver == 0) {
- m_cache->m_log.info("session (ID: %s) no longer in storage", getID());
- throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
- }
-
- if (timeout) {
- if (lastAccess == 0) {
- m_cache->m_log.error("session (ID: %s) did not report time of last access", getID());
- throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
- }
- // Adjust for expiration to recover last access time and check timeout.
- unsigned long cacheTimeout = m_cache->getCacheTimeout(app);
- lastAccess -= cacheTimeout;
- if (*timeout > 0 && now - lastAccess >= *timeout) {
- m_cache->m_log.info("session timed out (ID: %s)", getID());
- throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
- }
-
- // Update storage expiration, if possible.
- try {
- m_cache->m_storage->updateContext(getID(), now + cacheTimeout);
- }
- catch (std::exception& ex) {
- m_cache->m_log.error("failed to update session expiration: %s", ex.what());
- }
- }
-
- if (ver > curver) {
- // We got an updated record back.
- DDF newobj;
- istringstream in(record);
- in >> newobj;
- m_ids.clear();
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
- m_attributes.clear();
- m_attributeIndex.clear();
- m_obj.destroy();
- m_obj = newobj;
- }
-
- // We may need to write back a new address into the session using a versioned update loop.
- if (client_addr) {
- short attempts = 0;
- do {
- const char* saddr = getClientAddress(getAddressFamily(client_addr));
- if (saddr) {
- // Something snuck in and bound the session to this address type, so it better match what we have.
- if (!XMLString::equals(saddr, client_addr)) {
- m_cache->m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
- throw RetryableProfileException(
- "Your IP address ($1) does not match the address recorded at the time the session was established.",
- params(1, client_addr)
- );
- }
- break; // No need to update.
- }
- else {
- // Bind it into the session.
- setClientAddress(client_addr);
- }
-
- // Tentatively increment the version.
- m_obj["version"].integer(m_obj["version"].integer() + 1);
-
- ostringstream str;
- str << m_obj;
- record = str.str();
-
- try {
- ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer() - 1);
- }
- catch (std::exception&) {
- m_obj["version"].integer(m_obj["version"].integer() - 1);
- throw;
- }
-
- if (ver <= 0) {
- m_obj["version"].integer(m_obj["version"].integer() - 1);
- }
-
- if (!ver) {
- // Fatal problem with update.
- m_cache->m_log.error("updateText failed on StorageService for session (%s)", getID());
- throw IOException("Unable to update stored session.");
- }
- else if (ver < 0) {
- // Out of sync.
- if (++attempts > 10) {
- m_cache->m_log.error("failed to bind client address, update attempts exceeded limit");
- throw IOException("Unable to update stored session, exceeded retry limit.");
- }
- m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
- ver = m_cache->m_storage->readText(getID(), "session", &record);
- if (!ver) {
- m_cache->m_log.error("readText failed on StorageService for session (%s)", getID());
- throw IOException("Unable to read back stored session.");
- }
-
- // Reset object.
- DDF newobj;
- istringstream in(record);
- in >> newobj;
-
- m_ids.clear();
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
- m_attributes.clear();
- m_attributeIndex.clear();
- newobj["version"].integer(ver);
- m_obj.destroy();
- m_obj = newobj;
-
- ver = -1;
- }
- } while (ver < 0); // negative indicates a sync issue so we retry
- }
-#else
- throw ConfigurationException("Session touch requires a StorageService.");
-#endif
- }
-
- m_lastAccess = now;
-}
-
-#ifndef SHIBSP_LITE
-
-void StoredSession::addAttributes(const vector<Attribute*>& attributes)
-{
-#ifdef _DEBUG
- xmltooling::NDC ndc("addAttributes");
-#endif
-
- if (!m_cache->m_storage)
- throw ConfigurationException("Session modification requires a StorageService.");
-
- m_cache->m_log.debug("adding attributes to session (%s)", getID());
-
- int ver;
- short attempts = 0;
- do {
- DDF attr;
- DDF attrs = m_obj["attributes"];
- if (!attrs.islist())
- attrs = m_obj.addmember("attributes").list();
- for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {
- attr = (*a)->marshall();
- attrs.add(attr);
- }
-
- // Tentatively increment the version.
- m_obj["version"].integer(m_obj["version"].integer()+1);
-
- ostringstream str;
- str << m_obj;
- string record(str.str());
-
- try {
- ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer()-1);
- }
- catch (std::exception&) {
- // Roll back modification to record.
- m_obj["version"].integer(m_obj["version"].integer()-1);
- vector<Attribute*>::size_type count = attributes.size();
- while (count--)
- attrs.last().destroy();
- throw;
- }
-
- if (ver <= 0) {
- // Roll back modification to record.
- m_obj["version"].integer(m_obj["version"].integer()-1);
- vector<Attribute*>::size_type count = attributes.size();
- while (count--)
- attrs.last().destroy();
- }
- if (!ver) {
- // Fatal problem with update.
- throw IOException("Unable to update stored session.");
- }
- else if (ver < 0) {
- // Out of sync.
- if (++attempts > 10) {
- m_cache->m_log.error("failed to update stored session, update attempts exceeded limit");
- throw IOException("Unable to update stored session, exceeded retry limit.");
- }
- m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
- ver = m_cache->m_storage->readText(getID(), "session", &record);
- if (!ver) {
- m_cache->m_log.error("readText failed on StorageService for session (%s)", getID());
- throw IOException("Unable to read back stored session.");
- }
-
- // Reset object.
- DDF newobj;
- istringstream in(record);
- in >> newobj;
-
- m_ids.clear();
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
- m_attributes.clear();
- m_attributeIndex.clear();
- newobj["version"].integer(ver);
- m_obj.destroy();
- m_obj = newobj;
-
- ver = -1;
- }
- } while (ver < 0); // negative indicates a sync issue so we retry
-
- // We own them now, so clean them up.
- for_each(attributes.begin(), attributes.end(), xmltooling::cleanup<Attribute>());
-}
-
-const Assertion* StoredSession::getAssertion(const char* id) const
-{
- if (!m_cache->m_storage)
- throw ConfigurationException("Assertion retrieval requires a StorageService.");
-
- 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))
- throw FatalProfileException("Assertion not found in cache.");
-
- // Parse and bind the document into an XMLObject.
- istringstream instr(tokenstr);
- DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
- XercesJanitor<DOMDocument> janitor(doc);
- boost::shared_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
- janitor.release();
-
- boost::shared_ptr<Assertion> token = dynamic_pointer_cast<Assertion,XMLObject>(xmlObject);
- if (!token)
- throw FatalProfileException("Request for cached assertion returned an unknown object type.");
-
- m_tokens[id] = token;
- return token.get();
-}
-
-void StoredSession::addAssertion(Assertion* assertion)
+SessionCache::SessionCache()
{
-#ifdef _DEBUG
- xmltooling::NDC ndc("addAssertion");
-#endif
-
- if (!m_cache->m_storage)
- throw ConfigurationException("Session modification requires a StorageService.");
- else if (!assertion)
- throw FatalProfileException("Unknown object type passed to session for storage.");
-
- auto_ptr_char id(assertion->getID());
- if (!id.get() || !*id.get())
- throw IOException("Assertion did not carry an ID.");
- else if (strlen(id.get()) > m_cache->m_storage->getCapabilities().getKeySize())
- throw IOException("Assertion ID ($1) exceeds allowable storage key size.", params(1, id.get()));
-
- m_cache->m_log.debug("adding assertion (%s) to session (%s)", id.get(), getID());
-
- time_t exp = 0;
- if (!m_cache->m_storage->readText(getID(), "session", nullptr, &exp) || exp == 0)
- throw IOException("Unable to load expiration time for stored session.");
-
- ostringstream tokenstr;
- tokenstr << *assertion;
- if (!m_cache->m_storage->createText(getID(), id.get(), tokenstr.str().c_str(), exp))
- throw IOException("Attempted to insert duplicate assertion ID into session.");
-
- int ver;
- short attempts = 0;
- do {
- DDF token = DDF(nullptr).string(id.get());
- m_obj["assertions"].add(token);
-
- // Tentatively increment the version.
- m_obj["version"].integer(m_obj["version"].integer() + 1);
-
- ostringstream str;
- str << m_obj;
- string record(str.str());
-
- try {
- ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer()-1);
- }
- catch (std::exception&) {
- token.destroy();
- m_obj["version"].integer(m_obj["version"].integer() - 1);
- m_cache->m_storage->deleteText(getID(), id.get());
- throw;
- }
-
- if (ver <= 0) {
- token.destroy();
- m_obj["version"].integer(m_obj["version"].integer()-1);
- }
- if (!ver) {
- // Fatal problem with update.
- m_cache->m_log.error("updateText failed on StorageService for session (%s)", getID());
- m_cache->m_storage->deleteText(getID(), id.get());
- throw IOException("Unable to update stored session.");
- }
- else if (ver < 0) {
- // Out of sync.
- if (++attempts > 10) {
- m_cache->m_log.error("failed to update stored session, update attempts exceeded limit");
- throw IOException("Unable to update stored session, exceeded retry limit.");
- }
- m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
- ver = m_cache->m_storage->readText(getID(), "session", &record);
- if (!ver) {
- m_cache->m_log.error("readText failed on StorageService for session (%s)", getID());
- m_cache->m_storage->deleteText(getID(), id.get());
- throw IOException("Unable to read back stored session.");
- }
-
- // Reset object.
- DDF newobj;
- istringstream in(record);
- in >> newobj;
-
- m_ids.clear();
- for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
- m_attributes.clear();
- m_attributeIndex.clear();
- newobj["version"].integer(ver);
- m_obj.destroy();
- m_obj = newobj;
-
- ver = -1;
- }
- } while (ver < 0); // negative indicates a sync issue so we retry
-
- m_ids.clear();
- delete assertion;
}
-#endif
-
-SessionCache::SessionCache()
+SessionCache::~SessionCache()
{
}
-SessionCache::~SessionCache()
+Session* SessionCache::find(const Application& application, HTTPRequest& request, const char* client_addr, time_t* timeout)
{
+ return find(application, const_cast<const HTTPRequest&>(request), client_addr, timeout);
}
SSCache::SSCache(const DOMElement* e)
@@ -978,6 +217,53 @@ SSCache::~SSCache()
#endif
}
+unsigned long SSCache::getCacheTimeout(const Application& app) const
+{
+ // Computes offset for adjusting expiration of sessions.
+ // This can either be static, or dynamic based on the per-app session timeout or lifetime.
+ if (m_cacheTimeout)
+ return m_cacheTimeout;
+ pair<bool, unsigned int> timeout = pair<bool, unsigned int>(false, 3600);
+ const PropertySet* props = app.getPropertySet("Sessions");
+ if (props) {
+ timeout = props->getUnsignedInt("timeout");
+ if (!timeout.first)
+ timeout.second = 3600;
+ }
+ // As long as one of the two factors is set, add them together.
+ if (timeout.second > 0 || m_cacheAllowance > 0)
+ return timeout.second + m_cacheAllowance;
+
+ // If timeouts are off, and there's no cache slop set, then use the lifetime.
+ timeout = pair<bool, unsigned int>(false, 28800);
+ if (props) {
+ timeout = props->getUnsignedInt("lifetime");
+ if (!timeout.first || timeout.second == 0)
+ timeout.second = 28800;
+ }
+ return timeout.second;
+}
+
+string SSCache::active(const Application& app, const HTTPRequest& request)
+{
+ if (!m_inboundHeader.empty()) {
+ string session_id = request.getHeader(m_inboundHeader.c_str());
+ if (!session_id.empty())
+ return session_id;
+ }
+ pair<string, const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
+ const char* session_id = request.getCookie(shib_cookie.first.c_str());
+ return (session_id ? session_id : "");
+}
+
+Session* SSCache::find(const Application& app, const HTTPRequest& request, const char* client_addr, time_t* timeout)
+{
+ string id = active(app, request);
+ if (!id.empty())
+ return find(app, id.c_str(), client_addr, timeout);
+ return nullptr;
+}
+
#ifndef SHIBSP_LITE
void SSCache::test()
@@ -1133,7 +419,7 @@ void SSCache::insert(
string caddr(httpRequest.getRemoteAddr());
if (!caddr.empty()) {
DDF addrobj = obj.addmember("client_addr").structure();
- addrobj.addmember(getAddressFamily(caddr.c_str())).string(caddr.c_str());
+ addrobj.addmember(StoredSession::getAddressFamily(caddr.c_str())).string(caddr.c_str());
}
if (issuer)
@@ -2005,7 +1291,7 @@ void SSCache::receive(DDF& in, ostream& out)
istringstream src(record);
src >> sessionobj;
ver = sessionobj["version"].integer();
- const char* saddr = sessionobj["client_addr"][getAddressFamily(client_addr)].string();
+ const char* saddr = sessionobj["client_addr"][StoredSession::getAddressFamily(client_addr)].string();
if (saddr) {
// Something snuck in and bound the session to this address type, so it better match what we have.
if (!XMLString::equals(saddr, client_addr)) {
@@ -2019,7 +1305,7 @@ void SSCache::receive(DDF& in, ostream& out)
}
else {
// Bind it into the session.
- sessionobj["client_addr"].addmember(getAddressFamily(client_addr)).string(client_addr);
+ sessionobj["client_addr"].addmember(StoredSession::getAddressFamily(client_addr)).string(client_addr);
}
// Tentatively increment the version.
diff --git a/shibsp/impl/StorageServiceSessionCache.h b/shibsp/impl/StorageServiceSessionCache.h
new file mode 100644
index 0000000..1a00060
--- /dev/null
+++ b/shibsp/impl/StorageServiceSessionCache.h
@@ -0,0 +1,160 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you 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
+ *
+ * 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.
+ */
+
+/**
+ * StorageServiceSessionCache.h
+ *
+ * StorageService-based SessionCache implementation header.
+ */
+
+#ifndef __shibsp_sscache_h__
+#define __shibsp_sscache_h__
+
+#include <shibsp/SessionCache.h>
+#include <shibsp/remoting/ListenerService.h>
+
+#include <ctime>
+#include <boost/shared_ptr.hpp>
+
+namespace xmltooling {
+ class CondWait;
+ class RWLock;
+ class Thread;
+}
+
+#ifndef SHIBSP_LITE
+namespace opensaml {
+ class Assertion;
+
+ namespace saml2 {
+ class NameID;
+ };
+
+ namespace saml2md {
+ class EntityDescriptor;
+ };
+};
+#endif
+
+namespace shibsp {
+
+ class StoredSession;
+ class SSCache : public shibsp::SessionCache
+#ifndef SHIBSP_LITE
+ ,public virtual shibsp::Remoted
+#endif
+ {
+ public:
+ SSCache(const xercesc::DOMElement* e);
+ virtual ~SSCache();
+
+#ifndef SHIBSP_LITE
+ void receive(shibsp::DDF& in, std::ostream& out);
+
+ void insert(
+ std::string& sessionID,
+ const shibsp::Application& app,
+ const xmltooling::HTTPRequest& httpRequest,
+ xmltooling::HTTPResponse& httpResponse,
+ time_t expires,
+ const opensaml::saml2md::EntityDescriptor* issuer=nullptr,
+ const XMLCh* protocol=nullptr,
+ const opensaml::saml2::NameID* nameid=nullptr,
+ const XMLCh* authn_instant=nullptr,
+ const XMLCh* session_index=nullptr,
+ const XMLCh* authncontext_class=nullptr,
+ const XMLCh* authncontext_decl=nullptr,
+ const std::vector<const opensaml::Assertion*>* tokens=nullptr,
+ const std::vector<shibsp::Attribute*>* attributes=nullptr
+ );
+ std::vector<std::string>::size_type logout(
+ const shibsp::Application& app,
+ const opensaml::saml2md::EntityDescriptor* issuer,
+ const opensaml::saml2::NameID& nameid,
+ const std::set<std::string>* indexes,
+ time_t expires,
+ std::vector<std::string>& sessions
+ ) {
+ return _logout(app, issuer, nameid, indexes, expires, sessions, 0);
+ }
+ bool matches(
+ const shibsp::Application& app,
+ const xmltooling::HTTPRequest& request,
+ const opensaml::saml2md::EntityDescriptor* issuer,
+ const opensaml::saml2::NameID& nameid,
+ const std::set<std::string>* indexes
+ );
+#endif
+ shibsp::Session* find(const shibsp::Application& app, const char* key, const char* client_addr=nullptr, time_t* timeout=nullptr);
+ void remove(const shibsp::Application& app, const char* key);
+ void test();
+
+ std::string active(const shibsp::Application& app, const xmltooling::HTTPRequest& request);
+ shibsp::Session* find(const shibsp::Application& app, const xmltooling::HTTPRequest& request, const char* client_addr = nullptr, time_t* timeout = nullptr);
+ shibsp::Session* find(const shibsp::Application& app, xmltooling::HTTPRequest& request, const char* client_addr=nullptr, time_t* timeout=nullptr);
+ void remove(const shibsp::Application& app, const xmltooling::HTTPRequest& request, xmltooling::HTTPResponse* response=nullptr);
+
+ unsigned long getCacheTimeout(const shibsp::Application& app) const;
+
+ xmltooling::logging::Category& m_log;
+ bool inproc;
+#ifndef SHIBSP_LITE
+ xmltooling::StorageService* m_storage;
+ xmltooling::StorageService* m_storage_lite;
+#endif
+
+ private:
+#ifndef SHIBSP_LITE
+ // maintain back-mappings of NameID/SessionIndex -> session key
+ void insert(const char* key, time_t expires, const char* name, const char* index, short attempts=0);
+ std::vector<std::string>::size_type _logout(
+ const shibsp::Application& app,
+ const opensaml::saml2md::EntityDescriptor* issuer,
+ const opensaml::saml2::NameID& nameid,
+ const std::set<std::string>* indexes,
+ time_t expires,
+ std::vector<std::string>& sessions,
+ short attempts
+ );
+ bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const opensaml::saml2::NameID& n1, const opensaml::saml2::NameID& n2) const;
+ shibsp::LogoutEvent* newLogoutEvent(const shibsp::Application& app) const;
+
+ bool m_cacheAssertions,m_reverseIndex;
+ std::set<xmltooling::xstring> m_excludedNames;
+#endif
+ const xercesc::DOMElement* m_root; // Only valid during initialization
+ unsigned long m_inprocTimeout,m_cacheTimeout,m_cacheAllowance;
+ std::string m_inboundHeader,m_outboundHeader;
+
+ // inproc means we buffer sessions in memory
+ boost::scoped_ptr<xmltooling::RWLock> m_lock;
+ std::map<std::string,StoredSession*> m_hashtable;
+
+ // management of buffered sessions
+ void dormant(const char* key);
+ static void* cleanup_fn(void*);
+
+ bool shutdown;
+ boost::scoped_ptr<xmltooling::CondWait> shutdown_wait;
+ boost::scoped_ptr<xmltooling::Thread> cleanup_thread;
+ };
+
+}
+#endif /* __shibsp_sscache_h__ */
diff --git a/shibsp/impl/StoredSession.cpp b/shibsp/impl/StoredSession.cpp
new file mode 100644
index 0000000..94fbcf6
--- /dev/null
+++ b/shibsp/impl/StoredSession.cpp
@@ -0,0 +1,594 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you 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
+ *
+ * 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.
+ */
+
+/**
+ * StoredSession.cpp
+ *
+ * Implementation of Session subclass used by StorageService-backed SessionCache.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "ServiceProvider.h"
+#include "attribute/Attribute.h"
+#include "impl/StoredSession.h"
+#include "impl/StorageServiceSessionCache.h"
+
+#include <xmltooling/util/NDC.h>
+#include <xmltooling/util/Threads.h>
+
+#ifndef SHIBSP_LITE
+# include <saml/exceptions.h>
+# include <saml/saml2/core/Assertions.h>
+# include <xmltooling/XMLToolingConfig.h>
+# include <xmltooling/util/ParserPool.h>
+# include <xmltooling/util/StorageService.h>
+# include <xercesc/util/XMLStringTokenizer.hpp>
+using namespace opensaml::saml2md;
+#else
+# include <xercesc/util/XMLDateTime.hpp>
+#endif
+
+using namespace shibsp;
+using namespace opensaml;
+using namespace xmltooling;
+using namespace boost;
+using namespace std;
+
+Session::Session()
+{
+}
+
+Session::~Session()
+{
+}
+
+const char* StoredSession::getAddressFamily(const char* addr) {
+ if (strchr(addr, ':'))
+ return "6";
+ else
+ return "4";
+}
+
+StoredSession::StoredSession(SSCache* cache, DDF& obj)
+ : m_obj(obj), m_cache(cache), m_expires(0), m_lastAccess(time(nullptr))
+{
+ // Check for old address format.
+ if (m_obj["client_addr"].isstring()) {
+ const char* saddr = m_obj["client_addr"].string();
+ DDF addrobj = m_obj["client_addr"].structure();
+ if (saddr && *saddr) {
+ addrobj.addmember(getAddressFamily(saddr)).string(saddr);
+ }
+ }
+
+ auto_ptr_XMLCh exp(m_obj["expires"].string());
+ if (exp.get()) {
+ XMLDateTime iso(exp.get());
+ iso.parseDateTime();
+ m_expires = iso.getEpoch();
+ }
+
+#ifndef SHIBSP_LITE
+ const char* nameid = obj["nameid"].string();
+ if (nameid) {
+ // Parse and bind the document into an XMLObject.
+ istringstream instr(nameid);
+ DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
+ XercesJanitor<DOMDocument> janitor(doc);
+ m_nameid.reset(saml2::NameIDBuilder::buildNameID());
+ m_nameid->unmarshall(doc->getDocumentElement(), true);
+ janitor.release();
+ }
+#endif
+ if (cache->inproc)
+ m_lock.reset(Mutex::create());
+}
+
+StoredSession::~StoredSession()
+{
+ m_obj.destroy();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+}
+
+Lockable* StoredSession::lock()
+{
+ if (m_lock.get())
+ m_lock->lock();
+ return this;
+}
+void StoredSession::unlock()
+{
+ if (m_lock.get())
+ m_lock->unlock();
+ else
+ delete this;
+}
+
+const multimap<string, const Attribute*>& StoredSession::getIndexedAttributes() const
+{
+ if (m_attributeIndex.empty()) {
+ if (m_attributes.empty())
+ unmarshallAttributes();
+ for (vector<Attribute*>::const_iterator a = m_attributes.begin(); a != m_attributes.end(); ++a) {
+ const vector<string>& aliases = (*a)->getAliases();
+ for (vector<string>::const_iterator alias = aliases.begin(); alias != aliases.end(); ++alias)
+ m_attributeIndex.insert(multimap<string, const Attribute*>::value_type(*alias, *a));
+ }
+ }
+ return m_attributeIndex;
+}
+
+const vector<const char*>& StoredSession::getAssertionIDs() const
+{
+ if (m_ids.empty()) {
+ DDF ids = m_obj["assertions"];
+ DDF id = ids.first();
+ while (id.isstring()) {
+ m_ids.push_back(id.string());
+ id = ids.next();
+ }
+ }
+ return m_ids;
+}
+
+void StoredSession::unmarshallAttributes() const
+{
+ Attribute* attribute;
+ DDF attrs = m_obj["attributes"];
+ DDF attr = attrs.first();
+ while (!attr.isnull()) {
+ try {
+ attribute = Attribute::unmarshall(attr);
+ m_attributes.push_back(attribute);
+ if (m_cache->m_log.isDebugEnabled())
+ m_cache->m_log.debug("unmarshalled attribute (ID: %s) with %d value%s",
+ attribute->getId(), attr.first().integer(), attr.first().integer()!=1 ? "s" : "");
+ }
+ catch (AttributeException& ex) {
+ const char* id = attr.first().name();
+ m_cache->m_log.error("error unmarshalling attribute (ID: %s): %s", id ? id : "none", ex.what());
+ }
+ attr = attrs.next();
+ }
+}
+
+void StoredSession::validate(const Application& app, const char* client_addr, time_t* timeout)
+{
+ time_t now = time(nullptr);
+
+ // Basic expiration?
+ if (m_expires > 0) {
+ if (now > m_expires) {
+ m_cache->m_log.info("session expired (ID: %s)", getID());
+ throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
+ }
+ }
+
+ // Address check?
+ if (client_addr) {
+ const char* saddr = getClientAddress(getAddressFamily(client_addr));
+ if (saddr && *saddr) {
+ if (!XMLString::equals(saddr, client_addr)) {
+ m_cache->m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
+ throw RetryableProfileException(
+ "Your IP address ($1) does not match the address recorded at the time the session was established.",
+ params(1, client_addr)
+ );
+ }
+ client_addr = nullptr; // clear out parameter as signal that session need not be updated below
+ }
+ else {
+ m_cache->m_log.info("session (%s) not yet bound to client address type, binding it to (%s)", getID(), client_addr);
+ }
+ }
+
+ if (!timeout && !client_addr)
+ return;
+
+ if (!SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+ DDF in("touch::" STORAGESERVICE_SESSION_CACHE "::SessionCache"), out;
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(getID());
+ in.addmember("version").integer(m_obj["version"].integer());
+ in.addmember("application_id").string(app.getId());
+ if (client_addr) // signals we need to bind an additional address to the session
+ in.addmember("client_addr").string(client_addr);
+ if (timeout && *timeout) {
+ // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
+#ifndef HAVE_GMTIME_R
+ struct tm* ptime = gmtime(timeout);
+#else
+ struct tm res;
+ struct tm* ptime = gmtime_r(timeout,&res);
+#endif
+ char timebuf[32];
+ strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+ in.addmember("timeout").string(timebuf);
+ }
+
+ try {
+ out=app.getServiceProvider().getListenerService()->send(in);
+ }
+ catch (...) {
+ out.destroy();
+ throw;
+ }
+
+ if (out.isstruct()) {
+ // We got an updated record back.
+ m_cache->m_log.debug("session updated, reconstituting it");
+ m_ids.clear();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ m_obj.destroy();
+ m_obj = out;
+ }
+ }
+ else {
+#ifndef SHIBSP_LITE
+ if (!m_cache->m_storage)
+ throw ConfigurationException("Session touch requires a StorageService.");
+
+ // Versioned read, since we already have the data in hand if it's current.
+ string record;
+ time_t lastAccess = 0;
+ int curver = m_obj["version"].integer();
+ int ver = m_cache->m_storage->readText(getID(), "session", &record, &lastAccess, curver);
+ if (ver == 0) {
+ m_cache->m_log.info("session (ID: %s) no longer in storage", getID());
+ throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
+ }
+
+ if (timeout) {
+ if (lastAccess == 0) {
+ m_cache->m_log.error("session (ID: %s) did not report time of last access", getID());
+ throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
+ }
+ // Adjust for expiration to recover last access time and check timeout.
+ unsigned long cacheTimeout = m_cache->getCacheTimeout(app);
+ lastAccess -= cacheTimeout;
+ if (*timeout > 0 && now - lastAccess >= *timeout) {
+ m_cache->m_log.info("session timed out (ID: %s)", getID());
+ throw RetryableProfileException("Your session has expired, and you must re-authenticate.");
+ }
+
+ // Update storage expiration, if possible.
+ try {
+ m_cache->m_storage->updateContext(getID(), now + cacheTimeout);
+ }
+ catch (std::exception& ex) {
+ m_cache->m_log.error("failed to update session expiration: %s", ex.what());
+ }
+ }
+
+ if (ver > curver) {
+ // We got an updated record back.
+ DDF newobj;
+ istringstream in(record);
+ in >> newobj;
+ m_ids.clear();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ m_obj.destroy();
+ m_obj = newobj;
+ }
+
+ // We may need to write back a new address into the session using a versioned update loop.
+ if (client_addr) {
+ short attempts = 0;
+ do {
+ const char* saddr = getClientAddress(getAddressFamily(client_addr));
+ if (saddr) {
+ // Something snuck in and bound the session to this address type, so it better match what we have.
+ if (!XMLString::equals(saddr, client_addr)) {
+ m_cache->m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
+ throw RetryableProfileException(
+ "Your IP address ($1) does not match the address recorded at the time the session was established.",
+ params(1, client_addr)
+ );
+ }
+ break; // No need to update.
+ }
+ else {
+ // Bind it into the session.
+ setClientAddress(client_addr);
+ }
+
+ // Tentatively increment the version.
+ m_obj["version"].integer(m_obj["version"].integer() + 1);
+
+ ostringstream str;
+ str << m_obj;
+ record = str.str();
+
+ try {
+ ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer() - 1);
+ }
+ catch (std::exception&) {
+ m_obj["version"].integer(m_obj["version"].integer() - 1);
+ throw;
+ }
+
+ if (ver <= 0) {
+ m_obj["version"].integer(m_obj["version"].integer() - 1);
+ }
+
+ if (!ver) {
+ // Fatal problem with update.
+ m_cache->m_log.error("updateText failed on StorageService for session (%s)", getID());
+ throw IOException("Unable to update stored session.");
+ }
+ else if (ver < 0) {
+ // Out of sync.
+ if (++attempts > 10) {
+ m_cache->m_log.error("failed to bind client address, update attempts exceeded limit");
+ throw IOException("Unable to update stored session, exceeded retry limit.");
+ }
+ m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
+ ver = m_cache->m_storage->readText(getID(), "session", &record);
+ if (!ver) {
+ m_cache->m_log.error("readText failed on StorageService for session (%s)", getID());
+ throw IOException("Unable to read back stored session.");
+ }
+
+ // Reset object.
+ DDF newobj;
+ istringstream in(record);
+ in >> newobj;
+
+ m_ids.clear();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ newobj["version"].integer(ver);
+ m_obj.destroy();
+ m_obj = newobj;
+
+ ver = -1;
+ }
+ } while (ver < 0); // negative indicates a sync issue so we retry
+ }
+#else
+ throw ConfigurationException("Session touch requires a StorageService.");
+#endif
+ }
+
+ m_lastAccess = now;
+}
+
+#ifndef SHIBSP_LITE
+
+void StoredSession::addAttributes(const vector<Attribute*>& attributes)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("addAttributes");
+#endif
+
+ if (!m_cache->m_storage)
+ throw ConfigurationException("Session modification requires a StorageService.");
+
+ m_cache->m_log.debug("adding attributes to session (%s)", getID());
+
+ int ver;
+ short attempts = 0;
+ do {
+ DDF attr;
+ DDF attrs = m_obj["attributes"];
+ if (!attrs.islist())
+ attrs = m_obj.addmember("attributes").list();
+ for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {
+ attr = (*a)->marshall();
+ attrs.add(attr);
+ }
+
+ // Tentatively increment the version.
+ m_obj["version"].integer(m_obj["version"].integer()+1);
+
+ ostringstream str;
+ str << m_obj;
+ string record(str.str());
+
+ try {
+ ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer()-1);
+ }
+ catch (std::exception&) {
+ // Roll back modification to record.
+ m_obj["version"].integer(m_obj["version"].integer()-1);
+ vector<Attribute*>::size_type count = attributes.size();
+ while (count--)
+ attrs.last().destroy();
+ throw;
+ }
+
+ if (ver <= 0) {
+ // Roll back modification to record.
+ m_obj["version"].integer(m_obj["version"].integer()-1);
+ vector<Attribute*>::size_type count = attributes.size();
+ while (count--)
+ attrs.last().destroy();
+ }
+ if (!ver) {
+ // Fatal problem with update.
+ throw IOException("Unable to update stored session.");
+ }
+ else if (ver < 0) {
+ // Out of sync.
+ if (++attempts > 10) {
+ m_cache->m_log.error("failed to update stored session, update attempts exceeded limit");
+ throw IOException("Unable to update stored session, exceeded retry limit.");
+ }
+ m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
+ ver = m_cache->m_storage->readText(getID(), "session", &record);
+ if (!ver) {
+ m_cache->m_log.error("readText failed on StorageService for session (%s)", getID());
+ throw IOException("Unable to read back stored session.");
+ }
+
+ // Reset object.
+ DDF newobj;
+ istringstream in(record);
+ in >> newobj;
+
+ m_ids.clear();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ newobj["version"].integer(ver);
+ m_obj.destroy();
+ m_obj = newobj;
+
+ ver = -1;
+ }
+ } while (ver < 0); // negative indicates a sync issue so we retry
+
+ // We own them now, so clean them up.
+ for_each(attributes.begin(), attributes.end(), xmltooling::cleanup<Attribute>());
+}
+
+const Assertion* StoredSession::getAssertion(const char* id) const
+{
+ if (!m_cache->m_storage)
+ throw ConfigurationException("Assertion retrieval requires a StorageService.");
+
+ 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))
+ throw FatalProfileException("Assertion not found in cache.");
+
+ // Parse and bind the document into an XMLObject.
+ istringstream instr(tokenstr);
+ DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr);
+ XercesJanitor<DOMDocument> janitor(doc);
+ boost::shared_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
+ janitor.release();
+
+ boost::shared_ptr<Assertion> token = dynamic_pointer_cast<Assertion,XMLObject>(xmlObject);
+ if (!token)
+ throw FatalProfileException("Request for cached assertion returned an unknown object type.");
+
+ m_tokens[id] = token;
+ return token.get();
+}
+
+void StoredSession::addAssertion(Assertion* assertion)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("addAssertion");
+#endif
+
+ if (!m_cache->m_storage)
+ throw ConfigurationException("Session modification requires a StorageService.");
+ else if (!assertion)
+ throw FatalProfileException("Unknown object type passed to session for storage.");
+
+ auto_ptr_char id(assertion->getID());
+ if (!id.get() || !*id.get())
+ throw IOException("Assertion did not carry an ID.");
+ else if (strlen(id.get()) > m_cache->m_storage->getCapabilities().getKeySize())
+ throw IOException("Assertion ID ($1) exceeds allowable storage key size.", params(1, id.get()));
+
+ m_cache->m_log.debug("adding assertion (%s) to session (%s)", id.get(), getID());
+
+ time_t exp = 0;
+ if (!m_cache->m_storage->readText(getID(), "session", nullptr, &exp) || exp == 0)
+ throw IOException("Unable to load expiration time for stored session.");
+
+ ostringstream tokenstr;
+ tokenstr << *assertion;
+ if (!m_cache->m_storage->createText(getID(), id.get(), tokenstr.str().c_str(), exp))
+ throw IOException("Attempted to insert duplicate assertion ID into session.");
+
+ int ver;
+ short attempts = 0;
+ do {
+ DDF token = DDF(nullptr).string(id.get());
+ m_obj["assertions"].add(token);
+
+ // Tentatively increment the version.
+ m_obj["version"].integer(m_obj["version"].integer() + 1);
+
+ ostringstream str;
+ str << m_obj;
+ string record(str.str());
+
+ try {
+ ver = m_cache->m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer()-1);
+ }
+ catch (std::exception&) {
+ token.destroy();
+ m_obj["version"].integer(m_obj["version"].integer() - 1);
+ m_cache->m_storage->deleteText(getID(), id.get());
+ throw;
+ }
+
+ if (ver <= 0) {
+ token.destroy();
+ m_obj["version"].integer(m_obj["version"].integer()-1);
+ }
+ if (!ver) {
+ // Fatal problem with update.
+ m_cache->m_log.error("updateText failed on StorageService for session (%s)", getID());
+ m_cache->m_storage->deleteText(getID(), id.get());
+ throw IOException("Unable to update stored session.");
+ }
+ else if (ver < 0) {
+ // Out of sync.
+ if (++attempts > 10) {
+ m_cache->m_log.error("failed to update stored session, update attempts exceeded limit");
+ throw IOException("Unable to update stored session, exceeded retry limit.");
+ }
+ m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
+ ver = m_cache->m_storage->readText(getID(), "session", &record);
+ if (!ver) {
+ m_cache->m_log.error("readText failed on StorageService for session (%s)", getID());
+ m_cache->m_storage->deleteText(getID(), id.get());
+ throw IOException("Unable to read back stored session.");
+ }
+
+ // Reset object.
+ DDF newobj;
+ istringstream in(record);
+ in >> newobj;
+
+ m_ids.clear();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ newobj["version"].integer(ver);
+ m_obj.destroy();
+ m_obj = newobj;
+
+ ver = -1;
+ }
+ } while (ver < 0); // negative indicates a sync issue so we retry
+
+ m_ids.clear();
+ delete assertion;
+}
+
+#endif
+
diff --git a/shibsp/impl/StoredSession.h b/shibsp/impl/StoredSession.h
new file mode 100644
index 0000000..d5ac966
--- /dev/null
+++ b/shibsp/impl/StoredSession.h
@@ -0,0 +1,153 @@
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you 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
+ *
+ * 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.
+ */
+
+/**
+ * StoredSession.h
+ *
+ * Internal declaration of Session subclass used by StorageService-backed SessionCache.
+ */
+
+#ifndef __shibsp_storedsession_h__
+#define __shibsp_storedsession_h__
+
+#include <shibsp/SessionCache.h>
+#include <shibsp/remoting/ddf.h>
+
+#include <ctime>
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace xmltooling {
+ class Mutex;
+};
+
+#ifndef SHIBSP_LITE
+namespace opensaml {
+ class Assertion;
+
+ namespace saml2 {
+ class NameID;
+ };
+};
+#endif
+
+namespace shibsp {
+
+ class SSCache;
+
+ class StoredSession : public virtual shibsp::Session
+ {
+ public:
+ StoredSession(SSCache* cache, shibsp::DDF& obj);
+
+ virtual ~StoredSession();
+
+ xmltooling::Lockable* lock();
+ void unlock();
+
+ const char* getID() const {
+ return m_obj.name();
+ }
+ const char* getApplicationID() const {
+ return m_obj["application_id"].string();
+ }
+ const char* getClientAddress() const {
+ return m_obj["client_addr"].first().string();
+ }
+
+ const char* getClientAddress(const char* family) const {
+ if (family)
+ return m_obj["client_addr"][family].string();
+ return nullptr;
+ }
+ void setClientAddress(const char* client_addr) {
+ shibsp::DDF obj = m_obj["client_addr"];
+ if (!obj.isstruct())
+ obj = m_obj.addmember("client_addr").structure();
+ obj.addmember(getAddressFamily(client_addr)).string(client_addr);
+ }
+
+ const char* getEntityID() const {
+ return m_obj["entity_id"].string();
+ }
+ const char* getProtocol() const {
+ return m_obj["protocol"].string();
+ }
+ const char* getAuthnInstant() const {
+ return m_obj["authn_instant"].string();
+ }
+#ifndef SHIBSP_LITE
+ const opensaml::saml2::NameID* getNameID() const {
+ return m_nameid.get();
+ }
+#endif
+ const char* getSessionIndex() const {
+ return m_obj["session_index"].string();
+ }
+ const char* getAuthnContextClassRef() const {
+ return m_obj["authncontext_class"].string();
+ }
+ const char* getAuthnContextDeclRef() const {
+ return m_obj["authncontext_decl"].string();
+ }
+ const std::vector<shibsp::Attribute*>& getAttributes() const {
+ if (m_attributes.empty())
+ unmarshallAttributes();
+ return m_attributes;
+ }
+ const std::multimap<std::string, const shibsp::Attribute*>& getIndexedAttributes() const;
+
+ const std::vector<const char*>& getAssertionIDs() const;
+
+ void validate(const shibsp::Application& application, const char* client_addr, time_t* timeout);
+
+#ifndef SHIBSP_LITE
+ void addAttributes(const std::vector<shibsp::Attribute*>& attributes);
+ const opensaml::Assertion* getAssertion(const char* id) const;
+ void addAssertion(opensaml::Assertion* assertion);
+#endif
+
+ time_t getExpiration() const { return m_expires; }
+ time_t getLastAccess() const { return m_lastAccess; }
+
+ // Allows the cache to bind sessions to multiple client address
+ // families based on whatever this function returns.
+ static const char* getAddressFamily(const char* addr);
+
+ private:
+ void unmarshallAttributes() const;
+
+ shibsp::DDF m_obj;
+#ifndef SHIBSP_LITE
+ boost::scoped_ptr<opensaml::saml2::NameID> m_nameid;
+ mutable std::map< std::string,boost::shared_ptr<opensaml::Assertion> > m_tokens;
+#endif
+ mutable std::vector<shibsp::Attribute*> m_attributes;
+ mutable std::multimap<std::string,const shibsp::Attribute*> m_attributeIndex;
+ mutable std::vector<const char*> m_ids;
+
+ SSCache* m_cache;
+ time_t m_expires,m_lastAccess;
+ boost::scoped_ptr<xmltooling::Mutex> m_lock;
+ };
+
+}
+
+#endif /* __shibsp_storedsession_h__ */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list