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