[cpp-sp] branch main updated: WIP on cookie-backed cache.
Codeberg
noreply at shibboleth.net
Thu Oct 1 17:47:02 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository cpp-sp.
View the commit online:
https://codeberg.org/Shibboleth/cpp-sp/commit/d8ea7f55ae4fd756b7f273a6317d9b1daa65288f
The following commit(s) were added to refs/heads/main by this push:
new d8ea7f55 WIP on cookie-backed cache.
d8ea7f55 is described below
commit d8ea7f55ae4fd756b7f273a6317d9b1daa65288f
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Thu Oct 1 13:46:49 2026 -0400
WIP on cookie-backed cache.
---
Projects/vc22/shibsp.vcxproj | 1 +
Projects/vc22/shibsp.vcxproj.filters | 9 +-
shibsp/Makefile.am | 1 +
shibsp/RequestMapper.h | 2 +
shibsp/impl/XMLRequestMapper.cpp | 2 +
shibsp/io/CookieManager.h | 45 ++-
shibsp/io/impl/CookieManager.cpp | 31 +-
shibsp/session/SessionCache.h | 3 +
shibsp/session/impl/AbstractSessionCache.cpp | 30 +-
shibsp/session/impl/CookieSessionCache.cpp | 526 +++++++++++++++++++++++++++
10 files changed, 622 insertions(+), 28 deletions(-)
diff --git a/Projects/vc22/shibsp.vcxproj b/Projects/vc22/shibsp.vcxproj
index 39de4daa..cc02517d 100644
--- a/Projects/vc22/shibsp.vcxproj
+++ b/Projects/vc22/shibsp.vcxproj
@@ -172,6 +172,7 @@
<ClCompile Include="..\..\shibsp\remoting\impl\SecretSource.cpp" />
<ClCompile Include="..\..\shibsp\remoting\impl\WinHTTPRemotingService.cpp" />
<ClCompile Include="..\..\shibsp\session\impl\AbstractSessionCache.cpp" />
+ <ClCompile Include="..\..\shibsp\session\impl\CookieSessionCache.cpp" />
<ClCompile Include="..\..\shibsp\session\impl\FilesystemSessionCache.cpp" />
<ClCompile Include="..\..\shibsp\session\impl\MemorySessionCache.cpp" />
<ClCompile Include="..\..\shibsp\session\impl\StorageServiceSessionCache.cpp" />
diff --git a/Projects/vc22/shibsp.vcxproj.filters b/Projects/vc22/shibsp.vcxproj.filters
index d385cc47..ccd33cce 100644
--- a/Projects/vc22/shibsp.vcxproj.filters
+++ b/Projects/vc22/shibsp.vcxproj.filters
@@ -344,9 +344,6 @@
<ClCompile Include="..\..\shibsp\platform\iis\ModuleConfig.cpp">
<Filter>Source Files\Platform\iis</Filter>
</ClCompile>
- <ClCompile Include="..\..\shibsp\session\impl\AbstractSessionCache.cpp">
- <Filter>Source Files\Session</Filter>
- </ClCompile>
<ClCompile Include="..\..\shibsp\remoting\impl\AbstractHTTPRemotingService.cpp">
<Filter>Source Files\Remoting</Filter>
</ClCompile>
@@ -365,6 +362,12 @@
<ClCompile Include="..\..\shibsp\session\impl\FilesystemSessionCache.cpp">
<Filter>Source Files\Session</Filter>
</ClCompile>
+ <ClCompile Include="..\..\shibsp\session\impl\AbstractSessionCache.cpp">
+ <Filter>Source Files\Session</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\shibsp\session\impl\CookieSessionCache.cpp">
+ <Filter>Source Files\Session</Filter>
+ </ClCompile>
<ClCompile Include="..\..\shibsp\session\impl\MemorySessionCache.cpp">
<Filter>Source Files\Session</Filter>
</ClCompile>
diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index d11aae09..434519fe 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -133,6 +133,7 @@ libshibsp_la_SOURCES = \
remoting/impl/SecretSource.cpp \
remoting/impl/CurlHTTPRemotingService.cpp \
session/impl/AbstractSessionCache.cpp \
+ session/impl/CookieSessionCache.cpp \
session/impl/FilesystemSessionCache.cpp \
session/impl/MemorySessionCache.cpp \
session/impl/StorageServiceSessionCache.cpp \
diff --git a/shibsp/RequestMapper.h b/shibsp/RequestMapper.h
index ebd930bb..e7b7a538 100644
--- a/shibsp/RequestMapper.h
+++ b/shibsp/RequestMapper.h
@@ -73,6 +73,8 @@ namespace shibsp {
static const char REQUIRE_SESSION_PROP_NAME[];
static const char REQUIRE_LOGOUT_WITH_PROP_NAME[];
static const char SESSION_COOKIE_NAME_PROP_NAME[];
+ static const char SESSION_COOKIE_CHUNKED_NAME_PROP_NAME[];
+ static const char SESSION_COOKIE_LAST_USED_NAME_PROP_NAME[];
static const char SESSION_HOOK_PROP_NAME[];
static const char TARGET_PROP_NAME[];
static const char TIMEOUT_PROP_NAME[];
diff --git a/shibsp/impl/XMLRequestMapper.cpp b/shibsp/impl/XMLRequestMapper.cpp
index f8d70c3a..a7dd0da3 100644
--- a/shibsp/impl/XMLRequestMapper.cpp
+++ b/shibsp/impl/XMLRequestMapper.cpp
@@ -192,6 +192,8 @@ const char RequestMapper::REMOTE_USER_PROP_NAME[] = "REMOTE_USER";
const char RequestMapper::REQUIRE_LOGOUT_WITH_PROP_NAME[] = "requireLogoutWith";
const char RequestMapper::REQUIRE_SESSION_PROP_NAME[] = "requireSession";
const char RequestMapper::SESSION_COOKIE_NAME_PROP_NAME[] = "sessionCookieName";
+const char RequestMapper::SESSION_COOKIE_CHUNKED_NAME_PROP_NAME[] = "sessionCookieChunkedName";
+const char RequestMapper::SESSION_COOKIE_LAST_USED_NAME_PROP_NAME[] = "sessionCookieLastUsedName";
const char RequestMapper::SESSION_HOOK_PROP_NAME[] = "sessionHook";
const char RequestMapper::TARGET_PROP_NAME[] = "target";
const char RequestMapper::TIMEOUT_PROP_NAME[] = "timeout";
diff --git a/shibsp/io/CookieManager.h b/shibsp/io/CookieManager.h
index 1b7ccaae..916ffeac 100644
--- a/shibsp/io/CookieManager.h
+++ b/shibsp/io/CookieManager.h
@@ -157,6 +157,15 @@ namespace shibsp {
*/
void setSameSite(const char* sameSiteValue);
+ /**
+ * Sets the chunking size if using multiple cookies to store large values.
+ *
+ * <p>Defaults to 3072.</p>
+ *
+ * @param chunkSize max size of each cookie
+ */
+ void setChunkSize(unsigned int chunkSize);
+
/**
* Adds a cookie with the specified value to the outgoing response for the supplied request.
*
@@ -166,10 +175,14 @@ namespace shibsp {
* <p>The value will NOT be encoded by this method and must be encoded by the caller
* if necessary.</p>
*
+ * <p>Chunking is used for large values and will result in the data being split at
+ * the configured chunk size and stored in multiple related cookies.</p>
+ *
* @param request the request to operate on
* @param value cookie value
+ * @param chunked whether to spread cookie value across multiple cookies
*/
- void setCookie(SPRequest& request, const char* value) const;
+ void setCookie(SPRequest& request, const char* value, bool chunked=false) const;
/**
* Unsets a cookie via the outgoing response for the supplied request.
@@ -178,14 +191,15 @@ namespace shibsp {
* associated with the request.</p>
*
* @param request the request to operate on
+ * @param chunked whether to unset multiple cookies representing a chunked value
*/
- void unsetCookie(SPRequest& request) const;
+ void unsetCookie(SPRequest& request, bool chunked=false) const;
/**
* Gets the value (if any) for the specified cookie.
*
- * <p>If multiple cookies of the same name are present, it is unspecified
- * which value will be returned.</p>
+ * <p>If multiple cookies of the same name are present or if chunking was used to create
+ * the value, it is unspecified which value will be returned.</p>
*
* @param request the request to operate on
*
@@ -193,6 +207,28 @@ namespace shibsp {
*/
const char* getCookieValue(const SPRequest& request) const;
+ /**
+ * Gets the value of a chunked cookie, combining the chunks into a final result.
+ *
+ * @param request the request to operate on
+ *
+ * @return a value for the named chunked cookie or an empty string
+ */
+ std::string getChunkedCookieValue(const SPRequest& request) const;
+
+ // Common setting names used in multiple places.
+ static const char COOKIE_NAME_PROP_NAME[];
+ static const char COOKIE_SECURE_PROP_NAME[];
+ static const char COOKIE_HTTPONLY_PROP_NAME[];
+ static const char COOKIE_PATH_PROP_NAME[];
+ static const char COOKIE_DOMAIN_PROP_NAME[];
+ static const char COOKIE_MAXAGE_PROP_NAME[];
+ static const char COOKIE_SAMESITE_PROP_NAME[];
+ static bool COOKIE_SECURE_PROP_DEFAULT;
+ static bool COOKIE_HTTPONLY_PROP_DEFAULT;
+ static const char COOKIE_PATH_PROP_DEFAULT[];
+ static int COOKIE_MAXAGE_PROP_DEFAULT;
+
private:
std::string computeCookieName(const SPRequest& request) const;
void outputHeader(SPRequest& request, const char* value, int maxAge) const;
@@ -206,6 +242,7 @@ namespace shibsp {
bool m_secure;
bool m_httpOnly;
samesite_t m_sameSiteValue;
+ unsigned int m_chunkSize;
};
#if defined (_MSC_VER)
diff --git a/shibsp/io/impl/CookieManager.cpp b/shibsp/io/impl/CookieManager.cpp
index 23b9e923..b9391ddd 100644
--- a/shibsp/io/impl/CookieManager.cpp
+++ b/shibsp/io/impl/CookieManager.cpp
@@ -38,6 +38,20 @@ using namespace std;
# define strcasecmp _stricmp
#endif
+const char CookieManager::COOKIE_NAME_PROP_NAME[] = "cookieName";
+const char CookieManager::COOKIE_SECURE_PROP_NAME[] = "cookieSecure";
+const char CookieManager::COOKIE_HTTPONLY_PROP_NAME[] = "cookieHttpOnly";
+const char CookieManager::COOKIE_PATH_PROP_NAME[] = "cookiePath";
+const char CookieManager::COOKIE_DOMAIN_PROP_NAME[] = "cookieDomain";
+const char CookieManager::COOKIE_MAXAGE_PROP_NAME[] = "cookieMaxAge";
+const char CookieManager::COOKIE_SAMESITE_PROP_NAME[] = "cookieSameSite";
+
+bool CookieManager::COOKIE_SECURE_PROP_DEFAULT = true;
+bool CookieManager::COOKIE_HTTPONLY_PROP_DEFAULT = true;
+const char CookieManager::COOKIE_PATH_PROP_DEFAULT[] = "/";
+int CookieManager::COOKIE_MAXAGE_PROP_DEFAULT = -1;
+
+
CookieManager::CookieManager(const char* defaultName)
: m_defaultName(defaultName),
m_appSpecific(false),
@@ -45,7 +59,8 @@ CookieManager::CookieManager(const char* defaultName)
m_maxAge(-1),
m_secure(true),
m_httpOnly(true),
- m_sameSiteValue(SAMESITE_ABSENT)
+ m_sameSiteValue(SAMESITE_ABSENT),
+ m_chunkSize(3072)
{
}
@@ -110,6 +125,11 @@ void CookieManager::setSameSite(samesite_t value)
m_sameSiteValue = value;
}
+void CookieManager::setChunkSize(unsigned int chunkSize)
+{
+ m_chunkSize = chunkSize;
+}
+
string CookieManager::computeCookieName(const SPRequest& request) const
{
// If not app-specific, return the overridden or default name unadorned.
@@ -186,12 +206,17 @@ const char* CookieManager::getCookieValue(const SPRequest& request) const
return entry == cookies.end() ? nullptr : entry->second.c_str();
}
-void CookieManager::setCookie(SPRequest& request, const char* value) const
+string CookieManager::getChunkedCookieValue(const SPRequest& request) const
+{
+ return string();
+}
+
+void CookieManager::setCookie(SPRequest& request, const char* value, bool chunked) const
{
outputHeader(request, value, request.getRequestSettings().first->getInt(RequestMapper::COOKIE_MAXAGE_PROP_NAME, m_maxAge));
}
-void CookieManager::unsetCookie(SPRequest& request) const
+void CookieManager::unsetCookie(SPRequest& request, bool chunked) const
{
outputHeader(request, nullptr, 0);
}
diff --git a/shibsp/session/SessionCache.h b/shibsp/session/SessionCache.h
index 0bb07a4c..a12e95ea 100644
--- a/shibsp/session/SessionCache.h
+++ b/shibsp/session/SessionCache.h
@@ -231,6 +231,9 @@ namespace shibsp {
virtual void remove(const char* key)=0;
};
+ /** SessionCache implementation backed by encrypted, chunked cookies. */
+ #define COOKIE_SESSION_CACHE "cookie"
+
/** SessionCache implementation backed by the file system. */
#define FILESYSTEM_SESSION_CACHE "filesystem"
diff --git a/shibsp/session/impl/AbstractSessionCache.cpp b/shibsp/session/impl/AbstractSessionCache.cpp
index 239c9cf4..50ca33b7 100644
--- a/shibsp/session/impl/AbstractSessionCache.cpp
+++ b/shibsp/session/impl/AbstractSessionCache.cpp
@@ -48,6 +48,7 @@ using namespace boost::property_tree;
using namespace std;
namespace shibsp {
+ extern SessionCache* SHIBSP_DLLLOCAL CookieSessionCacheFactory(ptree& pt, bool deprecationSupport);
extern SessionCache* SHIBSP_DLLLOCAL FilesystemSessionCacheFactory(ptree& pt, bool deprecationSupport);
extern SessionCache* SHIBSP_DLLLOCAL StorageServiceSessionCacheFactory(ptree& pt, bool deprecationSupport);
extern SessionCache* SHIBSP_DLLLOCAL MemorySessionCacheFactory(ptree& pt, bool deprecationSupport);
@@ -58,23 +59,13 @@ static const char STORAGE_ACCESS_INTERVAL_PROP_NAME[] = "storageAccessInterval";
static const char INPROC_TIMEOUT_PROP_NAME[] = "inprocTimeout";
static const char ISSUER_ATTRIBUTE_PROP_NAME[] = "issuerAttribute";
static const char UNRELIABLE_NETWORKS_PROP_NAME[] = "unreliableNetworks";
-static const char COOKIE_NAME_PROP_NAME[] = "cookieName";
-static const char COOKIE_SECURE_PROP_NAME[] = "cookieSecure";
-static const char COOKIE_HTTPONLY_PROP_NAME[] = "cookieHttpOnly";
-static const char COOKIE_PATH_PROP_NAME[] = "cookiePath";
-static const char COOKIE_DOMAIN_PROP_NAME[] = "cookieDomain";
-static const char COOKIE_MAXAGE_PROP_NAME[] = "cookieMaxAge";
-static const char COOKIE_SAMESITE_PROP_NAME[] = "cookieSameSite";
static unsigned int CLEANUP_INTERVAL_PROP_DEFAULT = 900;
static unsigned int STORAGE_ACCESS_INTERVAL_PROP_DEFAULT = 600;
static unsigned int INPROC_TIMEOUT_PROP_DEFAULT = 900;
static const char ISSUER_ATTRIBUTE_PROP_DEFAULT[] = "Shib-Identity-Provider";
+
static const char COOKIE_NAME_PROP_DEFAULT[] = "__Host-shibsession";
-static bool COOKIE_SECURE_PROP_DEFAULT = true;
-static bool COOKIE_HTTPONLY_PROP_DEFAULT = true;
-static const char COOKIE_PATH_PROP_DEFAULT[] = "/";
-static int COOKIE_MAXAGE_PROP_DEFAULT = -1;
// Logging Macros
#define DEBUG_MARK request, m_log, Priority::SHIB_DEBUG
@@ -85,6 +76,7 @@ static int COOKIE_MAXAGE_PROP_DEFAULT = -1;
void SHIBSP_API shibsp::registerSessionCaches()
{
+ AgentConfig::getConfig().SessionCacheManager.registerFactory(COOKIE_SESSION_CACHE, CookieSessionCacheFactory);
AgentConfig::getConfig().SessionCacheManager.registerFactory(FILESYSTEM_SESSION_CACHE, FilesystemSessionCacheFactory);
AgentConfig::getConfig().SessionCacheManager.registerFactory(MEMORY_SESSION_CACHE, MemorySessionCacheFactory);
AgentConfig::getConfig().SessionCacheManager.registerFactory(STORAGESERVICE_SESSION_CACHE, StorageServiceSessionCacheFactory);
@@ -211,14 +203,14 @@ AbstractSessionCache::AbstractSessionCache(const ptree& pt)
m_storageAccessInterval = getUnsignedInt(STORAGE_ACCESS_INTERVAL_PROP_NAME, STORAGE_ACCESS_INTERVAL_PROP_DEFAULT);
// Set up cookie manager.
- m_cookieManager.reset(new CookieManager(getString(COOKIE_NAME_PROP_NAME, COOKIE_NAME_PROP_DEFAULT)));
+ m_cookieManager.reset(new CookieManager(getString(CookieManager::COOKIE_NAME_PROP_NAME, COOKIE_NAME_PROP_DEFAULT)));
m_cookieManager->setCookieNamePolicy(RequestMapper::SESSION_COOKIE_NAME_PROP_NAME, true);
- m_cookieManager->setSecure(getBool(COOKIE_SECURE_PROP_NAME, COOKIE_SECURE_PROP_DEFAULT));
- m_cookieManager->setHttpOnly(getBool(COOKIE_HTTPONLY_PROP_NAME, COOKIE_HTTPONLY_PROP_DEFAULT));
- m_cookieManager->setPath(getString(COOKIE_PATH_PROP_NAME, COOKIE_PATH_PROP_DEFAULT));
- m_cookieManager->setMaxAge(getInt(COOKIE_MAXAGE_PROP_NAME, COOKIE_MAXAGE_PROP_DEFAULT));
- m_cookieManager->setDomain(getString(COOKIE_DOMAIN_PROP_NAME));
- m_cookieManager->setSameSite(getString(COOKIE_SAMESITE_PROP_NAME));
+ m_cookieManager->setSecure(getBool(CookieManager::COOKIE_SECURE_PROP_NAME, CookieManager::COOKIE_SECURE_PROP_DEFAULT));
+ m_cookieManager->setHttpOnly(getBool(CookieManager::COOKIE_HTTPONLY_PROP_NAME, CookieManager::COOKIE_HTTPONLY_PROP_DEFAULT));
+ m_cookieManager->setPath(getString(CookieManager::COOKIE_PATH_PROP_NAME, CookieManager::COOKIE_PATH_PROP_DEFAULT));
+ m_cookieManager->setMaxAge(getInt(CookieManager::COOKIE_MAXAGE_PROP_NAME, CookieManager::COOKIE_MAXAGE_PROP_DEFAULT));
+ m_cookieManager->setDomain(getString(CookieManager::COOKIE_DOMAIN_PROP_NAME));
+ m_cookieManager->setSameSite(getString(CookieManager::COOKIE_SAMESITE_PROP_NAME));
const char* unreliableNetworks = getString(UNRELIABLE_NETWORKS_PROP_NAME);
if (unreliableNetworks) {
@@ -692,6 +684,8 @@ bool AbstractSessionCache::update(SPRequest& request, unique_lock<Session>& sess
return true;
}
else {
+ request.info("version mismatch on attempt to update session (%s), version (%u)",
+ session.mutex()->getID(), session.mutex()->getVersion());
return false;
}
}
diff --git a/shibsp/session/impl/CookieSessionCache.cpp b/shibsp/session/impl/CookieSessionCache.cpp
new file mode 100644
index 00000000..8c8237c4
--- /dev/null
+++ b/shibsp/session/impl/CookieSessionCache.cpp
@@ -0,0 +1,526 @@
+/**
+ * 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
+ *
+ * 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.
+ */
+
+/**
+ * session/impl/CookieSessionCache.cpp
+ *
+ * SessionCache implementation using encrypted cookies.
+ *
+ * <p>This implementation relies on the Hub to encrypt and decrypt data
+ * that is chunked across multiple cookies of a configurable size.</p>
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "Agent.h"
+#include "AgentConfig.h"
+#include "SPRequest.h"
+#include "io/CookieManager.h"
+#include "remoting/RemotingService.h"
+#include "session/AbstractSessionCache.h"
+#include "logging/Category.h"
+#include "util/Date.h"
+#include "util/Misc.h"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/property_tree/ptree.hpp>
+
+using namespace shibsp;
+using namespace boost::property_tree;
+using namespace std;
+
+namespace {
+ class CookieSessionCache : public virtual AbstractSessionCache {
+ public:
+ CookieSessionCache(const ptree& pt);
+ ~CookieSessionCache();
+
+ string cache_create(SPRequest* request, const char* candidateSessionID, DDF& sessionData);
+ DDF cache_read(
+ SPRequest* request,
+ const char* applicationId,
+ const char* key,
+ unsigned int version=1,
+ unsigned int lifetime=0,
+ unsigned int timeout=0,
+ const char* client_addr=nullptr
+ );
+ bool cache_update(SPRequest* request, const char* key, unsigned int version, DDF& data);
+ bool cache_touch(SPRequest* request, const char* key, unsigned int version=1, unsigned int timeout=0);
+ void cache_remove(SPRequest* request, const char* key);
+
+ private:
+ Category& m_spilog;
+ unsigned int m_cookieLifetime;
+ // We store the encrypted timestamp in a separate, unchunked cookie.
+ unique_ptr<CookieManager> m_lastUsedCookieManager;
+ unique_ptr<CookieManager> m_chunkedCookieManager;
+ };
+
+ static const char COOKIE_LIFETIME_PROP_NAME[] = "cookieLifetime";
+ static const char CHUNKED_COOKIE_NAME_PROP_NAME[] = "chunkedCookieName";
+ static const char LAST_USED_COOKIE_NAME_PROP_NAME[] = "lastUsedCookieName";
+
+ static unsigned int COOKIE_LIFETIME_PROP_DEFAULT = 24 * 3600;
+ static const char LAST_USED_COOKIE_NAME_PROP_DEFAULT[] = "__Host-shib_chk_ts";
+ static const char CHUNKED_COOKIE_NAME_PROP_DEFAULT[] = "__Host-shib_chk";
+};
+
+// Logging Macros
+#define DEBUG_MARK request, m_spilog, Priority::SHIB_DEBUG
+#define INFO_MARK request, m_spilog, Priority::SHIB_INFO
+#define WARN_MARK request, m_spilog, Priority::SHIB_WARN
+#define ERROR_MARK request, m_spilog, Priority::SHIB_ERROR
+#define CRIT_MARK request, m_spilog, Priority::SHIB_CRIT
+
+namespace shibsp {
+ SessionCache* SHIBSP_DLLLOCAL CookieSessionCacheFactory(ptree& pt, bool deprecationSupport) {
+ return new CookieSessionCache(pt);
+ }
+}
+
+CookieSessionCache::CookieSessionCache(const ptree& pt)
+ : AbstractSessionCache(pt), m_spilog(Category::getInstance(SHIBSP_LOGCAT ".SessionCache.StorageService"))
+{
+ m_cookieLifetime = getUnsignedInt(COOKIE_LIFETIME_PROP_NAME, COOKIE_LIFETIME_PROP_DEFAULT);
+
+ // Set up cookie managers.
+
+ m_lastUsedCookieManager.reset(new CookieManager(getString(LAST_USED_COOKIE_NAME_PROP_NAME, LAST_USED_COOKIE_NAME_PROP_DEFAULT)));
+ m_lastUsedCookieManager->setCookieNamePolicy(RequestMapper::SESSION_COOKIE_LAST_USED_NAME_PROP_NAME, true);
+ m_lastUsedCookieManager->setSecure(getBool(CookieManager::COOKIE_SECURE_PROP_NAME, CookieManager::COOKIE_SECURE_PROP_DEFAULT));
+ m_lastUsedCookieManager->setHttpOnly(getBool(CookieManager::COOKIE_HTTPONLY_PROP_NAME, CookieManager::COOKIE_HTTPONLY_PROP_DEFAULT));
+ m_lastUsedCookieManager->setPath(getString(CookieManager::COOKIE_PATH_PROP_NAME, CookieManager::COOKIE_PATH_PROP_DEFAULT));
+ m_lastUsedCookieManager->setMaxAge(getInt(CookieManager::COOKIE_MAXAGE_PROP_NAME, CookieManager::COOKIE_MAXAGE_PROP_DEFAULT));
+ m_lastUsedCookieManager->setDomain(getString(CookieManager::COOKIE_DOMAIN_PROP_NAME));
+ m_lastUsedCookieManager->setSameSite(getString(CookieManager::COOKIE_SAMESITE_PROP_NAME));
+
+ m_chunkedCookieManager.reset(new CookieManager(getString(CHUNKED_COOKIE_NAME_PROP_NAME, CHUNKED_COOKIE_NAME_PROP_DEFAULT)));
+ m_chunkedCookieManager->setCookieNamePolicy(RequestMapper::SESSION_COOKIE_CHUNKED_NAME_PROP_NAME, true);
+ m_chunkedCookieManager->setSecure(getBool(CookieManager::COOKIE_SECURE_PROP_NAME, CookieManager::COOKIE_SECURE_PROP_DEFAULT));
+ m_chunkedCookieManager->setHttpOnly(getBool(CookieManager::COOKIE_HTTPONLY_PROP_NAME, CookieManager::COOKIE_HTTPONLY_PROP_DEFAULT));
+ m_chunkedCookieManager->setPath(getString(CookieManager::COOKIE_PATH_PROP_NAME, CookieManager::COOKIE_PATH_PROP_DEFAULT));
+ m_chunkedCookieManager->setMaxAge(getInt(CookieManager::COOKIE_MAXAGE_PROP_NAME, CookieManager::COOKIE_MAXAGE_PROP_DEFAULT));
+ m_chunkedCookieManager->setDomain(getString(CookieManager::COOKIE_DOMAIN_PROP_NAME));
+ m_chunkedCookieManager->setSameSite(getString(CookieManager::COOKIE_SAMESITE_PROP_NAME));
+}
+
+CookieSessionCache::~CookieSessionCache()
+{
+}
+
+string CookieSessionCache::cache_create(SPRequest* request, const char* candidateSessionID, DDF& sessionData)
+{
+ if (!request) {
+ throw SessionException("Access to request object required to store cookie-backed session.");
+ }
+
+ // Put session ID in name of structure for later cross-check.
+ if (candidateSessionID) {
+ sessionData.name(candidateSessionID);
+ }
+ else {
+ sessionData.name(AgentConfig::getConfig().generateRandom(16));
+ }
+
+ // Serialize the data for encryption operation.
+ ostringstream sink;
+ sink << sessionData;
+
+ // Call for encryption of both the session and a timestamp.
+ const RemotingService* remoting = AgentConfig::getConfig().getAgent().getRemotingService();
+ DDF in = remoting->build("sealer", nullptr, request->getRequestID());
+ DDFJanitor injanitor(in);
+ DDF encList = in.addmember("encrypt").list();
+ DDF encChild1(nullptr);
+ encList.add(encChild1.string(sink.str()));
+
+ try {
+ // Inside try/catch in case it throws.
+ DDF encChild2(nullptr);
+ encList.add(encChild2.string(boost::lexical_cast<string>(time(nullptr))));
+
+ DDF out = remoting->send(in);
+ DDFJanitor outJanitor(out);
+ DDF encryptedList = out["encrypted"];
+ if (!encryptedList.islist() || encryptedList.integer() != 2) {
+ throw OperationException("No encrypted data returned from sealer operation.");
+ }
+
+ // Set the encrypted data via the chunking manager and the timestamp directly.
+ m_chunkedCookieManager->setCookie(*request, encryptedList.get(0).string(), true);
+ m_lastUsedCookieManager->setCookie(*request, encryptedList.get(1).string());
+ }
+ catch (const exception& e) {
+ log(ERROR_MARK, "exception attempting to encrypt session data via Hub: %s", e.what());
+ throw;
+ }
+
+ return sessionData.name();
+}
+
+DDF CookieSessionCache::cache_read(
+ SPRequest* request,
+ const char* applicationId,
+ const char* key,
+ unsigned int version,
+ unsigned int lifetime,
+ unsigned int timeout,
+ const char* client_addr
+ )
+{
+ if (!request) {
+ throw SessionException("Access to request object required to read cookie-backed session.");
+ }
+
+ string encryptedSession = m_chunkedCookieManager->getChunkedCookieValue(*request);
+ if (encryptedSession.empty()) {
+ log(DEBUG_MARK, "no cookie-backed session retreived from request");
+ return DDF();
+ }
+
+ const RemotingService* remoting = AgentConfig::getConfig().getAgent().getRemotingService();
+ DDF in = remoting->build("sealer", nullptr, request->getRequestID());
+ DDFJanitor injanitor(in);
+ DDF decList = in.addmember("decrypt").list();
+
+ if (timeout) {
+ const char* encryptedTimestamp = m_lastUsedCookieManager->getCookieValue(*request);
+ if (!encryptedTimestamp || !*encryptedTimestamp) {
+ log(WARN_MARK, "unable to load encrypted timestamp for session (%s) from cookie", key);
+ throw SessionException("Unable to load encrypted timestamp for session from cookie.");
+ }
+ DDF decChild1(nullptr);
+ decList.add((decChild1.string(encryptedTimestamp)));
+ }
+
+ DDF decChild2(nullptr);
+ decList.add((decChild2.string(encryptedSession)));
+
+ DDF out;
+ try {
+ // Add new timestamp to encrypt inside try/catch in case it throws a lexical_cast exception.
+ DDF encChild(nullptr);
+ in.addmember("encrypt").list().add(encChild.string(boost::lexical_cast<string>(time(nullptr))));
+ out = remoting->send(in);
+ }
+ catch (const exception& e) {
+ log(ERROR_MARK, "exception attempting to decrypt session (%s): %s", key, e.what());
+ throw;
+ }
+
+ DDFJanitor outjanitor(out);
+ DDF decryptedList = out["decrypted"];
+ if (!decryptedList.islist() || decryptedList.integer() != (timeout ? 2 : 1)) {
+ throw OperationException("No decrypted data returned from sealer operation.");
+ }
+ DDF encryptedList = out["encrypted"];
+ if (!encryptedList.islist() || encryptedList.integer() != 1) {
+ throw OperationException("No encrypted timestamp returned from sealer operation.");
+ }
+
+ DDF sessionData;
+ istringstream source(decryptedList.get(0).string());
+ source >> sessionData;
+ if (sessionData.isnull()) {
+ return DDF();
+ }
+
+ DDFJanitor sessionDataJanitor(sessionData);
+
+ // Check session ID.
+ if (!sessionData.name()) {
+ log(ERROR_MARK, "session did not carry an ID to cross check");
+ return DDF();
+ }
+ else if (strcmp(key, sessionData.name())) {
+ log(WARN_MARK, "session ID mismatch, requested (%s), decrypted (%s)", key, sessionData.name());
+ return DDF();
+ }
+
+ if (!isSessionDataValid(sessionData)) {
+ log(ERROR_MARK, "session data decrypted via Hub for (%s) was invalid", key);
+ throw IOException("Session data was invalid.");
+ }
+
+ // Check application. We know the member exists due to the previous check.
+ const char* appId = sessionData["app_id"].string();
+ if (strcmp(applicationId, appId)) {
+ log(WARN_MARK, "session (%s) issued for application (%s), accessed via application (%s)", key, appId, applicationId);
+ return DDF();
+ }
+
+ time_t now = time(nullptr);
+
+ if (timeout) {
+ const char* decryptedTimestamp = decryptedList.get(1).string();
+ time_t last_used = boost::lexical_cast<time_t>(decryptedTimestamp ? decryptedTimestamp : "0");
+ if (last_used + timeout < now) {
+ if (request->isPriorityEnabled(Priority::SHIB_INFO)) {
+ string ts(date::format("%FT%TZ", chrono::system_clock::from_time_t(last_used)));
+ log(INFO_MARK, "session (%s) expired for inactivity, timeout (%lu), last access (%s)", key, timeout, ts.c_str());
+ }
+ cache_remove(request, key);
+ return DDF();
+ }
+ }
+
+ if (lifetime) {
+ time_t notonorafter = sessionData["notonorafter"].longinteger();
+ if (notonorafter > 0 && notonorafter <= now) {
+ if (request->isPriorityEnabled(Priority::SHIB_INFO)) {
+ string expired(date::format("%FT%TZ", chrono::system_clock::from_time_t(notonorafter)));
+ log(INFO_MARK, "session (%s) has expired per NotOnOrAfter policy (%s)", key, expired.c_str());
+ }
+ cache_remove(request, key);
+ return DDF();
+ }
+
+ time_t start = sessionData["ts"].longinteger();
+ if (start + lifetime < now) {
+ if (request->isPriorityEnabled(Priority::SHIB_INFO)) {
+ string created(date::format("%FT%TZ", chrono::system_clock::from_time_t(start)));
+ string expired(date::format("%FT%TZ", chrono::system_clock::from_time_t(start + lifetime)));
+ log(INFO_MARK, "session (%s) has expired, created (%s), expired (%s)", key, created.c_str(), expired.c_str());
+ }
+ cache_remove(request, key);
+ return DDF();
+ }
+ }
+
+ if (client_addr) {
+ const char* family = getAddressFamily(client_addr);
+ const char* addr = sessionData[family].string();
+ if (addr) {
+ if (!isAddressMatch(client_addr, addr)) {
+ log(INFO_MARK, "session (%s) use invalid, bound to address (%s), accessed from (%s)", key, addr, client_addr);
+ return DDF();
+ }
+ }
+ else {
+ // We have to rebind the session to a new address family, requiring an update to the session.
+ log(INFO_MARK, "attempting update of session (%s) to rebind to new address (%s)", key, client_addr);
+
+ // Fill in the new address and attempt the update. We can do this using the "existing" output
+ // because we have the object janitor'd and the cache_update copies the object internally.
+ sessionData.addmember(family).string(client_addr);
+ try {
+ if (!cache_update(request, key, version, sessionData)) {
+ // This signals a version mismatch in which the original session we were asked to read
+ // has already been updated by another thread or process. In this case, we recurse the
+ // read attempt. This has to terminate eventually...right?
+
+ // We bump the version once, under the assunption it shouldn't be likely that it's been
+ // updated behind us more than once...
+ return cache_read(request, applicationId, key, version + 1, lifetime, timeout, client_addr);
+ }
+ }
+ catch (const exception& ex) {
+ // This is an outright error attempting the update, so we just fail hard.
+ log(ERROR_MARK, "exception attempting to update session (%s): %s", key, ex.what());
+ throw;
+ }
+ }
+ }
+
+ // Otherwise update the timestamp cookie and return the session structure.
+ m_lastUsedCookieManager->setCookie(*request, encryptedList.get(0).string());
+ return sessionDataJanitor.release();
+}
+
+bool CookieSessionCache::cache_update(SPRequest* request, const char* key, unsigned int version, DDF& sessionData)
+{
+ if (!request) {
+ throw SessionException("Access to request object required to update cookie-backed session.");
+ }
+
+ // Note this is absolutely racy, as we don't have a syncronizable "back-end" to prevent races.
+ // The "last" one out will "win" and subsequent operations will detect mismatches.
+
+ const RemotingService* remoting = AgentConfig::getConfig().getAgent().getRemotingService();
+ DDF in = remoting->build("sealer", nullptr, request->getRequestID());
+ DDFJanitor injanitor(in);
+
+ // We "assume" success to avoid an extra round trip, so we'll decrypt the existing data
+ // and encrypt the updated data and timestamp at the same time.
+
+ string encryptedSession = m_chunkedCookieManager->getChunkedCookieValue(*request);
+ if (encryptedSession.empty()) {
+ log(ERROR_MARK, "no cookie-backed session retreived from request");
+ throw SessionException("No cookie-backed session retrieved from request.");
+ }
+
+ DDF decChild(nullptr);
+ in.addmember("decrypt").list().add(decChild.string(encryptedSession));
+
+ // Ensure the new version is set accurately unless an error occurs.
+ sessionData.addmember("ver").integer(version + 1);
+
+ ostringstream sink;
+ sink << sessionData;
+
+ DDF encList = in.addmember("encrypt").list();
+ DDF encChild1(nullptr);
+ encList.add(encChild1.string(sink.str()));
+
+ DDF out;
+ try {
+ // Add new timestamp to encrypt inside try/catch in case it throws a lexical_cast exception.
+ DDF encChild2(nullptr);
+ encList.add(encChild2.string(boost::lexical_cast<string>(time(nullptr))));
+
+ out = remoting->send(in);
+ }
+ catch (const exception& e) {
+ log(ERROR_MARK, "exception attempting to encrypt/decrypt session (%s) via Hub: %s", key, e.what());
+ // Restore old version.
+ sessionData.addmember("ver").integer(version);
+ throw;
+ }
+
+ DDFJanitor outjanitor(out);
+
+ DDF decryptedList = out["decrypted"];
+ if (!decryptedList.islist() || decryptedList.integer() != 1) {
+ // Restore old version.
+ sessionData.addmember("ver").integer(version);
+ throw OperationException("No decrypted data returned from sealer operation.");
+ }
+ DDF encryptedList = out["encrypted"];
+ if (!encryptedList.islist() || encryptedList.integer() != 2) {
+ // Restore old version.
+ sessionData.addmember("ver").integer(version);
+ throw OperationException("No encrypted data returned from sealer operation.");
+ }
+
+ long oldVersion;
+ try {
+ // Reconstitute the old session to check for version mismatch.
+ istringstream source(decryptedList.get(0).string());
+ DDF oldSessionData;
+ source >> oldSessionData;
+ DDFJanitor oldSessionDataJanitor(oldSessionData);
+
+ oldVersion = oldSessionData["ver"].integer();
+ if (oldVersion == 0) {
+ // Implied version.
+ oldVersion = 1;
+ }
+ }
+ catch (const exception& e) {
+ // Restore old version.
+ sessionData.addmember("ver").integer(version);
+ throw;
+ }
+
+ // The decrypted session's version has to match the input version.
+ // Older should be impossible and treated as an error.
+ // Newer means a version mismatch, the session was updated by somebody else.
+
+ if (oldVersion > version) {
+ log(INFO_MARK, "decrypted session (%s) contains newer version (%ld) than input (%u)",
+ key, oldVersion, version);
+ // Restore old version.
+ sessionData.addmember("ver").integer(version);
+ return false;
+ }
+ else if (oldVersion < version) {
+ log(INFO_MARK, "decrypted session (%s) contains older version (%ld) than input (%u)",
+ key, oldVersion, version);
+ // Restore old version.
+ sessionData.addmember("ver").integer(version);
+ throw IOException("Unexpected out-of-date version found in decrypted session.");
+ }
+
+ m_chunkedCookieManager->setCookie(*request, encryptedList.get(0).string(), true);
+ m_lastUsedCookieManager->setCookie(*request, encryptedList.get(1).string());
+
+ return true;
+}
+
+bool CookieSessionCache::cache_touch(SPRequest* request, const char* key, unsigned int version, unsigned int timeout)
+{
+ if (!request) {
+ throw SessionException("Access to request object required to touch cookie-backed session.");
+ }
+
+ const RemotingService* remoting = AgentConfig::getConfig().getAgent().getRemotingService();
+ DDF in = remoting->build("sealer", nullptr, request->getRequestID());
+ DDFJanitor injanitor(in);
+
+ if (timeout) {
+ const char* encryptedTimestamp = m_lastUsedCookieManager->getCookieValue(*request);
+ if (!encryptedTimestamp || !*encryptedTimestamp) {
+ log(WARN_MARK, "unable to load encrypted timestamp for session (%s) from cookie", key);
+ return false;
+ }
+ DDF decChild(nullptr);
+ in.addmember("decrypt").list().add((decChild.string(encryptedTimestamp)));
+ }
+
+ DDF out;
+ try {
+ // Add new timestamp to encrypt inside try/catch in case it throws a lexical_cast exception.
+ DDF encChild(nullptr);
+ in.addmember("encrypt").list().add(encChild.string(boost::lexical_cast<string>(time(nullptr))));
+ out = remoting->send(in);
+ }
+ catch (const exception& e) {
+ log(ERROR_MARK, "exception attempting to encrypt/decrypt timestamps for session (%s): %s", key, e.what());
+ throw;
+ }
+
+ DDFJanitor outjanitor(out);
+ DDF decryptedList;
+ if (timeout) {
+ decryptedList = out["decrypted"];
+ if (!decryptedList.islist() || decryptedList.integer() != 1) {
+ throw OperationException("No decrypted timestamp returned from sealer operation.");
+ }
+ }
+ DDF encryptedList = out["encrypted"];
+ if (!encryptedList.islist() || encryptedList.integer() != 1) {
+ throw OperationException("No encrypted timestamp returned from sealer operation.");
+ }
+
+
+ if (timeout) {
+ const char* decryptedTimestamp = decryptedList.get(0).string();
+ time_t last_used = boost::lexical_cast<time_t>(decryptedTimestamp ? decryptedTimestamp : "0");
+ if (last_used + timeout < time(nullptr)) {
+ if (request->isPriorityEnabled(Priority::SHIB_INFO)) {
+ string ts(date::format("%FT%TZ", chrono::system_clock::from_time_t(last_used)));
+ log(INFO_MARK, "session (%s) expired for inactivity, timeout (%lu), last access (%s)", key, timeout, ts.c_str());
+ }
+ cache_remove(request, key);
+ return false;
+ }
+ }
+
+ // Update timestamp cookie.
+ m_lastUsedCookieManager->setCookie(*request, encryptedList.get(0).string());
+ return true;
+}
+
+void CookieSessionCache::cache_remove(SPRequest* request, const char* key)
+{
+ if (!request) {
+ throw SessionException("Access to request object required to remove cookie-backed session.");
+ }
+
+ m_chunkedCookieManager->unsetCookie(*request, true);
+ m_lastUsedCookieManager->unsetCookie(*request);
+
+ log(INFO_MARK, "removed cookie-backed session (%s)", key);
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list