[cpp-sp] branch main updated: File-backed session impl, cache refinements.

Scott Cantor cantor.2 at osu.edu
Thu Jun 5 18:05:24 UTC 2025


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository cpp-sp.

View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=d4536ed746c4c013aaca3c6d47bad63ea45d1ee8

The following commit(s) were added to refs/heads/main by this push:
     new d4536ed7 File-backed session impl, cache refinements.
d4536ed7 is described below

commit d4536ed746c4c013aaca3c6d47bad63ea45d1ee8
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Jun 5 14:05:16 2025 -0400

    File-backed session impl, cache refinements.
---
 shibsp/impl/DefaultAgent.cpp                       |   2 +-
 shibsp/session/AbstractSessionCache.h              |   2 +-
 shibsp/session/SessionCacheSPI.h                   |   8 +-
 shibsp/session/impl/AbstractSessionCache.cpp       |  58 ++++---
 shibsp/session/impl/FilesystemSessionCache.cpp     | 176 +++++++++++++++++++--
 shibsp/session/impl/MemorySessionCache.cpp         |   8 +-
 shibsp/util/Misc.cpp                               |  18 ++-
 shibsp/util/Misc.h                                 |  18 ++-
 tests/data/session/impl/filesystem-shibboleth.ini  |   1 +
 tests/session/impl/FilesystemSessionCacheTests.cpp |  56 +++++++
 10 files changed, 308 insertions(+), 39 deletions(-)

diff --git a/shibsp/impl/DefaultAgent.cpp b/shibsp/impl/DefaultAgent.cpp
index 11601fb7..93f0f74e 100644
--- a/shibsp/impl/DefaultAgent.cpp
+++ b/shibsp/impl/DefaultAgent.cpp
@@ -270,7 +270,7 @@ void DefaultAgent::doAttributeConfigurations()
     } else {
         string path("attributes.ini");
         AgentConfig::getConfig().getPathResolver().resolve(path, PathResolver::SHIBSP_CFG_FILE);
-        if (file_exists(path.c_str())) {
+        if (FileSupport::exists(path.c_str())) {
             m_attributeConfigurations["default"] = AttributeConfiguration::newAttributeConfiguration(path.c_str());
             m_log.info("installed 'default' AttributeConfiguration from %s", path.c_str());
         } else {
diff --git a/shibsp/session/AbstractSessionCache.h b/shibsp/session/AbstractSessionCache.h
index 7b91c719..65af298e 100644
--- a/shibsp/session/AbstractSessionCache.h
+++ b/shibsp/session/AbstractSessionCache.h
@@ -71,7 +71,7 @@ namespace shibsp {
         std::map<std::string,DDF> m_attributes;
 
         AbstractSessionCache& m_cache;
-        time_t m_creation,m_lastAccess,m_lastAccessReported;
+        time_t m_lastAccess,m_lastAccessReported;
         // TODO: possibly convert to a shared lock where possible?
         // I used exclusive because it avoided lock "upgrades"
         // when mutating or deleting sessions.
diff --git a/shibsp/session/SessionCacheSPI.h b/shibsp/session/SessionCacheSPI.h
index 6d446f27..f791bfde 100644
--- a/shibsp/session/SessionCacheSPI.h
+++ b/shibsp/session/SessionCacheSPI.h
@@ -33,6 +33,10 @@ namespace shibsp {
     /**
      * Interface to the "back-end" persistence mechanism to allow sessions to exist
      * independently of a specific agent process.
+     * 
+     * <p>As a general rule, implementations should log errors internally and raise exceptions
+     * such that the caller need not log the resulting object to ensure adequate logging of the
+     * outcome.</p>
      */
     class SHIBSP_API SessionCacheSPI
     {
@@ -85,7 +89,7 @@ namespace shibsp {
             unsigned int lifetime=0,
             unsigned int timeout=0,
             const char* client_addr=nullptr
-            ) const=0;
+            )=0;
 
         /**
          * Informs the storage medium that a session was used at the current point in time.
@@ -99,7 +103,7 @@ namespace shibsp {
          * 
          * @return true iff the session remains valid/available
          */
-        virtual bool cache_touch(SPRequest* request, const char* key, unsigned int timeout=0) const=0;
+        virtual bool cache_touch(SPRequest* request, const char* key, unsigned int timeout=0)=0;
 
         /**
          * Delete a session record from the underlying storage medium.
diff --git a/shibsp/session/impl/AbstractSessionCache.cpp b/shibsp/session/impl/AbstractSessionCache.cpp
index efeba049..86e94c6c 100644
--- a/shibsp/session/impl/AbstractSessionCache.cpp
+++ b/shibsp/session/impl/AbstractSessionCache.cpp
@@ -160,8 +160,9 @@ string AbstractSessionCache::create(SPRequest& request, DDF& session)
 
     // Add additional fields managed by agent.
     // attributes and data members should be present from hub.
-    session.addmember("creation").longinteger(time(nullptr));
-    session.addmember("app_id").string(request.getRequestSettings().first->getString("applicationId"));
+    session.addmember("ts").longinteger(time(nullptr));
+    session.addmember("app_id").string(request.getRequestSettings().first->getString(
+        RequestMapper::APPLICATION_ID_PROP_NAME, RequestMapper::APPLICATION_ID_PROP_DEFAULT));
     session.addmember("addr").string(request.getRemoteAddr());
 
     // Write the data to the back-end, obtaining a key.
@@ -170,8 +171,8 @@ string AbstractSessionCache::create(SPRequest& request, DDF& session)
         m_log.debug("writing new session to persistent store");
         key = cache_create(&request, session);
     }
-    catch (const IOException& ex) {
-        m_log.error("IOException writing new session to persistent store: %s", ex.what());
+    catch (const exception& ex) {
+        // Should be logged by the SPI.
         session.destroy();
         return string();
     }
@@ -276,14 +277,15 @@ unique_lock<Session> AbstractSessionCache::_find(
         // Cross-check application.
         if (strcmp(applicationId, session.mutex()->getApplicationID())) {
             m_log.warn("session (%s) issued for application (%s), accessed via application (%s)",
-                key, applicationId, session.mutex()->getApplicationID());
+                key, session.mutex()->getApplicationID(), applicationId);
             session.unlock();
         }
         else if (!dynamic_cast<BasicSession*>(session.mutex())->isValid(request, lifetime, timeout, client_addr)) {
             // Locally invalid on its face, so remove and return nothing.
             session.unlock();
             m_log.debug("session (%s) invalid, removing it", key);
-            remove(key);
+            // The record should be gone from the back-end but we need to dump it locally.
+            dormant(string(key));
         }
 
         // Return locked session or empty wrapper.
@@ -300,7 +302,7 @@ unique_lock<Session> AbstractSessionCache::_find(
         obj = cache_read(request, applicationId, key, lifetime, timeout, client_addr);
     }
     catch (const exception& ex) {
-        m_log.error("error reading session (%s) from persistent store: %s", key, ex.what());
+        // Should be logged by the SPI.
         return unique_lock<Session>();
     }
 
@@ -355,7 +357,12 @@ void AbstractSessionCache::remove(SPRequest& request)
         return;
     }
     dormant(string(key));
-    cache_remove(&request, key);
+    try {
+        cache_remove(&request, key);
+    }
+    catch (const exception& ex) {
+        // Should be logged by the SPI.
+    }
     m_cookieManager->unsetCookie(request);
 }
 
@@ -454,7 +461,7 @@ void* AbstractSessionCache::cleanup_fn(void* p)
         if (!stale_keys.empty()) {
             pcache->m_log.info("purging %u old sessions", stale_keys.size());
 
-            // Pass 2: walk through the list of stale entries and remove them from the cache
+            // Pass 2: walk through the list of stale entries and remove them from the local cache.
             for (const string& key : stale_keys) {
                 pcache->dormant(key.c_str());
             }
@@ -469,10 +476,8 @@ void* AbstractSessionCache::cleanup_fn(void* p)
 }
 
 BasicSession::BasicSession(AbstractSessionCache& cache, DDF& obj)
-    : m_obj(obj), m_cache(cache), m_creation(0), m_lastAccess(time(nullptr)), m_lastAccessReported(m_lastAccess)
+    : m_obj(obj), m_cache(cache), m_lastAccess(time(nullptr)), m_lastAccessReported(m_lastAccess)
 {
-    m_creation = m_obj["creation"].longinteger();
-
     // This is safe to directly expose for iteration of the attributes
     // as long as we maintain the mutex-based lock approach for exclusive
     // Session access. If we made that a shared lock, this all has to change.
@@ -519,6 +524,7 @@ bool BasicSession::isValid(SPRequest* request, unsigned int lifetime, unsigned i
     // TODO: Implement the fuzzy address matching.
     if (client_addr && strcmp(client_addr, getClientAddress())) {
         m_cache.log().warn("session (%s) invalid, bound to address (%s), accessed from (%s)", getID(), getClientAddress(), client_addr);
+        m_cache.cache_remove(request, getID());
         return false;
     }
 
@@ -532,6 +538,7 @@ bool BasicSession::isValid(SPRequest* request, unsigned int lifetime, unsigned i
                 string expired(date::format("%FT%TZ", chrono::system_clock::from_time_t(getCreation() + lifetime)));
                 m_cache.log().warn("session (%s) has expired, created (%s), expired (%s)", getID(), created.c_str(), expired.c_str());
             }
+            m_cache.cache_remove(request, getID());
             return false;
         }
     }
@@ -544,8 +551,15 @@ bool BasicSession::isValid(SPRequest* request, unsigned int lifetime, unsigned i
 
         if (m_lastAccess - m_lastAccessReported > m_cache.m_storageAccessInterval) {
             // It's been X seconds since we last wrote through to storage...
-            if (!m_cache.cache_touch(request, getID(), timeout)) {
-                m_cache.log().warn("session (%) missing or invalid in persistent store, invalidating locally", getID());
+            try {
+                // Pass a zero to bypass timeout enforcement as we know as well or better than the back-end...
+                if (!m_cache.cache_touch(request, getID(), 0)) {
+                    m_cache.log().warn("session (%) missing in persistent store, invalidating locally", getID());
+                    return false;
+                }
+            }
+            catch (const exception& ex) {
+                // Should be logged by the SPI.
                 return false;
             }
             // Update reporting timestamp.
@@ -553,10 +567,16 @@ bool BasicSession::isValid(SPRequest* request, unsigned int lifetime, unsigned i
         }
     }
     else {
-        // The session is locally invalid due to inactivity, but this isn't "truth" because other agent processes may
-        // actively be using it.
-        if (!m_cache.cache_touch(request, getID(), timeout)) {
-            m_cache.log().warn("session (%s) timed out due to inactivity", getID());
+        try {
+            // The session is locally invalid due to inactivity, but this isn't "truth" because other agent processes may
+            // actively be using it.
+            if (!m_cache.cache_touch(request, getID(), timeout)) {
+                m_cache.log().warn("session (%s) timed out due to inactivity", getID());
+                return false;
+            }
+        }
+        catch (const exception& ex) {
+            // Should be logged by the SPI.
             return false;
         }
         // Update reporting timestamp.
@@ -571,7 +591,7 @@ bool BasicSession::isValid(SPRequest* request, unsigned int lifetime, unsigned i
 
 time_t BasicSession::getCreation() const
 {
-    return m_creation;
+    return m_obj["ts"].longinteger();
 }
 
 time_t BasicSession::getLastAccess() const
diff --git a/shibsp/session/impl/FilesystemSessionCache.cpp b/shibsp/session/impl/FilesystemSessionCache.cpp
index e35ed369..056591e2 100644
--- a/shibsp/session/impl/FilesystemSessionCache.cpp
+++ b/shibsp/session/impl/FilesystemSessionCache.cpp
@@ -24,12 +24,19 @@
 #include "csprng/csprng.hpp"
 #include "session/AbstractSessionCache.h"
 #include "logging/Category.h"
+#include "util/Date.h"
 #include "util/Misc.h"
 #include "util/PathResolver.h"
 
 #include <cstdio>
 #include <fstream>
 
+#ifdef WIN32
+# include <sys/utime.h>
+#else
+# include <utime.h>
+#endif
+
 #include <boost/property_tree/ptree.hpp>
 
 using namespace shibsp;
@@ -50,11 +57,12 @@ namespace {
             unsigned int lifetime=0,
             unsigned int timeout=0,
             const char* client_addr=nullptr
-            ) const;
-        bool cache_touch(SPRequest* request, const char* key, unsigned int timeout=0) const;
+            );
+        bool cache_touch(SPRequest* request, const char* key, unsigned int timeout=0);
         void cache_remove(SPRequest* request, const char* key);
 
     private:
+        Category& m_spilog;
         string m_dir;
         duthomhas::csprng m_rng;
     };
@@ -69,12 +77,16 @@ namespace shibsp {
     }
 }
 
-FilesystemSessionCache::FilesystemSessionCache(const ptree& pt) : AbstractSessionCache(pt)
+FilesystemSessionCache::FilesystemSessionCache(const ptree& pt)
+    : AbstractSessionCache(pt), m_spilog(Category::getInstance(SHIBSP_LOGCAT ".SessionCache.Filesystem"))
 {
     m_dir = getString(CACHE_DIRECTORY_PROP_NAME, CACHE_DIRECTORY_PROP_DEFAULT);
     AgentConfig::getConfig().getPathResolver().resolve(m_dir, PathResolver::SHIBSP_CACHE_FILE);
+    if (m_dir.back() != '/') {
+        m_dir += '/';
+    }
 
-    string testPath = m_dir + '/' + hex_encode(m_rng(string(16,0)));
+    string testPath = m_dir + hex_encode(m_rng(string(16,0)));
 
     bool failed = true;
 
@@ -99,7 +111,7 @@ FilesystemSessionCache::FilesystemSessionCache(const ptree& pt) : AbstractSessio
     std::remove(testPath.c_str());
 
     if (failed) {
-        log().error("could not perform read/write in cache directory (%s), check permissions", m_dir.c_str());
+        m_spilog.error("could not perform read/write in cache directory (%s), check permissions", m_dir.c_str());
         throw ConfigurationException("Configured session cache directory was inaccessible to agent process.");
     }
 }
@@ -110,7 +122,29 @@ FilesystemSessionCache::~FilesystemSessionCache()
 
 string FilesystemSessionCache::cache_create(SPRequest* request, DDF& sessionData)
 {
-    return string();
+    string key;
+    string path;
+    int attempts = 0;
+    do {
+        key = hex_encode(m_rng(string(16,0)));
+        path = m_dir + key;
+        if (!FileSupport::exists(path.c_str())) {
+            ofstream os (path);
+            if (os) {
+                os << sessionData;
+                if (os) {
+                    m_spilog.debug("stored new session (%s)", key.c_str());
+                    return key;
+                }
+            }
+
+            m_spilog.error("error writing new session to file (%s), errno=%d", path.c_str(), errno);
+            throw IOException("Error writing new session to file.");
+        }
+    } while (++attempts < 3);
+
+    m_spilog.error("failed to write new session after 3 attempts to generate a unique key");
+    throw IOException("Exhausted attempts to generate a unique session key.");
 }
 
 DDF FilesystemSessionCache::cache_read(
@@ -120,16 +154,138 @@ DDF FilesystemSessionCache::cache_read(
     unsigned int lifetime,
     unsigned int timeout,
     const char* client_addr
-    ) const
+    )
 {
-    return DDF();
+    DDF obj;
+
+    string path = m_dir + key;
+    time_t lastAccess = FileSupport::getModificationTime(path.c_str());
+    ifstream is(path);
+    if (!is) {
+        int e = errno;
+        if (e == ENOENT) {
+            m_spilog.debug("session file (%s) does not exist", path.c_str());
+        }
+        else {
+            m_spilog.error("error opening session file (%s) for reading, errno=%d", path.c_str(), e);
+        }
+        return obj;
+    }
+
+    time_t now = time(nullptr);
+
+    if (timeout) {
+        if (lastAccess == 0) {
+            m_spilog.error("timeout specified, but unable to obtain mod time for file (%s)", path.c_str());
+            return obj;
+        }
+        else if (lastAccess + timeout < now) {
+            if (m_spilog.isInfoEnabled()) {
+                string ts(date::format("%FT%TZ", chrono::system_clock::from_time_t(lastAccess)));
+                m_spilog.info("session (%s) expired for inactivity, timeout (%lu), last access (%s)", key, timeout, ts.c_str());
+                cache_remove(request, key);
+            }
+            return obj;
+        }
+    }
+
+    is >> obj;
+    is.close();
+    if (obj.isnull()) {
+        m_spilog.error("error deserializing session from file (%s)", path.c_str());
+        return obj;
+    } else if (!obj.isstruct()) {
+        obj.destroy();
+        m_spilog.error("deserialized session from file (%s) was invalid", path.c_str());
+        return obj;
+    }
+
+    const char* appId = obj["appId"].string();
+    if (strcmp(applicationId, appId)) {
+        m_spilog.warn("session (%s) issued for application (%s), accessed via application (%s)", key, appId, applicationId);
+        obj.destroy();
+        return obj;
+    }
+
+    // TODO: Implement the fuzzy address matching.
+    if (client_addr) {
+        const char* addr = obj["addr"].string();
+        if (addr && strcmp(client_addr, addr)) {
+            m_spilog.warn("session (%s) invalid, bound to address (%s), accessed from (%s)", key, addr, client_addr);
+            obj.destroy();
+            cache_remove(request, key);
+            return obj;
+        }
+    }
+
+    if (lifetime) {
+        time_t start = obj["ts"].longinteger();
+        if (start + lifetime < now) {
+            obj.destroy();
+            if (m_spilog.isWarnEnabled()) {
+                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)));
+                m_spilog.info("session (%s) has expired, created (%s), expired (%s)", key, created.c_str(), expired.c_str());
+            }
+            cache_remove(request, key);
+            return obj;
+        }
+    }
+
+    return obj;
 }
 
-bool FilesystemSessionCache::cache_touch(SPRequest* request, const char* key, unsigned int timeout) const
+bool FilesystemSessionCache::cache_touch(SPRequest* request, const char* key, unsigned int timeout)
 {
-    return false;
+    string path = m_dir + key;
+    if (timeout) {
+        time_t lastAccess = FileSupport::getModificationTime(path.c_str());
+        if (lastAccess == 0) {
+            m_spilog.error("timeout specified, but unable to obtain mod time for file (%s)", path.c_str());
+            cache_remove(request, key);
+            return false;
+        }
+        else if (lastAccess + timeout < time(nullptr)) {
+            if (m_spilog.isInfoEnabled()) {
+                string ts(date::format("%FT%TZ", chrono::system_clock::from_time_t(lastAccess)));
+                m_spilog.info("session (%s) expired for inactivity, timeout (%lu), last access (%s)", key, timeout, ts.c_str());
+            }
+            cache_remove(request, key);
+            return false;
+        }
+    }
+
+#ifdef WIN32
+    if (_utime(path.c_str(), nullptr) != 0) {
+#else
+    if (utime(path.c_str(), nullptr) != 0) {
+#endif
+        int e = errno;
+        if (e == ENOENT) {
+            m_spilog.debug("unable to update access time, session file (%s) did not exist", path.c_str());
+            return false;
+        }
+        else {
+            m_spilog.error("unable to update access time for session (%s), errno=%d", path.c_str(), e);
+            // Debatable if we fall into returning true, but maybe we don't care?
+        }
+    }
+    return true;
 }
 
 void FilesystemSessionCache::cache_remove(SPRequest* request, const char* key)
 {
+    string path = m_dir + key;
+    if (std::remove(path.c_str()) != 0) {
+        int e = errno;
+        if (e == ENOENT) {
+            m_spilog.debug("session file (%s) did not exist", path.c_str());
+        }
+        else {
+            m_spilog.error("error removing file for session (%s), errno=%d", key, e);
+        }
+    }
+    else {
+        m_spilog.debug("removed session (%s)", key);
+    }
 }
diff --git a/shibsp/session/impl/MemorySessionCache.cpp b/shibsp/session/impl/MemorySessionCache.cpp
index 4306cde8..77dafd1a 100644
--- a/shibsp/session/impl/MemorySessionCache.cpp
+++ b/shibsp/session/impl/MemorySessionCache.cpp
@@ -49,8 +49,8 @@ namespace {
             unsigned int lifetime=0,
             unsigned int timeout=0,
             const char* client_addr=nullptr
-            ) const;
-        bool cache_touch(SPRequest* request, const char* key, unsigned int timeout=0) const;
+            );
+        bool cache_touch(SPRequest* request, const char* key, unsigned int timeout=0);
         void cache_remove(SPRequest* request, const char* key);
     
     private:
@@ -84,12 +84,12 @@ DDF MemorySessionCache::cache_read(
     unsigned int lifetime,
     unsigned int timeout,
     const char* client_addr
-    ) const
+    )
 {
     return DDF();
 }
 
-bool MemorySessionCache::cache_touch(SPRequest* request, const char* key, unsigned int timeout) const
+bool MemorySessionCache::cache_touch(SPRequest* request, const char* key, unsigned int timeout)
 {
     return true;
 }
diff --git a/shibsp/util/Misc.cpp b/shibsp/util/Misc.cpp
index f96632b4..46d1c3c1 100644
--- a/shibsp/util/Misc.cpp
+++ b/shibsp/util/Misc.cpp
@@ -50,7 +50,7 @@ set<string>::size_type shibsp::split_to_container(set<string>& container, const
     return container.size();
 }
 
-bool shibsp::file_exists(const char* path)
+bool FileSupport::exists(const char* path)
 {
 #ifdef WIN32
     struct _stat stat_buf;
@@ -65,3 +65,19 @@ bool shibsp::file_exists(const char* path)
 #endif
     return false;
 }
+
+time_t FileSupport::getModificationTime(const char* path)
+{
+#ifdef WIN32
+    struct _stat stat_buf;
+    if (_stat(path, &stat_buf) == 0) {
+        return stat_buf.st_mtime;
+    }
+#else
+    struct stat stat_buf;
+    if (stat(path, &stat_buf) == 0) {
+        return stat_buf.st_mtime;
+    }
+#endif
+    return 0;
+}
diff --git a/shibsp/util/Misc.h b/shibsp/util/Misc.h
index df62612b..441b42a1 100644
--- a/shibsp/util/Misc.h
+++ b/shibsp/util/Misc.h
@@ -83,7 +83,23 @@ namespace shibsp {
         }
     };
 
-    bool file_exists(const char* path);
+    struct FileSupport {
+        /**
+         * Checks whether a file exists.
+         * 
+         * @param path path to check
+         */
+        static bool exists(const char* path);
+
+        /**
+         * Gets the last time the file was modified.
+         * 
+         * @param path path to check
+         * 
+         * @return modification time or 0 if unable
+         */
+        static time_t getModificationTime(const char* path);
+    };
 
     /**
      * Splitter functions that trim the input and split on whitespace into a container.
diff --git a/tests/data/session/impl/filesystem-shibboleth.ini b/tests/data/session/impl/filesystem-shibboleth.ini
index 281d1621..a0161c93 100644
--- a/tests/data/session/impl/filesystem-shibboleth.ini
+++ b/tests/data/session/impl/filesystem-shibboleth.ini
@@ -9,6 +9,7 @@ defaultLevel = INFO
 
 [logging-categories]
 Shibboleth.SessionCache = DEBUG
+Shibboleth.SessionCache.Filesystem = DEBUG
 
 [session-cache]
 type = filesystem
diff --git a/tests/session/impl/FilesystemSessionCacheTests.cpp b/tests/session/impl/FilesystemSessionCacheTests.cpp
index 55618995..430a10b3 100644
--- a/tests/session/impl/FilesystemSessionCacheTests.cpp
+++ b/tests/session/impl/FilesystemSessionCacheTests.cpp
@@ -75,6 +75,62 @@ struct FilesystemFixture
 
 BOOST_FIXTURE_TEST_CASE(FilesystemSessionCache_tests, FilesystemFixture)
 {
+    bool started = AgentConfig::getConfig().start();
+    BOOST_CHECK(started);
+
+    DDF obj(nullptr);
+    DDFJanitor janitor(obj);
+
+    obj.addmember("session.opaque").string("foo");
+    DDF attrs = obj.addmember("session.attributes").list();
+
+    DDF issuer("Shib-Identity-Provider");
+    issuer.list();
+    issuer.add(DDF(nullptr).string("https://idp.example.org"));
+    attrs.add(issuer);
+
+    DDF affiliation("affiliation");
+    affiliation.list();
+    affiliation.add(DDF(nullptr).string("member"));
+    affiliation.add(DDF(nullptr).string("student"));
+    attrs.add(affiliation);
+
+    DummyRequest request("https://sp.example.org/secure/index.html");
+    DDF child = obj["session"];
+
+    SessionCache* cache = AgentConfig::getConfig().getAgent().getSessionCache();
+
+    string key = cache->create(request, child);
+
+    BOOST_CHECK(obj["session"].isnull());
+    BOOST_CHECK_EQUAL(key.c_str(), child.name());
+    string cookieName("__Host-shibsession_73702e6578616d706c652e6f7267637573746f6d");
+    string header(cookieName);
+    header += '=' + key;
+    header += "; max-age=-1; path=/; secure=1; HttpOnly=1; SameSite=None";
+    BOOST_CHECK_EQUAL(request.m_responseHeaders["Set-Cookie"], header);
+
+    string cookie(cookieName);
+    cookie += '=' + key;
+    request.m_requestHeaders["Cookie"] = cookie;
+
+    unique_lock<Session> session = cache->find(request, true, false);
+    BOOST_CHECK(session);
+    if (session) {
+        session.unlock();
+    }
+
+    // Clear old response headers.
+    request.m_responseHeaders.clear();
+
+    cache->remove(request);
+
+    header = cookieName;
+    header += "=; max-age=0; path=/; secure=1; HttpOnly=1; SameSite=None";
+    BOOST_CHECK_EQUAL(request.m_responseHeaders["Set-Cookie"], header);
+    
+    session = cache->find("custom", key.c_str());
+    BOOST_CHECK(!session);
 }
 
 }

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list