[cpp-sp] branch main updated: Raft of changes removing old Attribute APIs and adding new.
Scott Cantor
cantor.2 at osu.edu
Tue Apr 22 15:25:57 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=98c8feae16e283afc40a1657ac1094dc4accc75a
The following commit(s) were added to refs/heads/main by this push:
new 98c8feae Raft of changes removing old Attribute APIs and adding new.
98c8feae is described below
commit 98c8feae16e283afc40a1657ac1094dc4accc75a
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Apr 22 11:25:53 2025 -0400
Raft of changes removing old Attribute APIs and adding new.
---
Projects/vc22/shibsp.vcxproj | 8 +-
Projects/vc22/shibsp.vcxproj.filters | 16 +-
apache/mod_shib_24.cpp | 87 +++--
configs/Makefile.am | 1 +
configs/attributes.ini | 13 +
shibsp/Agent.cpp | 144 +------
shibsp/Agent.h | 17 +-
shibsp/Makefile.am | 8 +-
shibsp/attribute/Attribute.cpp | 175 ---------
shibsp/attribute/Attribute.h | 236 -----------
shibsp/attribute/AttributeConfiguration.h | 168 ++++++++
shibsp/attribute/ScopedAttribute.cpp | 130 -------
shibsp/attribute/ScopedAttribute.h | 107 -----
shibsp/attribute/SimpleAttribute.cpp | 73 ----
shibsp/attribute/SimpleAttribute.h | 73 ----
.../impl/DefaultAttributeConfiguration.cpp | 431 +++++++++++++++++++++
shibsp/handler/HandlerConfiguration.h | 2 +-
shibsp/handler/impl/AdminLogoutInitiator.cpp | 8 +-
shibsp/handler/impl/AttributeCheckerHandler.cpp | 6 +-
shibsp/handler/impl/LocalLogoutInitiator.cpp | 2 +-
shibsp/handler/impl/SessionHandler.cpp | 75 ++--
shibsp/impl/DefaultAgent.cpp | 43 +-
shibsp/impl/XMLAccessControl.cpp | 61 ++-
shibsp/session/AbstractSessionCache.h | 63 ++-
shibsp/session/SessionCache.h | 26 +-
shibsp/session/impl/AbstractSessionCache.cpp | 295 +++++++++++++-
tests/data/console-shibboleth.ini | 1 +
tests/data/fatal-exts-shibboleth.ini | 1 +
tests/data/impl/console-shibboleth.ini | 1 +
tests/data/nonfatal-exts-shibboleth.ini | 1 +
tests/data/platform/iis/console-shibboleth.ini | 1 +
tests/data/remoting/impl/shibboleth.ini | 1 +
tests/data/syslog-shibboleth.ini | 1 +
.../util/reloadablefile/console-shibboleth.ini | 1 +
34 files changed, 1178 insertions(+), 1098 deletions(-)
diff --git a/Projects/vc22/shibsp.vcxproj b/Projects/vc22/shibsp.vcxproj
index 4d286c22..fce607e8 100644
--- a/Projects/vc22/shibsp.vcxproj
+++ b/Projects/vc22/shibsp.vcxproj
@@ -32,9 +32,7 @@
<ClInclude Include="..\..\shibsp\AccessControl.h" />
<ClInclude Include="..\..\shibsp\Agent.h" />
<ClInclude Include="..\..\shibsp\AgentConfig.h" />
- <ClInclude Include="..\..\shibsp\attribute\Attribute.h" />
- <ClInclude Include="..\..\shibsp\attribute\ScopedAttribute.h" />
- <ClInclude Include="..\..\shibsp\attribute\SimpleAttribute.h" />
+ <ClInclude Include="..\..\shibsp\attribute\AttributeConfiguration.h" />
<ClInclude Include="..\..\shibsp\base.h" />
<ClInclude Include="..\..\shibsp\config_pub_win32.h" />
<ClInclude Include="..\..\shibsp\exceptions.h" />
@@ -86,9 +84,7 @@
<ItemGroup>
<ClCompile Include="..\..\shibsp\AbstractSPRequest.cpp" />
<ClCompile Include="..\..\shibsp\Agent.cpp" />
- <ClCompile Include="..\..\shibsp\attribute\Attribute.cpp" />
- <ClCompile Include="..\..\shibsp\attribute\ScopedAttribute.cpp" />
- <ClCompile Include="..\..\shibsp\attribute\SimpleAttribute.cpp" />
+ <ClCompile Include="..\..\shibsp\attribute\impl\DefaultAttributeConfiguration.cpp" />
<ClCompile Include="..\..\shibsp\exceptions.cpp" />
<ClCompile Include="..\..\shibsp\handler\impl\AbstractHandler.cpp">
<DisableSpecificWarnings Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">4296;%(DisableSpecificWarnings)</DisableSpecificWarnings>
diff --git a/Projects/vc22/shibsp.vcxproj.filters b/Projects/vc22/shibsp.vcxproj.filters
index 42830301..2b8edf71 100644
--- a/Projects/vc22/shibsp.vcxproj.filters
+++ b/Projects/vc22/shibsp.vcxproj.filters
@@ -78,13 +78,7 @@
</Filter>
</ItemGroup>
<ItemGroup>
- <ClInclude Include="..\..\shibsp\attribute\Attribute.h">
- <Filter>Header Files\Attribute</Filter>
- </ClInclude>
- <ClInclude Include="..\..\shibsp\attribute\ScopedAttribute.h">
- <Filter>Header Files\Attribute</Filter>
- </ClInclude>
- <ClInclude Include="..\..\shibsp\attribute\SimpleAttribute.h">
+ <ClInclude Include="..\..\shibsp\attribute\AttributeConfiguration.h">
<Filter>Header Files\Attribute</Filter>
</ClInclude>
<ClInclude Include="..\..\shibsp\config_pub_win32.h">
@@ -245,13 +239,7 @@
</ClInclude>
</ItemGroup>
<ItemGroup>
- <ClCompile Include="..\..\shibsp\attribute\Attribute.cpp">
- <Filter>Source Files\Attribute</Filter>
- </ClCompile>
- <ClCompile Include="..\..\shibsp\attribute\ScopedAttribute.cpp">
- <Filter>Source Files\Attribute</Filter>
- </ClCompile>
- <ClCompile Include="..\..\shibsp\attribute\SimpleAttribute.cpp">
+ <ClCompile Include="..\..\shibsp\attribute\impl\DefaultAttributeConfiguraton.cpp">
<Filter>Source Files\Attribute</Filter>
</ClCompile>
<ClCompile Include="..\..\shibsp\handler\impl\AbstractHandler.cpp">
diff --git a/apache/mod_shib_24.cpp b/apache/mod_shib_24.cpp
index 08b7bd8b..8e192407 100644
--- a/apache/mod_shib_24.cpp
+++ b/apache/mod_shib_24.cpp
@@ -42,7 +42,7 @@
#include <shibsp/Agent.h>
#include <shibsp/AgentConfig.h>
#include <shibsp/RequestMapper.h>
-#include <shibsp/attribute/Attribute.h>
+#include <shibsp/attribute/AttributeConfiguration.h>
#include <shibsp/session/SessionCache.h>
#include <shibsp/util/Lockable.h>
@@ -819,7 +819,14 @@ public:
aclresult_t doShibAttr(const ShibTargetApache& sta, const Session* session, const char* rule, const char* params) const;
private:
- bool checkAttribute(const SPRequest& request, const Attribute* attr, const char* toMatch, bool isRegex=false) const;
+ bool checkAttribute(
+ const SPRequest& request,
+ const Session& session,
+ const AttributeConfiguration& attrConfig,
+ const char* attributeID,
+ const char* toMatch,
+ bool isRegex=false
+ ) const;
};
AccessControl* htAccessFactory(const ptree&, bool)
@@ -946,36 +953,42 @@ AccessControl::aclresult_t htAccessControl::doAuthnContext(const ShibTargetApach
return shib_acl_false;
}
-bool htAccessControl::checkAttribute(const SPRequest& request, const Attribute* attr, const char* toMatch, bool isRegex) const
+bool htAccessControl::checkAttribute(
+ const SPRequest& request,
+ const Session& session,
+ const AttributeConfiguration& attrConfig,
+ const char* attributeID,
+ const char* toMatch,
+ bool isRegex
+ ) const
{
- bool caseSensitive = attr->isCaseSensitive();
- const vector<string>& vals = attr->getSerializedValues();
- for (vector<string>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
- if (isRegex) {
- regexp::regex_constants::syntax_option_type flags = regexp::regex_constants::extended;
- if (!caseSensitive) {
- flags |= regexp::regex_constants::icase;
- }
- try {
- regexp::regex exp(toMatch, flags);
- if (regexp::regex_match(*v, exp, regexp::regex_constants::match_any | regexp::regex_constants::match_not_null)) {
- if (request.isPriorityEnabled(Priority::SHIB_DEBUG))
- request.log(Priority::SHIB_DEBUG, string("htaccess: expecting regexp ") + toMatch + ", got " + *v + ": accepted");
- return true;
+ bool caseSensitive = attrConfig.isCaseSensitive(attributeID);
+ if (isRegex) {
+ regexp::regex_constants::syntax_option_type flags = regexp::regex_constants::extended;
+ if (!caseSensitive) {
+ flags |= regexp::regex_constants::icase;
+ }
+ try {
+ regexp::regex exp(toMatch, flags);
+ if (attrConfig.hasMatchingValue(session, attributeID, exp)) {
+ if (request.isPriorityEnabled(Priority::SHIB_DEBUG)) {
+ request.log(Priority::SHIB_DEBUG, string("htaccess: attribute (") + attributeID + ") matched regexp: " + toMatch);
}
- } catch (const regexp::regex_error& e) {
- request.log(Priority::SHIB_ERROR,
- string("htaccess plugin caught exception while parsing regular expression (") + toMatch + "): " + e.what());
+ 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());
}
- else if ((caseSensitive && *v == toMatch) || (!caseSensitive && !strcasecmp(v->c_str(), toMatch))) {
- if (request.isPriorityEnabled(Priority::SHIB_DEBUG))
- request.log(Priority::SHIB_DEBUG, string("htaccess: expecting ") + toMatch + ", got " + *v + ": accepted");
- return true;
- }
- else if (request.isPriorityEnabled(Priority::SHIB_DEBUG)) {
- request.log(Priority::SHIB_DEBUG, string("htaccess: expecting ") + toMatch + ", got " + *v + ": rejected");
+ }
+ else if (attrConfig.hasMatchingValue(session, attributeID, toMatch)) {
+ if (request.isPriorityEnabled(Priority::SHIB_DEBUG)) {
+ request.log(Priority::SHIB_DEBUG, string("htaccess: attribute (") + attributeID + ") matched " + toMatch);
}
+ return true;
+ }
+ else if (request.isPriorityEnabled(Priority::SHIB_DEBUG)) {
+ request.log(Priority::SHIB_DEBUG, string("htaccess: attribute (") + attributeID + ") did not match " + toMatch);
}
return false;
}
@@ -984,25 +997,27 @@ AccessControl::aclresult_t htAccessControl::doShibAttr(
const ShibTargetApache& sta, const Session* session, const char* rule, const char* params
) const
{
- // Find the attribute(s) matching the require rule.
- pair<multimap<string,const Attribute*>::const_iterator,multimap<string,const Attribute*>::const_iterator> attrs =
- session->getIndexedAttributes().equal_range(rule ? rule : "");
+ // "rule" is the attribute ID being eval'd
+ if (!rule || !session) {
+ return shib_acl_false;
+ }
+
+ const AttributeConfiguration& attrConfig =
+ sta.getAgent().getAttributeConfiguration(sta.getRequestSettings().first->getString("attributeConfigID"));
bool regexp = false;
- while (attrs.first != attrs.second && *params) {
+ while (*params) {
const char* w = ap_getword_conf(sta.m_req->pool, ¶ms);
if (*w == '~') {
regexp = true;
continue;
}
- pair<multimap<string,const Attribute*>::const_iterator,multimap<string,const Attribute*>::const_iterator> attrs2(attrs);
- for (; attrs2.first != attrs2.second; ++attrs2.first) {
- if (checkAttribute(sta, attrs2.first->second, w, regexp)) {
- return shib_acl_true;
- }
+ if (checkAttribute(sta, *session, attrConfig, rule, w, regexp)) {
+ return shib_acl_true;
}
}
+
return shib_acl_false;
}
diff --git a/configs/Makefile.am b/configs/Makefile.am
index c2a5f03d..74e10f46 100644
--- a/configs/Makefile.am
+++ b/configs/Makefile.am
@@ -19,6 +19,7 @@ CONFIGFILES = \
globalLogout.html \
partialLogout.html \
shibboleth.ini \
+ attributes.ini \
handlers.ini \
request-map.xml
diff --git a/configs/attributes.ini b/configs/attributes.ini
new file mode 100644
index 00000000..4e8c179b
--- /dev/null
+++ b/configs/attributes.ini
@@ -0,0 +1,13 @@
+[settings]
+#attributeValueDelimiter = ;
+#scopedDelimiter = @
+#exportDuplicateValues = true
+caseSensitiveAttributes = foo, bar
+legacyClassRefAttribute = Shib-AuthnContext-Class
+legacyAuthnTimeAttribute = Shib-Authentication-Instant
+# Set to URL to apply URL encoding
+#encoding =
+
+[mappings]
+foo = foo
+
diff --git a/shibsp/Agent.cpp b/shibsp/Agent.cpp
index 23a788b2..f7234e4e 100644
--- a/shibsp/Agent.cpp
+++ b/shibsp/Agent.cpp
@@ -24,7 +24,7 @@
#include "AgentConfig.h"
#include "AccessControl.h"
#include "SPRequest.h"
-#include "attribute/Attribute.h"
+#include "attribute/AttributeConfiguration.h"
#include "handler/Handler.h"
#include "handler/HandlerConfiguration.h"
#include "logging/Category.h"
@@ -60,7 +60,7 @@ bool Agent::CATCH_ALL_PROP_DEFAULT = false;
Agent::Agent()
{
- m_authTypes.insert("shibboleth");
+ m_authTypes = {"shibboleth"};
}
Agent::~Agent()
@@ -120,133 +120,6 @@ long Agent::handleError(Category& log, SPRequest& request, const Session* sessio
return request.sendResponse(msg, richEx ? richEx->getStatusCode() : HTTPResponse::SHIBSP_HTTP_STATUS_ERROR);
}
-void Agent::clearHeaders(SPRequest& request) const {
- request.clearHeader("Shib-Cookie-Name", "HTTP_SHIB_COOKIE_NAME");
- request.clearHeader("Shib-Session-ID", "HTTP_SHIB_SESSION_ID");
- request.clearHeader("Shib-Session-Index", "HTTP_SHIB_SESSION_INDEX");
- request.clearHeader("Shib-Session-Expires", "HTTP_SHIB_SESSION_EXPIRES");
- request.clearHeader("Shib-Session-Inactivity", "HTTP_SHIB_SESSION_INACTIVITY");
- request.clearHeader("Shib-Identity-Provider", "HTTP_SHIB_IDENTITY_PROVIDER");
- request.clearHeader("Shib-Authentication-Method", "HTTP_SHIB_AUTHENTICATION_METHOD");
- request.clearHeader("Shib-Authentication-Instant", "HTTP_SHIB_AUTHENTICATION_INSTANT");
- request.clearHeader("Shib-AuthnContext-Class", "HTTP_SHIB_AUTHNCONTEXT_CLASS");
- request.clearHeader("Shib-AuthnContext-Decl", "HTTP_SHIB_AUTHNCONTEXT_DECL");
- request.clearHeader("Shib-Assertion-Count", "HTTP_SHIB_ASSERTION_COUNT");
- request.clearHeader("Shib-Handler", "HTTP_SHIB_HANDLER");
- request.clearHeader("REMOTE_USER", "HTTP_REMOTE_USER");
- // TODO: Redo the handling of attribute headers in the code, likely supplanting all of the above...
- //request.clearAttributeHeaders();
-}
-
-void Agent::exportAttributes(SPRequest& request, const Session* session) const {
-
- RequestMapper::Settings settings = request.getRequestSettings();
-
- const char* enc = settings.first->getString("encoding");
- if (enc && strcmp(enc, "URL"))
- throw ConfigurationException(string("Unsupported value for 'encoding' content setting: ") + enc);
-
- const URLEncoder& encoder = AgentConfig::getConfig().getURLEncoder();
-
- // Default delimiter is semicolon but is now configurable.
- const char* delim = settings.first->getString("attributeValueDelimiter", ";");
- size_t delim_len = strlen(delim);
-
- bool exportDups = settings.first->getBool("exportDuplicateValues", true);
- const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
-
- // Default export strategy will include duplicates.
- if (exportDups) {
- for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
- if (a->second->isInternal())
- continue;
- string header(request.getSecureHeader(a->first.c_str()));
- const vector<string>& vals = a->second->getSerializedValues();
- for (vector<string>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
- if (!header.empty())
- header += delim;
- if (enc) {
- // If URL-encoding, any semicolons will get escaped anyway.
- header += encoder.encode(v->c_str());
- }
- else {
- string::size_type pos = v->find(delim, string::size_type(0));
- if (pos != string::npos) {
- string value(*v);
- for (; pos != string::npos; pos = value.find(delim, pos)) {
- value.insert(pos, "\\");
- pos += delim_len + 1;
- }
- header += value;
- }
- else {
- header += (*v);
- }
- }
- }
- request.setHeader(a->first.c_str(), header.c_str());
- }
- }
- else {
- // Capture values in a map of sets to check for duplicates on the fly.
- map< string,set<string> > valueMap;
- for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
- if (a->second->isInternal())
- continue;
- const vector<string>& vals = a->second->getSerializedValues();
- valueMap[a->first].insert(vals.begin(), vals.end());
- }
-
- // Export the mapped sets to the headers.
- for (map< string,set<string> >::const_iterator deduped = valueMap.begin(); deduped != valueMap.end(); ++deduped) {
- string header;
- for (set<string>::const_iterator v = deduped->second.begin(); v != deduped->second.end(); ++v) {
- if (!header.empty())
- header += delim;
- if (enc) {
- // If URL-encoding, any semicolons will get escaped anyway.
- header += encoder.encode(v->c_str());
- }
- else {
- string::size_type pos = v->find(delim, string::size_type(0));
- if (pos != string::npos) {
- string value(*v);
- for (; pos != string::npos; pos = value.find(delim, pos)) {
- value.insert(pos, "\\");
- pos += delim_len + 1;
- }
- header += value;
- }
- else {
- header += (*v);
- }
- }
- }
- request.setHeader(deduped->first.c_str(), header.c_str());
- }
- }
-
- // Check for REMOTE_USER.
- bool remoteUserSet = false;
- vector<string> dummy;
- const vector<string>& rmids = dummy; // app.getRemoteUserAttributeIds(); TODO: re implement this elsewhere
- for (vector<string>::const_iterator rmid = rmids.begin(); !remoteUserSet && rmid != rmids.end(); ++rmid) {
- pair<multimap<string,const Attribute*>::const_iterator,multimap<string,const Attribute*>::const_iterator> matches =
- attributes.equal_range(*rmid);
- for (; matches.first != matches.second; ++matches.first) {
- const vector<string>& vals = matches.first->second->getSerializedValues();
- if (!vals.empty()) {
- if (enc)
- request.setRemoteUser(encoder.encode(vals.front().c_str()).c_str());
- else
- request.setRemoteUser(vals.front().c_str());
- remoteUserSet = true;
- break;
- }
- }
- }
-}
-
pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
{
Category& log = Category::getInstance(SHIBSP_LOGCAT ".Agent");
@@ -305,8 +178,9 @@ pair<bool,long> Agent::doAuthentication(SPRequest& request, bool handler) const
return make_pair(true, request.returnDecline());
}
- // Fix for secadv 20050901
- clearHeaders(request);
+ request.getAgent().getAttributeConfiguration(
+ request.getRequestSettings().first->getString("attributeConfigID")
+ ).clearHeaders(request);
bool sessionExists = false;
try {
@@ -486,8 +360,8 @@ pair<bool,long> Agent::doExport(SPRequest& request, bool requireSession) const
request.setHeader("Shib-Session-ID", session->getID());
request.setHeader("Shib-Application-ID", session->getApplicationID());
- // Check for export of "standard" variables.
- request.setHeader( "Shib-Session-Expires", boost::lexical_cast<string>(session->getExpiration()).c_str());
+ unsigned int lifetime = settings.first->getUnsignedInt("lifetime", 28800);
+ request.setHeader( "Shib-Session-Expires", boost::lexical_cast<string>(session->getCreation() + lifetime).c_str());
unsigned int timeout = settings.first->getUnsignedInt("timeout", 3600);
if (timeout > 0) {
request.setHeader( "Shib-Session-Inactivity", boost::lexical_cast<string>(session->getLastAccess() + timeout).c_str());
@@ -501,7 +375,9 @@ pair<bool,long> Agent::doExport(SPRequest& request, bool requireSession) const
}
// Export the attributes.
- exportAttributes(request, session);
+ request.getAgent().getAttributeConfiguration(
+ request.getRequestSettings().first->getString("attributeConfigID")
+ ).exportAttributes(request, *session);
return make_pair(false,0L);
}
diff --git a/shibsp/Agent.h b/shibsp/Agent.h
index d0294d37..6328f9ba 100644
--- a/shibsp/Agent.h
+++ b/shibsp/Agent.h
@@ -28,6 +28,7 @@
namespace shibsp {
+ class SHIBSP_API AttributeConfiguration;
class SHIBSP_API Category;
class SHIBSP_API Handler;
class SHIBSP_API HandlerConfiguration;
@@ -101,7 +102,18 @@ namespace shibsp {
*
* @return the matching configuration
*/
- virtual HandlerConfiguration& getHandlerConfiguration(const char* id=nullptr) const=0;
+ virtual const HandlerConfiguration& getHandlerConfiguration(const char* id=nullptr) const=0;
+
+ /**
+ * Gets the identified AttributeConfiguration.
+ *
+ * <p>If no matching configurationn is found, an exception is raised.</p>
+ *
+ * @param id identifier for configuration (null is assumed to be the default)
+ *
+ * @return the matching configuration
+ */
+ virtual const AttributeConfiguration& getAttributeConfiguration(const char* id=nullptr) const=0;
/**
* Enforces requirements for an authenticated session.
@@ -175,9 +187,6 @@ namespace shibsp {
std::exception* ex=nullptr,
bool mayRedirect=true
) const;
- void clearHeaders(SPRequest& request) const;
- void exportAttributes(SPRequest& request, const Session* session) const;
-
};
#if defined (_MSC_VER)
diff --git a/shibsp/Makefile.am b/shibsp/Makefile.am
index 1bb36701..e9cb3969 100644
--- a/shibsp/Makefile.am
+++ b/shibsp/Makefile.am
@@ -36,9 +36,7 @@ libshibspinclude_HEADERS = \
version.h
attrinclude_HEADERS = \
- attribute/Attribute.h \
- attribute/ScopedAttribute.h \
- attribute/SimpleAttribute.h
+ attribute/AttributeConfiguration.h
handinclude_HEADERS = \
handler/AbstractHandler.h \
@@ -100,9 +98,7 @@ libshibsp_la_SOURCES = \
Agent.cpp \
exceptions.cpp \
version.cpp \
- attribute/Attribute.cpp \
- attribute/SimpleAttribute.cpp \
- attribute/ScopedAttribute.cpp \
+ attribute/impl/DefaultAttributeConfiguration.cpp \
handler/impl/AbstractHandler.cpp \
handler/impl/AdminLogoutInitiator.cpp \
handler/impl/AttributeCheckerHandler.cpp \
diff --git a/shibsp/attribute/Attribute.cpp b/shibsp/attribute/Attribute.cpp
deleted file mode 100644
index b5b07c4b..00000000
--- a/shibsp/attribute/Attribute.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * UCAID licenses this file to you 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.
- */
-
-/**
- * shibsp/attribute/Attribute.cpp
- *
- * A resolved attribute.
- */
-
-#include "internal.h"
-#include "exceptions.h"
-#include "attribute/SimpleAttribute.h"
-#include "util/SPConstants.h"
-
-using namespace shibsp;
-using namespace std;
-
-namespace shibsp {
- SHIBSP_DLLLOCAL Attribute* SimpleAttributeFactory(DDF& in);
- SHIBSP_DLLLOCAL Attribute* ScopedAttributeFactory(DDF& in);
-};
-
-void shibsp::registerAttributeFactories()
-{
- Attribute::registerFactory("", SimpleAttributeFactory);
- Attribute::registerFactory("Simple", SimpleAttributeFactory);
- Attribute::registerFactory("Scoped", ScopedAttributeFactory);
-}
-
-map<string,Attribute::AttributeFactory*> Attribute::m_factoryMap;
-
-void Attribute::registerFactory(const char* type, AttributeFactory* factory)
-{
- m_factoryMap[type] = factory;
-}
-
-void Attribute::deregisterFactory(const char* type)
-{
- m_factoryMap.erase(type);
-}
-
-void Attribute::deregisterFactories()
-{
- m_factoryMap.clear();
-}
-
-Attribute::Attribute(const vector<string>& ids) : m_id(ids), m_caseSensitive(true), m_internal(false)
-{
-}
-
-Attribute::Attribute(DDF& in) : m_caseSensitive(in["case_insensitive"].isnull()), m_internal(!in["internal"].isnull())
-{
- const char* id = in.first().name();
- if (id && *id)
- m_id.push_back(id);
- else
- throw AttributeException("No id found in marshalled attribute content.");
- DDF aliases = in["aliases"];
- if (aliases.islist()) {
- DDF alias = aliases.first();
- while (alias.isstring()) {
- m_id.push_back(alias.string());
- alias = aliases.next();
- }
- }
-}
-
-Attribute::~Attribute()
-{
-}
-
-const char* Attribute::getId() const
-{
- return m_id.front().c_str();
-}
-
-const vector<string>& Attribute::getAliases() const
-{
- return m_id;
-}
-
-vector<string>& Attribute::getAliases()
-{
- return m_id;
-}
-
-void Attribute::setCaseSensitive(bool caseSensitive)
-{
- m_caseSensitive = caseSensitive;
-}
-
-void Attribute::setInternal(bool internal)
-{
- m_internal = internal;
-}
-
-bool Attribute::isCaseSensitive() const
-{
- return m_caseSensitive;
-}
-
-bool Attribute::isInternal() const
-{
- return m_internal;
-}
-
-size_t Attribute::valueCount() const
-{
- return m_serialized.size();
-}
-
-const vector<string>& Attribute::getSerializedValues() const
-{
- return m_serialized;
-}
-
-const char* Attribute::getString(size_t index) const
-{
- return m_serialized[index].c_str();
-}
-
-const char* Attribute::getScope(size_t index) const
-{
- return nullptr;
-}
-
-void Attribute::removeValue(size_t index)
-{
- if (index < m_serialized.size())
- m_serialized.erase(m_serialized.begin() + index);
-}
-
-DDF Attribute::marshall() const
-{
- DDF ddf(nullptr);
- ddf.structure().addmember(m_id.front().c_str()).list();
- if (!m_caseSensitive)
- ddf.addmember("case_insensitive");
- if (m_internal)
- ddf.addmember("internal");
- if (m_id.size() > 1) {
- DDF alias;
- DDF aliases = ddf.addmember("aliases").list();
- for (std::vector<std::string>::const_iterator a = m_id.begin() + 1; a != m_id.end(); ++a) {
- alias = DDF(nullptr).string(a->c_str());
- aliases.add(alias);
- }
- }
- return ddf;
-}
-
-Attribute* Attribute::unmarshall(DDF& in)
-{
- map<string,AttributeFactory*>::const_iterator i = m_factoryMap.find(in.name() ? in.name() : "");
- if (i == m_factoryMap.end())
- throw AttributeException(string("No registered factory for Attribute of type ") + in.name());
- return (i->second)(in);
-}
diff --git a/shibsp/attribute/Attribute.h b/shibsp/attribute/Attribute.h
deleted file mode 100644
index 346c6462..00000000
--- a/shibsp/attribute/Attribute.h
+++ /dev/null
@@ -1,236 +0,0 @@
-/**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * UCAID licenses this file to you 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.
- */
-
-/**
- * @file shibsp/attribute/Attribute.h
- *
- * A resolved attribute.
- */
-
-#ifndef __shibsp_attribute_h__
-#define __shibsp_attribute_h__
-
-#include <shibsp/remoting/ddf.h>
-
-#include <map>
-#include <string>
-#include <vector>
-
-namespace shibsp {
-
-#if defined (_MSC_VER)
- #pragma warning( push )
- #pragma warning( disable : 4251 )
-#endif
-
- /**
- * A resolved attribute.
- *
- * <p>Resolved attributes are a neutral construct that represent both simple and
- * complex attribute data structures that might be found in SAML assertions
- * or obtained from other sources.
- *
- * <p>Attributes consist of an id/name that is locally unique (that is, unique to a
- * configuration at any given point in time) and zero or more values. Values can
- * be of any type or structure, but will generally be made available to applications
- * only if a serialized string form exists. More complex values can be used with
- * access control plugins and other components that understand them, however.
- */
- class SHIBSP_API Attribute
- {
- MAKE_NONCOPYABLE(Attribute);
- protected:
- /**
- * Constructor
- *
- * @param ids array with primary identifier in first position, followed by any aliases
- */
- Attribute(const std::vector<std::string>& ids);
-
- /**
- * Constructs based on a remoted Attribute.
- *
- * <p>This allows Attribute objects to be recreated after marshalling.
- * The DDF supplied must be a struct containing a single list member named
- * with the Attribute's "id" and containing the values.
- *
- * @param in input object containing marshalled Attribute
- */
- Attribute(DDF& in);
-
- /**
- * Maintains a copy of serialized attribute values, when possible.
- *
- * <p>Implementations should maintain the array when values are added or removed.
- */
- mutable std::vector<std::string> m_serialized;
-
- public:
- virtual ~Attribute();
-
- /**
- * Returns the Attribute identifier.
- *
- * @return the Attribute identifier
- */
- const char* getId() const;
-
- /**
- * Returns all of the effective names for the Attribute.
- *
- * @return immutable array of identifiers, with the primary ID in the first position
- */
- const std::vector<std::string>& getAliases() const;
-
- /**
- * Returns all of the effective names for the Attribute.
- *
- * @return mutable array of identifiers, with the primary ID in the first position
- */
- std::vector<std::string>& getAliases();
-
- /**
- * Sets whether case sensitivity should apply to basic value comparisons.
- *
- * @param caseSensitive true iff value comparisons should be case sensitive
- */
- void setCaseSensitive(bool caseSensitive);
-
- /**
- * Sets whether the attribute should be exported for CGI use.
- *
- * @param internal true iff the attribute should <strong>NOT</strong> be exported
- */
- void setInternal(bool internal);
-
- /**
- * Indicates whether case sensitivity should apply to basic value comparisons.
- *
- * @return true iff value comparisons should be case sensitive
- */
- bool isCaseSensitive() const;
-
- /**
- * Indicates whether the attribute should be exported for CGI use.
- *
- * @return true iff the attribute should <strong>NOT</strong> be exported
- */
- bool isInternal() const;
-
- /**
- * Returns the number of values.
- *
- * @return number of values
- */
- virtual size_t valueCount() const;
-
- /**
- * Returns serialized Attribute values encoded as UTF-8 strings.
- *
- * @return an immutable vector of values
- */
- virtual const std::vector<std::string>& getSerializedValues() const;
-
- /**
- * Informs the Attribute that values have changed and any serializations
- * must be cleared.
- */
- virtual void clearSerializedValues()=0;
-
- /**
- * Gets the string equivalent of the value at the specified position (starting from zero).
- *
- * @param index position of value
- * @return the specified value in its "string" form, or nullptr if undefined
- */
- virtual const char* getString(size_t index) const;
-
- /**
- * Gets the "scope" of the value at the specified position (starting from zero).
- *
- * @param index position of value
- * @return the specified value's "scope", or nullptr if attribute is unscoped
- */
- virtual const char* getScope(size_t index) const;
-
- /**
- * Removes the value at the specified position (starting from zero).
- *
- * @param index position of value to remove
- */
- virtual void removeValue(size_t index);
-
- /**
- * Marshalls an Attribute for remoting.
- *
- * <p>This allows Attribute objects to be communicated across process boundaries
- * without excess XML parsing. The DDF returned must be a struct containing
- * a single list member named with the Attribute's "id". The name of the struct
- * should contain the registered name of the Attribute implementation.
- */
- virtual DDF marshall() const;
-
- /**
- * Unmarshalls a remoted Attribute.
- *
- * @param in remoted Attribute data
- * @return a resolved Attribute of the proper subclass
- */
- static Attribute* unmarshall(DDF& in);
-
- /** A function that unmarshalls remoted data into the proper Attribute subclass. */
- typedef Attribute* AttributeFactory(DDF& in);
-
- /**
- * Registers an AttributeFactory function for a given attribute "type".
- *
- * @param type string used at the root of remoted Attribute structures
- * @param factory factory function
- */
- static void registerFactory(const char* type, AttributeFactory* factory);
-
- /**
- * Deregisters an AttributeFactory function for a given attribute "type".
- *
- * @param type string used at the root of remoted Attribute structures
- */
- static void deregisterFactory(const char* type);
-
- /**
- * Clears the map of factories.
- */
- static void deregisterFactories();
-
- private:
- static std::map<std::string,AttributeFactory*> m_factoryMap;
- std::vector<std::string> m_id;
- bool m_caseSensitive,m_internal;
- };
-
-#if defined (_MSC_VER)
- #pragma warning( pop )
-#endif
-
- /** Registers built-in Attribute types into the runtime. */
- void registerAttributeFactories();
-
-};
-
-#endif /* __shibsp_attribute_h__ */
diff --git a/shibsp/attribute/AttributeConfiguration.h b/shibsp/attribute/AttributeConfiguration.h
new file mode 100644
index 00000000..219b34de
--- /dev/null
+++ b/shibsp/attribute/AttributeConfiguration.h
@@ -0,0 +1,168 @@
+/**
+ * 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.
+ */
+
+/**
+ * @file shibsp/attribute/AttributeConfiguration.h
+ *
+ * Interface to settings and functionality for attribute data manipulation.
+ */
+
+#ifndef __shibsp_attrconfig_h__
+#define __shibsp_attrconfig_h__
+
+#include <shibsp/util/PropertySet.h>
+
+#include <memory>
+#include <set>
+
+#ifdef SHIBSP_USE_BOOST_REGEX
+# include <boost/regex_fwd.hpp>
+namespace regexp = boost;
+#else
+# include <regex>
+namespace regexp = std;
+#endif
+
+namespace shibsp {
+
+ class SHIBSP_API DDF;
+ class SHIBSP_API Session;
+ class SHIBSP_API SPRequest;
+
+ /**
+ * Interface to settings and functionality for attribute data manipulation.
+ *
+ * <p>This isn't an especially coherent interface but it centralizes config
+ * settings for a lot of what used to be scattered around the code base.</p>
+ */
+ class SHIBSP_API AttributeConfiguration : public virtual PropertySet
+ {
+ MAKE_NONCOPYABLE(AttributeConfiguration);
+ protected:
+ AttributeConfiguration();
+
+ public:
+ virtual ~AttributeConfiguration();
+
+ /** Used in ACL implementations to enforce legacy authnContextClassRef rule. */
+ static const char LEGACY_CLASSREF_ATTRIBUTE_PROP_PATH[];
+ static const char LEGACY_CLASSREF_ATTRIBUTE_PROP_DEFAULT[];
+
+ /** Used in ACL implementation to enforce legacy time-since-authn rule. */
+ static const char LEGACY_AUTHTIME_ATTRIBUTE_PROP_PATH[];
+ static const char LEGACY_AUTHTIME_ATTRIBUTE_PROP_DEFAULT[];
+
+ /**
+ * Post-process a collection of attributes and values from the hub for use by agent code.
+ *
+ * <p>This handles serialization of data not already in simple "string" form so that
+ * subsequent usage is simpler. In particular it allows scoped data to be recombined with
+ * an agent-controlled delimiter.</p>
+ *
+ * <p>If true is returned, the resulting object must be a list. Each element must
+ * also be a list with a non-empty name and at least one non-empty string value.
+ * Any non-conforming data must be removed from the object.</p>
+ *
+ * @param attributes a list object containing the attributes in hub-supplied format
+ *
+ * @return true iff at least one attribute exists after processing and the data is in
+ * the expected form
+ */
+ virtual bool processAttributes(DDF& attributes) const=0;
+
+ /**
+ * Gets whether an attribute is configured to be "case sensitive" for value comparison
+ * purposes.
+ *
+ * @param attributeID the ID of the attribute to check
+ *
+ * @return true iff the attribute's values should be handled with case sensitivity
+ */
+ virtual bool isCaseSensitive(const char* attributeID) const=0;
+
+ /**
+ * "Clears" any headers controlled by the agent to reserve them for trusted attribute export.
+ *
+ * <p>This method typically operates only when it detects the request will include header
+ * support, which is strongly discouraged.</p>
+ *
+ * @param request request to "clear"
+ */
+ virtual void clearHeaders(SPRequest& request) const=0;
+
+ /**
+ * Exports data from the session into variables/headers in the supplied request.
+ *
+ * @param request request to export into
+ * @param locked session to pull data from
+ */
+ virtual void exportAttributes(SPRequest& request, const Session& session) const=0;
+
+ /**
+ * Tests whether a given attribute in a session contains a matching value.
+ *
+ * <p>It is an internal implementation detail as to the relevance of case to this
+ * comparison.</p>
+ *
+ * @param session locked session to pull from
+ * @param attributeId the attribute to check
+ * @param value the value to check for
+ *
+ * @return true iff the value "matched"
+ */
+ virtual bool hasMatchingValue(const Session& session, const char* attributeId, const char* value) const=0;
+
+ /**
+ * Tests whether a given attribute in a session contains a matching value from a set.
+ *
+ * <p>It is an internal implementation detail as to the relevance of case to this
+ * comparison.</p>
+ *
+ * @param session locked session to pull from
+ * @param attributeId the attribute to check
+ * @param values the set of values to check for
+ *
+ * @return true iff at least one value "matched"
+ */
+ virtual bool hasMatchingValue(
+ const Session& session, const char* attributeId, const std::set<std::string>& values
+ ) const=0;
+
+ /**
+ * Tests whether a given attribute in a session contains a value matching a supplied
+ * regular expression.
+ *
+ * @param session locked session to pull from
+ * @param attributeId the attribute to check
+ * @param values the set of values to check for
+ *
+ * @return true iff at least one value "matched"
+ */
+ virtual bool hasMatchingValue(
+ const Session& session, const char* attributeId, const regexp::regex& expression
+ ) const=0;
+
+ /**
+ * Create a new AttributeConfiguration based on the supplied configuration file.
+ *
+ * @param pathname configuration file
+ *
+ * @return the corresponding AttributeConfiguration
+ */
+ static std::unique_ptr<AttributeConfiguration> newAttributeConfiguration(const char* pathname);
+ };
+
+};
+
+#endif /* __shibsp_attrconfig_h__ */
diff --git a/shibsp/attribute/ScopedAttribute.cpp b/shibsp/attribute/ScopedAttribute.cpp
deleted file mode 100644
index 5632139e..00000000
--- a/shibsp/attribute/ScopedAttribute.cpp
+++ /dev/null
@@ -1,130 +0,0 @@
-/**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * UCAID licenses this file to you 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.
- */
-
-/**
- * ScopedAttribute.cpp
- *
- * An Attribute whose values are relations of a value and a scope.
- */
-
-#include "internal.h"
-#include "attribute/ScopedAttribute.h"
-
-using namespace shibsp;
-using namespace std;
-
-namespace shibsp {
- SHIBSP_DLLLOCAL Attribute* ScopedAttributeFactory(DDF& in) {
- return new ScopedAttribute(in);
- }
-};
-
-ScopedAttribute::ScopedAttribute(const vector<string>& ids, char delimeter) : Attribute(ids), m_delimeter(delimeter)
-{
-}
-
-ScopedAttribute::ScopedAttribute(DDF& in) : Attribute(in), m_delimeter('@')
-{
- DDF val = in["_delimeter"];
- if (val.isint())
- m_delimeter = static_cast<char>(val.integer());
- val = in.first().first();
- while (!val.isnull()) {
- // There are two serializations supported. The new one is in 2.5.1 and fixes SPPCPP-504.
- // The original is the second branch and was vulnerable to non-ASCII characters in the value.
- // Supporting both means at least minimal support for rolling upgrades if a shibd instance is
- // shared.
- if (val.islist() && val.integer() == 2) {
- m_values.push_back(make_pair(string(val.first().string()), string(val.last().string())));
- }
- else if (val.name() && val.string()) {
- m_values.push_back(make_pair(string(val.name()), string(val.string())));
- }
- val = in.first().next();
- }
-}
-
-ScopedAttribute::~ScopedAttribute()
-{
-}
-
-vector< pair<string,string> >& ScopedAttribute::getValues()
-{
- return m_values;
-}
-
-const vector< pair<string,string> >& ScopedAttribute::getValues() const
-{
- return m_values;
-}
-
-size_t ScopedAttribute::valueCount() const
-{
- return m_values.size();
-}
-
-void ScopedAttribute::clearSerializedValues()
-{
- m_serialized.clear();
-}
-
-const char* ScopedAttribute::getString(size_t index) const
-{
- return m_values[index].first.c_str();
-}
-
-const char* ScopedAttribute::getScope(size_t index) const
-{
- return m_values[index].second.c_str();
-}
-
-void ScopedAttribute::removeValue(size_t index)
-{
- Attribute::removeValue(index);
- if (index < m_values.size())
- m_values.erase(m_values.begin() + index);
-}
-
-const vector<string>& ScopedAttribute::getSerializedValues() const
-{
- if (m_serialized.empty()) {
- for (vector< pair<string,string> >::const_iterator i=m_values.begin(); i!=m_values.end(); ++i)
- m_serialized.push_back(i->first + m_delimeter + i->second);
- }
- return Attribute::getSerializedValues();
-}
-
-DDF ScopedAttribute::marshall() const
-{
- DDF ddf = Attribute::marshall();
- ddf.name("Scoped");
- if (m_delimeter != '@')
- ddf.addmember("_delimeter").integer(m_delimeter);
- DDF vlist = ddf.first();
- for (vector< pair<string,string> >::const_iterator i=m_values.begin(); i!=m_values.end(); ++i) {
- DDF one = DDF(nullptr).string(i->first.c_str());
- DDF two = DDF(nullptr).string(i->second.c_str());
- DDF val = DDF(nullptr).list();
- val.add(one);
- val.add(two);
- vlist.add(val);
- }
- return ddf;
-}
diff --git a/shibsp/attribute/ScopedAttribute.h b/shibsp/attribute/ScopedAttribute.h
deleted file mode 100644
index 8e2e853d..00000000
--- a/shibsp/attribute/ScopedAttribute.h
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * UCAID licenses this file to you 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.
- */
-
-/**
- * @file shibsp/attribute/ScopedAttribute.h
- *
- * An Attribute whose values are relations of a value and a scope.
- */
-
-#ifndef __shibsp_scopedattr_h__
-#define __shibsp_scopedattr_h__
-
-#include <shibsp/attribute/Attribute.h>
-
-namespace shibsp {
-
-#if defined (_MSC_VER)
- #pragma warning( push )
- #pragma warning( disable : 4251 )
-#endif
-
- /**
- * An Attribute whose values are relations of a value and a scope.
- *
- * <p>In practice, scoped attributes are simple pairs of strings instead
- * of a single string. They can be expressed as a string easily using a delimeter,
- * typically an '@' symbol. The scope concept allows certain kinds of filtering to
- * be performed more intelligently and efficiently, although not all scoped
- * attributes can be effectively filtered (e.g. if the set of scope values is
- * unconstrained).
- */
- class SHIBSP_API ScopedAttribute : public Attribute
- {
- public:
- /**
- * Constructor.
- *
- * @param ids array with primary identifier in first position, followed by any aliases
- * @param delimeter value/scope delimeter when serializing
- */
- ScopedAttribute(const std::vector<std::string>& ids, char delimeter='@');
-
- /**
- * Constructs based on a remoted ScopedAttribute.
- *
- * @param in input object containing marshalled ScopedAttribute
- */
- ScopedAttribute(DDF& in);
-
- virtual ~ScopedAttribute();
-
- /**
- * Returns the set of values encoded as UTF-8 strings.
- *
- * <p>Each compound value is a pair containing the simple value and the scope.
- *
- * @return a mutable vector of the values
- */
- std::vector< std::pair<std::string,std::string> >& getValues();
-
- /**
- * Returns the set of values encoded as UTF-8 strings.
- *
- * <p>Each compound value is a pair containing the simple value and the scope.
- *
- * @return an immutable vector of the values
- */
- const std::vector< std::pair<std::string,std::string> >& getValues() const;
-
- // Virtual function overrides.
- size_t valueCount() const;
- void clearSerializedValues();
- const char* getString(size_t index) const;
- const char* getScope(size_t index) const;
- void removeValue(size_t index);
- const std::vector<std::string>& getSerializedValues() const;
- DDF marshall() const;
-
- private:
- char m_delimeter;
- std::vector< std::pair<std::string,std::string> > m_values;
- };
-
-#if defined (_MSC_VER)
- #pragma warning( pop )
-#endif
-
-};
-
-#endif /* __shibsp_scopedattr_h__ */
diff --git a/shibsp/attribute/SimpleAttribute.cpp b/shibsp/attribute/SimpleAttribute.cpp
deleted file mode 100644
index ac29fb1f..00000000
--- a/shibsp/attribute/SimpleAttribute.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * UCAID licenses this file to you 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.
- */
-
-/**
- * SimpleAttribute.cpp
- *
- * An Attribute whose values are simple strings.
- */
-
-#include "internal.h"
-#include "attribute/SimpleAttribute.h"
-
-using namespace shibsp;
-using namespace std;
-
-namespace shibsp {
- SHIBSP_DLLLOCAL Attribute* SimpleAttributeFactory(DDF& in) {
- return new SimpleAttribute(in);
- }
-};
-
-SimpleAttribute::SimpleAttribute(const vector<string>& ids) : Attribute(ids)
-{
-}
-
-SimpleAttribute::SimpleAttribute(DDF& in) : Attribute(in)
-{
- DDF val = in.first().first();
- while (val.string()) {
- m_serialized.push_back(val.string());
- val = in.first().next();
- }
-}
-
-SimpleAttribute::~SimpleAttribute()
-{
-}
-
-vector<string>& SimpleAttribute::getValues()
-{
- return m_serialized;
-}
-
-void SimpleAttribute::clearSerializedValues()
-{
- // Do nothing, since our values are already serialized.
-}
-
-DDF SimpleAttribute::marshall() const
-{
- DDF ddf = Attribute::marshall();
- DDF vlist = ddf.first();
- for (vector<string>::const_iterator i=m_serialized.begin(); i!=m_serialized.end(); ++i)
- vlist.add(DDF(nullptr).string(i->c_str()));
- return ddf;
-}
diff --git a/shibsp/attribute/SimpleAttribute.h b/shibsp/attribute/SimpleAttribute.h
deleted file mode 100644
index 55770c0c..00000000
--- a/shibsp/attribute/SimpleAttribute.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Licensed to the University Corporation for Advanced Internet
- * Development, Inc. (UCAID) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for
- * additional information regarding copyright ownership.
- *
- * UCAID licenses this file to you 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.
- */
-
-/**
- * @file shibsp/attribute/SimpleAttribute.h
- *
- * An Attribute whose values are simple strings.
- */
-
-#ifndef __shibsp_simpattr_h__
-#define __shibsp_simpattr_h__
-
-#include <shibsp/attribute/Attribute.h>
-
-namespace shibsp {
-
- /**
- * An Attribute whose values are simple strings.
- */
- class SHIBSP_API SimpleAttribute : public Attribute
- {
- public:
- /**
- * Constructor.
- *
- * @param ids array with primary identifier in first position, followed by any aliases
- */
- SimpleAttribute(const std::vector<std::string>& ids);
-
- /**
- * Constructs based on a remoted SimpleAttribute.
- *
- * @param in input object containing marshalled SimpleAttribute
- */
- SimpleAttribute(DDF& in);
-
- virtual ~SimpleAttribute();
-
- /**
- * Returns the set of values encoded as UTF-8 strings.
- *
- * <p>For simple values, the serialized form is just the actual string,
- * so the value array can be directly manipulated.
- *
- * @return a mutable vector of the values
- */
- std::vector<std::string>& getValues();
-
- // Virtual function overrides.
- void clearSerializedValues();
- DDF marshall() const;
- };
-
-};
-
-#endif /* __shibsp_simpattr_h__ */
diff --git a/shibsp/attribute/impl/DefaultAttributeConfiguration.cpp b/shibsp/attribute/impl/DefaultAttributeConfiguration.cpp
new file mode 100644
index 00000000..0802284d
--- /dev/null
+++ b/shibsp/attribute/impl/DefaultAttributeConfiguration.cpp
@@ -0,0 +1,431 @@
+/**
+ * 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.
+ */
+
+/**
+ * attribute/impl/DefaultAttributeConfiguration.cpp
+ *
+ * Default ptree-based AttributeConfiguration implementation.
+ */
+
+#include "internal.h"
+
+#include "exceptions.h"
+#include "AgentConfig.h"
+#include "RequestMapper.h"
+#include "SPRequest.h"
+#include "attribute/Attribute.h"
+#include "attribute/AttributeConfiguration.h"
+#include "logging/Category.h"
+#include "remoting/ddf.h"
+#include "session/SessionCache.h"
+#include "util/BoostPropertySet.h"
+#include "util/Misc.h"
+#include "util/URLEncoder.h"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/ini_parser.hpp>
+
+#include <map>
+#include <set>
+#include <string>
+#include <stdexcept>
+
+#ifdef SHIBSP_USE_BOOST_REGEX
+# include <boost/regex.hpp>
+#endif
+
+using namespace shibsp;
+using namespace boost::property_tree;
+using namespace std;
+
+#ifndef HAVE_STRCASECMP
+# define strcasecmp _stricmp
+#endif
+
+namespace {
+
+ class DefaultAttributeConfiguration : public virtual AttributeConfiguration, public virtual BoostPropertySet {
+ public:
+ DefaultAttributeConfiguration(const char* pathname);
+ ~DefaultAttributeConfiguration() {}
+
+ bool processAttributes(DDF& attributes) const;
+ bool isCaseSensitive(const char* attributeID) const;
+ void clearHeaders(SPRequest& request) const;
+ void exportAttributes(SPRequest& request, const Session& session) const;
+ bool hasMatchingValue(const Session& session, const char* attributeId, const char* value) const;
+ bool hasMatchingValue(const Session& session, const char* attributeId, const set<string>& values) const;
+ bool hasMatchingValue(const Session& session, const char* attributeId, const regexp::regex& expression) const;
+
+ private:
+ const char* getFirstValue(const Session& session, const char* attributeId) const;
+
+ Category& m_log;
+ ptree m_pt;
+ bool m_urlEncoding,m_exportDuplicates;
+ // headers are tracked as Raw name, CGI name
+ map<string,pair<string,string>> m_mappings;
+ set<string> m_caseSensitiveIds;
+ };
+
+};
+
+const char AttributeConfiguration::LEGACY_CLASSREF_ATTRIBUTE_PROP_PATH[] = "legacyClassRefAttribute";
+const char AttributeConfiguration::LEGACY_CLASSREF_ATTRIBUTE_PROP_DEFAULT[] = "Shib-AuthnContext-Class";
+
+const char AttributeConfiguration::LEGACY_AUTHTIME_ATTRIBUTE_PROP_PATH[] = "legacyAuthnTimeAttribute";
+const char AttributeConfiguration::LEGACY_AUTHTIME_ATTRIBUTE_PROP_DEFAULT[] = "Shib-Authentication-Instant";
+
+
+AttributeConfiguration::AttributeConfiguration() {}
+
+AttributeConfiguration::~AttributeConfiguration() {}
+
+DefaultAttributeConfiguration::DefaultAttributeConfiguration(const char* pathname)
+ : m_log(Category::getInstance(SHIBSP_LOGCAT ".AttributeConfiguration")), m_urlEncoding(false)
+{
+ ini_parser::read_ini(pathname, m_pt);
+ load(m_pt);
+
+ split_to_container(m_caseSensitiveIds, getString("caseSensitiveAttributes", ""));
+
+ m_urlEncoding = !strcmp(getString("encoding", ""), "URL");
+ m_exportDuplicates = getBool("exportDuplicateValues", true);
+
+ boost::optional<ptree&> mappings = m_pt.get_child_optional("mappings");
+ if (!mappings) {
+ return;
+ }
+
+ for (auto& child : *mappings) {
+ if (!child.first.empty()) {
+ string alias(child.second.get_value(""));
+ if (alias.empty()) {
+ continue;
+ }
+
+ string transformed("HTTP_");
+ const char* pch = alias.c_str();
+ while (*pch) {
+ transformed += (isalnum(*pch) ? toupper(*pch) : '_');
+ pch++;
+ }
+
+ m_mappings[child.first] = make_pair(alias, transformed);
+ }
+ }
+
+ m_mappings["Shib-Application-ID"] = pair<string,string>("Shib-Application-ID", "HTTP_SHIB_APPLICATION_ID");
+ m_mappings["Shib-Session-ID"] = pair<string,string>("Shib-Session-ID", "HTTP_SHIB_SESSION_ID");
+ m_mappings["Shib-Session-Expires"] = pair<string,string>("Shib-Session-Expires", "HTTP_SHIB_SESSION_EXPIRES");
+ m_mappings["Shib-Session-Inactivity"] = pair<string,string>("Shib-Session-Inactivity", "HTTP_SHIB_SESSION_INACTIVITY");
+ m_mappings["Shib-Cookie-Name"] = pair<string,string>("Shib-Cookie-Name", "HTTP_SHIB_COOKIE_NAME");
+ m_mappings["REMOTE_USER"] = pair<string,string>("REMOTE_USER", "HTTP_REMOTE_USER");
+}
+
+unique_ptr<AttributeConfiguration> AttributeConfiguration::newAttributeConfiguration(const char* pathname)
+{
+ return unique_ptr<AttributeConfiguration>(new DefaultAttributeConfiguration(pathname));
+}
+
+bool DefaultAttributeConfiguration::processAttributes(DDF& attributes) const
+{
+ if (!attributes.islist()) {
+ m_log.warn("invalid data supplied for session attributes");
+ return false;
+ }
+
+ DDF attr = attributes.first();
+ while (!attr.isnull()) {
+ if (!attr.name() || !isalnum(*(attr.name()))) {
+ m_log.warn("invalid unnamed attribute in session data");
+ attr.destroy();
+ }
+ else if (!attr.islist()) {
+ m_log.warn("invalid attribute in session data, '%s' was not a list", attr.name());
+ attr.destroy();
+ }
+ else {
+ DDF value = attr.first();
+ while (!value.isnull()) {
+ if (value.isstring()) {
+ if (!value.string() || !(*(value.string()))) {
+ m_log.warn("removing null or empty string value from attribute '%s'", attr.name());
+ value.destroy();
+ }
+ }
+ else if (value.isstruct()) {
+ const char* lhs = value.getmember("value").string();
+ const char* scope = value.getmember("scope").string();
+ if (lhs && scope && *lhs && *scope) {
+ string s = string(lhs) + getString("scopeDelimiter", "@") + scope;
+ value.string(s.c_str());
+ } else {
+ value.destroy();
+ m_log.warn("attribute '%s' scoped value had a null or empty value or scope", attr.name());
+ }
+ }
+ else if (value.isint()) {
+ try {
+ string s(boost::lexical_cast<string>(attr.integer()));
+ value.string(s.c_str());
+ } catch (const boost::bad_lexical_cast&) {
+ value.destroy();
+ m_log.warn("attribute '%s' value could not be converted from int to string", attr.name());
+ }
+ }
+ else if (value.islong()) {
+ try {
+ string s(boost::lexical_cast<string>(attr.longinteger()));
+ value.string(s.c_str());
+ } catch (const boost::bad_lexical_cast&) {
+ value.destroy();
+ m_log.warn("attribute '%s' value could not be converted from int to string", attr.name());
+ }
+ }
+ else {
+ value.destroy();
+ m_log.warn("attribute '%s' value was not a supported type", attr.name());
+ }
+ }
+
+ value = attr.next();
+ }
+
+ attr = attributes.next();
+ }
+
+ if (attributes.first().isnull()) {
+ m_log.warn("no valid attributes remain in session after processing");
+ return false;
+ }
+ return true;
+}
+
+bool DefaultAttributeConfiguration::isCaseSensitive(const char* attributeID) const
+{
+ if (!attributeID) {
+ return false;
+ }
+ return m_caseSensitiveIds.count(attributeID) > 0;
+}
+
+void DefaultAttributeConfiguration::clearHeaders(SPRequest& request) const
+{
+ for (const auto& names : m_mappings) {
+ request.clearHeader(names.second.first.c_str(), names.second.second.c_str());
+ }
+}
+
+void DefaultAttributeConfiguration::exportAttributes(SPRequest& request, const Session& session) const
+{
+ RequestMapper::Settings settings = request.getRequestSettings();
+ const URLEncoder& encoder = AgentConfig::getConfig().getURLEncoder();
+
+ const char* delim = getString("attributeValueDelimiter", ";");
+ size_t delim_len = strlen(delim);
+
+ // Default export strategy will include duplicates.
+ if (m_exportDuplicates) {
+ for (const auto& a : session.getAttributes()) {
+
+ const auto& headerMapping = m_mappings.find(a.first);
+ if (headerMapping == m_mappings.end()) {
+ // TODO: Check for use of headers, then log this...no mapping for attribute...
+ // If headers not used, we'd export it as is.
+ continue;
+ }
+
+ string header(request.getSecureHeader(headerMapping->second.first.c_str()));
+
+ DDF vals = a.second; // cheap copy drops const qualifier
+ DDF v = vals.first();
+ while (!v.isnull()) {
+ if (!header.empty()) {
+ header += delim;
+ }
+
+ if (m_urlEncoding) {
+ // If URL-encoding, any semicolons will get escaped anyway.
+ header += encoder.encode(v.string());
+ }
+ else {
+ string serialized(v.string());
+ string::size_type pos = serialized.find(delim, string::size_type(0));
+ if (pos != string::npos) {
+ for (; pos != string::npos; pos = serialized.find(delim, pos)) {
+ serialized.insert(pos, "\\");
+ pos += delim_len + 1;
+ }
+ }
+ header += serialized;
+ }
+
+ v = vals.next();
+ }
+ request.setHeader(headerMapping->second.first.c_str(), header.c_str());
+ }
+ }
+ else {
+ // Capture values in a map of sets to check for duplicates on the fly.
+ map<string,set<string>> valueMap;
+ for (const auto& a : session.getAttributes()) {
+
+ const auto& headerMapping = m_mappings.find(a.first);
+ if (headerMapping == m_mappings.end()) {
+ // TODO: Check for use of headers, then log this...no mapping for attribute...
+ // If headers not used, we'd export it as is.
+ continue;
+ }
+
+ DDF vals = a.second; // cheap copy drops const qualifier
+ DDF v = vals.first();
+ set<string>& targetSet = valueMap[headerMapping->first];
+ while (vals.isnull()) {
+ targetSet.insert(v.string());
+ v = vals.next();
+ }
+ }
+
+ // Export the mapped sets to the headers.
+ for (const auto& deduped : valueMap) {
+ string header;
+ for (const string& v : deduped.second) {
+ if (!header.empty())
+ header += delim;
+ if (m_urlEncoding) {
+ // If URL-encoding, any semicolons will get escaped anyway.
+ header += encoder.encode(v.c_str());
+ }
+ else {
+ string::size_type pos = v.find(delim, string::size_type(0));
+ if (pos != string::npos) {
+ string value(v);
+ for (; pos != string::npos; pos = value.find(delim, pos)) {
+ value.insert(pos, "\\");
+ pos += delim_len + 1;
+ }
+ header += value;
+ }
+ else {
+ header += v;
+ }
+ }
+ }
+ request.setHeader(deduped.first.c_str(), header.c_str());
+ }
+ }
+
+ // Check for REMOTE_USER.
+ vector<string> rmids;
+ split_to_container(rmids, settings.first->getString("REMOTE_USER", ""));
+ for (const string& rmid : rmids) {
+ const char* firstVal = getFirstValue(session, rmid.c_str());
+ if (firstVal && *firstVal) {
+ if (m_urlEncoding)
+ request.setRemoteUser(encoder.encode(firstVal).c_str());
+ else
+ request.setRemoteUser(firstVal);
+ break;
+ }
+ }
+}
+
+bool DefaultAttributeConfiguration::hasMatchingValue(
+ const Session& session, const char* attributeId, const set<string>& values
+ ) const
+{
+ const auto& attr = session.getAttributes().find(attributeId);
+ if (attr == session.getAttributes().end()) {
+ return false;
+ }
+
+ bool caseSensitive = m_caseSensitiveIds.count(attributeId) > 0;
+
+ DDF val = const_cast<DDF&>(attr->second).first();
+ while (!val.isnull()) {
+ if (caseSensitive) {
+ // Can just search the set.
+ if (values.find(val.string()) != values.end()) {
+ return true;
+ }
+ } else {
+ // Have to loop over each set member for a case-insensitive comparison.
+ for (const string& candidate : values) {
+ if (!strcasecmp(val.string(), candidate.c_str())) {
+ return true;
+ }
+ }
+ }
+
+ val = const_cast<DDF&>(attr->second).next();
+ }
+
+ return false;
+}
+
+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()) {
+ return false;
+ }
+
+ bool caseSensitive = m_caseSensitiveIds.count(attributeId) > 0;
+
+ DDF val = const_cast<DDF&>(attr->second).first();
+ while (!val.isnull()) {
+ int result = caseSensitive ? strcmp(val.string(), value) : strcasecmp(val.string(), value);
+ if (result == 0) {
+ return true;
+ }
+ val = const_cast<DDF&>(attr->second).next();
+ }
+
+ return false;
+}
+
+bool DefaultAttributeConfiguration::hasMatchingValue(
+ const Session& session, const char* attributeId, const regexp::regex& expression
+ ) const
+{
+ static regexp::regex_constants::match_flag_type match_flags =
+ regexp::regex_constants::match_any | regexp::regex_constants::match_not_null;
+
+ const auto& attr = session.getAttributes().find(attributeId);
+ if (attr == session.getAttributes().end()) {
+ return false;
+ }
+
+ DDF val = const_cast<DDF&>(attr->second).first();
+ while (!val.isnull()) {
+ if (regexp::regex_match(val.string(), expression, match_flags)) {
+ return true;
+ }
+ val = const_cast<DDF&>(attr->second).next();
+ }
+
+ return false;
+}
+
+const char* DefaultAttributeConfiguration::getFirstValue(const Session& session, const char* attributeId) const
+{
+ const auto& attr = session.getAttributes().find(attributeId);
+ if (attr == session.getAttributes().end()) {
+ return nullptr;
+ }
+
+ return const_cast<DDF&>(attr->second).first().string();
+}
diff --git a/shibsp/handler/HandlerConfiguration.h b/shibsp/handler/HandlerConfiguration.h
index f6bc32a4..906c1c4d 100644
--- a/shibsp/handler/HandlerConfiguration.h
+++ b/shibsp/handler/HandlerConfiguration.h
@@ -31,7 +31,7 @@ namespace shibsp {
class SHIBSP_API DDF;
/**
- * Pluggable runtime functionality that implement protocols and services
+ * Interface to the set of handlers that are exposed by an agent at specific path(s).
*/
class SHIBSP_API HandlerConfiguration
{
diff --git a/shibsp/handler/impl/AdminLogoutInitiator.cpp b/shibsp/handler/impl/AdminLogoutInitiator.cpp
index 06916ac4..abe588d5 100644
--- a/shibsp/handler/impl/AdminLogoutInitiator.cpp
+++ b/shibsp/handler/impl/AdminLogoutInitiator.cpp
@@ -95,12 +95,12 @@ pair<bool,long> AdminLogoutInitiator::run(SPRequest& request, bool isHandler) co
// With no session, we return a 404 after "revoking" the session just to be safe.
if (!session) {
AgentConfig::getConfig().getAgent().getSessionCache()->remove(
- request.getRequestSettings().first->getString("sessionBucket", "default"), sessionId);
+ request.getRequestSettings().first->getString("applicationId", "default"), sessionId);
istringstream msg("NOT FOUND");
return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_NOTFOUND));
}
- time_t revocationExp = session->getExpiration();
+ time_t revocationExp = session->getCreation() + request.getRequestSettings().first->getUnsignedInt("lifetime", 28800);
unique_lock<Session> sessionLocker(*session, adopt_lock);
@@ -112,7 +112,7 @@ pair<bool,long> AdminLogoutInitiator::run(SPRequest& request, bool isHandler) co
sessionLocker.unlock();
session = nullptr;
AgentConfig::getConfig().getAgent().getSessionCache()->remove(
- request.getRequestSettings().first->getString("sessionBucket", "default"), sessionId, revocationExp);
+ request.getRequestSettings().first->getString("applicationId", "default"), sessionId, revocationExp);
istringstream msg("PARTIAL");
return make_pair(true, request.sendResponse(msg, 206)); // misuse of an HTTP code, but whatever
@@ -122,7 +122,7 @@ pair<bool,long> AdminLogoutInitiator::run(SPRequest& request, bool isHandler) co
sessionLocker.unlock();
session = nullptr;
AgentConfig::getConfig().getAgent().getSessionCache()->remove(
- request.getRequestSettings().first->getString("sessionBucket", "default"), sessionId, revocationExp);
+ request.getRequestSettings().first->getString("applicationId", "default"), sessionId, revocationExp);
istringstream msg("OK");
return make_pair(true, request.sendResponse(msg, HTTPResponse::SHIBSP_HTTP_STATUS_OK));
diff --git a/shibsp/handler/impl/AttributeCheckerHandler.cpp b/shibsp/handler/impl/AttributeCheckerHandler.cpp
index b62b2cd0..097762af 100644
--- a/shibsp/handler/impl/AttributeCheckerHandler.cpp
+++ b/shibsp/handler/impl/AttributeCheckerHandler.cpp
@@ -131,13 +131,13 @@ pair<bool,long> AttributeCheckerHandler::run(SPRequest& request, bool isHandler)
bool checked = false;
if (session) {
if (!m_attributes.empty()) {
- const auto& indexed = session->getIndexedAttributes();
+ const auto& indexed = session->getAttributes();
// Lambda returns true if the candidate attribute ID is NOT in the session.
auto absent = [&indexed](const string& id) {
return indexed.find(id) == indexed.end();
};
- // Look for an attribute in the list that is not in the session multimap.
+ // Look for an attribute in the list that is not in the session map.
// If that fails, the check succeeds.
checked = find_if(m_attributes.begin(), m_attributes.end(), absent) == m_attributes.end();
}
@@ -153,7 +153,7 @@ pair<bool,long> AttributeCheckerHandler::run(SPRequest& request, bool isHandler)
}
if (m_flushSession && session) {
- time_t revocationExp = session->getExpiration();
+ time_t revocationExp = session->getCreation() + request.getRequestSettings().first->getUnsignedInt("lifetime", 28800);
sessionLocker.unlock(); // unlock the session
flushSession(request, revocationExp);
}
diff --git a/shibsp/handler/impl/LocalLogoutInitiator.cpp b/shibsp/handler/impl/LocalLogoutInitiator.cpp
index 8dfbd257..c73d1395 100644
--- a/shibsp/handler/impl/LocalLogoutInitiator.cpp
+++ b/shibsp/handler/impl/LocalLogoutInitiator.cpp
@@ -88,7 +88,7 @@ pair<bool,long> LocalLogoutInitiator::run(SPRequest& request, bool isHandler) co
bool result;
vector<string> sessions(1, session->getID());
result = notifyBackChannel(request, sessions, true);
- time_t revocationExp = session->getExpiration();
+ time_t revocationExp = session->getCreation() + request.getRequestSettings().first->getUnsignedInt("lifetime", 28800);
locker.unlock(); // unlock the session
request.getAgent().getSessionCache()->remove(request, revocationExp);
if (!result) {
diff --git a/shibsp/handler/impl/SessionHandler.cpp b/shibsp/handler/impl/SessionHandler.cpp
index 57feedb2..30819c6e 100644
--- a/shibsp/handler/impl/SessionHandler.cpp
+++ b/shibsp/handler/impl/SessionHandler.cpp
@@ -20,8 +20,9 @@
#include "internal.h"
#include "exceptions.h"
+#include "Agent.h"
#include "SPRequest.h"
-#include "attribute/Attribute.h"
+#include "attribute/AttributeConfiguration.h"
#include "handler/SecuredHandler.h"
#include "logging/Category.h"
#include "session/SessionCache.h"
@@ -151,10 +152,7 @@ pair<bool,long> SessionHandler::doJSON(SPRequest& request) const
s << "{ ";
s << "\"expiration\": ";
- if (session->getExpiration())
- s << ((session->getExpiration() - time(nullptr)) / 60);
- else
- s << 0;
+ s << ((session->getCreation() + request.getRequestSettings().first->getUnsignedInt("lifetime", 28800) - time(nullptr)) / 60);
/*
attributes: [ { "name": "foo", "values" : count } ]
@@ -164,12 +162,12 @@ pair<bool,long> SessionHandler::doJSON(SPRequest& request) const
]
*/
- const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
+ const map<string,DDF>& attributes = session->getAttributes();
if (!attributes.empty()) {
s << ", \"attributes\": [ ";
string key;
- vector<string>::size_type count=0;
- for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
+ int count=0;
+ for (map<string,DDF>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
if (a->first != key) {
// We're starting a new attribute.
if (a != attributes.begin()) {
@@ -187,19 +185,23 @@ pair<bool,long> SessionHandler::doJSON(SPRequest& request) const
}
if (m_values) {
- const vector<string>& vals = a->second->getSerializedValues();
- for (vector<string>::const_iterator v = vals.begin(); v!=vals.end(); ++v) {
- if (v != vals.begin() || a->first == key) {
+ bool first = true;
+ DDF val = const_cast<DDF&>(a->second).first();
+ while (!val.isnull()) {
+ if (!first || a->first == key) {
s << ", ";
}
else {
s << ", \"values\": [ ";
}
- json_safe(s, v->c_str());
+ json_safe(s, val.string());
+
+ val = const_cast<DDF&>(a->second).next();
+ first = false;
}
}
else {
- count += a->second->getSerializedValues().size();
+ count += a->second.integer();
}
key = a->first;
}
@@ -217,6 +219,12 @@ pair<bool,long> SessionHandler::doJSON(SPRequest& request) const
pair<bool,long> SessionHandler::doHTML(SPRequest& request) const
{
+ // Default delimiter is semicolon but is configurable.
+ const char* delim = request.getAgent().getAttributeConfiguration(
+ request.getRequestSettings().first->getString("attributeConfigID")
+ ).getString("attributeValueDelimiter", ";");
+ size_t delim_len = strlen(delim);
+
stringstream s;
s << "<html><head><title>Session Summary</title></head><body><pre>" << endl;
@@ -237,17 +245,13 @@ pair<bool,long> SessionHandler::doHTML(SPRequest& request) const
s << "<u>Miscellaneous</u>" << endl;
s << "<strong>Session Expiration (barring inactivity):</strong> ";
- if (session->getExpiration())
- s << ((session->getExpiration() - time(nullptr)) / 60) << " minute(s)" << endl;
- else
- s << "Infinite" << endl;
-
+ s << ((session->getCreation() + request.getRequestSettings().first->getUnsignedInt("lifetime", 28800) - time(nullptr)) / 60) << " minute(s)" << endl;
s << endl << "<u>Attributes</u>" << endl;
string key;
- vector<string>::size_type count=0;
- const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
- for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
+ int count=0;
+ const map<string,DDF>& attributes = session->getAttributes();
+ for (map<string,DDF>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
if (a->first != key) {
if (a != attributes.begin()) {
if (m_values)
@@ -261,30 +265,31 @@ pair<bool,long> SessionHandler::doHTML(SPRequest& request) const
}
if (m_values) {
- // Default delimiter is semicolon but is now configurable.
- const char* delim = request.getRequestSettings().first->getString("attributeValueDelimiter", ";");
- size_t delim_len = strlen(delim);
-
- const vector<string>& vals = a->second->getSerializedValues();
- for (vector<string>::const_iterator v = vals.begin(); v!=vals.end(); ++v) {
- if (v != vals.begin() || a->first == key)
+ bool first = true;
+ DDF val = const_cast<DDF&>(a->second).first();
+ while (!val.isnull()) {
+ if (!first || a->first == key) {
s << delim;
- string::size_type pos = v->find(delim, string::size_type(0));
+ }
+ string serialized(val.string());
+ string::size_type pos = serialized.find(delim, string::size_type(0));
if (pos != string::npos) {
- string value(*v);
- for (; pos != string::npos; pos = value.find(delim, pos)) {
- value.insert(pos, "\\");
+ for (; pos != string::npos; pos = serialized.find(delim, pos)) {
+ serialized.insert(pos, "\\");
pos += delim_len + 1;
}
- s << value;
+ s << serialized;
}
else {
- s << *v;
+ s << val.string();
}
+
+ val = const_cast<DDF&>(a->second).next();
+ first = false;
}
}
else {
- count += a->second->getSerializedValues().size();
+ count += a->second.integer();
}
key = a->first;
}
diff --git a/shibsp/impl/DefaultAgent.cpp b/shibsp/impl/DefaultAgent.cpp
index ac559c49..c717a029 100644
--- a/shibsp/impl/DefaultAgent.cpp
+++ b/shibsp/impl/DefaultAgent.cpp
@@ -25,6 +25,7 @@
#include "Agent.h"
#include "AgentConfig.h"
#include "RequestMapper.h"
+#include "attribute/AttributeConfiguration.h"
#include "handler/HandlerConfiguration.h"
#include "io/HTTPResponse.h"
#include "logging/Category.h"
@@ -77,7 +78,7 @@ namespace {
return m_requestMapper.get();
}
- HandlerConfiguration& getHandlerConfiguration(const char* id=nullptr) const {
+ const HandlerConfiguration& getHandlerConfiguration(const char* id=nullptr) const {
if (!id) {
id = "default";
}
@@ -88,11 +89,23 @@ namespace {
throw ConfigurationException(string("No HandlerConfiguration with ID of ") + id);
}
+ const AttributeConfiguration& getAttributeConfiguration(const char* id=nullptr) const {
+ if (!id) {
+ id = "default";
+ }
+ const auto& config = m_attributeConfigurations.find(id);
+ if (config != m_attributeConfigurations.end()) {
+ return *(config->second);
+ }
+ throw ConfigurationException(string("No AttributeConfiguration with ID of ") + id);
+ }
+
private:
void doRemotingService();
void doSessionCache();
void doRequestMapper();
void doHandlerConfigurations();
+ void doAttributeConfigurations();
ptree& m_pt;
Category& m_log;
@@ -104,6 +117,7 @@ namespace {
unique_ptr<SessionCache> m_sessionCache;
unique_ptr<RequestMapper> m_requestMapper;
map<string,unique_ptr<HandlerConfiguration>> m_handlerConfigurations;
+ map<string,unique_ptr<AttributeConfiguration>> m_attributeConfigurations;
};
#if defined (_MSC_VER)
@@ -218,3 +232,30 @@ void DefaultAgent::doHandlerConfigurations()
m_log.info("installed 'default' HandlerConfiguration from %s", path.c_str());
}
}
+
+void DefaultAgent::doAttributeConfigurations()
+{
+ // Check for testing boolean to disable attribute config.
+ if (getBool("skipAttributes", false)) {
+ return;
+ }
+
+ boost::optional<ptree&> child = m_pt.get_child_optional("attributes");
+ if (child) {
+ for (const auto& keys : *child) {
+ boost::optional<string> path = keys.second.get_value_optional<string>();
+ if (!path) {
+ m_log.warn("skipping property key with no value in [attributes] section");
+ continue;
+ }
+ AgentConfig::getConfig().getPathResolver().resolve(*path, PathResolver::SHIBSP_CFG_FILE);
+ m_attributeConfigurations[keys.first] = AttributeConfiguration::newAttributeConfiguration(path->c_str());
+ m_log.info("installed '%s' AttributeConfiguration from %s", keys.first.c_str(), path->c_str());
+ }
+ } else {
+ string path("attributes.ini");
+ AgentConfig::getConfig().getPathResolver().resolve(path, PathResolver::SHIBSP_CFG_FILE);
+ m_attributeConfigurations["default"] = AttributeConfiguration::newAttributeConfiguration(path.c_str());
+ m_log.info("installed 'default' AttributeConfiguration from %s", path.c_str());
+ }
+}
diff --git a/shibsp/impl/XMLAccessControl.cpp b/shibsp/impl/XMLAccessControl.cpp
index bdf78628..5f7c7cd3 100644
--- a/shibsp/impl/XMLAccessControl.cpp
+++ b/shibsp/impl/XMLAccessControl.cpp
@@ -24,8 +24,9 @@
#include "AccessControl.h"
#include "Agent.h"
#include "SPRequest.h"
-#include "attribute/Attribute.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"
@@ -193,31 +194,22 @@ AccessControl::aclresult_t Rule::authorized(const SPRequest& request, const Sess
actual_alias = request.getAgent().getString("legacy-classref-attribute", "Shib-AuthnContext-Class");
}
- // Find the attribute(s) matching the require rule.
- pair<multimap<string,const Attribute*>::const_iterator, multimap<string,const Attribute*>::const_iterator> attrs =
- session->getIndexedAttributes().equal_range(actual_alias);
- if (attrs.first == attrs.second) {
- request.log(Priority::SHIB_WARN, string("AccessControl rule requires attribute (") + actual_alias + "), not found in session");
+ // 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");
+ return shib_acl_true;
+ }
return shib_acl_false;
}
- else if (m_vals.empty()) {
- request.log(Priority::SHIB_DEBUG, string("AccessControl rule requires presence of attribute (") + actual_alias + "), authz granted");
- return shib_acl_true;
- }
- for (; attrs.first != attrs.second; ++attrs.first) {
- bool caseSensitive = attrs.first->second->isCaseSensitive();
-
- // Now we have to intersect the attribute's values against the rule's list.
- const vector<string>& vals = attrs.first->second->getSerializedValues();
- for (set<string>::const_iterator i = m_vals.begin(); i != m_vals.end(); ++i) {
- for (vector<string>::const_iterator j = vals.begin(); j != vals.end(); ++j) {
- if ((caseSensitive && *i == *j) || (!caseSensitive && !strcasecmp(i->c_str(),j->c_str()))) {
- request.log(Priority::SHIB_DEBUG, string("AccessControl rule expecting (") + *j + "), authz granted");
- return shib_acl_true;
- }
- }
- }
+ // Otherwise call into the helper logic to handle matching process..
+ const AttributeConfiguration& attributeConfig = request.getAgent().getAttributeConfiguration(
+ request.getRequestSettings().first->getString("attributeConfigID")
+ );
+ 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");
+ return shib_acl_true;
}
return shib_acl_false;
@@ -275,21 +267,14 @@ AccessControl::aclresult_t RuleRegex::authorized(const SPRequest& request, const
actual_alias = request.getAgent().getString("legacy-classref-attribute", "Shib-AuthnContext-Class");
}
- // Find the attribute(s) matching the require rule.
- auto attrs = session->getIndexedAttributes().equal_range(actual_alias);
- if (attrs.first == attrs.second) {
- request.log(Priority::SHIB_WARN, string("AccessControl rule requires attribute (") + actual_alias + "), not found in session");
- return shib_acl_false;
- }
-
- for (; attrs.first != attrs.second; ++attrs.first) {
- // Now we have to intersect the attribute's values against the regular expression.
- for (const string& v : attrs.first->second->getSerializedValues()) {
- if (regexp::regex_match(v, m_re, match_flags)) {
- request.log(Priority::SHIB_DEBUG, string("AccessControl rule expecting regex (") + m_exp + "), authz granted");
- return shib_acl_true;
- }
- }
+ // Call into the helper logic to handle matching process..
+ const AttributeConfiguration& attributeConfig = request.getAgent().getAttributeConfiguration(
+ request.getRequestSettings().first->getString("attributeConfigID")
+ );
+ if (attributeConfig.hasMatchingValue(*session, actual_alias.c_str(), m_re)) {
+ request.log(Priority::SHIB_DEBUG,
+ string("AccessControl rule for attribute (") + actual_alias + ") expecting regex (" + m_exp + ", authz granted");
+ return shib_acl_true;
}
return shib_acl_false;
diff --git a/shibsp/session/AbstractSessionCache.h b/shibsp/session/AbstractSessionCache.h
index 3e2d0f5f..e5968c10 100644
--- a/shibsp/session/AbstractSessionCache.h
+++ b/shibsp/session/AbstractSessionCache.h
@@ -18,18 +18,69 @@
* Base class for SessionCache implementations.
*/
+#ifndef __shibsp_abssessioncache_h__
+#define __shibsp_abssessioncache_h__
+
+#include <logging/Category.h>
+#include <remoting/ddf.h>
#include <session/SessionCache.h>
+#include <mutex>
+
namespace shibsp {
- class SHIBSP_API AbstractSessionCache : public virtual SessionCache {
- protected:
- /** Constructor. */
- AbstractSessionCache();
- virtual ~AbstractSessionCache();
+ class SHIBSP_API AbstractSessionCache;
+ class SHIBSP_API Attribute;
+ class SHIBSP_API BasicSession : public virtual Session
+ {
public:
- bool start();
+ BasicSession(AbstractSessionCache& cache, DDF& obj);
+ virtual ~BasicSession();
+
+ void lock();
+ bool try_lock();
+ void unlock();
+
+ const char* getID() const;
+ const char* getApplicationID() const;
+ const char* getClientAddress(const char* family) const;
+ void setClientAddress(const char* client_addr);
+ const std::map<std::string,DDF>& getAttributes() const;
+ time_t getCreation() const;
+ time_t getLastAccess() const;
+
+ void validate(const char* applicationId, const char* client_addr, time_t* timeout);
+
+ // Allows the cache to bind sessions to multiple client address
+ // families based on whatever this function returns.
+ static const char* getAddressFamily(const char* addr);
+
+ private:
+ DDF m_obj;
+ std::map<std::string,DDF> m_attributes;
+
+ AbstractSessionCache& m_cache;
+ time_t m_creation,m_lastAccess;
+ // TODO: possibly convert to a shared lock where possible?
+ // I used exclusive because it avoided lock "upgrades"
+ // when mutating or deleting sessions.
+ std::mutex m_lock;
};
+ class SHIBSP_API AbstractSessionCache : public virtual SessionCache {
+ protected:
+ /** Constructor. */
+ AbstractSessionCache();
+ virtual ~AbstractSessionCache();
+
+ Category& m_log;
+
+ public:
+ bool start();
+
+ friend class BasicSession;
+ };
};
+
+#endif /** __shibsp_abssessioncache_h__ */
\ No newline at end of file
diff --git a/shibsp/session/SessionCache.h b/shibsp/session/SessionCache.h
index 769402f8..b6f01d83 100644
--- a/shibsp/session/SessionCache.h
+++ b/shibsp/session/SessionCache.h
@@ -31,11 +31,14 @@
namespace shibsp {
- class SHIBSP_API Attribute;
+ class SHIBSP_API DDF;
class SHIBSP_API SPRequest;
/**
- * Encapsulates access to a user's security session.
+ * Encapsulates access to a session.
+ *
+ * <p>Sessions are returned from APIs and generally will be returned in a
+ * locked state and should be unlocked by the caller when done using them.</p>
*/
class SHIBSP_API Session : public virtual BasicLockable
{
@@ -60,11 +63,11 @@ namespace shibsp {
virtual const char* getApplicationID() const=0;
/**
- * Returns the session expiration.
+ * Returns the session creation time.
*
- * @return the session's expiration time or 0 for none
+ * @return the session's creation time
*/
- virtual time_t getExpiration() const=0;
+ virtual time_t getCreation() const=0;
/**
* Returns the last access time of the session.
@@ -73,19 +76,14 @@ namespace shibsp {
*/
virtual time_t getLastAccess() const=0;
- /**
- * Returns the resolved attributes associated with the session.
- *
- * @return an immutable array of attributes
- */
- virtual const std::vector<std::unique_ptr<Attribute>>& getAttributes() const=0;
-
/**
* Returns the resolved attributes associated with the session, indexed by ID.
+ *
+ * <p>Each "attribute" is a list containing the values (of various types).</p>
*
- * @return an immutable map of attributes keyed by attribute ID
+ * @return an immutable map of attribute data keyed by attribute ID
*/
- virtual const std::multimap<std::string,const Attribute*>& getIndexedAttributes() const=0;
+ virtual const std::map<std::string,DDF>& getAttributes() const=0;
};
/**
diff --git a/shibsp/session/impl/AbstractSessionCache.cpp b/shibsp/session/impl/AbstractSessionCache.cpp
index ee9ca01d..502ecc5b 100644
--- a/shibsp/session/impl/AbstractSessionCache.cpp
+++ b/shibsp/session/impl/AbstractSessionCache.cpp
@@ -21,6 +21,7 @@
#include "internal.h"
#include "exceptions.h"
#include "AgentConfig.h"
+#include "attribute/Attribute.h"
#include "session/AbstractSessionCache.h"
#include "logging/Category.h"
@@ -57,7 +58,7 @@ SessionCache::~SessionCache()
{
}
-AbstractSessionCache::AbstractSessionCache()
+AbstractSessionCache::AbstractSessionCache() : m_log(Category::getInstance(SHIBSP_LOGCAT ".SessionCache"))
{
}
@@ -68,3 +69,295 @@ AbstractSessionCache::~AbstractSessionCache()
bool AbstractSessionCache::start()
{
}
+
+BasicSession::BasicSession(AbstractSessionCache& cache, DDF& obj)
+ : m_obj(obj), m_cache(cache), m_creation(0), m_lastAccess(time(nullptr))
+{
+ m_creation = m_obj["creation"].longinteger();
+
+ // We have to index and vector-up the values of the attributes.
+ // (Among other reasons, the DDF iteration API isn't threadsafe.)
+ DDF attrs = m_obj["attributes"];
+ DDF attr = attrs.first();
+ while (!attr.isnull()) {
+ m_attributes[attr.name()] = attr;
+ attr = attrs.next();
+ }
+}
+
+BasicSession::~BasicSession()
+{
+ m_obj.destroy();
+}
+
+const char* BasicSession::getID() const
+{
+ return m_obj.name();
+}
+
+const char* BasicSession::getApplicationID() const
+{
+ return m_obj["applicaton_id"].string();
+}
+
+const char* BasicSession::getClientAddress(const char* family) const
+{
+ return m_obj["client_addr"][family].string();
+}
+
+void BasicSession::setClientAddress(const char* client_addr)
+{
+ DDF obj = m_obj["client_addr"];
+ if (!obj.isstruct()) {
+ obj = m_obj.addmember("client_addr").structure();
+ }
+ obj.addmember(getAddressFamily(client_addr)).string(client_addr);
+}
+
+const std::map<std::string,DDF>& BasicSession::getAttributes() const
+{
+ return m_attributes;
+}
+
+void BasicSession::validate(const char* applicationId, const char* client_addr, time_t* timeout)
+{
+}
+
+time_t BasicSession::getCreation() const
+{
+ return m_creation;
+}
+
+time_t BasicSession::getLastAccess() const
+{
+ return m_lastAccess;
+}
+
+void BasicSession::lock()
+{
+ m_lock.lock();
+}
+
+bool BasicSession::try_lock()
+{
+ return m_lock.try_lock();
+}
+
+void BasicSession::unlock()
+{
+ m_lock.unlock();
+}
+
+// Allows the cache to bind sessions to multiple client address
+// families based on whatever this function returns.
+const char* BasicSession::getAddressFamily(const char* addr)
+{
+ if (strchr(addr, ':'))
+ return "6";
+ else
+ return "4";
+}
+
+/*
+
+void BasicSession::validate(const char* applicationId, const char* client_addr, time_t* timeout)
+{
+ time_t now = time(nullptr);
+
+ // Basic expiration?
+ if (m_expires > 0) {
+ if (now > m_expires) {
+ m_cache.m_log.info("session expired (ID: %s)", getID());
+ throw SessionException("Your session has expired, and you must re-authenticate.");
+ }
+ }
+
+ // Address check?
+ if (client_addr) {
+ const char* saddr = getClientAddress(getAddressFamily(client_addr));
+ if (saddr && *saddr) {
+ if (!m_cache.compareAddresses(client_addr, saddr)) {
+ m_cache.m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
+ throw SessionException(
+ string("Your IP address (") + client_addr + ") does not match the address recorded at the time the session was established."
+ );
+ }
+ client_addr = nullptr; // clear out parameter as signal that session need not be updated below
+ }
+ else {
+ m_cache.m_log.info("session (%s) not yet bound to client address type, binding it to (%s)", getID(), client_addr);
+ }
+ }
+
+ if (!timeout && !client_addr)
+ return;
+
+ if (true) {
+ DDF in("touch::" STORAGESERVICE_SESSION_CACHE "::SessionCache"), out;
+ DDFJanitor jin(in);
+ in.structure();
+ in.addmember("key").string(getID());
+ in.addmember("version").integer(m_obj["version"].integer());
+ in.addmember("bucket_id").string(bucketID);
+ if (client_addr) // signals we need to bind an additional address to the session
+ in.addmember("client_addr").string(client_addr);
+ if (timeout && *timeout) {
+ // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.
+#ifndef HAVE_GMTIME_R
+ struct tm* ptime = gmtime(timeout);
+#else
+ struct tm res;
+ struct tm* ptime = gmtime_r(timeout,&res);
+#endif
+ char timebuf[32];
+ strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+ in.addmember("timeout").string(timebuf);
+ }
+
+ //out = app.getServiceProvider().getListenerService()->send(in);
+ if (out.isstruct()) {
+ // We got an updated record back.
+ m_cache.m_log.debug("session updated, reconstituting it");
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ m_obj.destroy();
+ m_obj = out;
+ }
+ else {
+ out.destroy();
+ }
+ }
+ else {
+#ifndef SHIBSP_LITE
+ if (!m_cache.m_storage)
+ throw ConfigurationException("Session touch requires a StorageService.");
+
+ // Versioned read, since we already have the data in hand if it's current.
+ string record;
+ time_t lastAccess = 0;
+ int curver = m_obj["version"].integer();
+ int ver = m_cache.m_storage->readText(getID(), "session", &record, &lastAccess, curver);
+ if (ver == 0) {
+ m_cache.m_log.info("session (ID: %s) no longer in storage", getID());
+ throw RetryableProfileException("Your session is not available in the session store, and you must re-authenticate.");
+ }
+
+ if (timeout) {
+ if (lastAccess == 0) {
+ m_cache.m_log.error("session (ID: %s) did not report time of last access", getID());
+ throw RetryableProfileException("Your session's last access time was missing, and you must re-authenticate.");
+ }
+ // Adjust for expiration to recover last access time and check timeout.
+ unsigned long cacheTimeout = m_cache.getCacheTimeout(app);
+ lastAccess -= cacheTimeout;
+ if (*timeout > 0 && now - lastAccess >= *timeout) {
+ m_cache.m_log.info("session timed out (ID: %s)", getID());
+ throw RetryableProfileException("Your session has timed out due to inactivity, and you must re-authenticate.");
+ }
+
+ // Update storage expiration, if possible.
+ try {
+ m_cache.m_storage->updateContext(getID(), now + cacheTimeout);
+ }
+ catch (std::exception& ex) {
+ m_cache.m_log.error("failed to update session expiration: %s", ex.what());
+ }
+ }
+
+ if (ver > curver) {
+ // We got an updated record back.
+ DDF newobj;
+ istringstream in(record);
+ in >> newobj;
+ m_ids.clear();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ m_obj.destroy();
+ m_obj = newobj;
+ }
+
+ // We may need to write back a new address into the session using a versioned update loop.
+ if (client_addr) {
+ short attempts = 0;
+ do {
+ const char* saddr = getClientAddress(getAddressFamily(client_addr));
+ if (saddr) {
+ // Something snuck in and bound the session to this address type, so it better match what we have.
+ if (!m_cache.compareAddresses(client_addr, saddr)) {
+ m_cache.m_log.warn("client address mismatch, client (%s), session (%s)", client_addr, saddr);
+ throw RetryableProfileException(
+ "Your IP address ($1) does not match the address recorded at the time the session was established.",
+ params(1, client_addr)
+ );
+ }
+ break; // No need to update.
+ }
+ else {
+ // Bind it into the session.
+ setClientAddress(client_addr);
+ }
+
+ // Tentatively increment the version.
+ m_obj["version"].integer(m_obj["version"].integer() + 1);
+
+ ostringstream str;
+ str << m_obj;
+ record = str.str();
+
+ try {
+ ver = m_cache.m_storage->updateText(getID(), "session", record.c_str(), 0, m_obj["version"].integer() - 1);
+ }
+ catch (std::exception&) {
+ m_obj["version"].integer(m_obj["version"].integer() - 1);
+ throw;
+ }
+
+ if (ver <= 0) {
+ m_obj["version"].integer(m_obj["version"].integer() - 1);
+ }
+
+ if (!ver) {
+ // Fatal problem with update.
+ m_cache.m_log.error("updateText failed on StorageService for session (%s)", getID());
+ throw IOException("Unable to update stored session.");
+ }
+ else if (ver < 0) {
+ // Out of sync.
+ if (++attempts > 10) {
+ m_cache.m_log.error("failed to bind client address, update attempts exceeded limit");
+ throw IOException("Unable to update stored session, exceeded retry limit.");
+ }
+ m_cache.m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");
+ ver = m_cache.m_storage->readText(getID(), "session", &record);
+ if (!ver) {
+ m_cache.m_log.error("readText failed on StorageService for session (%s)", getID());
+ throw IOException("Unable to read back stored session.");
+ }
+
+ // Reset object.
+ DDF newobj;
+ istringstream in(record);
+ in >> newobj;
+
+ m_ids.clear();
+ for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
+ m_attributes.clear();
+ m_attributeIndex.clear();
+ newobj["version"].integer(ver);
+ m_obj.destroy();
+ m_obj = newobj;
+
+ ver = -1;
+ }
+ } while (ver < 0); // negative indicates a sync issue so we retry
+ }
+#else
+ throw ConfigurationException("Session touch requires a StorageService.");
+#endif
+ }
+
+ m_lastAccess = now;
+}
+
+*/
\ No newline at end of file
diff --git a/tests/data/console-shibboleth.ini b/tests/data/console-shibboleth.ini
index 1fba6104..6bcd5083 100644
--- a/tests/data/console-shibboleth.ini
+++ b/tests/data/console-shibboleth.ini
@@ -1,5 +1,6 @@
[global]
skipHandlers = true
+skipAttributes = true
# Use "partial" for partial matching
regexMatching = full
diff --git a/tests/data/fatal-exts-shibboleth.ini b/tests/data/fatal-exts-shibboleth.ini
index a4d883c5..70a81156 100644
--- a/tests/data/fatal-exts-shibboleth.ini
+++ b/tests/data/fatal-exts-shibboleth.ini
@@ -1,5 +1,6 @@
[global]
skipHandlers = true
+skipAttributes = true
[extensions]
/path/to/extension.so = true
diff --git a/tests/data/impl/console-shibboleth.ini b/tests/data/impl/console-shibboleth.ini
index b841324a..8e205364 100644
--- a/tests/data/impl/console-shibboleth.ini
+++ b/tests/data/impl/console-shibboleth.ini
@@ -1,5 +1,6 @@
[global]
skipHandlers = true
+skipAttributes = true
[logging]
type = console
diff --git a/tests/data/nonfatal-exts-shibboleth.ini b/tests/data/nonfatal-exts-shibboleth.ini
index 7912aab0..e9ff5223 100644
--- a/tests/data/nonfatal-exts-shibboleth.ini
+++ b/tests/data/nonfatal-exts-shibboleth.ini
@@ -1,5 +1,6 @@
[global]
skipHandlers = true
+skipAttributes = true
[extensions]
/path/to/extension.so = false
diff --git a/tests/data/platform/iis/console-shibboleth.ini b/tests/data/platform/iis/console-shibboleth.ini
index 738c4845..bde240f9 100644
--- a/tests/data/platform/iis/console-shibboleth.ini
+++ b/tests/data/platform/iis/console-shibboleth.ini
@@ -1,5 +1,6 @@
[global]
skipHandlers = true
+skipAttributes = true
[logging]
type = console
diff --git a/tests/data/remoting/impl/shibboleth.ini b/tests/data/remoting/impl/shibboleth.ini
index b1baaf1e..8bd9c67c 100644
--- a/tests/data/remoting/impl/shibboleth.ini
+++ b/tests/data/remoting/impl/shibboleth.ini
@@ -1,5 +1,6 @@
[global]
skipHandlers = true
+skipAttributes = true
[remoting]
baseURL = https://localhost/idp/profile/sp
diff --git a/tests/data/syslog-shibboleth.ini b/tests/data/syslog-shibboleth.ini
index 7e164311..c9e78ee2 100644
--- a/tests/data/syslog-shibboleth.ini
+++ b/tests/data/syslog-shibboleth.ini
@@ -1,5 +1,6 @@
[global]
skipHandlers = true
+skipAttributes = true
[logging]
type = syslog
diff --git a/tests/data/util/reloadablefile/console-shibboleth.ini b/tests/data/util/reloadablefile/console-shibboleth.ini
index 6699f3b1..04d127d3 100644
--- a/tests/data/util/reloadablefile/console-shibboleth.ini
+++ b/tests/data/util/reloadablefile/console-shibboleth.ini
@@ -1,5 +1,6 @@
[global]
skipHandlers = true
+skipAttributes = true
[logging]
type = console
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list