#include "util/SPConstants.h"
#ifndef SHIBSP_LITE
-# include "SessionCache.h"
+# include "SessionCacheEx.h"
# include "security/SecurityPolicy.h"
# include "util/TemplateParameters.h"
# include <fstream>
void receive(DDF& in, ostream& out);
pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+#ifndef SHIBSP_LITE
+ void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const {
+ const char* loc = getString("Location").second;
+ string hurl(handlerURL);
+ if (*loc != '/')
+ hurl += '/';
+ hurl += loc;
+ auto_ptr_XMLCh widen(hurl.c_str());
+ SingleLogoutService* ep = SingleLogoutServiceBuilder::buildSingleLogoutService();
+ ep->setLocation(widen.get());
+ ep->setBinding(getXMLString("Binding").second);
+ role.getSingleLogoutServices().push_back(ep);
+ role.addSupport(samlconstants::SAML20P_NS);
+ }
+
+ const char* getType() const {
+ return "SingleLogoutService";
+ }
+#endif
+
private:
- pair<bool,long> doRequest(
- const Application& application, const char* session_id, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
- ) const;
+ pair<bool,long> doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
#ifndef SHIBSP_LITE
- bool stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const;
-
pair<bool,long> sendResponse(
const XMLCh* requestID,
const XMLCh* code,
SAMLConfig& conf = SAMLConfig::getConfig();
// Handle incoming binding.
- m_decoder = conf.MessageDecoderManager.newPlugin(getString("Binding").second,make_pair(e,shibspconstants::SHIB2SPCONFIG_NS));
+ m_decoder = conf.MessageDecoderManager.newPlugin(
+ getString("Binding").second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
+ );
m_decoder->setArtifactResolver(SPConfig::getConfig().getArtifactResolver());
if (m_decoder->isUserAgentPresent()) {
m_bindings.push_back(start);
try {
auto_ptr_char b(start);
- MessageEncoder * encoder = conf.MessageEncoderManager.newPlugin(b.get(),make_pair(e,shibspconstants::SHIB2SPCONFIG_NS));
+ MessageEncoder * encoder = conf.MessageEncoderManager.newPlugin(
+ b.get(), pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
+ );
m_encoders[start] = encoder;
m_log.debug("supporting outgoing front-channel binding (%s)", b.get());
}
}
else {
MessageEncoder* encoder = conf.MessageEncoderManager.newPlugin(
- getString("Binding").second,make_pair(e,shibspconstants::SHIB2SPCONFIG_NS)
+ getString("Binding").second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
);
m_encoders.insert(pair<const XMLCh*,MessageEncoder*>(NULL, encoder));
}
if (ret.first)
return ret;
- // Get the session_id in case this is a front-channel LogoutRequest.
- pair<string,const char*> shib_cookie = request.getApplication().getCookieNameProps("_shibsession_");
- const char* session_id = request.getCookie(shib_cookie.first.c_str());
-
SPConfig& conf = SPConfig::getConfig();
if (conf.isEnabled(SPConfig::OutOfProcess)) {
// When out of process, we run natively and directly process the message.
- return doRequest(request.getApplication(), session_id, request, request);
+ return doRequest(request.getApplication(), request, request);
}
else {
// When not out of process, we remote all the message processing.
- DDF out,in = wrap(request, NULL, true);
+ vector<string> headers(1,"Cookie");
+ DDF out,in = wrap(request, &headers, true);
DDFJanitor jin(in), jout(out);
- if (session_id)
- in.addmember("session_id").string(session_id);
out=request.getServiceProvider().getListenerService()->send(in);
return unwrap(request, out);
}
}
// Unpack the request.
+ auto_ptr<HTTPRequest> req(getRequest(in));
+
+ // Wrap a response shim.
DDF ret(NULL);
DDFJanitor jout(ret);
- auto_ptr<HTTPRequest> req(getRequest(in));
auto_ptr<HTTPResponse> resp(getResponse(ret));
// Since we're remoted, the result should either be a throw, which we pass on,
// a false/0 return, which we just return as an empty structure, or a response/redirect,
// which we capture in the facade and send back.
- doRequest(*app, in["session_id"].string(), *req.get(), *resp.get());
+ doRequest(*app, *req.get(), *resp.get());
out << ret;
}
-pair<bool,long> SAML2Logout::doRequest(
- const Application& application, const char* session_id, const HTTPRequest& request, HTTPResponse& response
- ) const
+pair<bool,long> SAML2Logout::doRequest(const Application& application, const HTTPRequest& request, HTTPResponse& response) const
{
#ifndef SHIBSP_LITE
+ // First capture the active session ID.
SessionCache* cache = application.getServiceProvider().getSessionCache();
- if (!strcmp(request.getMethod(),"GET") && request.getParameter("notifying")) {
+ SessionCacheEx* cacheex = dynamic_cast<SessionCacheEx*>(cache);
+ string session_id = cache->active(application, request);
+ if (!strcmp(request.getMethod(),"GET") && request.getParameter("notifying")) {
// This is returning from a front-channel notification, so we have to do the back-channel and then
// respond. To do that, we need state from the original request.
if (!request.getParameter("entityID")) {
- if (session_id)
- cache->remove(session_id, application);
+ cache->remove(application, request, &response);
throw FatalProfileException("Application notification loop did not return entityID for LogoutResponse.");
}
// Best effort on back channel and to remove the user agent's session.
bool worked1 = false,worked2 = false;
- if (session_id) {
+ if (!session_id.empty()) {
vector<string> sessions(1,session_id);
worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
try {
- cache->remove(session_id, application);
+ cache->remove(application, request, &response);
worked2 = true;
}
catch (exception& ex) {
}
// We need metadata to issue a response.
- Locker metadataLocker(application.getMetadataProvider());
- const EntityDescriptor* entity = application.getMetadataProvider()->getEntityDescriptor(request.getParameter("entityID"));
- if (!entity) {
+ MetadataProvider* m = application.getMetadataProvider();
+ Locker metadataLocker(m);
+ MetadataProvider::Criteria mc(request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
+ pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
+ if (!entity.first) {
throw MetadataException(
"Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", request.getParameter("entityID"))
);
}
- const IDPSSODescriptor* idp = entity->getIDPSSODescriptor(samlconstants::SAML20P_NS);
- if (!idp) {
+ else if (!entity.second) {
throw MetadataException(
"Unable to locate SAML 2.0 IdP role for identity provider ($entityID).",
namedparams(1, "entityID", request.getParameter("entityID"))
if (worked1 && worked2) {
// Successful LogoutResponse. Has to be front-channel or we couldn't be here.
return sendResponse(
- reqid.get(), StatusCode::SUCCESS, NULL, NULL, request.getParameter("RelayState"), idp, application, response, true
+ reqid.get(), StatusCode::SUCCESS, NULL, NULL, request.getParameter("RelayState"), entity.second, application, response, true
);
}
reqid.get(),
StatusCode::RESPONDER, NULL, "Unable to fully destroy principal's session.",
request.getParameter("RelayState"),
- idp,
+ entity.second,
application,
response,
true
auto_ptr<XMLObject> msg(m_decoder->decode(relayState, request, policy));
const LogoutRequest* logoutRequest = dynamic_cast<LogoutRequest*>(msg.get());
if (logoutRequest) {
- if (!policy.isSecure())
+ if (!policy.isAuthenticated())
throw SecurityPolicyException("Security of LogoutRequest not established.");
// Message from IdP to logout one or more sessions.
// If this is front-channel, we have to have a session_id to use already.
- if (m_decoder->isUserAgentPresent() && !session_id) {
+ if (m_decoder->isUserAgentPresent() && session_id.empty()) {
m_log.error("no active session");
return sendResponse(
logoutRequest->getID(),
);
}
+ auto_ptr<NameID> namewrapper(ownedName ? nameid : NULL);
+
// Suck indexes out of the request for next steps.
set<string> indexes;
EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL;
// For a front-channel LogoutRequest, we have to match the information in the request
// against the current session.
- if (session_id) {
- if (!cache->matches(session_id, entity, *nameid, &indexes, application)) {
+ if (!session_id.empty()) {
+ if (!cache->matches(application, request, entity, *nameid, &indexes)) {
return sendResponse(
logoutRequest->getID(),
- StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active sessions did not match logout request.",
+ StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match logout request.",
relayState.c_str(),
policy.getIssuerMetadata(),
application,
// Now we perform "logout" by finding the matching sessions.
vector<string> sessions;
try {
- time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
- cache->logout(entity, *nameid, &indexes, expires, application, sessions);
-
- // Now we actually terminate everything except for the active session,
- // if this is front-channel, for notification purposes.
- for (vector<string>::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit)
- if (session_id && strcmp(sit->c_str(), session_id))
- cache->remove(sit->c_str(), application);
+ if (cacheex) {
+ time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
+ cacheex->logout(application, entity, *nameid, &indexes, expires, sessions);
+
+ // Now we actually terminate everything except for the active session,
+ // if this is front-channel, for notification purposes.
+ for (vector<string>::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit)
+ if (*sit != session_id)
+ cacheex->remove(application, sit->c_str()); // using the ID-based removal operation
+ }
+ else {
+ m_log.warn("session cache does not support extended API, can't implement indirect logout of sessions");
+ if (!session_id.empty())
+ sessions.push_back(session_id);
+ }
}
catch (exception& ex) {
m_log.error("error while logging out matching sessions: %s", ex.what());
// For back-channel requests, or if no front-channel notification is needed...
bool worked1 = false,worked2 = false;
worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
- if (session_id) {
+ if (!session_id.empty()) {
// One last session to yoink...
try {
- cache->remove(session_id, application);
+ cache->remove(application, request, &response);
worked2 = true;
}
catch (exception& ex) {
- m_log.error("error removing active session (%s): %s", session_id, ex.what());
+ m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what());
}
}
// A LogoutResponse completes an SP-initiated logout sequence.
const LogoutResponse* logoutResponse = dynamic_cast<LogoutResponse*>(msg.get());
if (logoutResponse) {
- if (!policy.isSecure()) {
+ if (!policy.isAuthenticated()) {
SecurityPolicyException ex("Security of LogoutResponse not established.");
if (policy.getIssuerMetadata())
annotateException(&ex, policy.getIssuerMetadata()); // throws it
}
checkError(logoutResponse, policy.getIssuerMetadata()); // throws if Status doesn't look good...
+ // If relay state is set, recover the original return URL.
+ if (!relayState.empty())
+ recoverRelayState(application, request, response, relayState);
+ if (!relayState.empty())
+ return make_pair(true, response.sendRedirect(relayState.c_str()));
+
// Return template for completion of global logout, or redirect to homeURL.
- return sendLogoutPage(application, response, false, "Global logout completed.");
+ return sendLogoutPage(application, request, response, false, "Global logout completed.");
}
FatalProfileException ex("Incoming message was not a samlp:LogoutRequest or samlp:LogoutResponse.");
if (policy.getIssuerMetadata())
annotateException(&ex, policy.getIssuerMetadata()); // throws it
ex.raise();
- return make_pair(false,0); // never happen, satisfies compiler
+ return make_pair(false,0L); // never happen, satisfies compiler
#else
throw ConfigurationException("Cannot process logout message using lite version of shibsp library.");
#endif
#ifndef SHIBSP_LITE
-bool SAML2Logout::stronglyMatches(const XMLCh* idp, const XMLCh* sp, const saml2::NameID& n1, const saml2::NameID& n2) const
-{
- if (!XMLString::equals(n1.getName(), n2.getName()))
- return false;
-
- const XMLCh* s1 = n1.getFormat();
- const XMLCh* s2 = n2.getFormat();
- if (!s1 || !*s1)
- s1 = saml2::NameID::UNSPECIFIED;
- if (!s2 || !*s2)
- s2 = saml2::NameID::UNSPECIFIED;
- if (!XMLString::equals(s1,s2))
- return false;
-
- s1 = n1.getNameQualifier();
- s2 = n2.getNameQualifier();
- if (!s1 || !*s1)
- s1 = idp;
- if (!s2 || !*s2)
- s2 = idp;
- if (!XMLString::equals(s1,s2))
- return false;
-
- s1 = n1.getSPNameQualifier();
- s2 = n2.getSPNameQualifier();
- if (!s1 || !*s1)
- s1 = sp;
- if (!s2 || !*s2)
- s2 = sp;
- if (!XMLString::equals(s1,s2))
- return false;
-
- return true;
-}
-
pair<bool,long> SAML2Logout::sendResponse(
const XMLCh* requestID,
const XMLCh* code,
// Prepare response.
auto_ptr<LogoutResponse> logout(LogoutResponseBuilder::buildLogoutResponse());
logout->setInResponseTo(requestID);
- if (ep)
- logout->setDestination(ep->getLocation());
+ if (ep) {
+ const XMLCh* loc = ep->getResponseLocation();
+ if (!loc || !*loc)
+ loc = ep->getLocation();
+ logout->setDestination(loc);
+ }
Issuer* issuer = IssuerBuilder::buildIssuer();
logout->setIssuer(issuer);
issuer->setName(application.getXMLString("entityID").second);
fillStatus(*logout.get(), code, subcode, msg);
- auto_ptr_char dest(ep ? ep->getLocation() : NULL);
+ auto_ptr_char dest(logout->getDestination());
- long ret = sendMessage(*encoder, logout.get(), relayState, dest.get(), role, application, httpResponse, "signResponses");
+ long ret = sendMessage(*encoder, logout.get(), relayState, dest.get(), role, application, httpResponse);
logout.release(); // freed by encoder
return make_pair(true,ret);
}