X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fhandler%2Fimpl%2FSAML2Logout.cpp;h=cde5cbbafbee182c4b55d691d7e39527cca5e8eb;hb=c51bfd77603cf0ddb0b5e374c35586a8435895d6;hp=c843897950c5e601ac798c05e9608fbee1e2b64d;hpb=755adf7e70c2a059ca74e6b8e670e455f502bb45;p=shibboleth%2Fcpp-sp.git diff --git a/shibsp/handler/impl/SAML2Logout.cpp b/shibsp/handler/impl/SAML2Logout.cpp index c843897..cde5cbb 100644 --- a/shibsp/handler/impl/SAML2Logout.cpp +++ b/shibsp/handler/impl/SAML2Logout.cpp @@ -1,22 +1,26 @@ -/* - * 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 +/** + * 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,6 +28,7 @@ #include "exceptions.h" #include "Application.h" #include "ServiceProvider.h" +#include "SPRequest.h" #include "handler/AbstractHandler.h" #include "handler/LogoutHandler.h" #include "util/SPConstants.h" @@ -31,22 +36,31 @@ #ifndef SHIBSP_LITE # include "SessionCacheEx.h" # include "security/SecurityPolicy.h" +# include "security/SecurityPolicyProvider.h" # include "metadata/MetadataProviderCriteria.h" -# include "util/TemplateParameters.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 { @@ -55,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; @@ -92,12 +98,16 @@ namespace shibsp { return "SingleLogoutService"; } #endif + const XMLCh* getProtocolFamily() const { + return samlconstants::SAML20P_NS; + } private: pair doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const; #ifndef SHIBSP_LITE pair sendResponse( + LogoutEvent* logoutEvent, const XMLCh* requestID, const XMLCh* code, const XMLCh* subcode, @@ -109,11 +119,19 @@ namespace shibsp { bool front ) const; - xmltooling::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 }; @@ -128,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"); @@ -143,54 +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, 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(), pair(e,shibspconstants::SHIB2SPCONFIG_NS) + boost::shared_ptr encoder( + conf.MessageEncoderManager.newPlugin(*b, pair(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[*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, 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 @@ -216,6 +232,7 @@ pair SAML2Logout::run(SPRequest& request, bool isHandler) const else { // When not out of process, we remote all the message processing. vector headers(1,"Cookie"); + headers.push_back("User-Agent"); DDF out,in = wrap(request, &headers, true); DDFJanitor jin(in), jout(out); out=request.getServiceProvider().getListenerService()->send(in); @@ -226,26 +243,26 @@ 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. - auto_ptr req(getRequest(in)); + scoped_ptr req(getRequest(in)); // Wrap a response shim. - DDF ret(NULL); + DDF ret(nullptr); DDFJanitor jout(ret); - 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, *req.get(), *resp.get()); + doRequest(*app, *req, *resp); out << ret; } @@ -257,6 +274,10 @@ pair SAML2Logout::doRequest(const Application& application, const HTT 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. @@ -274,8 +295,8 @@ pair SAML2Logout::doRequest(const Application& application, const HTT 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()); } } else { @@ -285,11 +306,14 @@ pair SAML2Logout::doRequest(const Application& application, const HTT // We need metadata to issue a response. MetadataProvider* m = application.getMetadataProvider(); Locker metadataLocker(m); - MetadataProviderCriteria mc(application, request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS); + 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")) ); } else if (!entity.second) { @@ -302,14 +326,24 @@ pair SAML2Logout::doRequest(const Application& application, const HTT 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"), entity.second, 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"), entity.second, application, @@ -321,45 +355,40 @@ pair SAML2Logout::doRequest(const Application& application, const HTT // 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.empty()) { - 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. @@ -370,24 +399,23 @@ pair SAML2Logout::doRequest(const Application& application, const HTT 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( + decryptedID.reset( encname->decrypt( *cr, - application.getRelyingParty(policy.getIssuerMetadata() ? dynamic_cast(policy.getIssuerMetadata()->getParent()) : NULL)->getXMLString("entityID").second, + 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()); } } @@ -397,42 +425,46 @@ pair SAML2Logout::doRequest(const Application& application, const HTT // 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. + // 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 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. @@ -447,7 +479,7 @@ pair SAML2Logout::doRequest(const Application& application, const HTT // 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 + 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"); @@ -455,13 +487,19 @@ pair SAML2Logout::doRequest(const Application& application, const HTT 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() @@ -469,42 +507,53 @@ pair SAML2Logout::doRequest(const Application& application, const HTT } 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.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(application, request, &response); - worked2 = true; } - catch (exception& ex) { + catch (std::exception& ex) { + worked2 = false; m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what()); } } + 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() @@ -514,27 +563,59 @@ pair SAML2Logout::doRequest(const Application& application, const HTT // 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()); + } + + 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; } - 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()) + + // 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 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 @@ -545,6 +626,7 @@ pair SAML2Logout::doRequest(const Application& application, const HTT #ifndef SHIBSP_LITE pair SAML2Logout::sendResponse( + LogoutEvent* logoutEvent, const XMLCh* requestID, const XMLCh* code, const XMLCh* subcode, @@ -557,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; } } @@ -577,7 +660,7 @@ pair SAML2Logout::sendResponse( } } else { - encoder = m_encoders.begin()->second; + encoder = m_encoders.begin()->second.get(); } // Prepare response. @@ -592,13 +675,22 @@ pair SAML2Logout::sendResponse( Issuer* issuer = IssuerBuilder::buildIssuer(); logout->setIssuer(issuer); issuer->setName(application.getRelyingParty(dynamic_cast(role->getParent()))->getXMLString("entityID").second); - fillStatus(*logout.get(), code, subcode, msg); + 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