X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fhandler%2Fimpl%2FSAML2Logout.cpp;h=cde5cbbafbee182c4b55d691d7e39527cca5e8eb;hb=c51bfd77603cf0ddb0b5e374c35586a8435895d6;hp=ab2f67e4f29b1e4fedbc25d823834580d002d0aa;hpb=5b972d6ce55415e8c172b1d63eaa46de026a1365;p=shibboleth%2Fcpp-sp.git diff --git a/shibsp/handler/impl/SAML2Logout.cpp b/shibsp/handler/impl/SAML2Logout.cpp index ab2f67e..cde5cbb 100644 --- a/shibsp/handler/impl/SAML2Logout.cpp +++ b/shibsp/handler/impl/SAML2Logout.cpp @@ -1,22 +1,26 @@ -/* - * Copyright 2001-2007 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 +/** + * 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 + * 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. + * 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. */ /** * SAML2Logout.cpp - * + * * Handles SAML 2.0 single logout protocol messages. */ @@ -24,28 +28,39 @@ #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 "SessionCache.h" +# include "SessionCacheEx.h" # include "security/SecurityPolicy.h" -# include "util/TemplateParameters.h" +# include "security/SecurityPolicyProvider.h" +# include "metadata/MetadataProviderCriteria.h" # include +# include +# include +# include # include # include # include +# include # include # include using namespace opensaml::saml2; using namespace opensaml::saml2p; using namespace opensaml::saml2md; using namespace opensaml; +#else +# include "lite/SAMLConstants.h" #endif +#include + using namespace shibsp; using namespace xmltooling; +using namespace boost; using namespace std; namespace shibsp { @@ -54,21 +69,13 @@ namespace shibsp { #pragma warning( push ) #pragma warning( disable : 4250 ) #endif - + class SHIBSP_DLLLOCAL SAML2Logout : public AbstractHandler, public LogoutHandler { public: SAML2Logout(const DOMElement* e, const char* appId); - virtual ~SAML2Logout() { -#ifndef SHIBSP_LITE - if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) { - delete m_decoder; - XMLString::release(&m_outgoing); - for_each(m_encoders.begin(), m_encoders.end(), cleanup_pair()); - } -#endif - } - + virtual ~SAML2Logout() {} + void receive(DDF& in, ostream& out); pair run(SPRequest& request, bool isHandler=true) const; @@ -86,17 +93,21 @@ namespace shibsp { role.getSingleLogoutServices().push_back(ep); role.addSupport(samlconstants::SAML20P_NS); } + + const char* getType() const { + return "SingleLogoutService"; + } #endif + const XMLCh* getProtocolFamily() const { + return samlconstants::SAML20P_NS; + } private: - pair doRequest( - const Application& application, const char* session_id, const HTTPRequest& httpRequest, HTTPResponse& httpResponse - ) const; + pair 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 sendResponse( + LogoutEvent* logoutEvent, const XMLCh* requestID, const XMLCh* code, const XMLCh* subcode, @@ -108,11 +119,19 @@ namespace shibsp { bool front ) const; - QName m_role; - MessageDecoder* m_decoder; - XMLCh* m_outgoing; - vector m_bindings; - map m_encoders; + LogoutEvent* newLogoutEvent( + const Application& application, const HTTPRequest* request=nullptr, const Session* session=nullptr + ) const { + LogoutEvent* e = LogoutHandler::newLogoutEvent(application, request, session); + if (e) + e->m_protocol = m_protocol.get(); + return e; + } + + scoped_ptr m_decoder; + vector m_bindings; + map< string,boost::shared_ptr > m_encoders; + auto_ptr_char m_protocol; #endif }; @@ -127,13 +146,13 @@ namespace shibsp { }; SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId) - : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".Logout.SAML2")) + : 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_protocol(samlconstants::SAML20P_NS) #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"); @@ -142,50 +161,52 @@ SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId) SAMLConfig& conf = SAMLConfig::getConfig(); // Handle incoming binding. - m_decoder = conf.MessageDecoderManager.newPlugin(getString("Binding").second,make_pair(e,shibspconstants::SHIB2SPCONFIG_NS)); + m_decoder.reset( + conf.MessageDecoderManager.newPlugin( + getString("Binding").second, pair(e,shibspconstants::SHIB2SPCONFIG_NS) + ) + ); m_decoder->setArtifactResolver(SPConfig::getConfig().getArtifactResolver()); if (m_decoder->isUserAgentPresent()) { // Handle front-channel binding setup. - pair outgoing = getXMLString("outgoingBindings", m_configNS.get()); + string dupBindings; + pair outgoing = getString("outgoingBindings", m_configNS.get()); if (outgoing.first) { - m_outgoing = XMLString::replicate(outgoing.second); - XMLString::trim(m_outgoing); + dupBindings = outgoing.second; + trim(dupBindings); } else { // No override, so we'll install a default binding precedence. - string prec = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' + + dupBindings = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' + samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN + ' ' + samlconstants::SAML20_BINDING_HTTP_ARTIFACT; - m_outgoing = XMLString::transcode(prec.c_str()); } - int pos; - XMLCh* start = m_outgoing; - while (start && *start) { - pos = XMLString::indexOf(start,chSpace); - if (pos != -1) - *(start + pos)=chNull; - m_bindings.push_back(start); + split(m_bindings, dupBindings, is_space(), algorithm::token_compress_on); + for (vector::const_iterator b = m_bindings.begin(); b != m_bindings.end(); ++b) { try { - auto_ptr_char b(start); - MessageEncoder * encoder = conf.MessageEncoderManager.newPlugin(b.get(),make_pair(e,shibspconstants::SHIB2SPCONFIG_NS)); - m_encoders[start] = encoder; - m_log.debug("supporting outgoing front-channel binding (%s)", b.get()); + boost::shared_ptr encoder( + conf.MessageEncoderManager.newPlugin(*b, pair(e,shibspconstants::SHIB2SPCONFIG_NS)) + ); + if (encoder->isUserAgentPresent() && XMLString::equals(getProtocolFamily(), encoder->getProtocolFamily())) { + m_encoders[*b] = encoder; + m_log.debug("supporting outgoing binding (%s)", b->c_str()); + } + else { + m_log.warn("skipping outgoing binding (%s), not a SAML 2.0 front-channel mechanism", b->c_str()); + } } - catch (exception& ex) { + catch (std::exception& ex) { m_log.error("error building MessageEncoder: %s", ex.what()); } - if (pos != -1) - start = start + pos + 1; - else - break; } } else { - MessageEncoder* encoder = conf.MessageEncoderManager.newPlugin( - getString("Binding").second,make_pair(e,shibspconstants::SHIB2SPCONFIG_NS) + pair b = getString("Binding"); + boost::shared_ptr encoder( + conf.MessageEncoderManager.newPlugin(b.second, pair(e,shibspconstants::SHIB2SPCONFIG_NS)) ); - m_encoders.insert(pair(NULL, encoder)); + m_encoders[b.second] = encoder; } } #endif @@ -203,21 +224,17 @@ pair SAML2Logout::run(SPRequest& request, bool isHandler) const if (ret.first) return ret; - // Get the session_id in case this is a front-channel LogoutRequest. - pair 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 headers(1,"Cookie"); + headers.push_back("User-Agent"); + 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); } @@ -226,78 +243,80 @@ pair SAML2Logout::run(SPRequest& request, bool isHandler) const void SAML2Logout::receive(DDF& in, ostream& out) { // Find application. - const char* aid=in["application_id"].string(); - const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL; + const char* aid = in["application_id"].string(); + 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. - DDF ret(NULL); + scoped_ptr req(getRequest(in)); + + // Wrap a response shim. + DDF ret(nullptr); DDFJanitor jout(ret); - auto_ptr req(getRequest(in)); - auto_ptr resp(getResponse(ret)); - + scoped_ptr 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, *resp); out << ret; } -pair SAML2Logout::doRequest( - const Application& application, const char* session_id, const HTTPRequest& request, HTTPResponse& response - ) const +pair 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(cache); + string session_id = cache->active(application, request); + + scoped_ptr logout_event(newLogoutEvent(application, &request)); + if (logout_event.get() && !session_id.empty()) + logout_event->m_sessions.push_back(session_id); + 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); - // Clear the cookie. - pair shib_cookie=application.getCookieNameProps("_shibsession_"); - response.setCookie(shib_cookie.first.c_str(), shib_cookie.second); - } + 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 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) { - m_log.error("error removing session (%s): %s", session_id, ex.what()); + catch (std::exception& ex) { + m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what()); } - - // Clear the cookie. - pair shib_cookie=application.getCookieNameProps("_shibsession_"); - response.setCookie(shib_cookie.first.c_str(), shib_cookie.second); } else { worked1 = worked2 = true; } // 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); + MetadataProviderCriteria mc( + application, request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS + ); + pair entity = m->getEntityDescriptor(mc); + if (!entity.first) { throw MetadataException( - "Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", request.getParameter("entityID")) + "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")) @@ -307,16 +326,26 @@ pair SAML2Logout::doRequest( auto_ptr_XMLCh reqid(request.getParameter("ID")); if (worked1 && worked2) { // Successful LogoutResponse. Has to be front-channel or we couldn't be here. + if (logout_event.get()) + logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_GLOBAL; return sendResponse( - reqid.get(), StatusCode::SUCCESS, NULL, NULL, request.getParameter("RelayState"), idp, application, response, true + logout_event.get(), + reqid.get(), + StatusCode::SUCCESS, nullptr, nullptr, + request.getParameter("RelayState"), + entity.second, + application, + response, + true ); } return sendResponse( + logout_event.get(), 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"), - idp, + entity.second, application, response, true @@ -326,45 +355,40 @@ pair SAML2Logout::doRequest( // If we get here, it's an external protocol message to decode. // Locate policy key. - pair policyId = getString("policyId", m_configNS.get()); // namespace-qualified if inside handler element + pair policyId = getString("policyId", m_configNS.get()); // may be namespace-qualified inside handler element + if (!policyId.first) + policyId = getString("policyId"); // try unqualified if (!policyId.first) policyId = application.getString("policyId"); // unqualified in Application(s) element - - // Access policy properties. - const PropertySet* settings = application.getServiceProvider().getPolicySettings(policyId.second); - pair 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); - + scoped_ptr policy( + application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, &IDPSSODescriptor::ELEMENT_QNAME, policyId.second) + ); + // Decode the message. string relayState; - auto_ptr msg(m_decoder->decode(relayState, request, policy)); + scoped_ptr msg(m_decoder->decode(relayState, request, *policy)); const LogoutRequest* logoutRequest = dynamic_cast(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) { - m_log.error("no active session"); - return sendResponse( - logoutRequest->getID(), - StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "No active session found in request.", - relayState.c_str(), - policy.getIssuerMetadata(), - application, - response, - true - ); + if (logout_event) { + logout_event->m_saml2Request = logoutRequest; + if (policy->getIssuerMetadata()) + logout_event->m_peer = dynamic_cast(policy->getIssuerMetadata()->getParent()); + application.getServiceProvider().getTransactionLog()->write(*logout_event); + logout_event->m_saml2Request = nullptr; } - bool ownedName = false; + // Message from IdP to logout one or more sessions. + // Extract the NameID from the request, decrypting it if needed. + + scoped_ptr decryptedID; NameID* nameid = logoutRequest->getNameID(); if (!nameid) { // Check for EncryptedID. @@ -375,18 +399,23 @@ pair SAML2Logout::doRequest( m_log.warn("found encrypted NameID, but no decryption credential was available"); else { Locker credlocker(cr); - auto_ptr mcc( - policy.getIssuerMetadata() ? new MetadataCredentialCriteria(*policy.getIssuerMetadata()) : NULL + scoped_ptr mcc( + policy->getIssuerMetadata() ? new MetadataCredentialCriteria(*policy->getIssuerMetadata()) : nullptr ); try { - auto_ptr decryptedID(encname->decrypt(*cr,application.getXMLString("entityID").second,mcc.get())); + decryptedID.reset( + encname->decrypt( + *cr, + application.getRelyingParty( + policy->getIssuerMetadata() ? + dynamic_cast(policy->getIssuerMetadata()->getParent()) : + nullptr)->getXMLString("entityID").second, + mcc.get() + ) + ); nameid = dynamic_cast(decryptedID.get()); - if (nameid) { - ownedName = true; - decryptedID.release(); - } } - catch (exception& ex) { + catch (std::exception& ex) { m_log.error(ex.what()); } } @@ -396,63 +425,81 @@ pair SAML2Logout::doRequest( // No NameID, so must respond with an error. m_log.error("NameID not found in request"); return sendResponse( + logout_event.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 namewrapper(ownedName ? nameid : NULL); - // Suck indexes out of the request for next steps. set indexes; - EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast(policy.getIssuerMetadata()->getParent()) : NULL; + EntityDescriptor* entity = + policy->getIssuerMetadata() ? dynamic_cast(policy->getIssuerMetadata()->getParent()) : nullptr; const vector sindexes = logoutRequest->getSessionIndexs(); - for (vector::const_iterator i = sindexes.begin(); i != sindexes.end(); ++i) { - auto_ptr_char sindex((*i)->getSessionIndex()); + for (indirect_iterator::const_iterator> i = make_indirect_iterator(sindexes.begin()); + i != make_indirect_iterator(sindexes.end()); ++i) { + auto_ptr_char sindex(i->getSessionIndex()); indexes.insert(sindex.get()); } // 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)) { + // against the current session, if one is known/available. + if (!session_id.empty()) { + if (!cache->matches(application, request, entity, *nameid, &indexes)) { return sendResponse( + logout_event.get(), 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(), + policy->getIssuerMetadata(), application, response, true ); } - + } + else if (m_decoder->isUserAgentPresent()) { + m_log.info("processing front channel logout request with no active session"); } // Now we perform "logout" by finding the matching sessions. vector 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::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); + 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. + for (vector::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) { + catch (std::exception& ex) { m_log.error("error while logging out matching sessions: %s", ex.what()); + if (logout_event) { + logout_event->m_nameID = nameid; + logout_event->m_sessions = sessions; + logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL; + } return sendResponse( + logout_event.get(), logoutRequest->getID(), - StatusCode::RESPONDER, NULL, ex.what(), + StatusCode::RESPONDER, nullptr, ex.what(), relayState.c_str(), - policy.getIssuerMetadata(), + policy->getIssuerMetadata(), application, response, m_decoder->isUserAgentPresent() @@ -460,46 +507,53 @@ pair SAML2Logout::doRequest( } if (m_decoder->isUserAgentPresent()) { - // Pass control to the first front channel notification point, if any. - map parammap; - if (!relayState.empty()) - parammap["RelayState"] = relayState; - auto_ptr_char entityID(entity ? entity->getEntityID() : NULL); - if (entityID.get()) - parammap["entityID"] = entityID.get(); - auto_ptr_char reqID(logoutRequest->getID()); - if (reqID.get()) - parammap["ID"] = reqID.get(); - pair result = notifyFrontChannel(application, request, response, ¶mmap); - 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); - if (session_id) { + if (!session_id.empty()) { + // Pass control to the first front channel notification point, if any. + map parammap; + if (!relayState.empty()) + parammap["RelayState"] = relayState; + auto_ptr_char entityID(entity ? entity->getEntityID() : nullptr); + if (entityID.get()) + parammap["entityID"] = entityID.get(); + auto_ptr_char reqID(logoutRequest->getID()); + if (reqID.get()) + parammap["ID"] = reqID.get(); + pair result = notifyFrontChannel(application, request, response, ¶mmap); + if (result.first) + return result; + } + else { + m_log.info("client's session isn't available, skipping front-channel notifications"); + } + } + + // For back-channel requests, or if no front-channel notification is needed or possible... + bool worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false); + bool worked2 = true; + if (!session_id.empty()) { // One last session to yoink... try { - cache->remove(session_id, application); - worked2 = true; + cache->remove(application, request, &response); } - catch (exception& ex) { - m_log.error("error removing active session (%s): %s", session_id, ex.what()); + catch (std::exception& ex) { + worked2 = false; + m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what()); } - - // Clear the cookie. - pair shib_cookie=application.getCookieNameProps("_shibsession_"); - response.setCookie(shib_cookie.first.c_str(), shib_cookie.second); } + if (logout_event) { + logout_event->m_nameID = nameid; + logout_event->m_sessions = sessions; + logout_event->m_logoutType = (worked1 && worked2) ? LogoutEvent::LOGOUT_EVENT_PARTIAL : LogoutEvent::LOGOUT_EVENT_GLOBAL; + } return sendResponse( + logout_event.get(), 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() @@ -509,23 +563,61 @@ pair SAML2Logout::doRequest( // A LogoutResponse completes an SP-initiated logout sequence. const LogoutResponse* logoutResponse = dynamic_cast(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 - ex.raise(); + annotateException(&ex, policy->getIssuerMetadata()); // throws it + } + + if (logout_event) { + logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL; + logout_event->m_saml2Response = logoutResponse; + if (policy->getIssuerMetadata()) + logout_event->m_peer = dynamic_cast(policy->getIssuerMetadata()->getParent()); } - checkError(logoutResponse, policy.getIssuerMetadata()); // throws if Status doesn't look good... - // Return template for completion of global logout, or redirect to homeURL. - return sendLogoutPage(application, response, false, "Global logout completed."); + try { + checkError(logoutResponse, policy->getIssuerMetadata()); // throws if Status doesn't look good... + } + catch (std::exception& ex) { + if (logout_event) { + logout_event->m_exception = &ex; + application.getServiceProvider().getTransactionLog()->write(*logout_event); + } + throw; + } + + // 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)) { + if (logout_event) + application.getServiceProvider().getTransactionLog()->write(*logout_event); + return sendLogoutPage(application, request, response, "partial"); + } + + if (logout_event) { + logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_GLOBAL; + application.getServiceProvider().getTransactionLog()->write(*logout_event); + } + + if (!relayState.empty()) { + application.limitRedirect(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"); } 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,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 @@ -533,42 +625,8 @@ pair SAML2Logout::doRequest( #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 SAML2Logout::sendResponse( + LogoutEvent* logoutEvent, const XMLCh* requestID, const XMLCh* code, const XMLCh* subcode, @@ -581,15 +639,16 @@ pair SAML2Logout::sendResponse( ) 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(role); - for (vector::const_iterator b = m_bindings.begin(); idp && b!=m_bindings.end(); ++b) { - if (ep=EndpointManager(idp->getSingleLogoutServices()).getByBinding(*b)) { - map::const_iterator enc = m_encoders.find(*b); - if (enc!=m_encoders.end()) - encoder = enc->second; + for (vector::const_iterator b = m_bindings.begin(); idp && b != m_bindings.end(); ++b) { + auto_ptr_XMLCh wideb(b->c_str()); + if ((ep = EndpointManager(idp->getSingleLogoutServices()).getByBinding(wideb.get()))) { + map< string,boost::shared_ptr >::const_iterator enc = m_encoders.find(*b); + if (enc != m_encoders.end()) + encoder = enc->second.get(); break; } } @@ -601,7 +660,7 @@ pair SAML2Logout::sendResponse( } } else { - encoder = m_encoders.begin()->second; + encoder = m_encoders.begin()->second.get(); } // Prepare response. @@ -615,14 +674,23 @@ pair SAML2Logout::sendResponse( } Issuer* issuer = IssuerBuilder::buildIssuer(); logout->setIssuer(issuer); - issuer->setName(application.getXMLString("entityID").second); - fillStatus(*logout.get(), code, subcode, msg); + issuer->setName(application.getRelyingParty(dynamic_cast(role->getParent()))->getXMLString("entityID").second); + fillStatus(*logout, code, subcode, msg); + XMLCh* msgid = SAMLConfig::getConfig().generateIdentifier(); + logout->setID(msgid); + XMLString::release(&msgid); + logout->setIssueInstant(time(nullptr)); + + if (logoutEvent) { + logoutEvent->m_peer = dynamic_cast(role->getParent()); + logoutEvent->m_saml2Response = logout.get(); + application.getServiceProvider().getTransactionLog()->write(*logoutEvent); + } auto_ptr_char dest(logout->getDestination()); - - long ret = sendMessage(*encoder, logout.get(), relayState, dest.get(), role, application, httpResponse); + long ret = sendMessage(*encoder, logout.get(), relayState, dest.get(), role, application, httpResponse, front); logout.release(); // freed by encoder - return make_pair(true,ret); + return make_pair(true, ret); } #endif