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  * ExternalAuthHandler.cpp
23  *
24  * Handler for integrating with external authentication mechanisms.
25  */
26 
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "Application.h"
30 #include "ServiceProvider.h"
31 #include "SPRequest.h"
32 #include "handler/RemotedHandler.h"
33 #include "handler/SecuredHandler.h"
34 
35 #include <sstream>
36 #include <boost/scoped_ptr.hpp>
37 
38 #ifndef SHIBSP_LITE
39 # include "SessionCache.h"
40 # include "TransactionLog.h"
41 # include "attribute/SimpleAttribute.h"
42 # include "attribute/filtering/AttributeFilter.h"
43 # include "attribute/filtering/BasicFilteringContext.h"
44 # include "attribute/resolver/AttributeExtractor.h"
45 # include "attribute/resolver/AttributeResolver.h"
46 # include "attribute/resolver/ResolutionContext.h"
47 # include <boost/tokenizer.hpp>
48 # include <boost/iterator/indirect_iterator.hpp>
49 # include <saml/exceptions.h>
50 # include <saml/saml2/core/Assertions.h>
51 # include <saml/saml2/metadata/Metadata.h>
52 # include <saml/saml2/metadata/MetadataProvider.h>
53 # include <xmltooling/XMLToolingConfig.h>
54 # include <xmltooling/util/ParserPool.h>
55 # include <xmltooling/util/XMLHelper.h>
56 # include <xercesc/framework/MemBufInputSource.hpp>
57 # include <xercesc/framework/Wrapper4InputSource.hpp>
58 using namespace opensaml::saml2md;
59 using namespace opensaml;
60 using saml2::NameID;
61 using saml2::AuthnStatement;
62 using saml2::AuthnContext;
63 # ifndef min
64 #  define min(a,b)            (((a) < (b)) ? (a) : (b))
65 # endif
66 #endif
67 
68 using namespace shibspconstants;
69 using namespace shibsp;
70 using namespace xmltooling;
71 using namespace boost;
72 using namespace std;
73 
74 namespace shibsp {
75 
76 #if defined (_MSC_VER)
77     #pragma warning( push )
78     #pragma warning( disable : 4250 )
79 #endif
80 
81     class SHIBSP_API ExternalAuth : public SecuredHandler, public RemotedHandler
82     {
83     public:
84         ExternalAuth(const DOMElement* e, const char* appId);
~ExternalAuth()85         virtual ~ExternalAuth() {}
86 
87         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
88         void receive(DDF& in, ostream& out);
89 
getType() const90         const char* getType() const {
91             return "ExternalAuth";
92         }
93 
94     private:
95         pair<bool,long> processMessage(
96             const Application& application,
97             HTTPRequest& httpRequest,
98             HTTPResponse& httpResponse,
99             DDF& reqDDF,
100             const DDF* respDDF=nullptr
101             ) const;
102 #ifndef SHIBSP_LITE
103         LoginEvent* newLoginEvent(const Application& application, const HTTPRequest& request) const;
104         ResolutionContext* resolveAttributes(
105             const Application& application,
106             const GenericRequest* request,
107             const saml2md::RoleDescriptor* issuer,
108             const XMLCh* protocol,
109             const saml2::NameID* nameid,
110             const saml2::AuthnStatement* statement,
111             const XMLCh* authncontext_class,
112             const XMLCh* authncontext_decl,
113             const vector<const Assertion*>* tokens=nullptr,
114             const vector<Attribute*>* inputAttributes=nullptr
115             ) const;
116 #endif
117     };
118 
119 #if defined (_MSC_VER)
120     #pragma warning( pop )
121 #endif
122 
ExternalAuthFactory(const pair<const DOMElement *,const char * > & p,bool)123     Handler* SHIBSP_DLLLOCAL ExternalAuthFactory(const pair<const DOMElement*,const char*>& p, bool)
124     {
125         return new ExternalAuth(p.first, p.second);
126     }
127 
128 };
129 
130 namespace {
json_safe(ostream & os,const char * buf)131     static ostream& json_safe(ostream& os, const char* buf)
132     {
133         os << '"';
134         for (; *buf; ++buf) {
135             switch (*buf) {
136                 case '\\':
137                 case '"':
138                     os << '\\';
139                     os << *buf;
140                     break;
141                 case '\b':
142                     os << "\\b";
143                     break;
144                 case '\t':
145                     os << "\\t";
146                     break;
147                 case '\n':
148                     os << "\\n";
149                     break;
150                 case '\f':
151                     os << "\\f";
152                     break;
153                 case '\r':
154                     os << "\\r";
155                     break;
156                 default:
157                     os << *buf;
158             }
159         }
160         os << '"';
161         return os;
162     }
163 };
164 
ExternalAuth(const DOMElement * e,const char * appId)165 ExternalAuth::ExternalAuth(const DOMElement* e, const char* appId)
166     : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT ".Handler.ExternalAuth"), "acl", "127.0.0.1 ::1")
167 {
168     SPConfig::getConfig().deprecation().warn(EXTERNAL_AUTH_HANDLER" handler");
169 
170     pair<bool,const char*> prop = getString("Location");
171     if (!prop.first)
172         throw ConfigurationException("ExternalAuth handler requires Location property.");
173     string address(appId);
174     address += prop.second;
175     setAddress(address.c_str());
176 }
177 
run(SPRequest & request,bool isHandler) const178 pair<bool,long> ExternalAuth::run(SPRequest& request, bool isHandler) const
179 {
180     // Check ACL in base class.
181     pair<bool,long> ret = SecuredHandler::run(request, isHandler);
182     if (ret.first)
183         return ret;
184 
185     try {
186         if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
187             // When out of process, we run natively and directly process the message, except that we
188             // have to indirect the request anyway in order to override the client address. This is
189             // the simplest way to get a delegated HTTPRequest object, and since this code path is
190             // not really one we expect to use, it's good enough.
191             vector<string> headers(1, "User-Agent");
192             headers.push_back("Accept");
193             headers.push_back("Accept-Language");
194             headers.push_back("Cookie");
195             DDF in = wrap(request, &headers);
196             DDFJanitor jin(in);
197             scoped_ptr<HTTPRequest> fakedreq(getRequest(request.getApplication(), in));
198             return processMessage(request.getApplication(), *fakedreq, request, in);
199         }
200         else {
201             // When not out of process, we remote all the message processing.
202             vector<string> headers(1, "User-Agent");
203             headers.push_back("Accept");
204             headers.push_back("Accept-Language");
205             headers.push_back("Cookie");
206             DDF out,in = wrap(request, &headers);
207             DDFJanitor jin(in), jout(out);
208             out = send(request, in);
209             return unwrap(request, out);
210         }
211     }
212     catch (const std::exception& ex) {
213         m_log.error("error while processing request: %s", ex.what());
214         istringstream msg("External Authentication Failed");
215         return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
216     }
217 }
218 
receive(DDF & in,ostream & out)219 void ExternalAuth::receive(DDF& in, ostream& out)
220 {
221     // Find application.
222     const char* aid = in["application_id"].string();
223     const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
224     if (!app) {
225         // Something's horribly wrong.
226         m_log.error("couldn't find application (%s) for external authentication", aid ? aid : "(missing)");
227         throw ConfigurationException("Unable to locate application for external authentication, deleted?");
228     }
229 
230     // Unpack the request.
231     scoped_ptr<HTTPRequest> req(getRequest(*app, in));
232 
233     // Wrap a response shim.
234     DDF ret(nullptr);
235     DDFJanitor jout(ret);
236     scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
237 
238     // Since we're remoted, the result should either be a throw, a false/0 return,
239     // which we just return as an empty structure, or a response/redirect,
240     // which we capture in the facade and send back.
241     try {
242         processMessage(*app, *req, *resp, in, &ret);
243     }
244     catch (const std::exception& ex) {
245         m_log.error("raising exception: %s", ex.what());
246         throw;
247     }
248     out << ret;
249 }
250 
processMessage(const Application & application,HTTPRequest & httpRequest,HTTPResponse & httpResponse,DDF & reqDDF,const DDF * respDDF) const251 pair<bool,long> ExternalAuth::processMessage(
252     const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse, DDF& reqDDF, const DDF* respDDF
253     ) const
254 {
255 #ifndef SHIBSP_LITE
256     string session_id;
257     SessionCache* cache = application.getServiceProvider().getSessionCache();
258     MetadataProvider* m = application.getMetadataProvider(false);
259     Locker mocker(m);
260 
261     scoped_ptr<TransactionLog::Event> event;
262     LoginEvent* login_event = nullptr;
263     if (SPConfig::getConfig().isEnabled(SPConfig::Logging)) {
264         event.reset(SPConfig::getConfig().EventManager.newPlugin(LOGIN_EVENT, nullptr, false));
265         login_event = dynamic_cast<LoginEvent*>(event.get());
266         if (login_event)
267             login_event->m_app = &application;
268         else
269             m_log.warn("unable to audit event, log event object was of an incorrect type");
270     }
271 
272     string ctype(httpRequest.getContentType());
273     if (ctype == "text/xml" || ctype == "application/samlassertion+xml") {
274         const char* body = httpRequest.getRequestBody();
275         if (!body)
276             throw FatalProfileException("Request body was empty.");
277 
278         // Parse and bind the document into an XMLObject.
279         MemBufInputSource src(reinterpret_cast<const XMLByte*>(body), httpRequest.getContentLength(), "SAMLAssertion");
280         Wrapper4InputSource dsrc(&src, false);
281         DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(dsrc);
282         XercesJanitor<DOMDocument> janitor(doc);
283         scoped_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
284         janitor.release();
285 
286         saml2::Assertion* token = dynamic_cast<saml2::Assertion*>(xmlObject.get());
287         if (!token)
288             throw FatalProfileException("Request body did not contain a SAML 2.0 assertion.");
289         else if (token->getAuthnStatements().empty())
290             throw FatalProfileException("Assertion in request did not contain an AuthnStatement.");
291 
292         // We're not implementing a full SAML profile here, only a minimal one that ignores most
293         // security checking, conditions, etc. The caller is in full control here and we just consume
294         // what we're given. The only thing we're honoring is the authentication information we find
295         // and processing any attributes.
296 
297         const XMLCh* protocol = nullptr;
298         pair<const EntityDescriptor*, const RoleDescriptor*> issuer = pair<const EntityDescriptor*, const RoleDescriptor*>(nullptr,nullptr);
299         if (m && token->getIssuer() && token->getIssuer()->getName()) {
300             MetadataProvider::Criteria mc;
301             mc.entityID_unicode = token->getIssuer()->getName();
302             mc.role = &IDPSSODescriptor::ELEMENT_QNAME;
303             mc.protocol = samlconstants::SAML20P_NS;
304             issuer = m->getEntityDescriptor(mc);
305             if (!issuer.first) {
306                 auto_ptr_char iname(token->getIssuer()->getName());
307                 m_log.warn("no metadata found for issuer (%s)", iname.get());
308             }
309             else if (!issuer.second) {
310                 auto_ptr_char iname(token->getIssuer()->getName());
311                 m_log.warn("no IdP role found in metadata for issuer (%s)", iname.get());
312             }
313             protocol = mc.protocol;
314         }
315 
316         const saml2::NameID* nameid = nullptr;
317         if (token->getSubject())
318             nameid = token->getSubject()->getNameID();
319         const AuthnStatement* ssoStatement = token->getAuthnStatements().front();
320 
321         // authnskew allows rejection of SSO if AuthnInstant is too old.
322         const PropertySet* sessionProps = application.getPropertySet("Sessions");
323         pair<bool,unsigned int> authnskew = sessionProps ? sessionProps->getUnsignedInt("maxTimeSinceAuthn") : pair<bool,unsigned int>(false,0);
324 
325         time_t now(time(nullptr));
326         if (ssoStatement->getAuthnInstant() &&
327                 ssoStatement->getAuthnInstantEpoch() - XMLToolingConfig::getConfig().clock_skew_secs > now) {
328             throw FatalProfileException("The AuthnInstant was future-dated.");
329         }
330         else if (authnskew.first && authnskew.second && ssoStatement->getAuthnInstant() &&
331                 ssoStatement->getAuthnInstantEpoch() <= now && (now - ssoStatement->getAuthnInstantEpoch() > authnskew.second)) {
332             throw FatalProfileException("The gap between now and the AuthnInstant exceeds the allowed limit.");
333         }
334         else if (authnskew.first && authnskew.second && ssoStatement->getAuthnInstant() == nullptr) {
335             throw FatalProfileException("No AuthnInstant was supplied, violating local policy.");
336         }
337 
338         // Session expiration for SAML 2.0 is jointly IdP- and SP-driven.
339         time_t sessionExp = ssoStatement->getSessionNotOnOrAfter() ?
340             (ssoStatement->getSessionNotOnOrAfterEpoch() + XMLToolingConfig::getConfig().clock_skew_secs) : 0;
341         pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
342         if (!lifetime.first || lifetime.second == 0)
343             lifetime.second = 28800;
344         if (sessionExp == 0)
345             sessionExp = now + lifetime.second;     // IdP says nothing, calulate based on SP.
346         else
347             sessionExp = min(sessionExp, now + lifetime.second);    // Use the lowest.
348 
349         const XMLCh* authncontext_class = nullptr;
350         const XMLCh* authncontext_decl = nullptr;
351         const AuthnContext* authnContext = ssoStatement->getAuthnContext();
352         if (authnContext) {
353             authncontext_class = authnContext->getAuthnContextClassRef() ? authnContext->getAuthnContextClassRef()->getReference() : nullptr;
354             authncontext_decl = authnContext->getAuthnContextDeclRef() ? authnContext->getAuthnContextDeclRef()->getReference() : nullptr;
355         }
356 
357         // Extract client address.
358         reqDDF.addmember("client_addr").string((const char*)nullptr);
359         if (ssoStatement->getSubjectLocality() && ssoStatement->getSubjectLocality()->getAddress()) {
360             auto_ptr_char addr(ssoStatement->getSubjectLocality()->getAddress());
361             if (addr.get())
362                 reqDDF.getmember("client_addr").string(addr.get());
363         }
364 
365         // The context will handle deleting attributes and tokens.
366         vector<const Assertion*> tokens(1, token);
367         scoped_ptr<ResolutionContext> ctx(
368             resolveAttributes(
369                 application,
370                 &httpRequest,
371                 issuer.second,
372                 protocol,
373                 nameid,
374                 ssoStatement,
375                 authncontext_class,
376                 authncontext_decl,
377                 &tokens
378                 )
379             );
380         tokens.clear(); // don't store the original token in the session, since it was contrived
381 
382         if (ctx) {
383             // Copy over any new tokens, but leave them in the context for cleanup.
384             tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
385         }
386 
387         cache->insert(
388             session_id,
389             application,
390             httpRequest,
391             httpResponse,
392             sessionExp,
393             issuer.first,
394             protocol,
395             nameid,
396             ssoStatement->getAuthnInstant() ? ssoStatement->getAuthnInstant()->getRawData() : nullptr,
397             ssoStatement->getSessionIndex(),
398             authncontext_class,
399             authncontext_decl,
400             &tokens,
401             ctx ? &ctx->getResolvedAttributes() : nullptr
402             );
403 
404         if (login_event) {
405             login_event->m_binding = "ExternalAuth/XML";
406             login_event->m_sessionID = session_id.c_str();
407             login_event->m_peer = issuer.first;
408             auto_ptr_char prot(protocol);
409             login_event->m_protocol = prot.get();
410             login_event->m_nameID = nameid;
411             login_event->m_saml2AuthnStatement = ssoStatement;
412             if (ctx)
413                 login_event->m_attributes = &ctx->getResolvedAttributes();
414             try {
415                 application.getServiceProvider().getTransactionLog()->write(*login_event);
416             }
417             catch (std::exception& ex) {
418                 m_log.warn("exception auditing event: %s", ex.what());
419             }
420         }
421     }
422     else if (ctype == "application/x-www-form-urlencoded") {
423         auto_ptr_XMLCh protocol(httpRequest.getParameter("protocol"));
424         const char* param = httpRequest.getParameter("issuer");
425         pair<const EntityDescriptor*, const RoleDescriptor*> issuer = pair<const EntityDescriptor*, const RoleDescriptor*>(nullptr,nullptr);
426         if (m && param && *param) {
427             MetadataProvider::Criteria mc;
428             mc.entityID_ascii = param;
429             mc.role = &IDPSSODescriptor::ELEMENT_QNAME;
430             mc.protocol = protocol.get();
431             issuer = m->getEntityDescriptor(mc);
432             if (!issuer.first)
433                 m_log.warn("no metadata found for issuer (%s)", param);
434             else if (!issuer.second)
435                 m_log.warn("no IdP role found in metadata for issuer (%s)", param);
436         }
437 
438         scoped_ptr<saml2::NameID> nameid;
439         param = httpRequest.getParameter("NameID");
440         if (param && *param) {
441             nameid.reset(saml2::NameIDBuilder::buildNameID());
442             auto_arrayptr<XMLCh> n(fromUTF8(param));
443             nameid->setName(n.get());
444             param = httpRequest.getParameter("Format");
445             if (param && param) {
446                 auto_ptr_XMLCh f(param);
447                 nameid->setFormat(f.get());
448             }
449         }
450 
451         scoped_ptr<XMLDateTime> authn_instant;
452         param = httpRequest.getParameter("AuthnInstant");
453         if (param && *param) {
454             auto_ptr_XMLCh d(param);
455             try {
456                 authn_instant.reset(new XMLDateTime(d.get()));
457                 authn_instant->parseDateTime();
458             }
459             catch (const XMLException& e) {
460                 auto_ptr_char temp(e.getMessage());
461                 throw XMLObjectException(temp.get() ? temp.get() : "XMLException parsing date/time value.");
462             }
463         }
464 
465         auto_ptr_XMLCh session_index(httpRequest.getParameter("SessionIndex"));
466         auto_ptr_XMLCh authncontext_class(httpRequest.getParameter("AuthnContextClassRef"));
467         auto_ptr_XMLCh authncontext_decl(httpRequest.getParameter("AuthnContextDeclRef"));
468 
469         time_t sessionExp = 0;
470         param = httpRequest.getParameter("lifetime");
471         if (param && param)
472             sessionExp = atol(param);
473         if (sessionExp) {
474             sessionExp += time(nullptr);
475         }
476         else {
477             const PropertySet* sessionProps = application.getPropertySet("Sessions");
478             pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
479             if (!lifetime.first || lifetime.second == 0)
480                 lifetime.second = 28800;
481             sessionExp = time(nullptr) + lifetime.second;
482         }
483 
484         // Create simple attributes around whatever parameters are specified.
485         vector<Attribute*> resolvedAttributes;
486         param = httpRequest.getParameter("attributes");
487         if (param && *param) {
488             char_separator<char> sep(", ");
489             string dup(param);
490             tokenizer< char_separator<char> > tokens(dup, sep);
491             try {
492                 for (tokenizer< char_separator<char> >::iterator t = tokens.begin(); t != tokens.end(); ++t) {
493                     vector<const char*> vals;
494                     if (httpRequest.getParameters(t->c_str(), vals)) {
495                         vector<string> ids(1, *t);
496                         auto_ptr<SimpleAttribute> attr(new SimpleAttribute(ids));
497                         vector<string>& dest = attr->getValues();
498                         for (vector<const char*>::const_iterator v = vals.begin(); v != vals.end(); ++v)
499                             dest.push_back(*v);
500                         resolvedAttributes.push_back(attr.get());
501                         attr.release();
502                     }
503                 }
504             }
505             catch (const std::exception&) {
506                 for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
507                 throw;
508             }
509         }
510 
511         // Get actual client address.
512         reqDDF.addmember("client_addr").string(httpRequest.getParameter("address"));
513 
514         scoped_ptr<ResolutionContext> ctx(
515             resolveAttributes(
516                 application,
517                 &httpRequest,
518                 issuer.second,
519                 protocol.get(),
520                 nameid.get(),
521                 nullptr,
522                 authncontext_class.get(),
523                 authncontext_decl.get(),
524                 nullptr,
525                 &resolvedAttributes
526                 )
527             );
528 
529         vector<const Assertion*> tokens;
530         if (ctx) {
531             // Copy over any new tokens, but leave them in the context for cleanup.
532             tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
533         }
534 
535         cache->insert(
536             session_id,
537             application,
538             httpRequest,
539             httpResponse,
540             sessionExp,
541             issuer.first,
542             protocol.get(),
543             nameid.get(),
544             authn_instant ? authn_instant->getRawData() : nullptr,
545             session_index.get(),
546             authncontext_class.get(),
547             authncontext_decl.get(),
548             &tokens,
549             ctx ? &ctx->getResolvedAttributes() : nullptr
550             );
551 
552         if (login_event) {
553             login_event->m_binding = "ExternalAuth/POST";
554             login_event->m_sessionID = session_id.c_str();
555             login_event->m_peer = issuer.first;
556             login_event->m_protocol = httpRequest.getParameter("protocol");
557             login_event->m_nameID = nameid.get();
558             if (ctx)
559                 login_event->m_attributes = &ctx->getResolvedAttributes();
560             try {
561                 application.getServiceProvider().getTransactionLog()->write(*login_event);
562             }
563             catch (std::exception& ex) {
564                 m_log.warn("exception auditing event: %s", ex.what());
565             }
566         }
567     }
568     else {
569         throw FatalProfileException("Submission was not in a recognized SAML assertion or form-encoded format.");
570     }
571 
572     const char* param = httpRequest.getParameter("RelayState");
573     string target(param ? param : "");
574     try {
575         recoverRelayState(application, httpRequest, httpResponse, target);
576     }
577     catch (const std::exception& ex) {
578         m_log.error("error recovering relay state: %s", ex.what());
579         target.erase();
580     }
581 
582     stringstream os;
583     string accept = httpRequest.getHeader("Accept");
584     if (accept.find("application/json") != string::npos) {
585         httpResponse.setContentType("application/json");
586         os << "{ \"SessionID\": "; json_safe(os, session_id.c_str());
587         bool firstCookie = true;
588         if (respDDF) {
589             DDF hdr;
590             DDF hdrs = respDDF->getmember("headers");
591             hdr = hdrs.first();
592             while (hdr.isstring()) {
593                 if (!strcmp(hdr.name(), "Set-Cookie")) {
594                     if (firstCookie) {
595                         os << ", \"Cookies\": [ ";
596                         firstCookie = false;
597                     }
598                     else {
599                         os << ", ";
600                     }
601                     json_safe(os, hdr.string());
602                 }
603                 hdr = hdrs.next();
604             }
605         }
606         os << " ]";
607         if (!target.empty()) {
608             os << ", \"RelayState\": ";
609             json_safe(os, target.c_str());
610         }
611         os << " }";
612     }
613     else {
614         httpResponse.setContentType("text/xml");
615         static const XMLCh _ExternalAuth[] = UNICODE_LITERAL_12(E,x,t,e,r,n,a,l,A,u,t,h);
616         static const XMLCh _SessionID[] = UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D);
617         static const XMLCh _RelayState[] = UNICODE_LITERAL_10(R,e,l,a,y,S,t,a,t,e);
618         static const XMLCh _Cookie[] = UNICODE_LITERAL_6(C,o,o,k,i,e);
619         DOMDocument* retdoc = XMLToolingConfig::getConfig().getParser().newDocument();
620         XercesJanitor<DOMDocument> retjanitor(retdoc);
621         retdoc->appendChild(retdoc->createElement(_ExternalAuth));
622         auto_ptr_XMLCh wideid(session_id.c_str());
623         DOMElement* child = retdoc->createElement(_SessionID);
624         child->appendChild(retdoc->createTextNode(wideid.get()));
625         retdoc->getDocumentElement()->appendChild(child);
626         if (respDDF) {
627             DDF hdr;
628             DDF hdrs = respDDF->getmember("headers");
629             hdr = hdrs.first();
630             while (hdr.isstring()) {
631                 if (!strcmp(hdr.name(), "Set-Cookie")) {
632                     child = retdoc->createElement(_Cookie);
633                     auto_ptr_XMLCh wideval(hdr.string());
634                     child->appendChild(retdoc->createTextNode(wideval.get()));
635                     retdoc->getDocumentElement()->appendChild(child);
636                 }
637                 hdr = hdrs.next();
638             }
639         }
640         if (!target.empty()) {
641             auto_ptr_XMLCh widetar(target.c_str());
642             child = retdoc->createElement(_RelayState);
643             child->appendChild(retdoc->createTextNode(widetar.get()));
644             retdoc->getDocumentElement()->appendChild(child);
645         }
646         XMLHelper::serialize(retdoc->getDocumentElement(), os, true);
647     }
648     return make_pair(true, httpResponse.sendResponse(os));
649 #else
650     return make_pair(false, 0L);
651 #endif
652 }
653 
654 #ifndef SHIBSP_LITE
655 
656 namespace {
657     class SHIBSP_DLLLOCAL DummyContext : public ResolutionContext
658     {
659     public:
DummyContext(const vector<Attribute * > & attributes)660         DummyContext(const vector<Attribute*>& attributes) : m_attributes(attributes) {
661         }
662 
~DummyContext()663         virtual ~DummyContext() {
664             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
665         }
666 
getResolvedAttributes()667         vector<Attribute*>& getResolvedAttributes() {
668             return m_attributes;
669         }
getResolvedAssertions()670         vector<Assertion*>& getResolvedAssertions() {
671             return m_tokens;
672         }
673 
674     private:
675         vector<Attribute*> m_attributes;
676         static vector<Assertion*> m_tokens; // never any tokens, so just share an empty vector
677     };
678 };
679 
680 vector<Assertion*> DummyContext::m_tokens;
681 
resolveAttributes(const Application & application,const GenericRequest * request,const RoleDescriptor * issuer,const XMLCh * protocol,const saml2::NameID * nameid,const saml2::AuthnStatement * statement,const XMLCh * authncontext_class,const XMLCh * authncontext_decl,const vector<const Assertion * > * tokens,const vector<Attribute * > * inputAttributes) const682 ResolutionContext* ExternalAuth::resolveAttributes(
683     const Application& application,
684     const GenericRequest* request,
685     const RoleDescriptor* issuer,
686     const XMLCh* protocol,
687     const saml2::NameID* nameid,
688     const saml2::AuthnStatement* statement,
689     const XMLCh* authncontext_class,
690     const XMLCh* authncontext_decl,
691     const vector<const Assertion*>* tokens,
692     const vector<Attribute*>* inputAttributes
693     ) const
694 {
695     vector<Attribute*> resolvedAttributes;
696     if (inputAttributes)
697         resolvedAttributes = *inputAttributes;
698 
699     // First we do the extraction of any pushed information, including from metadata.
700     AttributeExtractor* extractor = application.getAttributeExtractor();
701     if (extractor) {
702         Locker extlocker(extractor);
703         if (issuer) {
704             pair<bool,const char*> mprefix = application.getString("metadataAttributePrefix");
705             if (mprefix.first) {
706                 m_log.debug("extracting metadata-derived attributes...");
707                 try {
708                     // We pass nullptr for "issuer" because the IdP isn't the one asserting metadata-based attributes.
709                     extractor->extractAttributes(application, request, nullptr, *issuer, resolvedAttributes);
710                     for (indirect_iterator<vector<Attribute*>::iterator> a = make_indirect_iterator(resolvedAttributes.begin());
711                             a != make_indirect_iterator(resolvedAttributes.end()); ++a) {
712                         vector<string>& ids = a->getAliases();
713                         for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
714                             *id = mprefix.second + *id;
715                     }
716                 }
717                 catch (const std::exception& ex) {
718                     m_log.error("caught exception extracting attributes: %s", ex.what());
719                 }
720             }
721         }
722 
723         m_log.debug("extracting pushed attributes...");
724 
725         if (nameid) {
726             try {
727                 extractor->extractAttributes(application, request, issuer, *nameid, resolvedAttributes);
728             }
729             catch (const std::exception& ex) {
730                 m_log.error("caught exception extracting attributes: %s", ex.what());
731             }
732         }
733 
734         if (statement) {
735             try {
736                 extractor->extractAttributes(application, request, issuer, *statement, resolvedAttributes);
737             }
738             catch (const std::exception& ex) {
739                 m_log.error("caught exception extracting attributes: %s", ex.what());
740             }
741         }
742 
743         if (tokens) {
744             for (indirect_iterator<vector<const Assertion*>::const_iterator> t = make_indirect_iterator(tokens->begin());
745                     t != make_indirect_iterator(tokens->end()); ++t) {
746                 try {
747                     extractor->extractAttributes(application, request, issuer, *t, resolvedAttributes);
748                 }
749                 catch (const std::exception& ex) {
750                     m_log.error("caught exception extracting attributes: %s", ex.what());
751                 }
752             }
753         }
754 
755         AttributeFilter* filter = application.getAttributeFilter();
756         if (filter && !resolvedAttributes.empty()) {
757             BasicFilteringContext fc(application, resolvedAttributes, issuer, authncontext_class, authncontext_decl);
758             Locker filtlocker(filter);
759             try {
760                 filter->filterAttributes(fc, resolvedAttributes);
761             }
762             catch (const std::exception& ex) {
763                 m_log.error("caught exception filtering attributes: %s", ex.what());
764                 m_log.error("dumping extracted attributes due to filtering exception");
765                 for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
766                 resolvedAttributes.clear();
767             }
768         }
769     }
770     else {
771         m_log.warn("no AttributeExtractor plugin installed, check log during startup");
772     }
773 
774     try {
775         AttributeResolver* resolver = application.getAttributeResolver();
776         if (resolver) {
777             m_log.debug("resolving attributes...");
778 
779             Locker locker(resolver);
780             auto_ptr<ResolutionContext> ctx(
781                 resolver->createResolutionContext(
782                     application,
783                     request,
784                     issuer ? dynamic_cast<const EntityDescriptor*>(issuer->getParent()) : nullptr,
785                     protocol,
786                     nameid,
787                     authncontext_class,
788                     authncontext_decl,
789                     tokens,
790                     &resolvedAttributes
791                     )
792                 );
793             resolver->resolveAttributes(*ctx);
794             // Copy over any pushed attributes.
795             while (!resolvedAttributes.empty()) {
796                 ctx->getResolvedAttributes().push_back(resolvedAttributes.back());
797                 resolvedAttributes.pop_back();
798             }
799             return ctx.release();
800         }
801     }
802     catch (const std::exception& ex) {
803         m_log.error("attribute resolution failed: %s", ex.what());
804     }
805 
806     if (!resolvedAttributes.empty()) {
807         try {
808             return new DummyContext(resolvedAttributes);
809         }
810         catch (const bad_alloc&) {
811             for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
812         }
813     }
814     return nullptr;
815 }
816 
newLoginEvent(const Application & application,const HTTPRequest & request) const817 LoginEvent* ExternalAuth::newLoginEvent(const Application& application, const HTTPRequest& request) const
818 {
819     if (!SPConfig::getConfig().isEnabled(SPConfig::Logging))
820         return nullptr;
821     try {
822         auto_ptr<TransactionLog::Event> event(SPConfig::getConfig().EventManager.newPlugin(LOGIN_EVENT, nullptr, false));
823         LoginEvent* login_event = dynamic_cast<LoginEvent*>(event.get());
824         if (login_event) {
825             login_event->m_request = &request;
826             login_event->m_app = &application;
827             login_event->m_binding = "ExternalAuth";
828             event.release();
829             return login_event;
830         }
831         else {
832             m_log.warn("unable to audit event, log event object was of an incorrect type");
833         }
834     }
835     catch (const std::exception& ex) {
836         m_log.warn("exception auditing event: %s", ex.what());
837     }
838     return nullptr;
839 }
840 
841 #endif
842