elementFormDefault="qualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
- version="2.4">
+ version="2.4.2">
<import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsig-core-schema.xsd" />
<import namespace="urn:oasis:names:tc:SAML:2.0:assertion" schemaLocation="saml-schema-assertion-2.0.xsd"/>
</restriction>
</simpleType>
+ <simpleType name="relayStateLimitType">
+ <restriction base="string">
+ <enumeration value="none"/>
+ <enumeration value="exact"/>
+ <enumeration value="host"/>
+ <enumeration value="whitelist"/>
+ </restriction>
+ </simpleType>
+
<complexType name="PluggableType">
<sequence>
<any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
<attribute name="postTemplate" type="conf:string"/>
<attribute name="postExpire" type="boolean"/>
<attribute name="relayState" type="conf:string"/>
+ <attribute name="relayStateLimit" type="conf:relayStateLimitType"/>
+ <attribute name="relayStateWhitelist" type="conf:listOfURIs"/>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>
/*
- * Copyright 2001-2010 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
}
}
+ void SHIBSP_DLLLOCAL limitRelayState(
+ Category& log, const Application& application, const HTTPRequest& httpRequest, const char* relayState
+ ) {
+ const PropertySet* sessionProps = application.getPropertySet("Sessions");
+ if (sessionProps) {
+ pair<bool,const char*> relayStateLimit = sessionProps->getString("relayStateLimit");
+ if (relayStateLimit.first) {
+ vector<string> whitelist;
+ if (!strcmp(relayStateLimit.second, "exact")) {
+ // Scheme and hostname have to match.
+ if (!strcmp(httpRequest.getScheme(), "https") && httpRequest.getPort() == 443) {
+ whitelist.push_back(string("https://") + httpRequest.getHostname() + '/');
+ }
+ else if (!strcmp(httpRequest.getScheme(), "http") && httpRequest.getPort() == 80) {
+ whitelist.push_back(string("http://") + httpRequest.getHostname() + '/');
+ }
+ ostringstream portstr;
+ portstr << httpRequest.getPort();
+ whitelist.push_back(string(httpRequest.getScheme()) + "://" + httpRequest.getHostname() + ':' + portstr.str() + '/');
+ }
+ else if (!strcmp(relayStateLimit.second, "host")) {
+ // Allow any scheme or port.
+ whitelist.push_back(string("https://") + httpRequest.getHostname() + '/');
+ whitelist.push_back(string("http://") + httpRequest.getHostname() + '/');
+ whitelist.push_back(string("https://") + httpRequest.getHostname() + ':');
+ whitelist.push_back(string("http://") + httpRequest.getHostname() + ':');
+ }
+ else if (!strcmp(relayStateLimit.second, "whitelist")) {
+ // Literal set of comparisons to use.
+ pair<bool,const char*> whitelistval = sessionProps->getString("relayStateWhitelist");
+ if (whitelistval.first) {
+#ifdef HAVE_STRTOK_R
+ char* pos=nullptr;
+ const char* token = strtok_r(const_cast<char*>(whitelistval.second), " ", &pos);
+#else
+ const char* token = strtok(const_cast<char*>(whitelistval.second), " ");
+#endif
+ while (token) {
+ whitelist.push_back(token);
+#ifdef HAVE_STRTOK_R
+ token = strtok_r(nullptr, " ", &pos);
+#else
+ token = strtok(nullptr, " ");
+#endif
+ }
+ }
+ }
+ for (vector<string>::const_iterator w = whitelist.begin(); w != whitelist.end(); ++w) {
+ if (XMLString::startsWithI(relayState, w->c_str())) {
+ return;
+ }
+ }
+
+ log.warn("relayStateLimit policy (%s), blocked redirect to (%s)", relayStateLimit.second, relayState);
+ throw opensaml::SecurityPolicyException("Blocked unacceptable redirect location.");
+ }
+ }
+ }
};
void SHIBSP_API shibsp::registerHandlers()
/*
- * Copyright 2001-2010 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
);
string relayState;
+ bool relayStateOK = true;
try {
// Decode the message and process it in a protocol-specific way.
auto_ptr<XMLObject> msg(m_decoder->decode(relayState, httpRequest, *(policy.get())));
DDF postData = recoverPostData(application, httpRequest, httpResponse, relayState.c_str());
DDFJanitor postjan(postData);
recoverRelayState(application, httpRequest, httpResponse, relayState);
+ limitRelayState(m_log, application, httpRequest, relayState.c_str());
implementProtocol(application, httpRequest, httpResponse, *(policy.get()), NULL, *msg.get());
auto_ptr_char issuer(policy->getIssuer() ? policy->getIssuer()->getName() : nullptr);
}
}
catch (XMLToolingException& ex) {
- // Check for isPassive error condition.
- const char* sc2 = ex.getProperty("statusCode2");
- if (sc2 && !strcmp(sc2, "urn:oasis:names:tc:SAML:2.0:status:NoPassive")) {
- pair<bool,bool> ignore = getBool("ignoreNoPassive", m_configNS.get()); // namespace-qualified if inside handler element
- if (ignore.first && ignore.second && !relayState.empty()) {
- m_log.debug("ignoring SAML status of NoPassive and redirecting to resource...");
- return make_pair(true, httpResponse.sendRedirect(relayState.c_str()));
+ if (relayStateOK) {
+ // Check for isPassive error condition.
+ const char* sc2 = ex.getProperty("statusCode2");
+ if (sc2 && !strcmp(sc2, "urn:oasis:names:tc:SAML:2.0:status:NoPassive")) {
+ pair<bool,bool> ignore = getBool("ignoreNoPassive", m_configNS.get()); // namespace-qualified if inside handler element
+ if (ignore.first && ignore.second && !relayState.empty()) {
+ m_log.debug("ignoring SAML status of NoPassive and redirecting to resource...");
+ return make_pair(true, httpResponse.sendRedirect(relayState.c_str()));
+ }
}
}
if (!relayState.empty())
/*
- * Copyright 2001-2009 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
// Route back to return location specified, or use the local template.
const char* dest = request.getParameter("return");
- if (dest)
+ if (dest) {
+ limitRelayState(m_log, app, request, dest);
return make_pair(true, request.sendRedirect(dest));
+ }
return sendLogoutPage(app, request, request, "local");
}
/*
- * Copyright 2001-2010 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
if (sc && XMLString::equals(sc->getValue(), StatusCode::PARTIAL_LOGOUT))
return sendLogoutPage(application, request, response, "partial");
- if (!relayState.empty())
+ if (!relayState.empty()) {
+ limitRelayState(m_log, application, request, relayState.c_str());
return make_pair(true, response.sendRedirect(relayState.c_str()));
+ }
// Return template for completion of logout.
return sendLogoutPage(application, request, response, "global");
/*
- * Copyright 2001-2010 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
else {
const char* returnloc = httpRequest.getParameter("return");
if (returnloc) {
+ limitRelayState(m_log, application, httpRequest, returnloc);
ret.second = httpResponse.sendRedirect(returnloc);
ret.first = true;
}
string relayState;
const char* returnloc = httpRequest.getParameter("return");
if (returnloc) {
+ limitRelayState(m_log, application, httpRequest, returnloc);
relayState = returnloc;
preserveRelayState(application, httpResponse, relayState);
}
/*
- * Copyright 2001-2010 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
// Always need to recover target URL to compute handler below.
recoverRelayState(app, request, request, target, false);
+ limitRelayState(m_log, app, request, target.c_str());
pair<bool,bool> flag = getBool("isPassive", request);
isPassive = (flag.first && flag.second);
/*
- * Copyright 2001-2010 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
// Since we're passing the ACS by value, we need to compute the return URL,
// so we'll need the target resource for real.
recoverRelayState(app, request, request, target, false);
+ limitRelayState(m_log, app, request, target.c_str());
}
else {
// Check for a hardwired target value in the map or handler.
/*
- * Copyright 2001-2010 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
// Since we're passing the ACS by value, we need to compute the return URL,
// so we'll need the target resource for real.
recoverRelayState(request.getApplication(), request, request, target, false);
+ limitRelayState(m_log, request.getApplication(), request, target.c_str());
prop.second = request.getParameter("discoveryURL");
if (prop.second && *prop.second)
/*
- * Copyright 2001-2007 Internet2
+ * Copyright 2001-2011 Internet2
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <memory>
#include <xmltooling/logging.h>
+#include <xmltooling/io/HTTPRequest.h>
+#include <shibsp/Application.h>
using namespace xmltooling::logging;
using namespace xercesc;
+namespace shibsp {
+ void SHIBSP_DLLLOCAL limitRelayState(
+ xmltooling::logging::Category& log,
+ const Application& application,
+ const xmltooling::HTTPRequest& httpRequest,
+ const char* relayState
+ );
+};
+
#endif /* __shibsp_internal_h__ */