/*
- * Copyright 2001-2007 Internet2
- *
+ * Copyright 2001-2010 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
/**
* SAML2Logout.cpp
- *
+ *
* Handles SAML 2.0 single logout protocol messages.
*/
#include "exceptions.h"
#include "Application.h"
#include "ServiceProvider.h"
+#include "SPRequest.h"
#include "handler/AbstractHandler.h"
#include "handler/LogoutHandler.h"
#include "util/SPConstants.h"
#ifndef SHIBSP_LITE
# include "SessionCacheEx.h"
# include "security/SecurityPolicy.h"
+# include "security/SecurityPolicyProvider.h"
+# include "metadata/MetadataProviderCriteria.h"
# include "util/TemplateParameters.h"
# include <fstream>
+# include <saml/exceptions.h>
# include <saml/SAMLConfig.h>
# include <saml/saml2/core/Protocols.h>
# include <saml/saml2/metadata/EndpointManager.h>
+# include <saml/saml2/metadata/Metadata.h>
# include <saml/saml2/metadata/MetadataCredentialCriteria.h>
# include <xmltooling/util/URLEncoder.h>
using namespace opensaml::saml2;
using namespace opensaml::saml2p;
using namespace opensaml::saml2md;
using namespace opensaml;
+#else
+# include "lite/SAMLConstants.h"
#endif
using namespace shibsp;
#pragma warning( push )
#pragma warning( disable : 4250 )
#endif
-
+
class SHIBSP_DLLLOCAL SAML2Logout : public AbstractHandler, public LogoutHandler
{
public:
}
#endif
}
-
+
void receive(DDF& in, ostream& out);
pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
return "SingleLogoutService";
}
#endif
+ const XMLCh* getProtocolFamily() const {
+ return samlconstants::SAML20P_NS;
+ }
private:
pair<bool,long> doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
bool front
) const;
- QName m_role;
+ xmltooling::QName m_role;
MessageDecoder* m_decoder;
XMLCh* m_outgoing;
vector<const XMLCh*> m_bindings;
SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId)
: AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".Logout.SAML2"))
#ifndef SHIBSP_LITE
- ,m_role(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME), m_decoder(NULL), m_outgoing(NULL)
+ ,m_role(samlconstants::SAML20MD_NS, IDPSSODescriptor::LOCAL_NAME), m_decoder(nullptr), m_outgoing(nullptr)
#endif
{
-#ifndef SHIBSP_LITE
m_initiator = false;
+#ifndef SHIBSP_LITE
m_preserve.push_back("ID");
m_preserve.push_back("entityID");
m_preserve.push_back("RelayState");
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());
+ if (encoder->isUserAgentPresent() && XMLString::equals(getProtocolFamily(), encoder->getProtocolFamily())) {
+ 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 SAML 2.0 front-channel mechanism", b.get());
+ }
}
catch (exception& ex) {
m_log.error("error building MessageEncoder: %s", ex.what());
MessageEncoder* encoder = conf.MessageEncoderManager.newPlugin(
getString("Binding").second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
);
- m_encoders.insert(pair<const XMLCh*,MessageEncoder*>(NULL, encoder));
+ m_encoders.insert(pair<const XMLCh*,MessageEncoder*>(nullptr, encoder));
}
}
#endif
{
// Find application.
const char* aid=in["application_id"].string();
- const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
+ const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
if (!app) {
// Something's horribly wrong.
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));
// Wrap a response shim.
- DDF ret(NULL);
+ DDF ret(nullptr);
DDFJanitor jout(ret);
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.
worked2 = true;
}
catch (exception& ex) {
- m_log.error("error removing session (%s): %s", session_id, ex.what());
+ m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
}
}
else {
// We need metadata to issue a response.
MetadataProvider* m = application.getMetadataProvider();
Locker metadataLocker(m);
- MetadataProvider::Criteria mc(request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
+ MetadataProviderCriteria mc(application, request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
if (!entity.first) {
throw MetadataException(
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"), entity.second, application, response, true
+ reqid.get(), StatusCode::SUCCESS, nullptr, nullptr, request.getParameter("RelayState"), entity.second, application, response, true
);
}
return sendResponse(
reqid.get(),
- StatusCode::RESPONDER, NULL, "Unable to fully destroy principal's session.",
+ StatusCode::RESPONDER, nullptr, "Unable to fully destroy principal's session.",
request.getParameter("RelayState"),
entity.second,
application,
pair<bool,const char*> policyId = getString("policyId", m_configNS.get()); // namespace-qualified if inside handler element
if (!policyId.first)
policyId = application.getString("policyId"); // unqualified in Application(s) element
-
- // Access policy properties.
- const PropertySet* settings = application.getServiceProvider().getPolicySettings(policyId.second);
- pair<bool,bool> validate = settings->getBool("validate");
// Lock metadata for use by policy.
Locker metadataLocker(application.getMetadataProvider());
// Create the policy.
- shibsp::SecurityPolicy policy(application, &m_role, validate.first && validate.second);
-
+ auto_ptr<SecurityPolicy> policy(
+ application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, &m_role, policyId.second)
+ );
+
// Decode the message.
string relayState;
- auto_ptr<XMLObject> msg(m_decoder->decode(relayState, request, policy));
+ auto_ptr<XMLObject> msg(m_decoder->decode(relayState, request, *policy.get()));
const LogoutRequest* logoutRequest = dynamic_cast<LogoutRequest*>(msg.get());
if (logoutRequest) {
- if (!policy.isAuthenticated())
+ 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.empty()) {
m_log.error("no active session");
logoutRequest->getID(),
StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "No active session found in request.",
relayState.c_str(),
- policy.getIssuerMetadata(),
+ policy->getIssuerMetadata(),
application,
response,
true
else {
Locker credlocker(cr);
auto_ptr<MetadataCredentialCriteria> mcc(
- policy.getIssuerMetadata() ? new MetadataCredentialCriteria(*policy.getIssuerMetadata()) : NULL
+ policy->getIssuerMetadata() ? new MetadataCredentialCriteria(*policy->getIssuerMetadata()) : nullptr
);
try {
auto_ptr<XMLObject> decryptedID(
encname->decrypt(
*cr,
- application.getRelyingParty(policy.getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL)->getXMLString("entityID").second,
+ application.getRelyingParty(
+ policy->getIssuerMetadata() ?
+ dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) :
+ nullptr)->getXMLString("entityID").second,
mcc.get()
)
);
logoutRequest->getID(),
StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "NameID not found in request.",
relayState.c_str(),
- policy.getIssuerMetadata(),
+ policy->getIssuerMetadata(),
application,
response,
m_decoder->isUserAgentPresent()
);
}
- auto_ptr<NameID> namewrapper(ownedName ? nameid : NULL);
+ auto_ptr<NameID> namewrapper(ownedName ? nameid : nullptr);
// Suck indexes out of the request for next steps.
set<string> indexes;
- EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : NULL;
+ EntityDescriptor* entity = policy->getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) : nullptr;
const vector<SessionIndex*> sindexes = logoutRequest->getSessionIndexs();
for (vector<SessionIndex*>::const_iterator i = sindexes.begin(); i != sindexes.end(); ++i) {
auto_ptr_char sindex((*i)->getSessionIndex());
logoutRequest->getID(),
StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match logout request.",
relayState.c_str(),
- policy.getIssuerMetadata(),
+ policy->getIssuerMetadata(),
application,
response,
true
if (cacheex) {
time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
cacheex->logout(application, entity, *nameid, &indexes, expires, sessions);
+ m_log.debug("session cache returned %d sessions bound to NameID in logout request", sessions.size());
// Now we actually terminate everything except for the active session,
// if this is front-channel, for notification purposes.
m_log.error("error while logging out matching sessions: %s", ex.what());
return sendResponse(
logoutRequest->getID(),
- StatusCode::RESPONDER, NULL, ex.what(),
+ StatusCode::RESPONDER, nullptr, ex.what(),
relayState.c_str(),
- policy.getIssuerMetadata(),
+ policy->getIssuerMetadata(),
application,
response,
m_decoder->isUserAgentPresent()
map<string,string> parammap;
if (!relayState.empty())
parammap["RelayState"] = relayState;
- auto_ptr_char entityID(entity ? entity->getEntityID() : NULL);
+ auto_ptr_char entityID(entity ? entity->getEntityID() : nullptr);
if (entityID.get())
parammap["entityID"] = entityID.get();
auto_ptr_char reqID(logoutRequest->getID());
if (result.first)
return result;
}
-
+
// For back-channel requests, or if no front-channel notification is needed...
- bool worked1 = false,worked2 = false;
- worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
+ bool worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
+ bool worked2 = true;
if (!session_id.empty()) {
// One last session to yoink...
try {
cache->remove(application, request, &response);
- worked2 = true;
}
catch (exception& ex) {
+ worked2 = false;
m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what());
}
}
return sendResponse(
logoutRequest->getID(),
(worked1 && worked2) ? StatusCode::SUCCESS : StatusCode::RESPONDER,
- (worked1 && worked2) ? NULL : StatusCode::PARTIAL_LOGOUT,
- NULL,
+ (worked1 && worked2) ? nullptr : StatusCode::PARTIAL_LOGOUT,
+ nullptr,
relayState.c_str(),
- policy.getIssuerMetadata(),
+ policy->getIssuerMetadata(),
application,
response,
m_decoder->isUserAgentPresent()
// A LogoutResponse completes an SP-initiated logout sequence.
const LogoutResponse* logoutResponse = dynamic_cast<LogoutResponse*>(msg.get());
if (logoutResponse) {
- if (!policy.isAuthenticated()) {
+ if (!policy->isAuthenticated()) {
SecurityPolicyException ex("Security of LogoutResponse not established.");
- if (policy.getIssuerMetadata())
- annotateException(&ex, policy.getIssuerMetadata()); // throws it
+ if (policy->getIssuerMetadata())
+ annotateException(&ex, policy->getIssuerMetadata()); // throws it
ex.raise();
}
- checkError(logoutResponse, policy.getIssuerMetadata()); // throws if Status doesn't look good...
+ 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);
+
+ // Check for partial logout.
+ const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : nullptr;
+ sc = sc ? sc->getStatusCode() : nullptr;
+ if (sc && XMLString::equals(sc->getValue(), StatusCode::PARTIAL_LOGOUT))
+ return sendLogoutPage(application, request, response, "partial");
+
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, request, response, false, "Global logout completed.");
+ // Return template for completion of logout.
+ return sendLogoutPage(application, request, response, "global");
}
FatalProfileException ex("Incoming message was not a samlp:LogoutRequest or samlp:LogoutResponse.");
- if (policy.getIssuerMetadata())
- annotateException(&ex, policy.getIssuerMetadata()); // throws it
+ if (policy->getIssuerMetadata())
+ annotateException(&ex, policy->getIssuerMetadata()); // throws it
ex.raise();
return make_pair(false,0L); // never happen, satisfies compiler
#else
) const
{
// Get endpoint and encoder to use.
- const EndpointType* ep = NULL;
- const MessageEncoder* encoder = NULL;
+ const EndpointType* ep = nullptr;
+ const MessageEncoder* encoder = nullptr;
if (front) {
const IDPSSODescriptor* idp = dynamic_cast<const IDPSSODescriptor*>(role);
for (vector<const XMLCh*>::const_iterator b = m_bindings.begin(); idp && b!=m_bindings.end(); ++b) {