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