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