[cpp-sp] branch main updated: CPPSP-36 - Implement passthrough handler

Codeberg noreply at shibboleth.net
Tue Dec 23 17:49:23 UTC 2025


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/4b41368267780589aed2944d7833f9fe8c438478

The following commit(s) were added to refs/heads/main by this push:
     new 4b413682 CPPSP-36 - Implement passthrough handler
4b413682 is described below

commit 4b41368267780589aed2944d7833f9fe8c438478
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Dec 23 12:48:02 2025 -0500

    CPPSP-36 - Implement passthrough handler
    
    https://shibboleth.atlassian.net/browse/CPPSP-36
    
    Add support for absolute handlers.
---
 shibsp/Agent.cpp                                   | 76 ++++++++++++++++------
 shibsp/handler/HandlerConfiguration.h              | 14 +++-
 .../handler/impl/DefaultHandlerConfiguration.cpp   | 70 +++++++++++++-------
 shibsp/remoting/ddf.h                              |  1 +
 shibsp/remoting/impl/ddf.cpp                       | 11 +++-
 5 files changed, 125 insertions(+), 47 deletions(-)

diff --git a/shibsp/Agent.cpp b/shibsp/Agent.cpp
index 0ca0503f..724da36e 100644
--- a/shibsp/Agent.cpp
+++ b/shibsp/Agent.cpp
@@ -118,8 +118,6 @@ long Agent::handleError(SPRequest& request, const Session* session, exception* e
 
 pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
 {
-    string targetURL = request.getRequestURL();
-
     try {
         RequestMapper::Settings settings = request.getRequestSettings();
 
@@ -143,7 +141,27 @@ pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
             }
         }
 
-        const char* handlerURL=request.getHandlerURL(targetURL.c_str());
+        // First check if this is a request to an absolute handler location.
+        const HandlerConfiguration& handlerConfig = request.getAgent().getHandlerConfiguration(
+            request.getRequestSettings().first->getString(RequestMapper::HANDLER_CONFIG_ID_PROP_NAME));
+        const Handler* absolute = handlerConfig.getAbsoluteHandler(request);
+        if (absolute) {
+            // Either dispatch directly or just pass back control based on parameter to this method.
+            if (handler) {
+                pair<bool,long> hret = absolute->run(request);
+                // Did the handler run successfully?
+                if (hret.first) {
+                    return hret;
+                }
+                throw ConfigurationException("Configured Shibboleth handler failed to process the request.");
+            }
+            else {
+                return make_pair(true, request.returnOK());
+            }
+        }
+
+        const char* targetURL = request.getRequestURL();
+        const char* handlerURL=request.getHandlerURL(targetURL);
         if (!handlerURL) {
             throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
         }
@@ -152,7 +170,19 @@ pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
         // directly or just pass back control based on parameter to this method.
         if (boost::contains(targetURL, handlerURL)) {
             if (handler) {
-                return doHandler(request);
+                // We dispatch based on our path info. We know the request URL begins with or equals the handler URL,
+                // so the path info is the next character (or null).
+                const Handler* relative = handlerConfig.getRelativeHandler(targetURL + strlen(handlerURL));
+                if (!relative) {
+                    throw ConfigurationException("Shibboleth handler invoked at an unconfigured location.");
+                }
+
+                pair<bool,long> hret = relative->run(request);
+                // Did the handler run successfully?
+                if (hret.first) {
+                    return hret;
+                }
+                throw ConfigurationException("Configured Shibboleth handler failed to process the request.");
             }
             else {
                 return make_pair(true, request.returnOK());
@@ -186,7 +216,7 @@ pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
         catch (const exception& e) {
             request.warn("error during session lookup: %s", e.what());
             // If it's not a retryable session failure, we throw to the outer handler for reporting.
-            if (dynamic_cast<const SessionValidationException*>(&e) == nullptr) {
+            if (!dynamic_cast<const SessionValidationException*>(&e)) {
                 throw;
             }
         }
@@ -223,10 +253,7 @@ pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
                 return make_pair(true, request.returnOK());
             }
 
-            // No session, but we require one. Initiate a new session.
-            const HandlerConfiguration& handlerConfig = request.getAgent().getHandlerConfiguration(
-                request.getRequestSettings().first->getString(RequestMapper::HANDLER_CONFIG_ID_PROP_NAME));
-
+            // No session, but we require one.
             // Dispatch to SessionInitiator. This MUST handle the request, or we want to fail here.
             // Used to fall through into doExport, but this is a cleaner exit path.
             pair<bool,long> ret = handlerConfig.getSessionInitiator().run(request, false);
@@ -251,8 +278,7 @@ pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
 pair<bool,long> Agent::doAuthorization(SPRequest& request) const
 {
     unique_lock<Session> session;
-    string targetURL = request.getRequestURL();
-
+    
     try {
         RequestMapper::Settings settings = request.getRequestSettings();
 
@@ -311,7 +337,6 @@ pair<bool,long> Agent::doAuthorization(SPRequest& request) const
 pair<bool,long> Agent::doExport(SPRequest& request, bool requireSession) const
 {
     unique_lock<Session> session;
-    string targetURL = request.getRequestURL();
 
     try {
         RequestMapper::Settings settings = request.getRequestSettings();
@@ -361,8 +386,6 @@ pair<bool,long> Agent::doExport(SPRequest& request, bool requireSession) const
 
 pair<bool,long> Agent::doHandler(SPRequest& request) const
 {
-    const char* targetURL = request.getRequestURL();
-
     try {
         RequestMapper::Settings settings = request.getRequestSettings();
 
@@ -385,27 +408,40 @@ pair<bool,long> Agent::doHandler(SPRequest& request) const
             }
         }
 
+        const HandlerConfiguration& handlerConfig = request.getAgent().getHandlerConfiguration(
+            request.getRequestSettings().first->getString(RequestMapper::HANDLER_CONFIG_ID_PROP_NAME));
+
+        // First check if this is a request to an absolute handler location.
+        const Handler* handler = handlerConfig.getAbsoluteHandler(request);
+        if (handler) {
+            pair<bool,long> hret = handler->run(request);
+            // Did the handler run successfully?
+            if (hret.first) {
+                return hret;
+            }
+            throw ConfigurationException("Configured Shibboleth handler failed to process the request.");
+        }
+
+        // Otherwise check for a relative handler.
+        const char* targetURL = request.getRequestURL();
         const char* handlerURL = request.getHandlerURL(targetURL);
         if (!handlerURL) {
             throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
         }
 
         // Make sure we only process handler requests and advance into the URL to find the handler's path.
-        if (!boost::contains(targetURL, handlerURL))
+        if (!boost::contains(targetURL, handlerURL)) {
             return make_pair(true, request.returnDecline());
+        }
 
         // We dispatch based on our path info. We know the request URL begins with or equals the handler URL,
         // so the path info is the next character (or null).
-
-        const HandlerConfiguration& handlerConfig = request.getAgent().getHandlerConfiguration(
-            request.getRequestSettings().first->getString(RequestMapper::HANDLER_CONFIG_ID_PROP_NAME));
-        const Handler* handler = handlerConfig.getHandler(targetURL + strlen(handlerURL));
+        handler = handlerConfig.getRelativeHandler(targetURL + strlen(handlerURL));
         if (!handler) {
             throw ConfigurationException("Shibboleth handler invoked at an unconfigured location.");
         }
 
         pair<bool,long> hret = handler->run(request);
-
         // Did the handler run successfully?
         if (hret.first) {
             return hret;
diff --git a/shibsp/handler/HandlerConfiguration.h b/shibsp/handler/HandlerConfiguration.h
index 906c1c4d..23139887 100644
--- a/shibsp/handler/HandlerConfiguration.h
+++ b/shibsp/handler/HandlerConfiguration.h
@@ -24,6 +24,7 @@
 #include <shibsp/base.h>
 
 #include <memory>
+#include <utility>
 
 namespace shibsp {
 
@@ -42,6 +43,15 @@ namespace shibsp {
     public:
         virtual ~HandlerConfiguration();
 
+        /**
+         * Gets the Handler installed at an absolute path if one is installed.
+         * 
+         * @param path absolute path to map to a Handler
+         * 
+         * @return the Handler configured at the request's location, or null
+         */
+        virtual const Handler* getAbsoluteHandler(SPRequest& request) const=0;
+
         /**
          * Gets the Handler installed at a particular path, relative to a "base" URL used for
          * triggering all Handlers.
@@ -50,7 +60,7 @@ namespace shibsp {
          * 
          * @return the Handler configured at the supplied location, or null
          */
-        virtual const Handler* getHandler(const char* path) const=0;
+        virtual const Handler* getRelativeHandler(const char* path) const=0;
 
         /**
          * Gets the Handler used for SSO session initiation.
@@ -75,7 +85,7 @@ namespace shibsp {
          * <p>Legacy configurations designed to avoid externally visible changes may continue
          * to operate multiple handlers to support different SSO bindings or patterns, while
          * newer deployments should avoid this practice and stick to a single location. The
-         * "meta-informatin" required to support this legacy practice will be embedded within
+         * "meta-information" required to support this legacy practice will be embedded within
          * the structure returned.</p>
          * 
          * @param handlerURL the handler base URL with which to prefix the token consumer
diff --git a/shibsp/handler/impl/DefaultHandlerConfiguration.cpp b/shibsp/handler/impl/DefaultHandlerConfiguration.cpp
index 7843a677..b87a072d 100644
--- a/shibsp/handler/impl/DefaultHandlerConfiguration.cpp
+++ b/shibsp/handler/impl/DefaultHandlerConfiguration.cpp
@@ -28,6 +28,7 @@
 #include "logging/Category.h"
 #include "remoting/ddf.h"
 
+#include <boost/algorithm/string.hpp>
 #include <boost/property_tree/ptree.hpp>
 #include <boost/property_tree/ini_parser.hpp>
 
@@ -46,13 +47,15 @@ namespace {
         DefaultHandlerConfiguration(const char* pathname);
         ~DefaultHandlerConfiguration() {}
 
-        const Handler* getHandler(const char* path) const;
+        const Handler* getAbsoluteHandler(SPRequest& request) const;
+        const Handler* getRelativeHandler(const char* path) const;
         const Handler& getSessionInitiator() const;
         DDF getTokenConsumerInfo(const char* handlerURL=nullptr) const;
 
     private:
         ptree m_pt;
-        map<string,unique_ptr<Handler>> m_handlerMap;
+        map<string,unique_ptr<Handler>> m_absoluteHandlerMap;
+        map<string,unique_ptr<Handler>> m_relativeHandlerMap;
         const Handler* m_sessionInitiator;
         DDF m_tokenConsumerConfig;
     };
@@ -89,8 +92,10 @@ DefaultHandlerConfiguration::DefaultHandlerConfiguration(const char* pathname)
             throw ConfigurationException("Multiple SessionInitiator handlers were configured, only one is permitted.");
         }
 
+        bool relative = false;
         string handlerPath(child.first);
         if (handlerPath.front() != '/') {
+            relative = true;
             handlerPath = '/' + handlerPath;
         }
 
@@ -100,25 +105,33 @@ DefaultHandlerConfiguration::DefaultHandlerConfiguration(const char* pathname)
         unique_ptr<Handler> handler(AgentConfig::getConfig().HandlerManager.newPlugin(
             type.get(), pair<ptree&,const char*>(child.second, handlerPath.c_str()), false));
             
-        m_handlerMap[handlerPath] = std::move(handler);
-        log.info("config (%s) installed %s handler at %s", pathname, type.get().c_str(), handlerPath.c_str());
-
-        // Save off a single SessionInitiator.
-        if (*type == SESSION_INITIATOR_HANDLER) {
-            m_sessionInitiator = m_handlerMap[handlerPath].get();
-        }
-        else if (*type == TOKEN_CONSUMER_HANDLER) {
-            DDF tokenConsumer(nullptr);
-            // String value of DDF is the handler location.
-            tokenConsumer.string(handlerPath.c_str());
-            m_tokenConsumerConfig.add(tokenConsumer);
-
-            // Check for legacy "binding" value to carry along with path as the name of the node.
-            boost::optional<string> legacyBinding = child.second.get_optional<string>(LEGACY_BINDING_PROP_NAME);
-            if (legacyBinding) {
-                tokenConsumer.name(legacyBinding->c_str());
+        if (relative) {
+            // Save off a single SessionInitiator.
+            if (*type == SESSION_INITIATOR_HANDLER) {
+                m_sessionInitiator = handler.get();
+            }
+            else if (*type == TOKEN_CONSUMER_HANDLER) {
+                DDF tokenConsumer(nullptr);
+                // String value of DDF is the handler location.
+                tokenConsumer.string(handlerPath);
+                m_tokenConsumerConfig.add(tokenConsumer);
+
+                // Check for legacy "binding" value to carry along with path as the name of the node.
+                boost::optional<string> legacyBinding = child.second.get_optional<string>(LEGACY_BINDING_PROP_NAME);
+                if (legacyBinding) {
+                    tokenConsumer.name(legacyBinding.get());
+                }
             }
+            m_relativeHandlerMap[handlerPath] = std::move(handler);
         }
+        else if (type.get() != PASSTHROUGH_HANDLER) {
+            throw ConfigurationException("Only Passtrhough handlers may be absolute.");
+        }
+        else {
+            m_absoluteHandlerMap[handlerPath] = std::move(handler);
+        }
+        log.info("config (%s) installed %s %s handler at %s", pathname, relative ? "relative" : "absolute",
+            type.get().c_str(), handlerPath.c_str());
     }
 
     // If a single token consumer with no binding label is installed, convert list to a string node.
@@ -130,13 +143,26 @@ DefaultHandlerConfiguration::DefaultHandlerConfiguration(const char* pathname)
     }
 }
 
-const Handler* DefaultHandlerConfiguration::getHandler(const char* path) const
+const Handler* DefaultHandlerConfiguration::getAbsoluteHandler(SPRequest& request) const
+{
+    // Check for a request URI (minus query string) that matches an absolute handler.
+    string wrapped_uri(request.getRequestURI());
+    wrapped_uri = wrapped_uri.substr(0, wrapped_uri.find(';'));
+    const auto& mapping = m_absoluteHandlerMap.find(wrapped_uri.substr(0, wrapped_uri.find('?')));
+    if (mapping != m_absoluteHandlerMap.end()) {
+        return mapping->second.get();
+    }
+
+    return nullptr;
+}
+
+const Handler* DefaultHandlerConfiguration::getRelativeHandler(const char* path) const
 {
     if (path) {
         string wrap(path);
         wrap = wrap.substr(0, wrap.find(';'));
-        const auto& mapping = m_handlerMap.find(wrap.substr(0, wrap.find('?')));
-        if (mapping != m_handlerMap.end()) {
+        const auto& mapping = m_relativeHandlerMap.find(wrap.substr(0, wrap.find('?')));
+        if (mapping != m_relativeHandlerMap.end()) {
             return mapping->second.get();
         }
     }
diff --git a/shibsp/remoting/ddf.h b/shibsp/remoting/ddf.h
index 1ce82409..77c2fe52 100644
--- a/shibsp/remoting/ddf.h
+++ b/shibsp/remoting/ddf.h
@@ -58,6 +58,7 @@ namespace shibsp {
         // property accessors
         const char* name() const;
         DDF& name(const char* n);
+        DDF& name(const std::string& n);
     
         // basic type checking
         bool isnull() const;
diff --git a/shibsp/remoting/impl/ddf.cpp b/shibsp/remoting/impl/ddf.cpp
index 0e7fd5ba..3eb114a4 100644
--- a/shibsp/remoting/impl/ddf.cpp
+++ b/shibsp/remoting/impl/ddf.cpp
@@ -201,15 +201,15 @@ const char* DDF::name() const
     return (m_handle) ? m_handle->name : nullptr;
 }
 
-DDF& DDF::name(const char* name)
+DDF& DDF::name(const char* n)
 {
     char trunc_name[MAX_NAME_LEN+1]="";
 
     if (m_handle) {
         if (m_handle->name)
             free(m_handle->name);
-        if (name && *name) {
-            strncpy(trunc_name,name,MAX_NAME_LEN);
+        if (n && *n) {
+            strncpy(trunc_name, n, MAX_NAME_LEN);
             trunc_name[MAX_NAME_LEN]='\0';
             m_handle->name=ddf_strdup(trunc_name);
             if (!m_handle->name)
@@ -221,6 +221,11 @@ DDF& DDF::name(const char* name)
     return *this;
 }
 
+DDF& DDF::name(const std::string& n)
+{
+    return name(n.c_str());
+}
+
 bool DDF::isnull() const
 {
     return m_handle ? false : true;

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


More information about the commits mailing list