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