[cpp-sp] branch main updated: Port over Time-based AccessControl plugin.
Scott Cantor
cantor.2 at osu.edu
Thu Oct 23 16:06:44 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:
https://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=817943166e49f4776bc5330b550a0bc48f45b3ba
The following commit(s) were added to refs/heads/main by this push:
new 81794316 Port over Time-based AccessControl plugin.
81794316 is described below
commit 817943166e49f4776bc5330b550a0bc48f45b3ba
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Oct 23 12:06:40 2025 -0400
Port over Time-based AccessControl plugin.
---
shibsp/impl/TimeAccessControl.cpp | 292 ++++++++++++++++++++++++++++++++++++++
1 file changed, 292 insertions(+)
diff --git a/shibsp/impl/TimeAccessControl.cpp b/shibsp/impl/TimeAccessControl.cpp
new file mode 100644
index 00000000..3ad0f9b1
--- /dev/null
+++ b/shibsp/impl/TimeAccessControl.cpp
@@ -0,0 +1,292 @@
+/**
+ * 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.
+ */
+
+/**
+ * TimeAccessControl.cpp
+ *
+ * Access control plugin for time-based policies.
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+
+#include "AccessControl.h"
+#include "Agent.h"
+#include "SPRequest.h"
+#include "attribute/AttributeConfiguration.h"
+#include "logging/Category.h"
+#include "remoting/ddf.h"
+#include "session/SessionCache.h"
+#include "util/Lockable.h"
+#include "util/Misc.h"
+#include "util/ReloadableXMLFile.h"
+
+#include <algorithm>
+#include <memory>
+#include <set>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/property_tree/ptree.hpp>
+
+#ifndef HAVE_STRCASECMP
+# define strcasecmp _stricmp
+#endif
+
+using namespace shibsp;
+using namespace boost::property_tree;
+using namespace std;
+
+namespace {
+
+ class Rule : public AccessControl, public NoOpSharedLockable
+ {
+ public:
+ Rule(const string& name, const ptree& pt);
+ ~Rule() {}
+
+ aclresult_t authorized(const SPRequest& request, const Session* session) const;
+
+ private:
+ enum { TM_AUTHN, TM_TIME, TM_YEAR, TM_MONTH, TM_DAY, TM_HOUR, TM_MINUTE, TM_SECOND, TM_WDAY } m_type;
+ enum { OP_LT, OP_LE, OP_EQ, OP_GE, OP_GT } m_op;
+ time_t m_value;
+ };
+
+ class TimeAccessControl : public AccessControl, public NoOpSharedLockable
+ {
+ public:
+ TimeAccessControl(const ptree& pt);
+ ~TimeAccessControl() {}
+
+ aclresult_t authorized(const SPRequest& request, const Session* session) const;
+
+ private:
+ enum { OP_AND, OP_OR } m_op;
+ vector<unique_ptr<Rule>> m_rules;
+ };
+}
+
+namespace shibsp {
+ AccessControl* SHIBSP_DLLLOCAL TimeAccessControlFactory(ptree& pt, bool deprecationSupport)
+ {
+ return new TimeAccessControl(pt);
+ }
+};
+
+Rule::Rule(const string& name, const ptree& pt)
+{
+ static const char Day[] = "Day";
+ static const char DayOfWeek[] = "DayOfWeek";
+ static const char Hour[] = "Hour";
+ static const char Minute[] = "Minute";
+ static const char Month[] = "Month";
+ static const char Second[] = "Second";
+ static const char Time[] = "Time";
+ static const char TimeSinceAuthn[] = "TimeSinceAuthn";
+ static const char Year[] = "Year";
+
+ // The TimeSinceAuthn rule operates on a Duration inside the element body,
+ // which should be the value of the tree.
+ if (name == TimeSinceAuthn) {
+ m_type = TM_AUTHN;
+ if (m_value = parseISODuration(pt.get_value("")) < 0) {
+ throw ConfigurationException("Unable to parse duration in TimeSinceAuthn rule.");
+ }
+ return;
+ }
+
+ // Anything else we have to parse the element body.
+ string s = pt.get_value("");
+ boost::trim(s);
+ vector<string> tokens;
+ if (boost::split(tokens, s, boost::is_space(), boost::algorithm::token_compress_on).size() != 2) {
+ throw ConfigurationException("Time-based rule requires element content of the form \"LT|LE|EQ|GE|GT value\".");
+ }
+ string& op = tokens.front();
+ if (op == "LT") { m_op = OP_LT; }
+ else if (op == "LE") { m_op = OP_LE; }
+ else if (op == "EQ") { m_op = OP_EQ; }
+ else if (op == "GE") { m_op = OP_GE; }
+ else if (op == "GT") { m_op = OP_GT; }
+ else {
+ throw ConfigurationException("First component of time-based rule must be one of LT, LE, EQ, GE, GT.");
+ }
+
+ if (name == Time) {
+ m_type = TM_TIME;
+ if (m_value = parseISODateTime(tokens.back()) < 0) {
+ throw ConfigurationException("Error parsing timestamp in Time rule.");
+ }
+ return;
+ }
+
+ m_value = boost::lexical_cast<time_t>(tokens.back());
+ if (name == Year) { m_type = TM_YEAR; }
+ else if (name == Month) { m_type = TM_MONTH; }
+ else if (name == Day) { m_type = TM_DAY; }
+ else if (name == Hour) { m_type = TM_HOUR; }
+ else if (name == Minute) { m_type = TM_MINUTE; }
+ else if (name == Second) { m_type = TM_SECOND; }
+ else if (name == DayOfWeek) { m_type = TM_WDAY; }
+ else {
+ throw ConfigurationException("Unrecognized time-based rule.");
+ }
+}
+
+AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Session* session) const
+{
+ time_t operand = 0;
+
+ if (m_type == TM_AUTHN) {
+ if (session) {
+ // Locate the Attribute to be used for accessing the auth timestamp.
+ const AttributeConfiguration& config = request.getAgent().getAttributeConfiguration(
+ request.getRequestSettings().first->getString(RequestMapper::ATTRIBUTE_CONFIG_ID_PROP_NAME));
+ const auto attr = session->getAttributes().find(
+ config.getString(AttributeConfiguration::LEGACY_AUTHTIME_ATTRIBUTE_PROP_NAME,
+ AttributeConfiguration::LEGACY_AUTHTIME_ATTRIBUTE_PROP_DEFAULT));
+ if (attr == session->getAttributes().end()) {
+ request.debug("Attribute carrying authentication time unnvailable");
+ return shib_acl_false;
+ }
+
+ const char* authtime = const_cast<DDF&>(attr->second).first().string();
+ if (authtime) {
+ if (operand = parseISODateTime(authtime) < 0) {
+ request.error("Error parsing authentication time from designated Attribute.");
+ }
+
+ if (time(nullptr) - operand <= m_value) {
+ return shib_acl_true;
+ }
+ request.debug("elapsed time since authentication exceeds limit");
+ return shib_acl_false;
+ }
+ else {
+ request.debug("Attribute carrying authentication time unnvailable");
+ return shib_acl_false;
+ }
+ }
+ else {
+ request.debug("session unnvailable");
+ return shib_acl_false;
+ }
+ }
+
+ // Extract value from tm struct or time directly.
+ operand = time(nullptr);
+ if (m_type != TM_TIME) {
+#ifndef HAVE_LOCALTIME_R
+ struct tm* ptime = localtime(&operand);
+#else
+ struct tm res;
+ struct tm* ptime = localtime_r(&operand, &res);
+#endif
+ switch (m_type) {
+ case TM_YEAR:
+ operand = ptime->tm_year + 1900;
+ break;
+ case TM_MONTH:
+ operand = ptime->tm_mon + 1;
+ break;
+ case TM_DAY:
+ operand = ptime->tm_mday;
+ break;
+ case TM_HOUR:
+ operand = ptime->tm_hour;
+ break;
+ case TM_MINUTE:
+ operand = ptime->tm_min;
+ break;
+ case TM_SECOND:
+ operand = ptime->tm_sec;
+ break;
+ case TM_WDAY:
+ operand = ptime->tm_wday;
+ break;
+ }
+ }
+
+ // Compare operand to test value in rule using rule operator.
+ switch (m_op) {
+ case OP_LT:
+ return (operand < m_value) ? shib_acl_true : shib_acl_false;
+ case OP_LE:
+ return (operand <= m_value) ? shib_acl_true : shib_acl_false;
+ case OP_EQ:
+ return (operand == m_value) ? shib_acl_true : shib_acl_false;
+ case OP_GE:
+ return (operand >= m_value) ? shib_acl_true : shib_acl_false;
+ case OP_GT:
+ return (operand > m_value) ? shib_acl_true : shib_acl_false;
+ }
+ return shib_acl_false;
+}
+
+TimeAccessControl::TimeAccessControl(const ptree& pt) : m_op(OP_AND)
+{
+ static const char OPERATOR_PROP_PATH[] = "<xmlattr>.operator";
+ static const char OR_OPERATOR_VALUE[] = "OR";
+ static const char AND_OPERATOR_VALUE[] = "AND";
+
+ string op = pt.get(OPERATOR_PROP_PATH, AND_OPERATOR_VALUE);
+ if (op == AND_OPERATOR_VALUE) {
+ m_op = OP_AND;
+ }
+ else if (op == OR_OPERATOR_VALUE) {
+ m_op = OP_OR;
+ }
+ else {
+ throw ConfigurationException("Unrecognized operator in Time AccessControl configuration.");
+ }
+
+ for (const auto& child : pt) {
+ if (child.first != "<xmlattr>") {
+ m_rules.push_back(unique_ptr<Rule>(new Rule(child.first, child.second)));
+ }
+ }
+
+ if (m_rules.empty())
+ throw ConfigurationException("Time AccessControl plugin requires at least one rule.");
+}
+
+
+AccessControl::aclresult_t TimeAccessControl::authorized(const SPRequest& request, const Session* session) const
+{
+ switch (m_op) {
+ case OP_AND:
+ {
+ for (auto& rule : m_rules) {
+ if (rule->authorized(request, session) != shib_acl_true) {
+ request.debug("time-based rule unsuccessful, denying access");
+ return shib_acl_false;
+ }
+ }
+ return shib_acl_true;
+ }
+
+ case OP_OR:
+ {
+ for (auto& rule : m_rules) {
+ if (rule->authorized(request,session) == shib_acl_true)
+ return shib_acl_true;
+ }
+ request.debug("all time-based rules unsuccessful, denying access");
+ return shib_acl_false;
+ }
+ }
+ request.warn("unknown operator in access control policy, denying access");
+ return shib_acl_false;
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list