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