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