a5371aa77805b19ecab99e93eff04aba5bd6a465
[shibboleth/cpp-sp.git] / shibsp / handler / impl / SAML2Logout.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * SAML2Logout.cpp
23  *
24  * Handles SAML 2.0 single logout protocol messages.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "Application.h"
30 #include "ServiceProvider.h"
31 #include "SPRequest.h"
32 #include "handler/AbstractHandler.h"
33 #include "handler/LogoutHandler.h"
34 #include "util/SPConstants.h"
35
36 #ifndef SHIBSP_LITE
37 # include "SessionCacheEx.h"
38 # include "security/SecurityPolicy.h"
39 # include "security/SecurityPolicyProvider.h"
40 # include "metadata/MetadataProviderCriteria.h"
41 # include <fstream>
42 # include <boost/algorithm/string.hpp>
43 # include <boost/iterator/indirect_iterator.hpp>
44 # include <boost/lambda/bind.hpp>
45 # include <boost/lambda/if.hpp>
46 # include <boost/lambda/lambda.hpp>
47 # include <saml/exceptions.h>
48 # include <saml/SAMLConfig.h>
49 # include <saml/saml2/core/Protocols.h>
50 # include <saml/saml2/metadata/EndpointManager.h>
51 # include <saml/saml2/metadata/Metadata.h>
52 # include <saml/saml2/metadata/MetadataCredentialCriteria.h>
53 # include <xmltooling/util/URLEncoder.h>
54 using namespace opensaml::saml2;
55 using namespace opensaml::saml2p;
56 using namespace opensaml::saml2md;
57 using namespace opensaml;
58 using namespace boost::lambda;
59 #else
60 # include "lite/SAMLConstants.h"
61 #endif
62
63 #include <boost/scoped_ptr.hpp>
64
65 using namespace shibsp;
66 using namespace xmltooling;
67 using namespace boost;
68 using namespace std;
69
70 namespace shibsp {
71
72 #if defined (_MSC_VER)
73     #pragma warning( push )
74     #pragma warning( disable : 4250 )
75 #endif
76
77     class SHIBSP_DLLLOCAL SAML2Logout : public AbstractHandler, public LogoutHandler
78     {
79     public:
80         SAML2Logout(const DOMElement* e, const char* appId);
81         virtual ~SAML2Logout() {}
82
83         void receive(DDF& in, ostream& out);
84         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
85
86 #ifndef SHIBSP_LITE
87         void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const {
88             const char* loc = getString("Location").second;
89             string hurl(handlerURL);
90             if (*loc != '/')
91                 hurl += '/';
92             hurl += loc;
93             auto_ptr_XMLCh widen(hurl.c_str());
94             SingleLogoutService* ep = SingleLogoutServiceBuilder::buildSingleLogoutService();
95             ep->setLocation(widen.get());
96             ep->setBinding(getXMLString("Binding").second);
97             role.getSingleLogoutServices().push_back(ep);
98             role.addSupport(samlconstants::SAML20P_NS);
99         }
100
101         const char* getType() const {
102             return "SingleLogoutService";
103         }
104 #endif
105         const XMLCh* getProtocolFamily() const {
106             return samlconstants::SAML20P_NS;
107         }
108
109     private:
110         pair<bool,long> doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
111
112 #ifndef SHIBSP_LITE
113         pair<bool,long> sendResponse(
114             LogoutEvent* logoutEvent,
115             const XMLCh* requestID,
116             const XMLCh* code,
117             const XMLCh* subcode,
118             const char* msg,
119             const char* relayState,
120             const RoleDescriptor* role,
121             const Application& application,
122             HTTPResponse& httpResponse,
123             bool front
124             ) const;
125
126         LogoutEvent* newLogoutEvent(
127             const Application& application, const HTTPRequest* request=nullptr, const Session* session=nullptr
128             ) const {
129             LogoutEvent* e = LogoutHandler::newLogoutEvent(application, request, session);
130             if (e)
131                 e->m_protocol = m_protocol.get();
132             return e;
133         }
134
135         scoped_ptr<MessageDecoder> m_decoder;
136         vector<string> m_bindings;
137         map< string,boost::shared_ptr<MessageEncoder> > m_encoders;
138         auto_ptr_char m_protocol;
139 #endif
140     };
141
142 #if defined (_MSC_VER)
143     #pragma warning( pop )
144 #endif
145
146     Handler* SHIBSP_DLLLOCAL SAML2LogoutFactory(const pair<const DOMElement*,const char*>& p)
147     {
148         return new SAML2Logout(p.first, p.second);
149     }
150 };
151
152 SAML2Logout::SAML2Logout(const DOMElement* e, const char* appId)
153     : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".Logout.SAML2"))
154 #ifndef SHIBSP_LITE
155         ,m_protocol(samlconstants::SAML20P_NS)
156 #endif
157 {
158     m_initiator = false;
159 #ifndef SHIBSP_LITE
160     m_preserve.push_back("ID");
161     m_preserve.push_back("entityID");
162     m_preserve.push_back("RelayState");
163
164     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
165         SAMLConfig& conf = SAMLConfig::getConfig();
166
167         // Handle incoming binding.
168         m_decoder.reset(
169             conf.MessageDecoderManager.newPlugin(
170                 getString("Binding").second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS)
171                 )
172             );
173         m_decoder->setArtifactResolver(SPConfig::getConfig().getArtifactResolver());
174
175         if (m_decoder->isUserAgentPresent()) {
176             // Handle front-channel binding setup.
177             string dupBindings;
178             pair<bool,const char*> outgoing = getString("outgoingBindings", m_configNS.get());
179             if (outgoing.first) {
180                 dupBindings = outgoing.second;
181             }
182             else {
183                 // No override, so we'll install a default binding precedence.
184                 dupBindings = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' +
185                     samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN + ' ' + samlconstants::SAML20_BINDING_HTTP_ARTIFACT;
186             }
187
188             split(m_bindings, dupBindings, is_space(), algorithm::token_compress_on);
189             for (vector<string>::const_iterator b = m_bindings.begin(); b != m_bindings.end(); ++b) {
190                 try {
191                     boost::shared_ptr<MessageEncoder> encoder(
192                         conf.MessageEncoderManager.newPlugin(*b, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS))
193                         );
194                     if (encoder->isUserAgentPresent() && XMLString::equals(getProtocolFamily(), encoder->getProtocolFamily())) {
195                         m_encoders[*b] = encoder;
196                         m_log.debug("supporting outgoing binding (%s)", b->c_str());
197                     }
198                     else {
199                         m_log.warn("skipping outgoing binding (%s), not a SAML 2.0 front-channel mechanism", b->c_str());
200                     }
201                 }
202                 catch (std::exception& ex) {
203                     m_log.error("error building MessageEncoder: %s", ex.what());
204                 }
205             }
206         }
207         else {
208             pair<bool,const char*> b = getString("Binding");
209             boost::shared_ptr<MessageEncoder> encoder(
210                 conf.MessageEncoderManager.newPlugin(b.second, pair<const DOMElement*,const XMLCh*>(e,shibspconstants::SHIB2SPCONFIG_NS))
211                 );
212             m_encoders[b.second] = encoder;
213         }
214     }
215 #endif
216
217     string address(appId);
218     address += getString("Location").second;
219     setAddress(address.c_str());
220 }
221
222 pair<bool,long> SAML2Logout::run(SPRequest& request, bool isHandler) const
223 {
224     // Defer to base class for front-channel loop first.
225     // This won't initiate the loop, only continue/end it.
226     pair<bool,long> ret = LogoutHandler::run(request, isHandler);
227     if (ret.first)
228         return ret;
229
230     SPConfig& conf = SPConfig::getConfig();
231     if (conf.isEnabled(SPConfig::OutOfProcess)) {
232         // When out of process, we run natively and directly process the message.
233         return doRequest(request.getApplication(), request, request);
234     }
235     else {
236         // When not out of process, we remote all the message processing.
237         vector<string> headers(1,"Cookie");
238         headers.push_back("User-Agent");
239         DDF out,in = wrap(request, &headers, true);
240         DDFJanitor jin(in), jout(out);
241         out=request.getServiceProvider().getListenerService()->send(in);
242         return unwrap(request, out);
243     }
244 }
245
246 void SAML2Logout::receive(DDF& in, ostream& out)
247 {
248     // Find application.
249     const char* aid = in["application_id"].string();
250     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
251     if (!app) {
252         // Something's horribly wrong.
253         m_log.error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
254         throw ConfigurationException("Unable to locate application for logout, deleted?");
255     }
256
257     // Unpack the request.
258     scoped_ptr<HTTPRequest> req(getRequest(in));
259
260     // Wrap a response shim.
261     DDF ret(nullptr);
262     DDFJanitor jout(ret);
263     scoped_ptr<HTTPResponse> resp(getResponse(ret));
264
265     // Since we're remoted, the result should either be a throw, which we pass on,
266     // a false/0 return, which we just return as an empty structure, or a response/redirect,
267     // which we capture in the facade and send back.
268     doRequest(*app, *req, *resp);
269     out << ret;
270 }
271
272 pair<bool,long> SAML2Logout::doRequest(const Application& application, const HTTPRequest& request, HTTPResponse& response) const
273 {
274 #ifndef SHIBSP_LITE
275     // First capture the active session ID.
276     SessionCache* cache = application.getServiceProvider().getSessionCache();
277     SessionCacheEx* cacheex = dynamic_cast<SessionCacheEx*>(cache);
278     string session_id = cache->active(application, request);
279
280     scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(application, &request));
281     if (logout_event.get() && !session_id.empty())
282         logout_event->m_sessions.push_back(session_id);
283
284     if (!strcmp(request.getMethod(),"GET") && request.getParameter("notifying")) {
285         // This is returning from a front-channel notification, so we have to do the back-channel and then
286         // respond. To do that, we need state from the original request.
287         if (!request.getParameter("entityID")) {
288             cache->remove(application, request, &response);
289             throw FatalProfileException("Application notification loop did not return entityID for LogoutResponse.");
290         }
291
292         // Best effort on back channel and to remove the user agent's session.
293         bool worked1 = false,worked2 = false;
294         if (!session_id.empty()) {
295             vector<string> sessions(1,session_id);
296             worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
297             try {
298                 cache->remove(application, request, &response);
299                 worked2 = true;
300             }
301             catch (std::exception& ex) {
302                 m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
303             }
304         }
305         else {
306             worked1 = worked2 = true;
307         }
308
309         // We need metadata to issue a response.
310         MetadataProvider* m = application.getMetadataProvider();
311         Locker metadataLocker(m);
312         MetadataProviderCriteria mc(
313             application, request.getParameter("entityID"), &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS
314             );
315         pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
316         if (!entity.first) {
317             throw MetadataException(
318                 "Unable to locate metadata for identity provider ($entityID)",
319                 namedparams(1, "entityID", request.getParameter("entityID"))
320                 );
321         }
322         else if (!entity.second) {
323             throw MetadataException(
324                 "Unable to locate SAML 2.0 IdP role for identity provider ($entityID).",
325                 namedparams(1, "entityID", request.getParameter("entityID"))
326                 );
327         }
328
329         auto_ptr_XMLCh reqid(request.getParameter("ID"));
330         if (worked1 && worked2) {
331             // Successful LogoutResponse. Has to be front-channel or we couldn't be here.
332             if (logout_event.get())
333                 logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_GLOBAL;
334             return sendResponse(
335                 logout_event.get(),
336                 reqid.get(),
337                 StatusCode::SUCCESS, nullptr, nullptr,
338                 request.getParameter("RelayState"),
339                 entity.second,
340                 application,
341                 response,
342                 true
343                 );
344         }
345
346         return sendResponse(
347             logout_event.get(),
348             reqid.get(),
349             StatusCode::RESPONDER, nullptr, "Unable to fully destroy principal's session.",
350             request.getParameter("RelayState"),
351             entity.second,
352             application,
353             response,
354             true
355             );
356     }
357
358     // If we get here, it's an external protocol message to decode.
359
360     // Locate policy key.
361     pair<bool,const char*> policyId = getString("policyId", m_configNS.get());  // namespace-qualified if inside handler element
362     if (!policyId.first)
363         policyId = application.getString("policyId");   // unqualified in Application(s) element
364
365     // Lock metadata for use by policy.
366     Locker metadataLocker(application.getMetadataProvider());
367
368     // Create the policy.
369     scoped_ptr<SecurityPolicy> policy(
370         application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, &IDPSSODescriptor::ELEMENT_QNAME, policyId.second)
371         );
372
373     // Decode the message.
374     string relayState;
375     scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, request, *policy));
376     const LogoutRequest* logoutRequest = dynamic_cast<LogoutRequest*>(msg.get());
377     if (logoutRequest) {
378         if (!policy->isAuthenticated())
379             throw SecurityPolicyException("Security of LogoutRequest not established.");
380
381         if (logout_event) {
382             logout_event->m_saml2Request = logoutRequest;
383             if (policy->getIssuerMetadata())
384                 logout_event->m_peer = dynamic_cast<const EntityDescriptor*>(policy->getIssuerMetadata()->getParent());
385             application.getServiceProvider().getTransactionLog()->write(*logout_event);
386             logout_event->m_saml2Request = nullptr;
387         }
388
389         // Message from IdP to logout one or more sessions.
390
391         // If this is front-channel, we have to have a session_id to use already.
392         if (m_decoder->isUserAgentPresent() && session_id.empty()) {
393             m_log.error("no active session");
394             return sendResponse(
395                 logout_event.get(),
396                 logoutRequest->getID(),
397                 StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "No active session found in request.",
398                 relayState.c_str(),
399                 policy->getIssuerMetadata(),
400                 application,
401                 response,
402                 true
403                 );
404         }
405
406         scoped_ptr<XMLObject> decryptedID;
407         NameID* nameid = logoutRequest->getNameID();
408         if (!nameid) {
409             // Check for EncryptedID.
410             EncryptedID* encname = logoutRequest->getEncryptedID();
411             if (encname) {
412                 CredentialResolver* cr=application.getCredentialResolver();
413                 if (!cr)
414                     m_log.warn("found encrypted NameID, but no decryption credential was available");
415                 else {
416                     Locker credlocker(cr);
417                     scoped_ptr<MetadataCredentialCriteria> mcc(
418                         policy->getIssuerMetadata() ? new MetadataCredentialCriteria(*policy->getIssuerMetadata()) : nullptr
419                         );
420                     try {
421                         decryptedID.reset(
422                             encname->decrypt(
423                                 *cr,
424                                 application.getRelyingParty(
425                                     policy->getIssuerMetadata() ?
426                                         dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) :
427                                             nullptr)->getXMLString("entityID").second,
428                                 mcc.get()
429                                 )
430                             );
431                         nameid = dynamic_cast<NameID*>(decryptedID.get());
432                     }
433                     catch (std::exception& ex) {
434                         m_log.error(ex.what());
435                     }
436                 }
437             }
438         }
439         if (!nameid) {
440             // No NameID, so must respond with an error.
441             m_log.error("NameID not found in request");
442             return sendResponse(
443                 logout_event.get(),
444                 logoutRequest->getID(),
445                 StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "NameID not found in request.",
446                 relayState.c_str(),
447                 policy->getIssuerMetadata(),
448                 application,
449                 response,
450                 m_decoder->isUserAgentPresent()
451                 );
452         }
453
454         // Suck indexes out of the request for next steps.
455         set<string> indexes;
456         EntityDescriptor* entity = policy->getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) : nullptr;
457         const vector<SessionIndex*> sindexes = logoutRequest->getSessionIndexs();
458         for (indirect_iterator<vector<SessionIndex*>::const_iterator> i = make_indirect_iterator(sindexes.begin());
459                 i != make_indirect_iterator(sindexes.end()); ++i) {
460             auto_ptr_char sindex(i->getSessionIndex());
461             indexes.insert(sindex.get());
462         }
463
464         // For a front-channel LogoutRequest, we have to match the information in the request
465         // against the current session.
466         if (!session_id.empty()) {
467             if (!cache->matches(application, request, entity, *nameid, &indexes)) {
468                 return sendResponse(
469                     logout_event.get(),
470                     logoutRequest->getID(),
471                     StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match logout request.",
472                     relayState.c_str(),
473                     policy->getIssuerMetadata(),
474                     application,
475                     response,
476                     true
477                     );
478             }
479
480         }
481
482         // Now we perform "logout" by finding the matching sessions.
483         vector<string> sessions;
484         try {
485             if (cacheex) {
486                 time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
487                 cacheex->logout(application, entity, *nameid, &indexes, expires, sessions);
488                 m_log.debug("session cache returned %d sessions bound to NameID in logout request", sessions.size());
489
490                 // Now we actually terminate everything except for the active session,
491                 // if this is front-channel, for notification purposes.
492                 for_each(
493                     sessions.begin(), sessions.end(),
494                     if_(_1 != session_id)[lambda::bind(&SessionCacheEx::remove, cacheex, boost::ref(application), lambda::bind(&string::c_str, _1))]
495                     );
496             }
497             else {
498                 m_log.warn("session cache does not support extended API, can't implement indirect logout of sessions");
499                 if (!session_id.empty())
500                     sessions.push_back(session_id);
501             }
502         }
503         catch (std::exception& ex) {
504             m_log.error("error while logging out matching sessions: %s", ex.what());
505             if (logout_event) {
506                 logout_event->m_nameID = nameid;
507                 logout_event->m_sessions = sessions;
508                 logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
509             }
510             return sendResponse(
511                 logout_event.get(),
512                 logoutRequest->getID(),
513                 StatusCode::RESPONDER, nullptr, ex.what(),
514                 relayState.c_str(),
515                 policy->getIssuerMetadata(),
516                 application,
517                 response,
518                 m_decoder->isUserAgentPresent()
519                 );
520         }
521
522         if (m_decoder->isUserAgentPresent()) {
523             // Pass control to the first front channel notification point, if any.
524             map<string,string> parammap;
525             if (!relayState.empty())
526                 parammap["RelayState"] = relayState;
527             auto_ptr_char entityID(entity ? entity->getEntityID() : nullptr);
528             if (entityID.get())
529                 parammap["entityID"] = entityID.get();
530             auto_ptr_char reqID(logoutRequest->getID());
531             if (reqID.get())
532                 parammap["ID"] = reqID.get();
533             pair<bool,long> result = notifyFrontChannel(application, request, response, &parammap);
534             if (result.first)
535                 return result;
536         }
537
538         // For back-channel requests, or if no front-channel notification is needed...
539         bool worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
540         bool worked2 = true;
541         if (!session_id.empty()) {
542             // One last session to yoink...
543             try {
544                 cache->remove(application, request, &response);
545             }
546             catch (std::exception& ex) {
547                 worked2 = false;
548                 m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what());
549             }
550         }
551
552         if (logout_event) {
553             logout_event->m_nameID = nameid;
554             logout_event->m_sessions = sessions;
555             logout_event->m_logoutType = (worked1 && worked2) ? LogoutEvent::LOGOUT_EVENT_PARTIAL : LogoutEvent::LOGOUT_EVENT_GLOBAL;
556         }
557         return sendResponse(
558             logout_event.get(),
559             logoutRequest->getID(),
560             (worked1 && worked2) ? StatusCode::SUCCESS : StatusCode::RESPONDER,
561             (worked1 && worked2) ? nullptr : StatusCode::PARTIAL_LOGOUT,
562             nullptr,
563             relayState.c_str(),
564             policy->getIssuerMetadata(),
565             application,
566             response,
567             m_decoder->isUserAgentPresent()
568             );
569     }
570
571     // A LogoutResponse completes an SP-initiated logout sequence.
572     const LogoutResponse* logoutResponse = dynamic_cast<LogoutResponse*>(msg.get());
573     if (logoutResponse) {
574         if (!policy->isAuthenticated()) {
575             SecurityPolicyException ex("Security of LogoutResponse not established.");
576             if (policy->getIssuerMetadata())
577                 annotateException(&ex, policy->getIssuerMetadata()); // throws it
578             ex.raise();
579         }
580  
581         if (logout_event) {
582             logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
583             logout_event->m_saml2Response = logoutResponse;
584             if (policy->getIssuerMetadata())
585                 logout_event->m_peer = dynamic_cast<const EntityDescriptor*>(policy->getIssuerMetadata()->getParent());
586         }
587
588         try {
589             checkError(logoutResponse, policy->getIssuerMetadata()); // throws if Status doesn't look good...
590         }
591         catch (std::exception& ex) {
592             if (logout_event) {
593                 logout_event->m_exception = &ex;
594                 application.getServiceProvider().getTransactionLog()->write(*logout_event);
595             }
596             throw;
597         }
598
599         // If relay state is set, recover the original return URL.
600         if (!relayState.empty())
601             recoverRelayState(application, request, response, relayState);
602
603         // Check for partial logout.
604         const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : nullptr;
605         sc = sc ? sc->getStatusCode() : nullptr;
606         if (sc && XMLString::equals(sc->getValue(), StatusCode::PARTIAL_LOGOUT)) {
607             if (logout_event)
608                 application.getServiceProvider().getTransactionLog()->write(*logout_event);
609             return sendLogoutPage(application, request, response, "partial");
610         }
611
612         if (logout_event) {
613             logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_GLOBAL;
614             application.getServiceProvider().getTransactionLog()->write(*logout_event);
615         }
616
617         if (!relayState.empty()) {
618             limitRelayState(m_log, application, request, relayState.c_str());
619             return make_pair(true, response.sendRedirect(relayState.c_str()));
620         }
621
622         // Return template for completion of logout.
623         return sendLogoutPage(application, request, response, "global");
624     }
625
626     FatalProfileException ex("Incoming message was not a samlp:LogoutRequest or samlp:LogoutResponse.");
627     if (policy->getIssuerMetadata())
628         annotateException(&ex, policy->getIssuerMetadata()); // throws it
629     ex.raise();
630     return make_pair(false,0L);  // never happen, satisfies compiler
631 #else
632     throw ConfigurationException("Cannot process logout message using lite version of shibsp library.");
633 #endif
634 }
635
636 #ifndef SHIBSP_LITE
637
638 pair<bool,long> SAML2Logout::sendResponse(
639     LogoutEvent* logoutEvent,
640     const XMLCh* requestID,
641     const XMLCh* code,
642     const XMLCh* subcode,
643     const char* msg,
644     const char* relayState,
645     const RoleDescriptor* role,
646     const Application& application,
647     HTTPResponse& httpResponse,
648     bool front
649     ) const
650 {
651     // Get endpoint and encoder to use.
652     const EndpointType* ep = nullptr;
653     const MessageEncoder* encoder = nullptr;
654     if (front) {
655         const IDPSSODescriptor* idp = dynamic_cast<const IDPSSODescriptor*>(role);
656         for (vector<string>::const_iterator b = m_bindings.begin(); idp && b != m_bindings.end(); ++b) {
657             auto_ptr_XMLCh wideb(b->c_str());
658             if ((ep = EndpointManager<SingleLogoutService>(idp->getSingleLogoutServices()).getByBinding(wideb.get()))) {
659                 map< string,boost::shared_ptr<MessageEncoder> >::const_iterator enc = m_encoders.find(*b);
660                 if (enc != m_encoders.end())
661                     encoder = enc->second.get();
662                 break;
663             }
664         }
665         if (!ep || !encoder) {
666             auto_ptr_char id(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
667             m_log.error("unable to locate compatible SLO service for provider (%s)", id.get());
668             MetadataException ex("Unable to locate endpoint at IdP ($entityID) to send LogoutResponse.");
669             annotateException(&ex, role);   // throws it
670         }
671     }
672     else {
673         encoder = m_encoders.begin()->second.get();
674     }
675
676     // Prepare response.
677     auto_ptr<LogoutResponse> logout(LogoutResponseBuilder::buildLogoutResponse());
678     logout->setInResponseTo(requestID);
679     if (ep) {
680         const XMLCh* loc = ep->getResponseLocation();
681         if (!loc || !*loc)
682             loc = ep->getLocation();
683         logout->setDestination(loc);
684     }
685     Issuer* issuer = IssuerBuilder::buildIssuer();
686     logout->setIssuer(issuer);
687     issuer->setName(application.getRelyingParty(dynamic_cast<EntityDescriptor*>(role->getParent()))->getXMLString("entityID").second);
688     fillStatus(*logout, code, subcode, msg);
689     logout->setID(SAMLConfig::getConfig().generateIdentifier());
690     logout->setIssueInstant(time(nullptr));
691
692     if (logoutEvent) {
693         logoutEvent->m_peer = dynamic_cast<EntityDescriptor*>(role->getParent());
694         logoutEvent->m_saml2Response = logout.get();
695         application.getServiceProvider().getTransactionLog()->write(*logoutEvent);
696     }
697
698     auto_ptr_char dest(logout->getDestination());
699     long ret = sendMessage(*encoder, logout.get(), relayState, dest.get(), role, application, httpResponse, front);
700     logout.release();  // freed by encoder
701     return make_pair(true, ret);
702 }
703
704 #endif