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