[cpp-sp] branch main updated: Initial sketch of admin logout handler.

Codeberg noreply at shibboleth.net
Thu Oct 8 19:47:32 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/f08c41751020237cfce583bb14833e84a81d8d2c

The following commit(s) were added to refs/heads/main by this push:
     new f08c4175 Initial sketch of admin logout handler.
f08c4175 is described below

commit f08c41751020237cfce583bb14833e84a81d8d2c
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Thu Oct 8 15:47:14 2026 -0400

    Initial sketch of admin logout handler.
---
 Projects/vc22/shibsp.vcxproj                 |   1 +
 Projects/vc22/shibsp.vcxproj.filters         |  21 +--
 configs/handlers.ini                         |   7 +
 shibsp/Makefile.am                           |   1 +
 shibsp/handler/Handler.h                     |   2 +-
 shibsp/handler/impl/AbstractHandler.cpp      |   2 +
 shibsp/handler/impl/AdminLogoutHandler.cpp   | 112 +++++++++++++
 shibsp/handler/impl/AdminLogoutInitiator.cpp | 228 ---------------------------
 shibsp/session/impl/CookieSessionCache.cpp   |   4 +-
 9 files changed, 138 insertions(+), 240 deletions(-)

diff --git a/Projects/vc22/shibsp.vcxproj b/Projects/vc22/shibsp.vcxproj
index cc02517d..93278457 100644
--- a/Projects/vc22/shibsp.vcxproj
+++ b/Projects/vc22/shibsp.vcxproj
@@ -93,6 +93,7 @@
     </ClCompile>
     <ClCompile Include="..\..\shibsp\handler\impl\AttributeCheckerHandler.cpp" />
     <ClCompile Include="..\..\shibsp\handler\impl\DefaultHandlerConfiguration.cpp" />
+    <ClCompile Include="..\..\shibsp\handler\impl\AdminLogoutHandler.cpp" />
     <ClCompile Include="..\..\shibsp\handler\impl\LogoutConsumer.cpp" />
     <ClCompile Include="..\..\shibsp\handler\impl\LogoutHandler.cpp" />
     <ClCompile Include="..\..\shibsp\handler\impl\LogoutInitiator.cpp" />
diff --git a/Projects/vc22/shibsp.vcxproj.filters b/Projects/vc22/shibsp.vcxproj.filters
index ccd33cce..c2fe848e 100644
--- a/Projects/vc22/shibsp.vcxproj.filters
+++ b/Projects/vc22/shibsp.vcxproj.filters
@@ -230,6 +230,12 @@
     </ClInclude>
   </ItemGroup>
   <ItemGroup>
+    <ClCompile Include="..\..\shibsp\handler\impl\DefaultHandlerConfiguration.cpp">
+      <Filter>Source Files\Handler</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\shibsp\handler\impl\AdminLogoutHandler.cpp">
+      <Filter>Source Files\Handler</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\shibsp\handler\impl\AbstractHandler.cpp">
       <Filter>Source Files\Handler</Filter>
     </ClCompile>
@@ -242,6 +248,9 @@
     <ClCompile Include="..\..\shibsp\handler\impl\LogoutInitiator.cpp">
       <Filter>Source Files\Handler</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\shibsp\handler\impl\LogoutConsumer.cpp">
+      <Filter>Source Files\Handler</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\shibsp\handler\impl\Passthrough.cpp">
       <Filter>Source Files\Handler</Filter>
     </ClCompile>
@@ -257,6 +266,9 @@
     <ClCompile Include="..\..\shibsp\handler\impl\StatusHandler.cpp">
       <Filter>Source Files\Handler</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\shibsp\handler\impl\TokenConsumer.cpp">
+      <Filter>Source Files\Handler</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\shibsp\impl\AgentConfig.cpp">
       <Filter>Source Files\Impl</Filter>
     </ClCompile>
@@ -356,9 +368,6 @@
     <ClCompile Include="..\..\shibsp\logging\impl\WindowsLoggingService.cpp">
       <Filter>Source Files\Logging</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\shibsp\handler\impl\DefaultHandlerConfiguration.cpp">
-      <Filter>Source Files\Handler</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\shibsp\session\impl\FilesystemSessionCache.cpp">
       <Filter>Source Files\Session</Filter>
     </ClCompile>
@@ -377,18 +386,12 @@
     <ClCompile Include="..\..\shibsp\remoting\impl\CurlHTTPRemotingService.cpp">
       <Filter>Source Files\Remoting</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\shibsp\handler\impl\TokenConsumer.cpp">
-      <Filter>Source Files\Handler</Filter>
-    </ClCompile>
     <ClCompile Include="..\..\shibsp\remoting\impl\WinHTTPRemotingService.cpp">
       <Filter>Source Files\Remoting</Filter>
     </ClCompile>
     <ClCompile Include="..\..\shibsp\attribute\impl\DefaultAttributeConfiguration.cpp">
       <Filter>Source Files\Attribute</Filter>
     </ClCompile>
-    <ClCompile Include="..\..\shibsp\handler\impl\LogoutConsumer.cpp">
-      <Filter>Source Files\Handler</Filter>
-    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\..\shibsp\shibsp.rc">
diff --git a/configs/handlers.ini b/configs/handlers.ini
index 02eb1709..4890d6e3 100644
--- a/configs/handlers.ini
+++ b/configs/handlers.ini
@@ -22,6 +22,13 @@ type = LogoutConsumer
 # true requires valid logout request matching active session
 #matchRequired = false
 
+[Logout/Admin]
+type = AdminLogout
+acl = 127.0.0.1 ::1
+# true suppresses upstream signaling via Hub
+#localOnly = true
+
+
 # Legacy SSO/SLO endpoints for SP3 compatibility
 
 #[SAML2/POST]
diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index 434519fe..63bcfcb4 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -100,6 +100,7 @@ libshibsp_la_SOURCES = \
 	version.cpp \
 	attribute/impl/DefaultAttributeConfiguration.cpp \
 	handler/impl/AbstractHandler.cpp \
+	handler/impl/AdminLogoutHandler.cpp \
 	handler/impl/AttributeCheckerHandler.cpp \
 	handler/impl/DefaultHandlerConfiguration.cpp \
 	handler/impl/LogoutConsumer.cpp \
diff --git a/shibsp/handler/Handler.h b/shibsp/handler/Handler.h
index f59eba61..e3a8af26 100644
--- a/shibsp/handler/Handler.h
+++ b/shibsp/handler/Handler.h
@@ -77,7 +77,7 @@ namespace shibsp {
     /** Handler for logout. */
     #define LOGOUT_CONSUMER_HANDLER "LogoutConsumer"
 
-    /** LogoutInitiator that supports administrative logout. */
+    /** Handler that supports administrative logout. */
     #define ADMIN_LOGOUT_HANDLER "AdminLogout"
 
     /** Handler for hooking new sessions with attribute checking. */
diff --git a/shibsp/handler/impl/AbstractHandler.cpp b/shibsp/handler/impl/AbstractHandler.cpp
index 59bafd8b..eef09d98 100644
--- a/shibsp/handler/impl/AbstractHandler.cpp
+++ b/shibsp/handler/impl/AbstractHandler.cpp
@@ -57,6 +57,7 @@ namespace shibsp {
     extern SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<ptree&,const char*> >::Factory TokenConsumerFactory;
     extern SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<ptree&,const char*> >::Factory LogoutInitiatorFactory;
     extern SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<ptree&,const char*> >::Factory LogoutConsumerFactory;
+    extern SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<ptree&,const char*> >::Factory AdminLogoutHandlerFactory;
 };
 
 void SHIBSP_API shibsp::registerHandlers()
@@ -73,6 +74,7 @@ void SHIBSP_API shibsp::registerHandlers()
     conf.HandlerManager.registerFactory(TOKEN_CONSUMER_HANDLER, TokenConsumerFactory);
     conf.HandlerManager.registerFactory(LOGOUT_INITIATOR_HANDLER, LogoutInitiatorFactory);
     conf.HandlerManager.registerFactory(LOGOUT_CONSUMER_HANDLER, LogoutConsumerFactory);
+    conf.HandlerManager.registerFactory(ADMIN_LOGOUT_HANDLER, AdminLogoutHandlerFactory);
 } 
 
 Handler::Handler()
diff --git a/shibsp/handler/impl/AdminLogoutHandler.cpp b/shibsp/handler/impl/AdminLogoutHandler.cpp
new file mode 100644
index 00000000..5a21ee24
--- /dev/null
+++ b/shibsp/handler/impl/AdminLogoutHandler.cpp
@@ -0,0 +1,112 @@
+/**
+ * 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.
+ */
+
+/**
+ * handler/impl/AdminLogoutHandler.cpp
+ *
+ * Handles administrative logout of a session.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "Agent.h"
+#include "AgentConfig.h"
+#include "handler/SecuredHandler.h"
+#include "logging/Category.h"
+#include "session/SessionCache.h"
+
+#include <sstream>
+#ifdef HAVE_CXX14
+# include <shared_mutex>
+#endif
+
+using namespace shibsp;
+using namespace boost::property_tree;
+using namespace std;
+
+namespace shibsp {
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 )
+#endif
+
+    class SHIBSP_DLLLOCAL AdminLogoutHandler : public SecuredHandler
+    {
+    public:
+        AdminLogoutHandler(const ptree& pt);
+        virtual ~AdminLogoutHandler() {}
+
+        pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    Handler* SHIBSP_DLLLOCAL AdminLogoutHandlerFactory(const pair<ptree&,const char*> p, bool)
+    {
+        return new AdminLogoutHandler(p.first);
+    }
+};
+
+AdminLogoutHandler::AdminLogoutHandler(const ptree& pt) : AbstractHandler(pt), SecuredHandler(pt)
+{
+}
+
+pair<bool,long> AdminLogoutHandler::run(SPRequest& request, bool isHandler) const
+{
+    // No front-channel notifications, so skip calling logout base class.
+
+    // Check ACL in base class.
+    pair<bool,long> ret = SecuredHandler::run(request, isHandler);
+    if (ret.first)
+        return ret;
+
+    const char* sessionId = request.getParameter("session");
+    if (!sessionId || !*sessionId) {
+        request.error("no session parameter supplied for request");
+        istringstream msg("NO SESSION PARAMETER");
+        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_BADREQUEST));
+    }
+
+    const char* applicationId = request.getRequestSettings().first->getString("applicationId", "default");
+
+    unique_lock<Session> session;
+    try {
+        session = AgentConfig::getConfig().getAgent().getSessionCache()->find(applicationId, sessionId);
+    }
+    catch (const std::exception& ex) {
+        request.error("error locating session: %s", ex.what());
+    }
+
+    // With no session, we return a 404 after "revoking" the session just to be safe.
+    if (!session) {
+        AgentConfig::getConfig().getAgent().getSessionCache()->remove(sessionId);
+        istringstream msg("NOT FOUND");
+        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_NOTFOUND));
+    }
+
+    bool localOnly = getBool("localOnly", true);
+
+    if (localOnly) {
+        session.unlock();
+        AgentConfig::getConfig().getAgent().getSessionCache()->remove(sessionId);
+
+        istringstream msg("OK");
+        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_OK));
+    }
+
+    return make_pair(false, 0L);
+}
diff --git a/shibsp/handler/impl/AdminLogoutInitiator.cpp b/shibsp/handler/impl/AdminLogoutInitiator.cpp
deleted file mode 100644
index feea7053..00000000
--- a/shibsp/handler/impl/AdminLogoutInitiator.cpp
+++ /dev/null
@@ -1,228 +0,0 @@
-/**
- * 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.
- */
-
-/**
- * handler/impl/AdminLogoutInitiator.cpp
- *
- * Triggers administrative logout of a session.
- */
-
-#include "internal.h"
-#include "exceptions.h"
-#include "Agent.h"
-#include "AgentConfig.h"
-#include "handler/SecuredHandler.h"
-#include "handler/LogoutInitiator.h"
-#include "logging/Category.h"
-#include "session/SessionCache.h"
-
-#include <sstream>
-#ifdef HAVE_CXX14
-# include <shared_mutex>
-#endif
-
-using namespace shibsp;
-using namespace boost::property_tree;
-using namespace std;
-
-namespace shibsp {
-
-#if defined (_MSC_VER)
-    #pragma warning( push )
-    #pragma warning( disable : 4250 )
-#endif
-
-    class SHIBSP_DLLLOCAL AdminLogoutInitiator : public SecuredHandler, public LogoutInitiator
-    {
-    public:
-        AdminLogoutInitiator(const ptree& pt);
-        virtual ~AdminLogoutInitiator() {}
-
-        pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
-    };
-
-#if defined (_MSC_VER)
-    #pragma warning( pop )
-#endif
-
-    Handler* SHIBSP_DLLLOCAL AdminLogoutInitiatorFactory(const pair<ptree&,const char*> p, bool)
-    {
-        return new AdminLogoutInitiator(p.first);
-    }
-};
-
-AdminLogoutInitiator::AdminLogoutInitiator(const ptree& pt) : SecuredHandler(pt)
-{
-}
-
-pair<bool,long> AdminLogoutInitiator::run(SPRequest& request, bool isHandler) const
-{
-    // No front-channel notifications, so skip calling logout base class.
-
-    // Check ACL in base class.
-    pair<bool,long> ret = SecuredHandler::run(request, isHandler);
-    if (ret.first)
-        return ret;
-
-    const char* sessionId = request.getParameter("session");
-    if (!sessionId || !*sessionId) {
-        // Something's horribly wrong.
-        request.error("no session parameter supplied for request");
-        istringstream msg("NO SESSION PARAMETER");
-        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_BADREQUEST));
-    }
-
-    const char* applicationId = request.getRequestSettings().first->getString("applicationId", "default");
-
-    unique_lock<Session> session;
-    try {
-        session = AgentConfig::getConfig().getAgent().getSessionCache()->find(applicationId, sessionId);
-    }
-    catch (const std::exception& ex) {
-        request.error(string("error accessing designated session: ") + ex.what());
-    }
-
-    // With no session, we return a 404 after "revoking" the session just to be safe.
-    if (!session) {
-        AgentConfig::getConfig().getAgent().getSessionCache()->remove(sessionId);
-        istringstream msg("NOT FOUND");
-        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_NOTFOUND));
-    }
-
-    bool doSAML = false;
-
-    // Do back channel notification.
-    vector<string> sessions(1, session.mutex()->getID());
-    if (!notifyBackChannel(request, sessions, true)) {
-        session.unlock();
-        AgentConfig::getConfig().getAgent().getSessionCache()->remove(sessionId);
-        
-        istringstream msg("PARTIAL");
-        return make_pair(true, request.sendResponse(msg, 206)); // misuse of an HTTP code, but whatever
-    }
-
-    if (!doSAML) {
-        session.unlock();
-        AgentConfig::getConfig().getAgent().getSessionCache()->remove(sessionId);
-
-        istringstream msg("OK");
-        return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_OK));
-    }
-
-#ifndef SHIBSP_LITE
-    pair<bool,long> ret = make_pair(false, 0L);
-    try {
-        // With a session in hand, we can create a LogoutRequest message, if we can find a compatible SOAP endpoint.
-        MetadataProvider* m = application.getMetadataProvider();
-        Locker metadataLocker(m);
-        MetadataProviderCriteria mc(application, session->getEntityID(), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
-        pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
-        if (!entity.first) {
-            throw MetadataException(
-                "Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", session->getEntityID())
-                );
-        }
-        else if (!entity.second) {
-            throw MetadataException(
-                "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).", namedparams(1, "entityID", session->getEntityID())
-                );
-        }
-
-        const IDPSSODescriptor* role = dynamic_cast<const IDPSSODescriptor*>(entity.second);
-        if (role->getSingleLogoutServices().empty()) {
-            throw MetadataException(
-                "No SingleLogoutService endpoints in metadata for identity provider ($entityID).", namedparams(1, "entityID", session->getEntityID())
-                );
-        }
-
-        shibsp::SecurityPolicy policy(application);
-        shibsp::SOAPClient soaper(policy);
-        MetadataCredentialCriteria mcc(*role);
-
-        bool requestSent = false;
-        LogoutResponse* logoutResponse = nullptr;
-        scoped_ptr<StatusResponseType> srt;
-        auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);
-        const vector<SingleLogoutService*>& endpoints = role->getSingleLogoutServices();
-        for (indirect_iterator<vector<SingleLogoutService*>::const_iterator> epit = make_indirect_iterator(endpoints.begin());
-                !logoutResponse && epit != make_indirect_iterator(endpoints.end()); ++epit) {
-            try {
-                if (!XMLString::equals(epit->getBinding(), binding.get()))
-                    continue;
-
-                requestSent = true;
-                auto_ptr<LogoutRequest> msg(buildRequest(application, *session, *role, epit->getLocation()));
-
-                SAML2SOAPClient client(soaper, false);
-                auto_ptr_char dest(epit->getLocation());
-                client.sendSAML(msg.release(), application.getId(), mcc, dest.get());
-                srt.reset(client.receiveSAML());
-                if (!(logoutResponse = dynamic_cast<LogoutResponse*>(srt.get()))) {
-                    break;
-                }
-            }
-            catch (const std::exception& ex) {
-                m_log.error("error sending LogoutRequest message: %s", ex.what());
-                soaper.reset();
-            }
-        }
-
-        // No answer at all?
-        if (!logoutResponse) {
-            if (!requestSent)
-                m_log.info("IdP (%s) doesn't support SOAP-based single logout protocol", session->getEntityID());
-        }
-        else {
-            // Check the status, looking for non-success or a partial logout code.
-            const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : nullptr;
-            bool partial = (!sc || !XMLString::equals(sc->getValue(), StatusCode::SUCCESS));
-            if (!partial && sc->getStatusCode()) {
-                // Success, but still need to check for partial.
-                partial = XMLString::equals(sc->getStatusCode()->getValue(), StatusCode::PARTIAL_LOGOUT);
-            }
-
-            if (!partial) {
-                sessionLocker.assign();
-                session = nullptr;
-                application.getServiceProvider().getSessionCache()->remove(application, sessionId, revocationExp);
-                istringstream msg("OK");
-                ret = make_pair(true, httpResponse.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_OK));
-            }
-        }
-    }
-    catch (const MetadataException& mex) {
-        // Less noise for IdPs that don't support logout (i.e. most)
-        m_log.info("unable to attempt SAML 2.0 logout: %s", mex.what());
-    }
-    catch (const std::exception& ex) {
-        m_log.error("error issuing SAML 2.0 logout request: %s", ex.what());
-    }
-
-    if (session) {
-        sessionLocker.assign();
-        session = nullptr;
-        application.getServiceProvider().getSessionCache()->remove(application, sessionId, revocationExp);
-    }
-
-    if (ret.first)
-        return ret;
-
-    istringstream msg("PARTIAL");
-    return make_pair(true, httpResponse.sendResponse(msg, 206)); // misuse of an HTTP code, but whatever
-
-#else
-    throw ConfigurationException("Cannot perform SAML logout using lite version of shibsp library.");
-#endif
-}
-
diff --git a/shibsp/session/impl/CookieSessionCache.cpp b/shibsp/session/impl/CookieSessionCache.cpp
index 40066536..7a2a10ca 100644
--- a/shibsp/session/impl/CookieSessionCache.cpp
+++ b/shibsp/session/impl/CookieSessionCache.cpp
@@ -180,7 +180,7 @@ DDF CookieSessionCache::cache_read(
     )
 {
     if (!request) {
-        throw SessionException("Access to request object required to read cookie-backed session.");
+        throw SessionException("Access to request object required to locate cookie-backed session.");
     }
 
     string encryptedSession = m_chunkedCookieManager->getChunkedCookieValue(*request);
@@ -515,7 +515,7 @@ bool CookieSessionCache::cache_touch(SPRequest* request, const char* key, unsign
 void CookieSessionCache::cache_remove(SPRequest* request, const char* key)
 {
     if (!request) {
-        throw SessionException("Access to request object required to remove cookie-backed session.");
+        throw SessionException("Cookie-backed session cache does not support admninistrative logout.");
     }
 
     m_chunkedCookieManager->unsetCookie(*request, true);

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


More information about the commits mailing list