[cpp-sp] branch main updated: Implement option for partial regex matching.

Scott Cantor cantor.2 at osu.edu
Wed Jun 25 13:13:12 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=7656339c6d679218e7390a6da21b0063aa920599

The following commit(s) were added to refs/heads/main by this push:
     new 7656339c Implement option for partial regex matching.
7656339c is described below

commit 7656339c6d679218e7390a6da21b0063aa920599
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Jun 25 09:13:04 2025 -0400

    Implement option for partial regex matching.
---
 apache/mod_shib_24.cpp                             | 92 ++++++----------------
 configs/shibboleth.ini                             |  3 +-
 shibsp/Agent.cpp                                   |  8 +-
 shibsp/Agent.h                                     |  2 +
 .../impl/DefaultAttributeConfiguration.cpp         | 13 ++-
 shibsp/impl/XMLAccessControl.cpp                   | 29 ++++---
 shibsp/impl/XMLRequestMapper.cpp                   | 22 ++++--
 7 files changed, 75 insertions(+), 94 deletions(-)

diff --git a/apache/mod_shib_24.cpp b/apache/mod_shib_24.cpp
index 0ec33634..f92b56d8 100644
--- a/apache/mod_shib_24.cpp
+++ b/apache/mod_shib_24.cpp
@@ -814,13 +814,18 @@ extern "C" int shib_fixups(request_rec* r)
 class htAccessControl : virtual public AccessControl, public NoOpSharedLockable
 {
 public:
-    htAccessControl() {}
+    htAccessControl() {
+        m_partialRegexMatching = AgentConfig::getConfig().getAgent().getBool(
+            Agent::PARTIAL_REGEX_MATCHING_PROP_NAME, Agent::PARTIAL_REGEX_MATCHING_PROP_DEFAULT);
+    }
     ~htAccessControl() {}
-    aclresult_t authorized(const SPRequest& request, const Session* session) const;
+    aclresult_t authorized(const SPRequest& request, const Session* session) const {
+        // We should never be invoked in Apache 2.4+ as an SP plugin.
+        throw ConfigurationException("Save my walrus!");
+    }
 
     aclresult_t doAccessControl(const ShibTargetApache& sta, const Session* session, const char* plugin) const;
     aclresult_t doUser(const ShibTargetApache& sta, const char* params) const;
-    aclresult_t doAuthnContext(const ShibTargetApache& sta, const char* acRef, const char* params) const;
     aclresult_t doShibAttr(const ShibTargetApache& sta, const Session* session, const char* rule, const char* params) const;
 
 private:
@@ -832,6 +837,8 @@ private:
         const char* toMatch,
         bool isRegex=false
     ) const;
+
+    bool m_partialRegexMatching;
 };
 
 AccessControl* htAccessFactory(const ptree&, bool)
@@ -881,17 +888,19 @@ AccessControl::aclresult_t htAccessControl::doUser(const ShibTargetApache& sta,
             continue;
         }
 
+        static regexp::match_flag_type match_flags = regexp::regex_constants::match_any | regexp::regex_constants::match_not_null;
+
         // Figure out if there's a match.
         bool match = false;
         if (regexp) {
             try {
-                // TODO: support regex options?
                 regexp::regex re(w, regexp::regex_constants::extended);
-                match = regexp::regex_match(sta.getRemoteUser(), re, regexp::regex_constants::match_any | regexp::regex_constants::match_not_null);
+                match = m_partialRegexMatching ?
+                    regexp::regex_search(sta.getRemoteUser(), re, match_flags) :
+                    regexp::regex_match(sta.getRemoteUser(), re, match_flags);
             }
             catch (const regexp::regex_error& e) {
-                sta.log(Priority::SHIB_ERROR,
-                    string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + e.what());
+                sta.error(string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + e.what());
             }
         }
         else if (sta.getRemoteUser() == w) {
@@ -899,65 +908,15 @@ AccessControl::aclresult_t htAccessControl::doUser(const ShibTargetApache& sta,
         }
 
         if (match) {
-            if (sta.isPriorityEnabled(Priority::SHIB_DEBUG))
-                sta.log(Priority::SHIB_DEBUG,
-                    string("htaccess: require user ") + (negated ? "rejecting (" : "accepting (") + sta.getRemoteUser() + ")");
+            if (sta.isPriorityEnabled(Priority::SHIB_DEBUG)) {
+                sta.debug(string("htaccess: require user ") + (negated ? "rejecting (" : "accepting (") + sta.getRemoteUser() + ")");
+            }
             return (negated ? shib_acl_false : shib_acl_true);
         }
     }
     return (negated ? shib_acl_true : shib_acl_false);
 }
 
-AccessControl::aclresult_t htAccessControl::doAuthnContext(const ShibTargetApache& sta, const char* ref, const char* params) const
-{
-    if (ref && *ref) {
-        bool regexp = false;
-        bool negated = false;
-        while (ref && *params) {
-            const char* w = ap_getword_conf(sta.m_req->pool, &params);
-            if (*w == '~') {
-                regexp = true;
-                continue;
-            }
-            else if (*w == '!') {
-                // A negated rule presumes success unless a match is found.
-                negated = true;
-                if (*(w+1) == '~')
-                    regexp = true;
-                continue;
-            }
-
-            // Figure out if there's a match.
-            bool match = false;
-            if (regexp) {
-                try {
-                    regexp::regex re(w, regexp::regex_constants::extended);
-                    match = regexp::regex_match(ref, re, regexp::regex_constants::match_any | regexp::regex_constants::match_not_null);
-                }
-                catch (const regexp::regex_error& e) {
-                    sta.log(Priority::SHIB_ERROR,
-                        string("htaccess plugin caught exception while parsing regular expression (") + w + "): " + e.what());
-                }
-            }
-            else if (!strcmp(w, ref)) {
-                match = true;
-            }
-
-            if (match) {
-                if (sta.isPriorityEnabled(Priority::SHIB_DEBUG))
-                    sta.log(Priority::SHIB_DEBUG,
-                        string("htaccess: require authnContext ") + (negated ? "rejecting (" : "accepting (") + ref + ")");
-                return (negated ? shib_acl_false : shib_acl_true);
-            }
-        }
-        return (negated ? shib_acl_true : shib_acl_false);
-    }
-
-    if (sta.isPriorityEnabled(Priority::SHIB_DEBUG))
-        sta.debug("htaccess: require authnContext rejecting session with no context associated");
-    return shib_acl_false;
-}
-
 bool htAccessControl::checkAttribute(
     const SPRequest& request,
     const Session& session,
@@ -982,8 +941,7 @@ bool htAccessControl::checkAttribute(
                 return true;
             }
         } catch (const regexp::regex_error& e) {
-            request.log(Priority::SHIB_ERROR,
-                string("htaccess plugin caught exception while parsing regular expression (") + toMatch + "): " + e.what());
+            request.error(string("htaccess plugin caught exception while parsing regular expression (") + toMatch + "): " + e.what());
         }
     }
     else if (attrConfig.hasMatchingValue(session, attributeID, toMatch)) {
@@ -1026,12 +984,6 @@ AccessControl::aclresult_t htAccessControl::doShibAttr(
     return shib_acl_false;
 }
 
-AccessControl::aclresult_t htAccessControl::authorized(const SPRequest& request, const Session* session) const
-{
-    // We should never be invoked in 2.4+ as an SP plugin.
-    throw ConfigurationException("Save my walrus!");
-}
-
 class ApacheRequestMapper : public virtual RequestMapper, public virtual PropertySet
 {
 public:
@@ -1300,7 +1252,9 @@ extern "C" authz_status shib_acclass_check_authz(request_rec* r, const char* req
     try {
         unique_lock<Session> session = sta.first->getSession(false, true);
         if (session && hta.doShibAttr(*sta.first, session.mutex(),
-                sta.first->getAgent().getString("legacy-classref-attribute", "Shib-AuthnContext-Class"),
+                sta.first->getAgent().getString(
+                    AttributeConfiguration::LEGACY_CLASSREF_ATTRIBUTE_PROP_NAME,
+                    AttributeConfiguration::LEGACY_CLASSREF_ATTRIBUTE_PROP_DEFAULT),
                 require_line) == AccessControl::shib_acl_true)
             return AUTHZ_GRANTED;
         return session ? AUTHZ_DENIED : AUTHZ_DENIED_NO_USER;
diff --git a/configs/shibboleth.ini b/configs/shibboleth.ini
index 63ca0238..461e4dfc 100644
--- a/configs/shibboleth.ini
+++ b/configs/shibboleth.ini
@@ -2,7 +2,7 @@
 agentID = sp.example.org
 
 [logging]
-
+defaultLevel = INFO
 
 [logging-categories]
 ;Shibboleth.RequestMapper = DEBUG
@@ -24,4 +24,5 @@ type = filesystem
 [request-mapper]
 path = request-map.xml
 reloadChanges = true
+#partialRegexMatching = false
 
diff --git a/shibsp/Agent.cpp b/shibsp/Agent.cpp
index edd56a6e..22d493c5 100644
--- a/shibsp/Agent.cpp
+++ b/shibsp/Agent.cpp
@@ -49,14 +49,14 @@ using namespace shibsp;
 using namespace std;
 
 const char Agent::UNSET_HEADER_VALUE_PROP_NAME[] = "unsetHeaderValue";
-
 const char Agent::CHECK_SPOOFING_PROP_NAME[] = "checkSpoofing";
-bool Agent::CHECK_SPOOFING_PROP_DEFAULT = true;
-
 const char Agent::SPOOF_KEY_PROP_NAME[] = "spoofKey";
-
 const char Agent::CATCH_ALL_PROP_NAME[] = "catchAll";
+const char Agent::PARTIAL_REGEX_MATCHING_PROP_NAME[] = "partialRegexMatching";
+
+bool Agent::CHECK_SPOOFING_PROP_DEFAULT = true;
 bool Agent::CATCH_ALL_PROP_DEFAULT = false;
+bool Agent::PARTIAL_REGEX_MATCHING_PROP_DEFAULT = false;
 
 Agent::Agent()
 {
diff --git a/shibsp/Agent.h b/shibsp/Agent.h
index 53e37d09..00d5f5d8 100644
--- a/shibsp/Agent.h
+++ b/shibsp/Agent.h
@@ -181,9 +181,11 @@ namespace shibsp {
         static const char CHECK_SPOOFING_PROP_NAME[];
         static const char SPOOF_KEY_PROP_NAME[];
         static const char CATCH_ALL_PROP_NAME[];
+        static const char PARTIAL_REGEX_MATCHING_PROP_NAME[];
 
         static bool CHECK_SPOOFING_PROP_DEFAULT;
         static bool CATCH_ALL_PROP_DEFAULT;
+        static bool PARTIAL_REGEX_MATCHING_PROP_DEFAULT;
 
     protected:
         /** The AuthTypes to "recognize" (defaults to "shibboleth"). */
diff --git a/shibsp/attribute/impl/DefaultAttributeConfiguration.cpp b/shibsp/attribute/impl/DefaultAttributeConfiguration.cpp
index 7011f780..658008b0 100644
--- a/shibsp/attribute/impl/DefaultAttributeConfiguration.cpp
+++ b/shibsp/attribute/impl/DefaultAttributeConfiguration.cpp
@@ -21,6 +21,7 @@
 #include "internal.h"
 
 #include "exceptions.h"
+#include "Agent.h"
 #include "AgentConfig.h"
 #include "RequestMapper.h"
 #include "SPRequest.h"
@@ -73,7 +74,7 @@ namespace {
 
         Category& m_log;
         ptree m_pt;
-        bool m_urlEncoding,m_exportDuplicates;
+        bool m_urlEncoding,m_exportDuplicates,m_partialRegexMatching;
         map<string,string> m_mappings;
         set<string> m_caseSensitiveIds;
     };
@@ -113,6 +114,9 @@ DefaultAttributeConfiguration::DefaultAttributeConfiguration(const char* pathnam
     m_urlEncoding = !strcmp(getString("encoding", ""), "URL");
     m_exportDuplicates = getBool("exportDuplicateValues", true);
 
+    m_partialRegexMatching = AgentConfig::getConfig().getAgent().getBool(
+        Agent::PARTIAL_REGEX_MATCHING_PROP_NAME, Agent::PARTIAL_REGEX_MATCHING_PROP_DEFAULT);
+
     boost::optional<ptree&> mappings = m_pt.get_child_optional("mappings");
     if (!mappings) {
         return;
@@ -385,7 +389,9 @@ bool DefaultAttributeConfiguration::hasMatchingValue(
     return false;
 }
 
-bool DefaultAttributeConfiguration::hasMatchingValue(const Session& session, const char* attributeId, const char* value) const
+bool DefaultAttributeConfiguration::hasMatchingValue(
+    const Session& session, const char* attributeId, const char* value
+    ) const
 {
     const auto& attr = session.getAttributes().find(attributeId);
     if (attr == session.getAttributes().end()) {
@@ -420,7 +426,8 @@ bool DefaultAttributeConfiguration::hasMatchingValue(
 
     DDF val = const_cast<DDF&>(attr->second).first();
     while (!val.isnull()) {
-        if (regexp::regex_match(val.string(), expression, match_flags)) {
+        if (m_partialRegexMatching ? regexp::regex_search(val.string(), expression, match_flags) :
+                regexp::regex_match(val.string(), expression, match_flags)) {
             return true;
         }
         val = const_cast<DDF&>(attr->second).next();
diff --git a/shibsp/impl/XMLAccessControl.cpp b/shibsp/impl/XMLAccessControl.cpp
index 444542bf..a3e51d31 100644
--- a/shibsp/impl/XMLAccessControl.cpp
+++ b/shibsp/impl/XMLAccessControl.cpp
@@ -172,20 +172,20 @@ AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Sess
 
     // Map alias in rule to the attribute.
     if (!session) {
-        request.log(Priority::SHIB_WARN, "AccessControl plugin not given a valid session to evaluate, are you using lazy sessions?");
+        request.warn("AccessControl plugin not given a valid session to evaluate, are you using lazy sessions?");
         return shib_acl_false;
     }
 
     if (m_alias == "valid-user") {
         if (session) {
-            request.log(Priority::SHIB_DEBUG," AccessControl rule accepting valid-user based on active session");
+            request.debug("AccessControl rule accepting valid-user based on active session");
             return shib_acl_true;
         }
         return shib_acl_false;
     }
     else if (m_alias == "user") {
         if (m_vals.find(request.getRemoteUser()) != m_vals.end()) {
-            request.log(Priority::SHIB_DEBUG, string("AccessControl rule expecting REMOTE_USER (") + request.getRemoteUser() + "), authz granted");
+            request.debug(string("AccessControl rule expecting REMOTE_USER (") + request.getRemoteUser() + "), authz granted");
             return shib_acl_true;
         }
         return shib_acl_false;
@@ -206,7 +206,7 @@ AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Sess
     // Empty case is historical and not terribly smart, but we'll brute force it.
     if (m_vals.empty()) {
         if (session->getAttributes().find(actual_alias.c_str()) != session->getAttributes().end()) {
-            request.log(Priority::SHIB_DEBUG, string("AccessControl rule requires presence of attribute (") + actual_alias + "), authz granted");
+            request.debug(string("AccessControl rule requires presence of attribute (") + actual_alias + "), authz granted");
             return shib_acl_true;
         }
         return shib_acl_false;
@@ -214,7 +214,7 @@ AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Sess
 
     // Otherwise call into the helper logic to handle matching process..
     if (attributeConfig.hasMatchingValue(*session, actual_alias.c_str(), m_vals)) {
-        request.log(Priority::SHIB_DEBUG, string("AccessControl rule satisfied for attribute (") + actual_alias + "), authz granted");
+        request.debug(string("AccessControl rule satisfied for attribute (") + actual_alias + "), authz granted");
         return shib_acl_true;
     }
 
@@ -244,26 +244,31 @@ RuleRegex::RuleRegex(const ptree& pt)
 
 AccessControl::aclresult_t RuleRegex::authorized(const SPRequest& request, const Session* session) const
 {
-
     static regexp::regex_constants::match_flag_type match_flags = regexp::regex_constants::match_any | regexp::regex_constants::match_not_null;
 
     string actual_alias(m_alias);
 
     if (!session) {
-        request.log(Priority::SHIB_WARN, "AccessControl plugin not given a valid session to evaluate, are you using lazy sessions?");
+        request.warn("AccessControl plugin not given a valid session to evaluate, are you using lazy sessions?");
         return shib_acl_false;
     }
 
     if (m_alias == "valid-user") {
         if (session) {
-            request.log(Priority::SHIB_DEBUG,"AccessControl rule accepting valid-user based on active session");
+            request.debug("AccessControl rule accepting valid-user based on active session");
             return shib_acl_true;
         }
         return shib_acl_false;
     }
     else if (m_alias == "user") {
-        if (regexp::regex_match(request.getRemoteUser(), m_re, match_flags)) {
-            request.log(Priority::SHIB_DEBUG, string("AccessControl rule expecting REMOTE_USER regex (") + m_exp + "), authz granted");
+
+        bool partial = request.getAgent().getBool(
+            Agent::PARTIAL_REGEX_MATCHING_PROP_NAME, Agent::PARTIAL_REGEX_MATCHING_PROP_DEFAULT);
+
+        bool result = partial ? regexp::regex_search(request.getRemoteUser(), m_re, match_flags) :
+                regexp::regex_match(request.getRemoteUser(), m_re, match_flags);
+        if (result) {
+            request.debug(string("AccessControl rule expecting REMOTE_USER regex (") + m_exp + "), authz granted");
             return shib_acl_true;
         }
         return shib_acl_false;
@@ -283,7 +288,7 @@ AccessControl::aclresult_t RuleRegex::authorized(const SPRequest& request, const
 
     // Call into the helper logic to handle matching process..
     if (attributeConfig.hasMatchingValue(*session, actual_alias.c_str(), m_re)) {
-        request.log(Priority::SHIB_DEBUG,
+        request.debug(
             string("AccessControl rule for attribute (") + actual_alias + ") expecting regex (" + m_exp  + ", authz granted");
         return shib_acl_true;
     }
@@ -356,7 +361,7 @@ AccessControl::aclresult_t Operator::authorized(const SPRequest& request, const
             return shib_acl_false;
         }
     }
-    request.log(Priority::SHIB_WARN,"unknown operation in access control policy, denying access");
+    request.warn("unknown operation in access control policy, denying access");
     return shib_acl_false;
 }
 
diff --git a/shibsp/impl/XMLRequestMapper.cpp b/shibsp/impl/XMLRequestMapper.cpp
index 69edf4b7..d287f436 100644
--- a/shibsp/impl/XMLRequestMapper.cpp
+++ b/shibsp/impl/XMLRequestMapper.cpp
@@ -20,6 +20,7 @@
 #include "internal.h"
 #include "exceptions.h"
 #include "AccessControl.h"
+#include "Agent.h"
 #include "AgentConfig.h"
 #include "RequestMapper.h"
 #include "SPRequest.h"
@@ -144,8 +145,19 @@ namespace {
         return new XMLRequestMapper(pt);
     }
 
-    static regexp::regex_constants::match_flag_type match_flags =
-        regexp::regex_constants::match_any | regexp::regex_constants::match_not_null;
+    static bool doRegex(const regexp::regex& exp, const char* input) {
+        static regexp::regex_constants::match_flag_type match_flags =
+            regexp::regex_constants::match_any | regexp::regex_constants::match_not_null;
+
+        bool partial = AgentConfig::getConfig().getAgent().getBool(
+            Agent::PARTIAL_REGEX_MATCHING_PROP_NAME, Agent::PARTIAL_REGEX_MATCHING_PROP_DEFAULT);
+        if (partial) {
+            return regexp::regex_search(input, exp, match_flags);
+        }
+        else {
+            return regexp::regex_match(input, exp, match_flags);
+        }
+    }
 }
 
 void SHIBSP_API shibsp::registerRequestMappers()
@@ -453,7 +465,7 @@ const Override* Override::locate(const HTTPRequest& request) const
     // If there's anything left, we try for a regex match on the rest of the path minus the query string.
     if (*path) {
         for (const auto& re : m_regexps) {
-            if (regex_match(path, re.first, match_flags)) {
+            if (doRegex(re.first, path)) {
                 o = re.second.get();
                 break;
             }
@@ -473,7 +485,7 @@ const Override* Override::locate(const HTTPRequest& request) const
                     if (get<1>(*q)) {
                         // We have to match one of the values.
                         while (vals.first != vals.second) {
-                            if (regexp::regex_match(vals.first->second, get<1>(*q).get(), match_flags)) {
+                            if (doRegex(get<1>(*q).get(), vals.first->second)) {
                                 o = get<2>(*q).get();
                                 descended = true;
                                 break;
@@ -654,7 +666,7 @@ const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const HTTP
     }
     else {
         for (const auto& re : m_regexps) {
-            if (regexp::regex_match(vhost, re.first, match_flags)) {
+            if (doRegex(re.first, vhost)) {
                 o = re.second.get();
             }
         }

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


More information about the commits mailing list