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