56645a25f9d712c6f1e37ecf2898d269a1dfe714
[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());  // may be namespace-qualified inside handler element
358     if (!policyId.first)
359         policyId = getString("policyId");   // try unqualified
360     if (!policyId.first)
361         policyId = application.getString("policyId");   // unqualified in Application(s) element
362
363     // Lock metadata for use by policy.
364     Locker metadataLocker(application.getMetadataProvider());
365
366     // Create the policy.
367     scoped_ptr<SecurityPolicy> policy(
368         application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, &IDPSSODescriptor::ELEMENT_QNAME, policyId.second)
369         );
370
371     // Decode the message.
372     string relayState;
373     scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, request, *policy));
374     const LogoutRequest* logoutRequest = dynamic_cast<LogoutRequest*>(msg.get());
375     if (logoutRequest) {
376         if (!policy->isAuthenticated())
377             throw SecurityPolicyException("Security of LogoutRequest not established.");
378
379         if (logout_event) {
380             logout_event->m_saml2Request = logoutRequest;
381             if (policy->getIssuerMetadata())
382                 logout_event->m_peer = dynamic_cast<const EntityDescriptor*>(policy->getIssuerMetadata()->getParent());
383             application.getServiceProvider().getTransactionLog()->write(*logout_event);
384             logout_event->m_saml2Request = nullptr;
385         }
386
387         // Message from IdP to logout one or more sessions.
388         // Extract the NameID from the request, decrypting it if needed.
389
390         scoped_ptr<XMLObject> decryptedID;
391         NameID* nameid = logoutRequest->getNameID();
392         if (!nameid) {
393             // Check for EncryptedID.
394             EncryptedID* encname = logoutRequest->getEncryptedID();
395             if (encname) {
396                 CredentialResolver* cr=application.getCredentialResolver();
397                 if (!cr)
398                     m_log.warn("found encrypted NameID, but no decryption credential was available");
399                 else {
400                     Locker credlocker(cr);
401                     scoped_ptr<MetadataCredentialCriteria> mcc(
402                         policy->getIssuerMetadata() ? new MetadataCredentialCriteria(*policy->getIssuerMetadata()) : nullptr
403                         );
404                     try {
405                         decryptedID.reset(
406                             encname->decrypt(
407                                 *cr,
408                                 application.getRelyingParty(
409                                     policy->getIssuerMetadata() ?
410                                         dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) :
411                                             nullptr)->getXMLString("entityID").second,
412                                 mcc.get()
413                                 )
414                             );
415                         nameid = dynamic_cast<NameID*>(decryptedID.get());
416                     }
417                     catch (std::exception& ex) {
418                         m_log.error(ex.what());
419                     }
420                 }
421             }
422         }
423         if (!nameid) {
424             // No NameID, so must respond with an error.
425             m_log.error("NameID not found in request");
426             return sendResponse(
427                 logout_event.get(),
428                 logoutRequest->getID(),
429                 StatusCode::REQUESTER, StatusCode::UNKNOWN_PRINCIPAL, "NameID not found in request.",
430                 relayState.c_str(),
431                 policy->getIssuerMetadata(),
432                 application,
433                 response,
434                 m_decoder->isUserAgentPresent()
435                 );
436         }
437
438         // Suck indexes out of the request for next steps.
439         set<string> indexes;
440         EntityDescriptor* entity =
441             policy->getIssuerMetadata() ? dynamic_cast<EntityDescriptor*>(policy->getIssuerMetadata()->getParent()) : nullptr;
442         const vector<SessionIndex*> sindexes = logoutRequest->getSessionIndexs();
443         for (indirect_iterator<vector<SessionIndex*>::const_iterator> i = make_indirect_iterator(sindexes.begin());
444                 i != make_indirect_iterator(sindexes.end()); ++i) {
445             auto_ptr_char sindex(i->getSessionIndex());
446             indexes.insert(sindex.get());
447         }
448
449         // For a front-channel LogoutRequest, we have to match the information in the request
450         // against the current session, if one is known/available.
451         if (!session_id.empty()) {
452             if (!cache->matches(application, request, entity, *nameid, &indexes)) {
453                 return sendResponse(
454                     logout_event.get(),
455                     logoutRequest->getID(),
456                     StatusCode::REQUESTER, StatusCode::REQUEST_DENIED, "Active session did not match logout request.",
457                     relayState.c_str(),
458                     policy->getIssuerMetadata(),
459                     application,
460                     response,
461                     true
462                     );
463             }
464         }
465         else if (m_decoder->isUserAgentPresent()) {
466             m_log.info("processing front channel logout request with no active session");
467         }
468
469         // Now we perform "logout" by finding the matching sessions.
470         vector<string> sessions;
471         try {
472             if (cacheex) {
473                 time_t expires = logoutRequest->getNotOnOrAfter() ? logoutRequest->getNotOnOrAfterEpoch() : 0;
474                 cacheex->logout(application, entity, *nameid, &indexes, expires, sessions);
475                 m_log.debug("session cache returned %d sessions bound to NameID in logout request", sessions.size());
476
477                 // Now we actually terminate everything except for the active session,
478                 // if this is front-channel, for notification purposes.
479                 for (vector<string>::const_iterator sit = sessions.begin(); sit != sessions.end(); ++sit)
480                     if (*sit != session_id)
481                         cacheex->remove(application, sit->c_str()); // using the ID-based removal operation
482             }
483             else {
484                 m_log.warn("session cache does not support extended API, can't implement indirect logout of sessions");
485                 if (!session_id.empty())
486                     sessions.push_back(session_id);
487             }
488         }
489         catch (std::exception& ex) {
490             m_log.error("error while logging out matching sessions: %s", ex.what());
491             if (logout_event) {
492                 logout_event->m_nameID = nameid;
493                 logout_event->m_sessions = sessions;
494                 logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
495             }
496             return sendResponse(
497                 logout_event.get(),
498                 logoutRequest->getID(),
499                 StatusCode::RESPONDER, nullptr, ex.what(),
500                 relayState.c_str(),
501                 policy->getIssuerMetadata(),
502                 application,
503                 response,
504                 m_decoder->isUserAgentPresent()
505                 );
506         }
507
508         if (m_decoder->isUserAgentPresent()) {
509             if (!session_id.empty()) {
510                 // Pass control to the first front channel notification point, if any.
511                 map<string,string> parammap;
512                 if (!relayState.empty())
513                     parammap["RelayState"] = relayState;
514                 auto_ptr_char entityID(entity ? entity->getEntityID() : nullptr);
515                 if (entityID.get())
516                     parammap["entityID"] = entityID.get();
517                 auto_ptr_char reqID(logoutRequest->getID());
518                 if (reqID.get())
519                     parammap["ID"] = reqID.get();
520                 pair<bool,long> result = notifyFrontChannel(application, request, response, &parammap);
521                 if (result.first)
522                     return result;
523             }
524             else {
525                 m_log.info("client's session isn't available, skipping front-channel notifications");
526             }
527         }
528
529         // For back-channel requests, or if no front-channel notification is needed or possible...
530         bool worked1 = notifyBackChannel(application, request.getRequestURL(), sessions, false);
531         bool worked2 = true;
532         if (!session_id.empty()) {
533             // One last session to yoink...
534             try {
535                 cache->remove(application, request, &response);
536             }
537             catch (std::exception& ex) {
538                 worked2 = false;
539                 m_log.error("error removing active session (%s): %s", session_id.c_str(), ex.what());
540             }
541         }
542
543         if (logout_event) {
544             logout_event->m_nameID = nameid;
545             logout_event->m_sessions = sessions;
546             logout_event->m_logoutType = (worked1 && worked2) ? LogoutEvent::LOGOUT_EVENT_PARTIAL : LogoutEvent::LOGOUT_EVENT_GLOBAL;
547         }
548         return sendResponse(
549             logout_event.get(),
550             logoutRequest->getID(),
551             (worked1 && worked2) ? StatusCode::SUCCESS : StatusCode::RESPONDER,
552             (worked1 && worked2) ? nullptr : StatusCode::PARTIAL_LOGOUT,
553             nullptr,
554             relayState.c_str(),
555             policy->getIssuerMetadata(),
556             application,
557             response,
558             m_decoder->isUserAgentPresent()
559             );
560     }
561
562     // A LogoutResponse completes an SP-initiated logout sequence.
563     const LogoutResponse* logoutResponse = dynamic_cast<LogoutResponse*>(msg.get());
564     if (logoutResponse) {
565         if (!policy->isAuthenticated()) {
566             SecurityPolicyException ex("Security of LogoutResponse not established.");
567             annotateException(&ex, policy->getIssuerMetadata()); // throws it
568         }
569  
570         if (logout_event) {
571             logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
572             logout_event->m_saml2Response = logoutResponse;
573             if (policy->getIssuerMetadata())
574                 logout_event->m_peer = dynamic_cast<const EntityDescriptor*>(policy->getIssuerMetadata()->getParent());
575         }
576
577         try {
578             checkError(logoutResponse, policy->getIssuerMetadata()); // throws if Status doesn't look good...
579         }
580         catch (std::exception& ex) {
581             if (logout_event) {
582                 logout_event->m_exception = &ex;
583                 application.getServiceProvider().getTransactionLog()->write(*logout_event);
584             }
585             throw;
586         }
587
588         // If relay state is set, recover the original return URL.
589         if (!relayState.empty())
590             recoverRelayState(application, request, response, relayState);
591
592         // Check for partial logout.
593         const StatusCode* sc = logoutResponse->getStatus() ? logoutResponse->getStatus()->getStatusCode() : nullptr;
594         sc = sc ? sc->getStatusCode() : nullptr;
595         if (sc && XMLString::equals(sc->getValue(), StatusCode::PARTIAL_LOGOUT)) {
596             if (logout_event)
597                 application.getServiceProvider().getTransactionLog()->write(*logout_event);
598             return sendLogoutPage(application, request, response, "partial");
599         }
600
601         if (logout_event) {
602             logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_GLOBAL;
603             application.getServiceProvider().getTransactionLog()->write(*logout_event);
604         }
605
606         if (!relayState.empty()) {
607             application.limitRedirect(request, relayState.c_str());
608             return make_pair(true, response.sendRedirect(relayState.c_str()));
609         }
610
611         // Return template for completion of logout.
612         return sendLogoutPage(application, request, response, "global");
613     }
614
615     FatalProfileException ex("Incoming message was not a samlp:LogoutRequest or samlp:LogoutResponse.");
616     if (policy->getIssuerMetadata())
617         annotateException(&ex, policy->getIssuerMetadata()); // throws it
618     ex.raise();
619     return make_pair(false,0L);  // never happen, satisfies compiler
620 #else
621     throw ConfigurationException("Cannot process logout message using lite version of shibsp library.");
622 #endif
623 }
624
625 #ifndef SHIBSP_LITE
626
627 pair<bool,long> SAML2Logout::sendResponse(
628     LogoutEvent* logoutEvent,
629     const XMLCh* requestID,
630     const XMLCh* code,
631     const XMLCh* subcode,
632     const char* msg,
633     const char* relayState,
634     const RoleDescriptor* role,
635     const Application& application,
636     HTTPResponse& httpResponse,
637     bool front
638     ) const
639 {
640     // Get endpoint and encoder to use.
641     const EndpointType* ep = nullptr;
642     const MessageEncoder* encoder = nullptr;
643     if (front) {
644         const IDPSSODescriptor* idp = dynamic_cast<const IDPSSODescriptor*>(role);
645         for (vector<string>::const_iterator b = m_bindings.begin(); idp && b != m_bindings.end(); ++b) {
646             auto_ptr_XMLCh wideb(b->c_str());
647             if ((ep = EndpointManager<SingleLogoutService>(idp->getSingleLogoutServices()).getByBinding(wideb.get()))) {
648                 map< string,boost::shared_ptr<MessageEncoder> >::const_iterator enc = m_encoders.find(*b);
649                 if (enc != m_encoders.end())
650                     encoder = enc->second.get();
651                 break;
652             }
653         }
654         if (!ep || !encoder) {
655             auto_ptr_char id(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
656             m_log.error("unable to locate compatible SLO service for provider (%s)", id.get());
657             MetadataException ex("Unable to locate endpoint at IdP ($entityID) to send LogoutResponse.");
658             annotateException(&ex, role);   // throws it
659         }
660     }
661     else {
662         encoder = m_encoders.begin()->second.get();
663     }
664
665     // Prepare response.
666     auto_ptr<LogoutResponse> logout(LogoutResponseBuilder::buildLogoutResponse());
667     logout->setInResponseTo(requestID);
668     if (ep) {
669         const XMLCh* loc = ep->getResponseLocation();
670         if (!loc || !*loc)
671             loc = ep->getLocation();
672         logout->setDestination(loc);
673     }
674     Issuer* issuer = IssuerBuilder::buildIssuer();
675     logout->setIssuer(issuer);
676     issuer->setName(application.getRelyingParty(dynamic_cast<EntityDescriptor*>(role->getParent()))->getXMLString("entityID").second);
677     fillStatus(*logout, code, subcode, msg);
678     XMLCh* msgid = SAMLConfig::getConfig().generateIdentifier();
679     logout->setID(msgid);
680     XMLString::release(&msgid);
681     logout->setIssueInstant(time(nullptr));
682
683     if (logoutEvent) {
684         logoutEvent->m_peer = dynamic_cast<EntityDescriptor*>(role->getParent());
685         logoutEvent->m_saml2Response = logout.get();
686         application.getServiceProvider().getTransactionLog()->write(*logoutEvent);
687     }
688
689     auto_ptr_char dest(logout->getDestination());
690     long ret = sendMessage(*encoder, logout.get(), relayState, dest.get(), role, application, httpResponse, front);
691     logout.release();  // freed by encoder
692     return make_pair(true, ret);
693 }
694
695 #endif