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  * adfs.cpp
23  *
24  * ADFSv1 extension library.
25  */
26 
27 #if defined (_MSC_VER) || defined(__BORLANDC__)
28 # include "config_win32.h"
29 #else
30 # include "config.h"
31 #endif
32 
33 #ifdef WIN32
34 # define _CRT_NONSTDC_NO_DEPRECATE 1
35 # define _CRT_SECURE_NO_DEPRECATE 1
36 # define ADFS_EXPORTS __declspec(dllexport)
37 #else
38 # define ADFS_EXPORTS
39 #endif
40 
41 #include <shibsp/base.h>
42 #include <shibsp/exceptions.h>
43 #include <shibsp/Application.h>
44 #include <shibsp/ServiceProvider.h>
45 #include <shibsp/SessionCache.h>
46 #include <shibsp/SPConfig.h>
47 #include <shibsp/SPRequest.h>
48 #include <shibsp/TransactionLog.h>
49 #include <shibsp/handler/AssertionConsumerService.h>
50 #include <shibsp/handler/LogoutInitiator.h>
51 #include <shibsp/handler/SessionInitiator.h>
52 #include <xmltooling/logging.h>
53 #include <xmltooling/util/NDC.h>
54 #include <xmltooling/util/URLEncoder.h>
55 #include <xmltooling/util/XMLHelper.h>
56 #include <memory>
57 
58 #ifndef SHIBSP_LITE
59 # include <shibsp/attribute/resolver/ResolutionContext.h>
60 # include <shibsp/metadata/MetadataProviderCriteria.h>
61 # include <saml/SAMLConfig.h>
62 # include <saml/exceptions.h>
63 # include <saml/binding/SecurityPolicy.h>
64 # include <saml/saml1/core/Assertions.h>
65 # include <saml/saml2/core/Assertions.h>
66 # include <saml/saml2/metadata/Metadata.h>
67 # include <saml/saml2/metadata/EndpointManager.h>
68 # include <xmltooling/XMLToolingConfig.h>
69 # include <xmltooling/impl/AnyElement.h>
70 # include <xmltooling/util/ParserPool.h>
71 # include <xmltooling/validation/ValidatorSuite.h>
72 using namespace opensaml::saml2md;
73 # ifndef min
74 #  define min(a,b)            (((a) < (b)) ? (a) : (b))
75 # endif
76 #endif
77 using namespace shibsp;
78 using namespace opensaml;
79 using namespace xmltooling::logging;
80 using namespace xmltooling;
81 using namespace xercesc;
82 using namespace boost;
83 using namespace std;
84 
85 #define WSFED_NS "http://schemas.xmlsoap.org/ws/2003/07/secext"
86 #define WSTRUST_NS "http://schemas.xmlsoap.org/ws/2005/02/trust"
87 
88 namespace {
89 
90 #ifndef SHIBSP_LITE
91     class SHIBSP_DLLLOCAL ADFSDecoder : public MessageDecoder
92     {
93         auto_ptr_XMLCh m_ns;
94     public:
ADFSDecoder()95         ADFSDecoder() : m_ns(WSTRUST_NS) {}
~ADFSDecoder()96         virtual ~ADFSDecoder() {}
97 
getProtocolFamily() const98         const XMLCh* getProtocolFamily() const {
99             return m_ns.get();
100         }
101 
102         XMLObject* decode(
103             string& relayState,
104             const GenericRequest& genericRequest,
105             const GenericResponse* genericResponse,
106             SecurityPolicy& policy
107         ) const;
108 
109     protected:
extractMessageDetails(const XMLObject & message,const GenericRequest & req,const XMLCh * protocol,SecurityPolicy & policy) const110         void extractMessageDetails(
111             const XMLObject& message, const GenericRequest& req, const XMLCh* protocol, SecurityPolicy& policy
112             ) const {
113         }
114     };
115 
ADFSDecoderFactory(const DOMElement * const &,bool)116     MessageDecoder* ADFSDecoderFactory(const DOMElement* const &, bool)
117     {
118         return new ADFSDecoder();
119     }
120 #endif
121 
122 #if defined (_MSC_VER)
123     #pragma warning( push )
124     #pragma warning( disable : 4250 )
125 #endif
126 
127     class SHIBSP_DLLLOCAL ADFSSessionInitiator : public SessionInitiator, public AbstractHandler, public RemotedHandler
128     {
129     public:
ADFSSessionInitiator(const DOMElement * e,const char * appId)130         ADFSSessionInitiator(const DOMElement* e, const char* appId)
131             : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT ".SessionInitiator.ADFS"), nullptr, this), m_appId(appId), m_binding(WSFED_NS) {
132             SPConfig::getConfig().deprecation().warn("ADFS SessionInitiator");
133 
134             // If Location isn't set, defer address registration until the setParent call.
135             pair<bool,const char*> loc = getString("Location");
136             if (loc.first) {
137                 string address = m_appId + loc.second + "::run::ADFSSI";
138                 setAddress(address.c_str());
139             }
140         }
~ADFSSessionInitiator()141         virtual ~ADFSSessionInitiator() {}
142 
setParent(const PropertySet * parent)143         void setParent(const PropertySet* parent) {
144             DOMPropertySet::setParent(parent);
145             pair<bool,const char*> loc = getString("Location");
146             if (loc.first) {
147                 string address = m_appId + loc.second + "::run::ADFSSI";
148                 setAddress(address.c_str());
149             }
150             else {
151                 m_log.warn("no Location property in ADFS SessionInitiator (or parent), can't register as remoted handler");
152             }
153         }
154 
155         void receive(DDF& in, ostream& out);
156         pair<bool,long> unwrap(SPRequest& request, DDF& out) const;
157         pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
158 
getProtocolFamily() const159         const XMLCh* getProtocolFamily() const {
160             return m_binding.get();
161         }
162 
163 #ifndef SHIBSP_LITE
generateMetadata(saml2md::SPSSODescriptor & role,const char * handlerURL) const164         void generateMetadata(saml2md::SPSSODescriptor& role, const char* handlerURL) const {
165             doGenerateMetadata(role, handlerURL);
166         }
167 #endif
168 
169     private:
170         pair<bool,long> doRequest(
171             const Application& application,
172             const HTTPRequest* httpRequest,
173             HTTPResponse& httpResponse,
174             const char* entityID,
175             const char* acsLocation,
176             const char* authnContextClassRef,
177             string& relayState
178             ) const;
179         string m_appId;
180         auto_ptr_XMLCh m_binding;
181     };
182 
183     class SHIBSP_DLLLOCAL ADFSConsumer : public shibsp::AssertionConsumerService
184     {
185         auto_ptr_XMLCh m_protocol;
186     public:
ADFSConsumer(const DOMElement * e,const char * appId,bool deprecationSupport)187         ADFSConsumer(const DOMElement* e, const char* appId, bool deprecationSupport)
188             : shibsp::AssertionConsumerService(e, appId, Category::getInstance(SHIBSP_LOGCAT ".SSO.ADFS"), nullptr, nullptr, deprecationSupport),
189                 m_protocol(WSFED_NS) {
190             SPConfig::getConfig().deprecation().warn("ADFS AssertionConsumerService");
191         }
~ADFSConsumer()192         virtual ~ADFSConsumer() {}
193 
194 #ifndef SHIBSP_LITE
generateMetadata(SPSSODescriptor & role,const char * handlerURL) const195         void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const {
196             AssertionConsumerService::generateMetadata(role, handlerURL);
197             role.addSupport(m_protocol.get());
198         }
199 
200     private:
getProfile() const201         const char* getProfile() const {
202             return WSFED_NS;
203         }
204 
205         void implementProtocol(
206             const Application& application,
207             const HTTPRequest& httpRequest,
208             HTTPResponse& httpResponse,
209             SecurityPolicy& policy,
210             const PropertySet*,
211             const XMLObject& xmlObject
212             ) const;
213 #else
getProtocolFamily() const214         const XMLCh* getProtocolFamily() const {
215             return m_protocol.get();
216         }
217 #endif
218     };
219 
220     class SHIBSP_DLLLOCAL ADFSLogoutInitiator : public AbstractHandler, public LogoutInitiator
221     {
222     public:
ADFSLogoutInitiator(const DOMElement * e,const char * appId)223         ADFSLogoutInitiator(const DOMElement* e, const char* appId)
224                 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT ".LogoutInitiator.ADFS")), m_appId(appId), m_binding(WSFED_NS) {
225 
226             SPConfig::getConfig().deprecation().warn("ADFS LogoutInitiator");
227 
228             // If Location isn't set, defer address registration until the setParent call.
229             pair<bool,const char*> loc = getString("Location");
230             if (loc.first) {
231                 string address = m_appId + loc.second + "::run::ADFSLI";
232                 setAddress(address.c_str());
233             }
234         }
~ADFSLogoutInitiator()235         virtual ~ADFSLogoutInitiator() {}
236 
setParent(const PropertySet * parent)237         void setParent(const PropertySet* parent) {
238             DOMPropertySet::setParent(parent);
239             pair<bool,const char*> loc = getString("Location");
240             if (loc.first) {
241                 string address = m_appId + loc.second + "::run::ADFSLI";
242                 setAddress(address.c_str());
243             }
244             else {
245                 m_log.warn("no Location property in ADFS LogoutInitiator (or parent), can't register as remoted handler");
246             }
247         }
248 
249         void receive(DDF& in, ostream& out);
250         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
251 
getProtocolFamily() const252         const XMLCh* getProtocolFamily() const {
253             return m_binding.get();
254         }
255 
256     private:
257         pair<bool,long> doRequest(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse, Session* session) const;
258 
259         string m_appId;
260         auto_ptr_XMLCh m_binding;
261     };
262 
263     class SHIBSP_DLLLOCAL ADFSLogout : public AbstractHandler, public LogoutHandler
264     {
265     public:
ADFSLogout(const DOMElement * e,const char * appId,bool deprecationSupport)266         ADFSLogout(const DOMElement* e, const char* appId, bool deprecationSupport)
267                 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT ".Logout.ADFS")), m_login(e, appId, deprecationSupport) {
268 
269             SPConfig::getConfig().deprecation().warn("ADFS Logout handler");
270 
271             m_initiator = false;
272 #ifndef SHIBSP_LITE
273             m_preserve.push_back("wreply");
274             string address = string(appId) + getString("Location").second + "::run::ADFSLO";
275             setAddress(address.c_str());
276 #endif
277         }
~ADFSLogout()278         virtual ~ADFSLogout() {}
279 
280         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
281 
282 #ifndef SHIBSP_LITE
generateMetadata(SPSSODescriptor & role,const char * handlerURL) const283         void generateMetadata(SPSSODescriptor& role, const char* handlerURL) const {
284             m_login.generateMetadata(role, handlerURL);
285             const char* loc = getString("Location").second;
286             string hurl(handlerURL);
287             if (*loc != '/')
288                 hurl += '/';
289             hurl += loc;
290             auto_ptr_XMLCh widen(hurl.c_str());
291             SingleLogoutService* ep = SingleLogoutServiceBuilder::buildSingleLogoutService();
292             ep->setLocation(widen.get());
293             ep->setBinding(m_login.getProtocolFamily());
294             role.getSingleLogoutServices().push_back(ep);
295         }
296 
getType() const297         const char* getType() const {
298             return m_login.getType();
299         }
300 #endif
getProtocolFamily() const301         const XMLCh* getProtocolFamily() const {
302             return m_login.getProtocolFamily();
303         }
304 
305     private:
306         ADFSConsumer m_login;
307     };
308 
309 #if defined (_MSC_VER)
310     #pragma warning( pop )
311 #endif
312 
ADFSSessionInitiatorFactory(const pair<const DOMElement *,const char * > & p,bool)313     SessionInitiator* ADFSSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p, bool)
314     {
315         return new ADFSSessionInitiator(p.first, p.second);
316     }
317 
ADFSLogoutFactory(const pair<const DOMElement *,const char * > & p,bool deprecationSupport)318     Handler* ADFSLogoutFactory(const pair<const DOMElement*,const char*>& p, bool deprecationSupport)
319     {
320         return new ADFSLogout(p.first, p.second, deprecationSupport);
321     }
322 
ADFSLogoutInitiatorFactory(const pair<const DOMElement *,const char * > & p,bool)323     Handler* ADFSLogoutInitiatorFactory(const pair<const DOMElement*,const char*>& p, bool)
324     {
325         return new ADFSLogoutInitiator(p.first, p.second);
326     }
327 
328     const XMLCh RequestedSecurityToken[] =      UNICODE_LITERAL_22(R,e,q,u,e,s,t,e,d,S,e,c,u,r,i,t,y,T,o,k,e,n);
329     const XMLCh RequestSecurityTokenResponse[] =UNICODE_LITERAL_28(R,e,q,u,e,s,t,S,e,c,u,r,i,t,y,T,o,k,e,n,R,e,s,p,o,n,s,e);
330 };
331 
xmltooling_extension_init(void *)332 extern "C" int ADFS_EXPORTS xmltooling_extension_init(void*)
333 {
334     SPConfig& conf=SPConfig::getConfig();
335     conf.SessionInitiatorManager.registerFactory("ADFS", ADFSSessionInitiatorFactory);
336     conf.LogoutInitiatorManager.registerFactory("ADFS", ADFSLogoutInitiatorFactory);
337     conf.AssertionConsumerServiceManager.registerFactory("ADFS", ADFSLogoutFactory);
338     conf.AssertionConsumerServiceManager.registerFactory(WSFED_NS, ADFSLogoutFactory);
339 #ifndef SHIBSP_LITE
340     SAMLConfig::getConfig().MessageDecoderManager.registerFactory(WSFED_NS, ADFSDecoderFactory);
341     XMLObjectBuilder::registerBuilder(xmltooling::QName(WSTRUST_NS,"RequestedSecurityToken"), new AnyElementBuilder());
342     XMLObjectBuilder::registerBuilder(xmltooling::QName(WSTRUST_NS,"RequestSecurityTokenResponse"), new AnyElementBuilder());
343 #endif
344     return 0;
345 }
346 
xmltooling_extension_term()347 extern "C" void ADFS_EXPORTS xmltooling_extension_term()
348 {
349     /* should get unregistered during normal shutdown...
350     SPConfig& conf=SPConfig::getConfig();
351     conf.SessionInitiatorManager.deregisterFactory("ADFS");
352     conf.LogoutInitiatorManager.deregisterFactory("ADFS");
353     conf.AssertionConsumerServiceManager.deregisterFactory("ADFS");
354     conf.AssertionConsumerServiceManager.deregisterFactory(WSFED_NS);
355 #ifndef SHIBSP_LITE
356     SAMLConfig::getConfig().MessageDecoderManager.deregisterFactory(WSFED_NS);
357 #endif
358     */
359 }
360 
run(SPRequest & request,string & entityID,bool isHandler) const361 pair<bool,long> ADFSSessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const
362 {
363     // We have to know the IdP to function.
364     if (entityID.empty() || !checkCompatibility(request, isHandler))
365         return make_pair(false, 0L);
366 
367     string target;
368     pair<bool,const char*> prop;
369     pair<bool,const char*> acClass;
370     const Handler* ACS = nullptr;
371     const Application& app = request.getApplication();
372 
373     if (isHandler) {
374         prop.second = request.getParameter("acsIndex");
375         if (prop.second && *prop.second) {
376             SPConfig::getConfig().deprecation().warn("Use of acsIndex when specifying response endpoint");
377             ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second));
378             if (!ACS)
379                 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using acsIndex property");
380         }
381 
382         prop = getString("target", request);
383         if (prop.first)
384             target = prop.second;
385 
386         // Since we're passing the ACS by value, we need to compute the return URL,
387         // so we'll need the target resource for real.
388         recoverRelayState(app, request, request, target, false);
389         app.limitRedirect(request, target.c_str());
390 
391         // Default is to allow externally supplied settings.
392         pair<bool,bool> externalInput = getBool("externalInput");
393         unsigned int settingMask = HANDLER_PROPERTY_MAP | HANDLER_PROPERTY_FIXED;
394         if (!externalInput.first || externalInput.second)
395             settingMask |= HANDLER_PROPERTY_REQUEST;
396 
397         acClass = getString("authnContextClassRef", request, settingMask);
398     }
399     else {
400         // Check for a hardwired target value in the map or handler.
401         prop = getString("target", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
402         if (prop.first)
403             target = prop.second;
404         else
405             target = request.getRequestURL();
406 
407         acClass = getString("authnContextClassRef", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
408     }
409 
410     if (!ACS) {
411         pair<bool,unsigned int> index = getUnsignedInt("acsIndex", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
412         if (index.first) {
413             SPConfig::getConfig().deprecation().warn("Use of acsIndex when specifying response endpoint");
414             ACS = app.getAssertionConsumerServiceByIndex(index.second);
415         }
416     }
417 
418     // Validate the ACS for use with this protocol.
419     if (!ACS || !XMLString::equals(getProtocolFamily(), ACS->getProtocolFamily())) {
420         if (ACS)
421             request.log(SPRequest::SPWarn, "invalid acsIndex property, or non-ADFS ACS, using default ADFS ACS");
422         ACS = app.getAssertionConsumerServiceByProtocol(getProtocolFamily());
423         if (!ACS)
424             throw ConfigurationException("Unable to locate an ADFS-compatible ACS in the configuration.");
425     }
426 
427     // Since we're not passing by index, we need to fully compute the return URL.
428     // Compute the ACS URL. We add the ACS location to the base handlerURL.
429     string ACSloc = request.getHandlerURL(target.c_str());
430     prop = ACS->getString("Location");
431     if (prop.first)
432         ACSloc += prop.second;
433 
434     if (isHandler) {
435         // We may already have RelayState set if we looped back here,
436         // but we've turned it back into a resource by this point, so if there's
437         // a target on the URL, reset to that value.
438         prop.second = request.getParameter("target");
439         if (prop.second && *prop.second)
440             target = prop.second;
441     }
442 
443     m_log.debug("attempting to initiate session using ADFS with provider (%s)", entityID.c_str());
444 
445     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
446         // Out of process means the POST data via the request can be exposed directly to the private method.
447         // The method will handle POST preservation if necessary *before* issuing the response, but only if
448         // it dispatches to an IdP.
449         return doRequest(app, &request, request, entityID.c_str(), ACSloc.c_str(), (acClass.first ? acClass.second : nullptr), target);
450     }
451 
452     // Remote the call.
453     DDF out,in = DDF(m_address.c_str()).structure();
454     DDFJanitor jin(in), jout(out);
455     in.addmember("application_id").string(app.getId());
456     in.addmember("entity_id").string(entityID.c_str());
457     in.addmember("acsLocation").string(ACSloc.c_str());
458     if (!target.empty())
459         in.addmember("RelayState").unsafe_string(target.c_str());
460     if (acClass.first)
461         in.addmember("authnContextClassRef").string(acClass.second);
462 
463     // Remote the processing.
464     out = send(request, in);
465     return unwrap(request, out);
466 }
467 
unwrap(SPRequest & request,DDF & out) const468 pair<bool,long> ADFSSessionInitiator::unwrap(SPRequest& request, DDF& out) const
469 {
470     // See if there's any response to send back.
471     if (!out["redirect"].isnull() || !out["response"].isnull()) {
472         // If so, we're responsible for handling the POST data, probably by dropping a cookie.
473         preservePostData(request.getApplication(), request, request, out["RelayState"].string());
474     }
475     return RemotedHandler::unwrap(request, out);
476 }
477 
receive(DDF & in,ostream & out)478 void ADFSSessionInitiator::receive(DDF& in, ostream& out)
479 {
480     // Find application.
481     const char* aid = in["application_id"].string();
482     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
483     if (!app) {
484         // Something's horribly wrong.
485         m_log.error("couldn't find application (%s) to generate ADFS request", aid ? aid : "(missing)");
486         throw ConfigurationException("Unable to locate application for new session, deleted?");
487     }
488 
489     const char* entityID = in["entity_id"].string();
490     const char* acsLocation = in["acsLocation"].string();
491     if (!entityID || !acsLocation)
492         throw ConfigurationException("No entityID or acsLocation parameter supplied to remoted SessionInitiator.");
493 
494     DDF ret(nullptr);
495     DDFJanitor jout(ret);
496 
497     // Wrap the outgoing object with a Response facade.
498     scoped_ptr<HTTPResponse> http(getResponse(*app, ret));
499 
500     string relayState(in["RelayState"].string() ? in["RelayState"].string() : "");
501 
502     // Since we're remoted, the result should either be a throw, which we pass on,
503     // a false/0 return, which we just return as an empty structure, or a response/redirect,
504     // which we capture in the facade and send back.
505     doRequest(*app, nullptr, *http, entityID, acsLocation, in["authnContextClassRef"].string(), relayState);
506     if (!ret.isstruct())
507         ret.structure();
508     ret.addmember("RelayState").unsafe_string(relayState.c_str());
509     out << ret;
510 }
511 
doRequest(const Application & app,const HTTPRequest * httpRequest,HTTPResponse & httpResponse,const char * entityID,const char * acsLocation,const char * authnContextClassRef,string & relayState) const512 pair<bool,long> ADFSSessionInitiator::doRequest(
513     const Application& app,
514     const HTTPRequest* httpRequest,
515     HTTPResponse& httpResponse,
516     const char* entityID,
517     const char* acsLocation,
518     const char* authnContextClassRef,
519     string& relayState
520     ) const
521 {
522 #ifndef SHIBSP_LITE
523     // Use metadata to invoke the SSO service directly.
524     MetadataProvider* m = app.getMetadataProvider();
525     Locker locker(m);
526     MetadataProviderCriteria mc(app, entityID, &IDPSSODescriptor::ELEMENT_QNAME, m_binding.get());
527     pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
528     if (!entity.first) {
529         m_log.warn("unable to locate metadata for provider (%s)", entityID);
530         throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", entityID));
531     }
532     else if (!entity.second) {
533         m_log.log(getParent() ? Priority::INFO : Priority::WARN, "unable to locate ADFS-aware identity provider role for provider (%s)", entityID);
534         if (getParent())
535             return make_pair(false, 0L);
536         throw MetadataException("Unable to locate ADFS-aware identity provider role for provider ($entityID)", namedparams(1, "entityID", entityID));
537     }
538     const EndpointType* ep = EndpointManager<SingleSignOnService>(
539         dynamic_cast<const IDPSSODescriptor*>(entity.second)->getSingleSignOnServices()
540         ).getByBinding(m_binding.get());
541     if (!ep) {
542         m_log.warn("unable to locate compatible SSO service for provider (%s)", entityID);
543         if (getParent())
544             return make_pair(false, 0L);
545         throw MetadataException("Unable to locate compatible SSO service for provider ($entityID)", namedparams(1, "entityID", entityID));
546     }
547 
548     preserveRelayState(app, httpResponse, relayState);
549 
550     scoped_ptr<AuthnRequestEvent> ar_event(newAuthnRequestEvent(app, httpRequest));
551     if (ar_event.get()) {
552         ar_event->m_binding = WSFED_NS;
553         ar_event->m_protocol = WSFED_NS;
554         ar_event->m_peer = entity.first;
555         app.getServiceProvider().getTransactionLog()->write(*ar_event);
556     }
557 
558     // UTC timestamp
559     time_t epoch=time(nullptr);
560 #ifndef HAVE_GMTIME_R
561     struct tm* ptime=gmtime(&epoch);
562 #else
563     struct tm res;
564     struct tm* ptime=gmtime_r(&epoch,&res);
565 #endif
566     char timebuf[32];
567     strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
568 
569     auto_ptr_char dest(ep->getLocation());
570     const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
571 
572     const PropertySet* relyingParty = app.getRelyingParty(entity.first);
573 
574     string req=string(dest.get()) + (strchr(dest.get(),'?') ? '&' : '?') + "wa=wsignin1.0&wreply=" + urlenc->encode(acsLocation) +
575         "&wct=" + urlenc->encode(timebuf) + "&wtrealm=" + urlenc->encode(relyingParty->getString("entityID").second);
576     if (!authnContextClassRef) {
577         pair<bool,const char*> rpClass = relyingParty->getString("authnContextClassRef");
578         if (rpClass.first)
579             authnContextClassRef = rpClass.second;
580     }
581     if (authnContextClassRef)
582         req += "&wauth=" + urlenc->encode(authnContextClassRef);
583     if (!relayState.empty())
584         req += "&wctx=" + urlenc->encode(relayState.c_str());
585 
586     if (httpRequest) {
587         // If the request object is available, we're responsible for the POST data.
588         preservePostData(app, *httpRequest, httpResponse, relayState.c_str());
589     }
590 
591     return make_pair(true, httpResponse.sendRedirect(req.c_str()));
592 #else
593     return make_pair(false, 0L);
594 #endif
595 }
596 
597 #ifndef SHIBSP_LITE
598 
decode(string & relayState,const GenericRequest & genericRequest,const GenericResponse *,SecurityPolicy & policy) const599 XMLObject* ADFSDecoder::decode(
600     string& relayState, const GenericRequest& genericRequest, const GenericResponse*, SecurityPolicy& policy
601     ) const
602 {
603 #ifdef _DEBUG
604     xmltooling::NDC ndc("decode");
605 #endif
606     Category& log = Category::getInstance(SHIBSP_LOGCAT ".MessageDecoder.ADFS");
607 
608     log.debug("validating input");
609     const HTTPRequest* httpRequest=dynamic_cast<const HTTPRequest*>(&genericRequest);
610     if (!httpRequest)
611         throw BindingException("Unable to cast request object to HTTPRequest type.");
612     if (strcmp(httpRequest->getMethod(),"POST"))
613         throw BindingException("Invalid HTTP method ($1).", params(1, httpRequest->getMethod()));
614     const char* param = httpRequest->getParameter("wa");
615     if (!param || strcmp(param, "wsignin1.0"))
616         throw BindingException("Missing or invalid wa parameter (should be wsignin1.0).");
617     param = httpRequest->getParameter("wctx");
618     if (param)
619         relayState = param;
620 
621     param = httpRequest->getParameter("wresult");
622     if (!param)
623         throw BindingException("Request missing wresult parameter.");
624 
625     log.debug("decoded ADFS response:\n%s", param);
626 
627     // Parse and bind the document into an XMLObject.
628     istringstream is(param);
629     DOMDocument* doc = (policy.getValidating() ? XMLToolingConfig::getConfig().getValidatingParser()
630         : XMLToolingConfig::getConfig().getParser()).parse(is);
631     XercesJanitor<DOMDocument> janitor(doc);
632     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
633     janitor.release();
634 
635     if (!XMLString::equals(xmlObject->getElementQName().getLocalPart(), RequestSecurityTokenResponse)) {
636     	log.error("unrecognized root element on message: %s", xmlObject->getElementQName().toString().c_str());
637         throw BindingException("Decoded message was not of the appropriate type.");
638     }
639 
640     SchemaValidators.validate(xmlObject.get());
641 
642     // Skip policy step here, there's no security in the wrapper.
643     // policy.evaluate(*xmlObject.get(), &genericRequest);
644 
645     return xmlObject.release();
646 }
647 
implementProtocol(const Application & application,const HTTPRequest & httpRequest,HTTPResponse & httpResponse,SecurityPolicy & policy,const PropertySet *,const XMLObject & xmlObject) const648 void ADFSConsumer::implementProtocol(
649     const Application& application,
650     const HTTPRequest& httpRequest,
651     HTTPResponse& httpResponse,
652     SecurityPolicy& policy,
653     const PropertySet*,
654     const XMLObject& xmlObject
655     ) const
656 {
657     // Implementation of ADFS profile.
658     m_log.debug("processing message against ADFS Passive Requester profile");
659 
660     // With ADFS, all the security comes from the assertion, which is two levels down in the message.
661 
662     const ElementProxy* response = dynamic_cast<const ElementProxy*>(&xmlObject);
663     if (!response || !response->hasChildren())
664         throw FatalProfileException("Incoming message was not of the proper type or contains no security token.");
665 
666     const Assertion* token = nullptr;
667     for (vector<XMLObject*>::const_iterator xo = response->getUnknownXMLObjects().begin(); xo != response->getUnknownXMLObjects().end(); ++xo) {
668     	// Look for the RequestedSecurityToken element.
669     	if (XMLString::equals((*xo)->getElementQName().getLocalPart(), RequestedSecurityToken)) {
670     	    response = dynamic_cast<const ElementProxy*>(*xo);
671     	    if (!response || !response->hasChildren())
672     	        throw FatalProfileException("Token wrapper element did not contain a security token.");
673     	    token = dynamic_cast<const Assertion*>(response->getUnknownXMLObjects().front());
674     	    if (!token || !token->getSignature())
675     	        throw FatalProfileException("Incoming message did not contain a signed SAML assertion.");
676     	    break;
677     	}
678     }
679 
680     // Extract message and issuer details from assertion.
681     extractMessageDetails(*token, m_protocol.get(), policy);
682 
683     // Populate recipient as audience.
684     const EntityDescriptor* entity = policy.getIssuerMetadata() ? dynamic_cast<const EntityDescriptor*>(policy.getIssuerMetadata()->getParent()) : nullptr;
685     policy.getAudiences().push_back(application.getRelyingParty(entity)->getXMLString("entityID").second);
686 
687     // Run the policy over the assertion. Handles replay, freshness, and
688     // signature verification, assuming the relevant rules are configured,
689     // along with condition enforcement.
690     policy.evaluate(*token, &httpRequest);
691 
692     // If no security is in place now, we kick it.
693     if (!policy.isAuthenticated())
694         throw SecurityPolicyException("Unable to establish security of incoming assertion.");
695 
696     const saml1::NameIdentifier* saml1name=nullptr;
697     const saml1::AuthenticationStatement* saml1statement=nullptr;
698     const saml2::NameID* saml2name=nullptr;
699     const saml2::AuthnStatement* saml2statement=nullptr;
700     const XMLCh* authMethod=nullptr;
701     const XMLCh* authInstant=nullptr;
702     time_t now = time(nullptr), sessionExp = 0;
703     const PropertySet* sessionProps = application.getPropertySet("Sessions");
704 
705     const saml1::Assertion* saml1token = dynamic_cast<const saml1::Assertion*>(token);
706     if (saml1token) {
707         // Now do profile validation to ensure we can use it for SSO.
708         if (!saml1token->getConditions() || !saml1token->getConditions()->getNotBefore() || !saml1token->getConditions()->getNotOnOrAfter())
709             throw FatalProfileException("Assertion did not contain time conditions.");
710         else if (saml1token->getAuthenticationStatements().empty())
711             throw FatalProfileException("Assertion did not contain an authentication statement.");
712 
713         // authnskew allows rejection of SSO if AuthnInstant is too old.
714         pair<bool,unsigned int> authnskew = sessionProps ? sessionProps->getUnsignedInt("maxTimeSinceAuthn") : pair<bool,unsigned int>(false,0);
715 
716         saml1statement = saml1token->getAuthenticationStatements().front();
717         if (saml1statement->getAuthenticationInstant()) {
718             if (saml1statement->getAuthenticationInstantEpoch() - XMLToolingConfig::getConfig().clock_skew_secs > now) {
719                 throw FatalProfileException("The login time at your identity provider was future-dated.");
720             }
721             else if (authnskew.first && authnskew.second && saml1statement->getAuthenticationInstantEpoch() <= now &&
722                     (now - saml1statement->getAuthenticationInstantEpoch() > authnskew.second)) {
723                 throw FatalProfileException("The gap between now and the time you logged into your identity provider exceeds the allowed limit.");
724             }
725         }
726         else if (authnskew.first && authnskew.second) {
727             throw FatalProfileException("Your identity provider did not supply a time of login, violating local policy.");
728         }
729 
730         // Address checking.
731         saml1::SubjectLocality* locality = saml1statement->getSubjectLocality();
732         if (locality && locality->getIPAddress()) {
733             auto_ptr_char ip(locality->getIPAddress());
734             checkAddress(application, httpRequest, ip.get());
735         }
736 
737         saml1name = saml1statement->getSubject()->getNameIdentifier();
738         authMethod = saml1statement->getAuthenticationMethod();
739         if (saml1statement->getAuthenticationInstant())
740             authInstant = saml1statement->getAuthenticationInstant()->getRawData();
741 
742         // Session expiration.
743         pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
744         if (!lifetime.first || lifetime.second == 0)
745             lifetime.second = 28800;
746         sessionExp = now + lifetime.second;
747     }
748     else {
749         const saml2::Assertion* saml2token = dynamic_cast<const saml2::Assertion*>(token);
750         if (!saml2token)
751             throw FatalProfileException("Incoming message did not contain a recognized type of SAML assertion.");
752 
753         // Now do profile validation to ensure we can use it for SSO.
754         if (!saml2token->getConditions() || !saml2token->getConditions()->getNotBefore() || !saml2token->getConditions()->getNotOnOrAfter())
755             throw FatalProfileException("Assertion did not contain time conditions.");
756         else if (saml2token->getAuthnStatements().empty())
757             throw FatalProfileException("Assertion did not contain an authentication statement.");
758 
759         // authnskew allows rejection of SSO if AuthnInstant is too old.
760         pair<bool,unsigned int> authnskew = sessionProps ? sessionProps->getUnsignedInt("maxTimeSinceAuthn") : pair<bool,unsigned int>(false,0);
761 
762         saml2statement = saml2token->getAuthnStatements().front();
763         if (authnskew.first && authnskew.second &&
764                 saml2statement->getAuthnInstant() && (now - saml2statement->getAuthnInstantEpoch() > authnskew.second))
765             throw FatalProfileException("The gap between now and the time you logged into your identity provider exceeds the limit.");
766 
767         // Address checking.
768         saml2::SubjectLocality* locality = saml2statement->getSubjectLocality();
769         if (locality && locality->getAddress()) {
770             auto_ptr_char ip(locality->getAddress());
771             checkAddress(application, httpRequest, ip.get());
772         }
773 
774         saml2name = saml2token->getSubject() ? saml2token->getSubject()->getNameID() : nullptr;
775         if (saml2statement->getAuthnContext() && saml2statement->getAuthnContext()->getAuthnContextClassRef())
776             authMethod = saml2statement->getAuthnContext()->getAuthnContextClassRef()->getReference();
777         if (saml2statement->getAuthnInstant())
778             authInstant = saml2statement->getAuthnInstant()->getRawData();
779 
780         // Session expiration for SAML 2.0 is jointly IdP- and SP-driven.
781         sessionExp = saml2statement->getSessionNotOnOrAfter() ? saml2statement->getSessionNotOnOrAfterEpoch() : 0;
782         pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
783         if (!lifetime.first || lifetime.second == 0)
784             lifetime.second = 28800;
785         if (sessionExp == 0)
786             sessionExp = now + lifetime.second;     // IdP says nothing, calulate based on SP.
787         else
788             sessionExp = min(sessionExp, now + lifetime.second);    // Use the lowest.
789     }
790 
791     m_log.debug("ADFS profile processing completed successfully");
792 
793     // We've successfully "accepted" the SSO token.
794     // To complete processing, we need to extract and resolve attributes and then create the session.
795 
796     // Normalize a SAML 1.x NameIdentifier...
797     scoped_ptr<saml2::NameID> nameid(saml1name ? saml2::NameIDBuilder::buildNameID() : nullptr);
798     if (saml1name) {
799         nameid->setName(saml1name->getName());
800         nameid->setFormat(saml1name->getFormat());
801         nameid->setNameQualifier(saml1name->getNameQualifier());
802     }
803 
804     // The context will handle deleting attributes and new tokens.
805     vector<const Assertion*> tokens(1,token);
806     scoped_ptr<ResolutionContext> ctx(
807         resolveAttributes(
808             application,
809             &httpRequest,
810             policy.getIssuerMetadata(),
811             m_protocol.get(),
812             nullptr,
813             saml1name,
814             saml1statement,
815             (saml1name ? nameid.get() : saml2name),
816             saml2statement,
817             authMethod,
818             nullptr,
819             &tokens
820             )
821         );
822 
823     if (ctx.get()) {
824         // Copy over any new tokens, but leave them in the context for cleanup.
825         tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
826     }
827 
828     string session_id;
829     application.getServiceProvider().getSessionCache()->insert(
830         session_id,
831         application,
832         httpRequest,
833         httpResponse,
834         sessionExp,
835         entity,
836         m_protocol.get(),
837         (saml1name ? nameid.get() : saml2name),
838         authInstant,
839         nullptr,
840         authMethod,
841         nullptr,
842         &tokens,
843         ctx ? &ctx->getResolvedAttributes() : nullptr
844         );
845 
846     scoped_ptr<LoginEvent> login_event(newLoginEvent(application, httpRequest));
847     if (login_event) {
848         login_event->m_sessionID = session_id.c_str();
849         login_event->m_peer = entity;
850         login_event->m_protocol = WSFED_NS;
851         login_event->m_binding = WSFED_NS;
852         login_event->m_saml1AuthnStatement = saml1statement;
853         login_event->m_nameID = (saml1name ? nameid.get() : saml2name);
854         login_event->m_saml2AuthnStatement = saml2statement;
855         if (ctx)
856             login_event->m_attributes = &ctx->getResolvedAttributes();
857         application.getServiceProvider().getTransactionLog()->write(*login_event);
858     }
859 }
860 
861 #endif
862 
run(SPRequest & request,bool) const863 pair<bool,long> ADFSLogoutInitiator::run(SPRequest& request, bool) const
864 {
865     // Normally we'd do notifications and session clearage here, but ADFS logout
866     // is missing the needed request/response features, so we have to rely on
867     // the IdP half to notify us back about the logout and do the work there.
868     // Basically we have no way to tell in the Logout receiving handler whether
869     // we initiated the logout or not.
870 
871     Session* session = nullptr;
872     try {
873         session = request.getSession(false, true, false);  // don't cache it and ignore all checks
874         if (!session)
875             return make_pair(false, 0L);
876 
877         // We only handle ADFS sessions.
878         if (!XMLString::equals(session->getProtocol(), WSFED_NS) || !session->getEntityID()) {
879             session->unlock();
880             return make_pair(false, 0L);
881         }
882     }
883     catch (std::exception& ex) {
884         m_log.error("error accessing current session: %s", ex.what());
885         return make_pair(false,0L);
886     }
887 
888     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
889         // When out of process, we run natively.
890         return doRequest(request.getApplication(), request, request, session);
891     }
892     else {
893         // When not out of process, we remote the request.
894         session->unlock();
895         vector<string> headers(1,"Cookie");
896         headers.push_back("User-Agent");
897         DDF out,in = wrap(request, &headers);
898         DDFJanitor jin(in), jout(out);
899         out = send(request, in);
900         return unwrap(request, out);
901     }
902 }
903 
receive(DDF & in,ostream & out)904 void ADFSLogoutInitiator::receive(DDF& in, ostream& out)
905 {
906 #ifndef SHIBSP_LITE
907     // Defer to base class for notifications
908     if (in["notify"].integer() == 1)
909         return LogoutHandler::receive(in, out);
910 
911     // Find application.
912     const char* aid = in["application_id"].string();
913     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
914     if (!app) {
915         // Something's horribly wrong.
916         m_log.error("couldn't find application (%s) for logout", aid ? aid : "(missing)");
917         throw ConfigurationException("Unable to locate application for logout, deleted?");
918     }
919 
920     // Unpack the request.
921     scoped_ptr<HTTPRequest> req(getRequest(*app, in));
922 
923     // Set up a response shim.
924     DDF ret(nullptr);
925     DDFJanitor jout(ret);
926     scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
927 
928     Session* session = nullptr;
929     try {
930          session = app->getServiceProvider().getSessionCache()->find(*app, *req, nullptr, nullptr);
931     }
932     catch (std::exception& ex) {
933         m_log.error("error accessing current session: %s", ex.what());
934     }
935 
936     // With no session, we just skip the request and let it fall through to an empty struct return.
937     if (session) {
938         if (session->getEntityID()) {
939             // Since we're remoted, the result should either be a throw, which we pass on,
940             // a false/0 return, which we just return as an empty structure, or a response/redirect,
941             // which we capture in the facade and send back.
942             doRequest(*app, *req, *resp, session);
943         }
944         else {
945             m_log.error("no issuing entityID found in session");
946             time_t revocationExp = session->getExpiration();
947             session->unlock();
948             app->getServiceProvider().getSessionCache()->remove(*app, *req, resp.get(), revocationExp);
949         }
950     }
951     out << ret;
952 #else
953     throw ConfigurationException("Cannot perform logout using lite version of shibsp library.");
954 #endif
955 }
956 
doRequest(const Application & application,const HTTPRequest & httpRequest,HTTPResponse & httpResponse,Session * session) const957 pair<bool,long> ADFSLogoutInitiator::doRequest(
958     const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse, Session* session
959     ) const
960 {
961     Locker sessionLocker(session, false);
962 
963     // Do back channel notification.
964     vector<string> sessions(1, session->getID());
965     if (!notifyBackChannel(application, httpRequest.getRequestURL(), sessions, false)) {
966 #ifndef SHIBSP_LITE
967         scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(application, &httpRequest, session));
968         if (logout_event) {
969             logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_PARTIAL;
970             application.getServiceProvider().getTransactionLog()->write(*logout_event);
971         }
972 #endif
973         time_t revocationExp = session->getExpiration();
974         sessionLocker.assign();
975         session = nullptr;
976         application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
977         return sendLogoutPage(application, httpRequest, httpResponse, "partial");
978     }
979 
980 #ifndef SHIBSP_LITE
981     pair<bool,long> ret = make_pair(false, 0L);
982 
983     try {
984         // With a session in hand, we can create a request message, if we can find a compatible endpoint.
985         MetadataProvider* m = application.getMetadataProvider();
986         Locker metadataLocker(m);
987         MetadataProviderCriteria mc(application, session->getEntityID(), &IDPSSODescriptor::ELEMENT_QNAME, m_binding.get());
988         pair<const EntityDescriptor*,const RoleDescriptor*> entity=m->getEntityDescriptor(mc);
989         if (!entity.first) {
990             throw MetadataException(
991                 "Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", session->getEntityID())
992                 );
993         }
994         else if (!entity.second) {
995             throw MetadataException(
996                 "Unable to locate ADFS IdP role for identity provider ($entityID).", namedparams(1, "entityID", session->getEntityID())
997                 );
998         }
999 
1000         const EndpointType* ep = EndpointManager<SingleLogoutService>(
1001             dynamic_cast<const IDPSSODescriptor*>(entity.second)->getSingleLogoutServices()
1002             ).getByBinding(m_binding.get());
1003         if (!ep) {
1004             throw MetadataException(
1005                 "Unable to locate ADFS single logout service for identity provider ($entityID).",
1006                 namedparams(1, "entityID", session->getEntityID())
1007                 );
1008         }
1009 
1010         const char* returnloc = httpRequest.getParameter("return");
1011         if (returnloc)
1012             application.limitRedirect(httpRequest, returnloc);
1013 
1014         // Log the request.
1015         scoped_ptr<LogoutEvent> logout_event(newLogoutEvent(application, &httpRequest, session));
1016         if (logout_event) {
1017             logout_event->m_logoutType = LogoutEvent::LOGOUT_EVENT_UNKNOWN;
1018             application.getServiceProvider().getTransactionLog()->write(*logout_event);
1019         }
1020 
1021         auto_ptr_char dest(ep->getLocation());
1022         string req=string(dest.get()) + (strchr(dest.get(),'?') ? '&' : '?') + "wa=wsignout1.0";
1023         if (returnloc) {
1024             req += "&wreply=";
1025             if (*returnloc == '/') {
1026                 string s(returnloc);
1027                 httpRequest.absolutize(s);
1028                 req += XMLToolingConfig::getConfig().getURLEncoder()->encode(s.c_str());
1029             }
1030             else {
1031                 req += XMLToolingConfig::getConfig().getURLEncoder()->encode(returnloc);
1032             }
1033         }
1034         ret.second = httpResponse.sendRedirect(req.c_str());
1035         ret.first = true;
1036 
1037         if (session) {
1038             time_t revocationExp = session->getExpiration();
1039             sessionLocker.assign();
1040             session = nullptr;
1041             application.getServiceProvider().getSessionCache()->remove(application, httpRequest, &httpResponse, revocationExp);
1042         }
1043     }
1044     catch (const MetadataException& mex) {
1045         // Less noise for IdPs that don't support logout
1046         m_log.info("unable to issue ADFS logout request: %s", mex.what());
1047     }
1048     catch (const std::exception& ex) {
1049         m_log.error("error issuing ADFS logout request: %s", ex.what());
1050     }
1051 
1052     return ret;
1053 #else
1054     throw ConfigurationException("Cannot perform logout using lite version of shibsp library.");
1055 #endif
1056 }
1057 
run(SPRequest & request,bool isHandler) const1058 pair<bool,long> ADFSLogout::run(SPRequest& request, bool isHandler) const
1059 {
1060     // Defer to base class for front-channel loop first.
1061     // This won't initiate the loop, only continue/end it.
1062     pair<bool,long> ret = LogoutHandler::run(request, isHandler);
1063     if (ret.first)
1064         return ret;
1065 
1066     // wa parameter indicates the "action" to perform
1067     bool returning = false;
1068     const char* param = request.getParameter("wa");
1069     if (param) {
1070         if (!strcmp(param, "wsignin1.0"))
1071             return m_login.run(request, isHandler);
1072         else if (strcmp(param, "wsignout1.0") && strcmp(param, "wsignoutcleanup1.0"))
1073             throw FatalProfileException("Unsupported WS-Federation action parameter ($1).", params(1, param));
1074     }
1075     else if (strcmp(request.getMethod(),"GET") || !request.getParameter("notifying"))
1076         throw FatalProfileException("Unsupported request to ADFS protocol endpoint.");
1077     else
1078         returning = true;
1079 
1080     param = request.getParameter("wreply");
1081     const Application& app = request.getApplication();
1082 
1083     if (!returning) {
1084         // Pass control to the first front channel notification point, if any.
1085         map<string,string> parammap;
1086         if (param)
1087             parammap["wreply"] = param;
1088         pair<bool,long> result = notifyFrontChannel(app, request, request, &parammap);
1089         if (result.first)
1090             return result;
1091     }
1092 
1093     // Best effort on back channel and to remove the user agent's session.
1094     string session_id = app.getServiceProvider().getSessionCache()->active(app, request);
1095     if (!session_id.empty()) {
1096         vector<string> sessions(1,session_id);
1097         notifyBackChannel(app, request.getRequestURL(), sessions, false);
1098         try {
1099             app.getServiceProvider().getSessionCache()->remove(app, request, &request);
1100         }
1101         catch (const std::exception& ex) {
1102             m_log.error("error removing session (%s): %s", session_id.c_str(), ex.what());
1103         }
1104     }
1105 
1106     if (param) {
1107         if (*param == '/') {
1108             string p(param);
1109             request.absolutize(p);
1110             return make_pair(true, request.sendRedirect(p.c_str()));
1111         }
1112         else {
1113             app.limitRedirect(request, param);
1114             return make_pair(true, request.sendRedirect(param));
1115         }
1116     }
1117     return sendLogoutPage(app, request, request, "global");
1118 }
1119