[cpp-sp] branch main updated: Draft of logout consumer handler.

Codeberg noreply at shibboleth.net
Tue May 19 13:44:47 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/d06bf9bee0613360cfd49ed5c68708b15856d14e

The following commit(s) were added to refs/heads/main by this push:
     new d06bf9be Draft of logout consumer handler.
d06bf9be is described below

commit d06bf9bee0613360cfd49ed5c68708b15856d14e
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Tue May 19 09:44:33 2026 -0400

    Draft of logout consumer handler.
---
 shibsp/Makefile.am                      |   1 +
 shibsp/handler/LogoutHandler.h          |  34 +-----
 shibsp/handler/impl/LogoutConsumer.cpp  | 192 ++++++++++++++++++++++++++++++++
 shibsp/handler/impl/LogoutHandler.cpp   |  29 ++---
 shibsp/handler/impl/LogoutInitiator.cpp |   6 +-
 5 files changed, 210 insertions(+), 52 deletions(-)

diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index b60ef307..bf667822 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -108,6 +108,7 @@ libshibsp_la_SOURCES = \
 	handler/impl/AbstractHandler.cpp \
 	handler/impl/AttributeCheckerHandler.cpp \
 	handler/impl/DefaultHandlerConfiguration.cpp \
+	handler/impl/LogoutConsumer.cpp \
 	handler/impl/LogoutHandler.cpp \
 	handler/impl/LogoutInitiator.cpp \
 	handler/impl/Passthrough.cpp \
diff --git a/shibsp/handler/LogoutHandler.h b/shibsp/handler/LogoutHandler.h
index 77b9fffd..9b94e3d7 100644
--- a/shibsp/handler/LogoutHandler.h
+++ b/shibsp/handler/LogoutHandler.h
@@ -23,10 +23,6 @@
 
 #include <shibsp/handler/AbstractHandler.h>
 
-#include <map>
-#include <string>
-#include <vector>
-
 #include <boost/property_tree/ptree_fwd.hpp>
 
 namespace shibsp {
@@ -45,42 +41,22 @@ namespace shibsp {
     public:
         virtual ~LogoutHandler();
 
-        /**
-         * The base method will iteratively attempt front-channel notification
-         * of logout of the current session.
-         * 
-         * <p>Nothing will be done unless the handler detects that it is the "top" level
-         * logout handler. If the method returns false, then the specialized class should
-         * perform its work assuming that the notifications are completed.</p>
-         *
-         * <p>Note that the current session is NOT removed from the cache.</p>
-         * 
-         * @param request   SP request
-         * @param isHandler true iff executing in the context of a direct handler invocation
-         * @return  a pair containing a "request completed" indicator and a server-specific response code
-         */
-        std::pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
-
     protected:
         LogoutHandler(const boost::property_tree::ptree& pt);
         
         /** Flag indicating whether the subclass is acting as a LogoutInitiator. */
         bool m_initiator;
 
-        /** Array of query string parameters to preserve across front-channel notifications, if present. */
-        std::vector<std::string> m_preserve;
-
         /**
          * Perform front-channel logout notifications for an Application.
          *
          * @param request       last request from browser
-         * @param params        map of query string parameters to preserve across this notification
-         * @return  indicator of a completed response along with the status code to return from the handler
+         * @param continueOnly  flag indicating whether to initiate notification or only continue/complete it
+         * @param token         optional token string/parameter from Hub when initiating the loop
+         * 
+         * @return indicator of a completed response along with the status code to return from the handler
          */
-        std::pair<bool,long> notifyFrontChannel(
-            SPRequest& request, const std::map<std::string,std::string>* params=nullptr
-            ) const;
-
+        std::pair<bool,long> notifyFrontChannel(SPRequest& request, bool continueOnly=true, const char* token=nullptr) const;
     };
 
 #if defined (_MSC_VER)
diff --git a/shibsp/handler/impl/LogoutConsumer.cpp b/shibsp/handler/impl/LogoutConsumer.cpp
new file mode 100644
index 00000000..37c68aec
--- /dev/null
+++ b/shibsp/handler/impl/LogoutConsumer.cpp
@@ -0,0 +1,192 @@
+/**
+ * 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.
+ */
+
+/**
+ * LogoutConsumer.cpp
+ * 
+ * Pluggable runtime functionality that handles consuming logout messages.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "Agent.h"
+#include "SPRequest.h"
+#include "handler/LogoutHandler.h"
+#include "remoting/ddf.h"
+#include "remoting/RemotingService.h"
+#include "session/SessionCache.h"
+
+#include <boost/property_tree/ptree.hpp>
+
+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 LogoutConsumer : public virtual LogoutHandler
+    {
+    public:
+        LogoutConsumer(const ptree& pt) : AbstractHandler(pt), LogoutHandler(pt) {}
+        virtual ~LogoutConsumer() {}
+
+        void init(const char* location);    // encapsulates actions that need to run either in the c'tor or setParent
+
+        pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+
+    private:
+        pair <bool,long> completeLogout(SPRequest& request, const char* token) const;
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    Handler* SHIBSP_DLLLOCAL LogoutConsumerFactory(const pair<ptree&,const char*>& p, bool)
+    {
+        return new LogoutConsumer(p.first);
+    }
+}
+
+pair<bool,long> LogoutConsumer::run(SPRequest& request, bool isHandler) const
+{
+    // Defer to base class for front-channel loop first, but only to continue/finish the loop.
+    pair<bool,long> ret = notifyFrontChannel(request);
+    if (ret.first) {
+        return ret;
+    }
+
+    // Check for a notifying parameter indicating we are completing a notification loop.
+    // In this scenario, we call a completion method to set up the final call to the Hub
+    // to produce a logout response message or local redirect.
+    if (request.getParameter("notifying")) {
+        return completeLogout(request, request.getParameter("token"));
+    }
+
+    // With a fresh message inbound from an IdP, we check for an active session to supply the
+    // necessary opaque data to the Hub to match against in processing a logout request.
+
+    unique_lock<Session> session;
+    try {
+        session = request.getSession(false, true);  // don't cache it and ignore all checks
+    }
+    catch (const exception& ex) {
+        request.error("error accessing current session: %s", ex.what());
+    }
+
+    DDF input = request.getAgent().getRemotingService()->build("logout-consumer", request);
+    DDFJanitor inputJanitor(input);
+
+    if (session) {
+        DDF opaqueData = session.mutex()->getOpaqueData().copy();
+        if (opaqueData.isnull()) {
+            request.debug("session (%s) contains no Hub-supplied data", session.mutex()->getID());
+        } else {
+            input.addmember("session").structure().add(opaqueData);
+        }
+    }
+
+    static set<string> emptyHeaderSet;
+    DDF wrapped = wrapRequest(request, emptyHeaderSet, false);
+    input.add(wrapped);
+
+    // Call the Hub to process the message, suppressing any errors that occur.
+
+    DDF output;
+    try {
+        output = request.getAgent().getRemotingService()->send(input, false);
+    }
+    catch (exception& ex) {
+        AgentException* agent_ex = dynamic_cast<AgentException*>(&ex);
+        if (agent_ex) {
+            agent_ex->addProperty(AgentException::HANDLER_TYPE_PROP_NAME, LOGOUT_CONSUMER_HANDLER);
+        }
+        request.error("error invoking logout-consumer operation");
+        request.log(Priority::SHIB_ERROR, ex);
+    }
+    DDFJanitor outputJanitor(output);
+
+    // There are broadly two cases here, a logout request or a response being processed
+    // from an IdP, allowing that a dozen or more different errors can take place.
+    // The request case will potentially feed back a "token" member for use later.
+
+    // If we actually have a session in hand, we need to initiate the notification loop.
+    // Any token provided by the Hub call will be attached to that process.
+    if (session) {
+        ret = notifyFrontChannel(request, false, output.getmember("token").string());
+        if (ret.first) {
+            // A loop was started, so we are finished at this stage until we're called back.
+            return ret;
+        }
+
+        // We unlock the session here as we are done using it and will need to remove it shortly.
+        session.unlock();
+    }
+
+    // If we get here, either no session existed or no notification was required.
+    // We invoke our completion operation with the token from the Hub, if any.
+    return completeLogout(request, output.getmember("token").string());
+}
+
+pair <bool,long> LogoutConsumer::completeLogout(SPRequest& request, const char* token) const
+{
+    // Dispose of any active session.
+    request.getAgent().getSessionCache()->remove(request);
+
+    DDF output;
+
+    // Check for a token parameter, signifying we need to call the Hub to finish
+    // processing a logout request from an IdP.
+    if (token) {
+        DDF input = request.getAgent().getRemotingService()->build("logout-consumer", request);
+        DDFJanitor inputJanitor(input);
+        input.addmember("token").string(token);
+
+        try {
+            output = request.getAgent().getRemotingService()->send(input);
+        }
+        catch (exception& ex) {
+            AgentException* agent_ex = dynamic_cast<AgentException*>(&ex);
+            if (agent_ex) {
+                agent_ex->addProperty(AgentException::HANDLER_TYPE_PROP_NAME, LOGOUT_CONSUMER_HANDLER);
+            }
+            request.error("error invoking logout-consumer operation to complete logout request processing");
+            request.log(Priority::SHIB_ERROR, ex);
+        }
+    }
+
+    // At this point, output may contain a wrapped response to relay, or it may be null due to
+    // errors, or because we're processing a logout response from an IdP, or...reasons.
+    // We either relay the wrapped response, or we generate a final redirect locally.
+
+    DDFJanitor outputJanitor(output);
+
+    DDF wrapped = output.getmember("http");
+    if (wrapped.isstruct()) {
+        return unwrapResponse(request, wrapped);
+    }
+
+    const char* post_logout_url = request.getRequestSettings().first->getString(RequestMapper::LOGOUT_URL_PROP_NAME);
+    if (!post_logout_url) {
+        post_logout_url = request.getRequestSettings().first->getString(
+            RequestMapper::HOME_URL_PROP_NAME, RequestMapper::HOME_URL_PROP_DEFAULT);
+    }
+
+    return make_pair(true, request.sendRedirect(post_logout_url));
+}
diff --git a/shibsp/handler/impl/LogoutHandler.cpp b/shibsp/handler/impl/LogoutHandler.cpp
index 5b0bb6ca..32dfd1de 100644
--- a/shibsp/handler/impl/LogoutHandler.cpp
+++ b/shibsp/handler/impl/LogoutHandler.cpp
@@ -39,18 +39,12 @@ LogoutHandler::~LogoutHandler()
 {
 }
 
-pair<bool,long> LogoutHandler::run(SPRequest& request, bool isHandler) const
+pair<bool,long> LogoutHandler::notifyFrontChannel(SPRequest& request, bool continueOnly, const char* token) const
 {
-    // If this isn't a LogoutInitiator, we only "continue" a notification loop, rather than starting one.
-    if (!m_initiator && !request.getParameter("notifying"))
+    if (continueOnly && !request.getParameter("notifying")) {
         return make_pair(false,0L);
+    }
 
-    // Try another front-channel notification. No extra parameters and the session is implicit.
-    return notifyFrontChannel(request);
-}
-
-pair<bool,long> LogoutHandler::notifyFrontChannel(SPRequest& request, const map<string,string>* params) const
-{
     // Index of notification point starts at 0.
     unsigned int index = 0;
     const char* param = request.getParameter("index");
@@ -85,19 +79,12 @@ pair<bool,long> LogoutHandler::notifyFrontChannel(SPRequest& request, const map<
         locstr = locstr + "&return=" + encoder.encode(param);
     }
 
-    // We preserve anything we're instructed to directly.
-    if (params) {
-        for (const auto& p : *params) {
-            locstr = locstr + '&' + p.first + '=' + encoder.encode(p.second.c_str());
-        }
+    // Token may come from caller when initiating loop or via the URL.
+    if (!token) {
+        token = request.getParameter("token");
     }
-    else {
-        for (const auto& q : m_preserve) {
-            param = request.getParameter(q.c_str());
-            if (param) {
-                locstr = locstr + '&' + q + '=' + encoder.encode(param);
-            }
-        }
+    if (token) {
+        locstr = locstr + "&token=" + encoder.encode(param);
     }
 
     // Add the notifier's return parameter to the destination location and redirect.
diff --git a/shibsp/handler/impl/LogoutInitiator.cpp b/shibsp/handler/impl/LogoutInitiator.cpp
index 7202905f..bc485df0 100644
--- a/shibsp/handler/impl/LogoutInitiator.cpp
+++ b/shibsp/handler/impl/LogoutInitiator.cpp
@@ -50,12 +50,14 @@ LogoutInitiator::~LogoutInitiator()
 
 pair<bool,long> LogoutInitiator::run(SPRequest& request, bool isHandler) const
 {
-    // Defer to base class first; this will initiate, continue, or complete notification.
-    pair<bool,long> ret = LogoutHandler::run(request, isHandler);
+    // Initiate, continue, or complete notification.
+    pair<bool,long> ret = notifyFrontChannel(request);
     if (ret.first) {
         return ret;
     }
 
+    // Notification has completed (or did not occur).
+
     bool localOnly = getBool("localOnly", false);
 
     unique_lock<Session> session;

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


More information about the commits mailing list