/*
- * Copyright 2001-2007 Internet2
- *
+ * Copyright 2001-2009 Internet2
+ *
* 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
/**
* SAML2LogoutInitiator.cpp
- *
+ *
* Triggers SP-initiated logout for SAML 2.0 sessions.
*/
#ifndef SHIBSP_LITE
# include "binding/SOAPClient.h"
+# include "metadata/MetadataProviderCriteria.h"
# include <saml/SAMLConfig.h>
# include <saml/saml2/core/Protocols.h>
# include <saml/saml2/binding/SAML2SOAPClient.h>
# include <saml/saml2/metadata/EndpointManager.h>
# include <saml/saml2/metadata/MetadataCredentialCriteria.h>
+# include <saml/signature/ContentReference.h>
+# include <xmltooling/security/Credential.h>
+# include <xmltooling/signature/Signature.h>
using namespace opensaml::saml2;
using namespace opensaml::saml2p;
using namespace opensaml::saml2md;
#pragma warning( push )
#pragma warning( disable : 4250 )
#endif
-
+
class SHIBSP_DLLLOCAL SAML2LogoutInitiator : public AbstractHandler, public LogoutHandler
{
public:
}
#endif
}
-
+
void setParent(const PropertySet* parent);
void receive(DDF& in, ostream& out);
pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
+#ifndef SHIBSP_LITE
+ const char* getType() const {
+ return "LogoutInitiator";
+ }
+#endif
+
private:
- pair<bool,long> doRequest(const Application& application, const char* requestURL, Session* session, HTTPResponse& httpResponse) const;
+ pair<bool,long> doRequest(
+ const Application& application, const HTTPRequest& request, HTTPResponse& httpResponse, Session* session
+ ) const;
string m_appId;
#ifndef SHIBSP_LITE
LogoutRequest* buildRequest(
- const Application& application, const Session& session, const IDPSSODescriptor& role, const MessageEncoder* encoder=NULL
+ const Application& application, const Session& session, const RoleDescriptor& role, const MessageEncoder* encoder=NULL
) const;
XMLCh* m_outgoing;
auto_ptr_char b(start);
MessageEncoder * encoder =
SAMLConfig::getConfig().MessageEncoderManager.newPlugin(b.get(),pair<const DOMElement*,const XMLCh*>(e,NULL));
- m_encoders[start] = encoder;
- m_log.debug("supporting outgoing binding (%s)", b.get());
+ if (encoder->isUserAgentPresent()) {
+ m_encoders[start] = encoder;
+ m_log.debug("supporting outgoing binding (%s)", b.get());
+ }
+ else {
+ delete encoder;
+ m_log.warn("skipping outgoing binding (%s), not a front-channel mechanism", b.get());
+ }
}
catch (exception& ex) {
m_log.error("error building MessageEncoder: %s", ex.what());
try {
session = request.getSession(false, true, false); // don't cache it and ignore all checks
if (!session)
- return make_pair(false,0);
+ return make_pair(false,0L);
// We only handle SAML 2.0 sessions.
if (!XMLString::equals(session->getProtocol(), m_protocol.get())) {
session->unlock();
- return make_pair(false,0);
+ return make_pair(false,0L);
}
}
catch (exception& ex) {
m_log.error("error accessing current session: %s", ex.what());
- return make_pair(false,0);
+ return make_pair(false,0L);
}
if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
// When out of process, we run natively.
- return doRequest(request.getApplication(), request.getRequestURL(), session, request);
+ return doRequest(request.getApplication(), request, request, session);
}
else {
// When not out of process, we remote the request.
- Locker locker(session, false);
- DDF out,in(m_address.c_str());
+ session->unlock();
+ vector<string> headers(1,"Cookie");
+ DDF out,in = wrap(request,&headers);
DDFJanitor jin(in), jout(out);
- in.addmember("application_id").string(request.getApplication().getId());
- in.addmember("session_id").string(session->getID());
- in.addmember("url").string(request.getRequestURL());
out=request.getServiceProvider().getListenerService()->send(in);
return unwrap(request, out);
}
m_log.error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
throw ConfigurationException("Unable to locate application for logout, deleted?");
}
-
+
+ // Unpack the request.
+ auto_ptr<HTTPRequest> req(getRequest(in));
+
// Set up a response shim.
DDF ret(NULL);
DDFJanitor jout(ret);
auto_ptr<HTTPResponse> resp(getResponse(ret));
-
+
Session* session = NULL;
try {
- session = app->getServiceProvider().getSessionCache()->find(in["session_id"].string(), *app, NULL, NULL);
+ session = app->getServiceProvider().getSessionCache()->find(*app, *req.get(), NULL, NULL);
}
catch (exception& ex) {
m_log.error("error accessing current session: %s", ex.what());
// 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["url"].string(), session, *resp.get());
+ doRequest(*app, *req.get(), *resp.get(), session);
}
else {
m_log.error("no NameID or issuing entityID found in session");
session->unlock();
- app->getServiceProvider().getSessionCache()->remove(in["session_id"].string(), *app);
-
- // Clear the cookie.
- pair<string,const char*> shib_cookie=app->getCookieNameProps("_shibsession_");
- resp->setCookie(shib_cookie.first.c_str(), shib_cookie.second);
+ app->getServiceProvider().getSessionCache()->remove(*app, *req.get(), resp.get());
}
}
out << ret;
}
pair<bool,long> SAML2LogoutInitiator::doRequest(
- const Application& application, const char* requestURL, Session* session, HTTPResponse& response
+ const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse, Session* session
) const
{
- // Clear the cookie.
- pair<string,const char*> shib_cookie=application.getCookieNameProps("_shibsession_");
- response.setCookie(shib_cookie.first.c_str(), shib_cookie.second);
-
// Do back channel notification.
vector<string> sessions(1, session->getID());
- if (!notifyBackChannel(application, requestURL, sessions, false)) {
+ if (!notifyBackChannel(application, httpRequest.getRequestURL(), sessions, false)) {
session->unlock();
- application.getServiceProvider().getSessionCache()->remove(sessions.front().c_str(), application);
- return sendLogoutPage(application, response, true, "Partial logout failure.");
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
+ return sendLogoutPage(application, httpRequest, httpResponse, true, "Partial logout failure.");
}
#ifndef SHIBSP_LITE
- pair<bool,long> ret = make_pair(false,0);
+ pair<bool,long> ret = make_pair(false,0L);
try {
// With a session in hand, we can create a LogoutRequest message, if we can find a compatible endpoint.
- Locker metadataLocker(application.getMetadataProvider());
- const EntityDescriptor* entity = application.getMetadataProvider()->getEntityDescriptor(session->getEntityID());
- if (!entity) {
+ MetadataProvider* m = application.getMetadataProvider();
+ Locker metadataLocker(m);
+ MetadataProviderCriteria mc(application, session->getEntityID(), &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", session->getEntityID())
+ "Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", session->getEntityID())
);
}
- const IDPSSODescriptor* role = entity->getIDPSSODescriptor(samlconstants::SAML20P_NS);
- if (!role) {
+ else if (!entity.second) {
throw MetadataException(
- "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).",
- namedparams(1, "entityID", session->getEntityID())
+ "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).", namedparams(1, "entityID", session->getEntityID())
);
}
+ const IDPSSODescriptor* role = dynamic_cast<const IDPSSODescriptor*>(entity.second);
const EndpointType* ep=NULL;
const MessageEncoder* encoder=NULL;
vector<const XMLCh*>::const_iterator b;
}
}
- if (!logoutResponse)
- ret = sendLogoutPage(application, response, false, "Identity provider did not respond to logout request.");
+ if (!logoutResponse) {
+ ret = sendLogoutPage(
+ application, httpRequest, httpResponse, false,
+ endpoints.empty() ?
+ "Identity provider does not support SAML 2 Single Logout protocol." :
+ "Identity provider did not respond to logout request."
+ );
+ }
else if (!logoutResponse->getStatus() || !logoutResponse->getStatus()->getStatusCode() ||
!XMLString::equals(logoutResponse->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) {
delete logoutResponse;
- ret = sendLogoutPage(application, response, false, "Identity provider returned a SAML error in response to logout request.");
+ ret = sendLogoutPage(application, httpRequest, httpResponse, false, "Identity provider returned a SAML error in response to logout request.");
}
else {
delete logoutResponse;
- ret = sendLogoutPage(application, response, false, "Logout completed successfully.");
+ const char* returnloc = httpRequest.getParameter("return");
+ if (returnloc) {
+ ret.second = httpResponse.sendRedirect(returnloc);
+ ret.first = true;
+ }
+ ret = sendLogoutPage(application, httpRequest, httpResponse, false, "Logout completed successfully.");
}
if (session) {
- string session_id = session->getID();
session->unlock();
session = NULL;
- application.getServiceProvider().getSessionCache()->remove(session_id.c_str(), application);
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
}
return ret;
}
+ // Save off return location as RelayState.
+ string relayState;
+ const char* returnloc = httpRequest.getParameter("return");
+ if (returnloc) {
+ relayState = returnloc;
+ preserveRelayState(application, httpResponse, relayState);
+ }
+
auto_ptr<LogoutRequest> msg(buildRequest(application, *session, *role, encoder));
msg->setDestination(ep->getLocation());
auto_ptr_char dest(ep->getLocation());
- ret.second = sendMessage(*encoder, msg.get(), NULL, dest.get(), role, application, response);
+ ret.second = sendMessage(*encoder, msg.get(), relayState.c_str(), dest.get(), role, application, httpResponse);
ret.first = true;
msg.release(); // freed by encoder
}
}
if (session) {
- string session_id = session->getID();
session->unlock();
session = NULL;
- application.getServiceProvider().getSessionCache()->remove(session_id.c_str(), application);
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
}
return ret;
#else
- string session_id = session->getID();
session->unlock();
- application.getServiceProvider().getSessionCache()->remove(session_id.c_str(), application);
+ application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse);
throw ConfigurationException("Cannot perform logout using lite version of shibsp library.");
#endif
}
#ifndef SHIBSP_LITE
LogoutRequest* SAML2LogoutInitiator::buildRequest(
- const Application& application, const Session& session, const IDPSSODescriptor& role, const MessageEncoder* encoder
+ const Application& application, const Session& session, const RoleDescriptor& role, const MessageEncoder* encoder
) const
{
+ const PropertySet* relyingParty = application.getRelyingParty(dynamic_cast<EntityDescriptor*>(role.getParent()));
+
auto_ptr<LogoutRequest> msg(LogoutRequestBuilder::buildLogoutRequest());
Issuer* issuer = IssuerBuilder::buildIssuer();
msg->setIssuer(issuer);
- issuer->setName(application.getXMLString("entityID").second);
+ issuer->setName(relyingParty->getXMLString("entityID").second);
auto_ptr_XMLCh index(session.getSessionIndex());
if (index.get() && *index.get()) {
SessionIndex* si = SessionIndexBuilder::buildSessionIndex();
}
const NameID* nameid = session.getNameID();
- const PropertySet* relyingParty = application.getRelyingParty(dynamic_cast<EntityDescriptor*>(role.getParent()));
pair<bool,const char*> flag = relyingParty->getString("encryption");
if (flag.first &&
(!strcmp(flag.second, "true") || (encoder && !strcmp(flag.second, "front")) || (!encoder && !strcmp(flag.second, "back")))) {
);
msg->setEncryptedID(encrypted.release());
}
+ else {
+ msg->setNameID(nameid->cloneNameID());
+ }
if (!encoder) {
// No encoder being used, so sign for SOAP client manually.
Locker credLocker(credResolver);
// Fill in criteria to use.
MetadataCredentialCriteria mcc(role);
- mcc.setUsage(CredentialCriteria::SIGNING_CREDENTIAL);
+ mcc.setUsage(Credential::SIGNING_CREDENTIAL);
pair<bool,const char*> keyName = relyingParty->getString("keyName");
if (keyName.first)
mcc.getKeyNames().insert(keyName.second);
if (cr)
cr->setDigestAlgorithm(sigalg.second);
}
-
+
// Sign response while marshalling.
vector<xmlsignature::Signature*> sigs(1,sig);
msg->marshall((DOMDocument*)NULL,&sigs,cred);