[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