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.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
22 * ExternalAuthHandler.cpp
24 * Handler for integrating with external authentication mechanisms.
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"
36 #include <boost/scoped_ptr.hpp>
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;
62 using saml2::AuthnStatement;
63 using saml2::AuthnContext;
65 # define min(a,b) (((a) < (b)) ? (a) : (b))
69 using namespace shibspconstants;
70 using namespace shibsp;
71 using namespace xmltooling;
72 using namespace boost;
77 #if defined (_MSC_VER)
78 #pragma warning( push )
79 #pragma warning( disable : 4250 )
82 class SHIBSP_API ExternalAuth : public SecuredHandler, public RemotedHandler
85 ExternalAuth(const DOMElement* e, const char* appId);
86 virtual ~ExternalAuth() {}
88 pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
89 void receive(DDF& in, ostream& out);
91 const char* getType() const {
92 return "ExternalAuth";
96 pair<bool,long> processMessage(
97 const Application& application,
98 HTTPRequest& httpRequest,
99 HTTPResponse& httpResponse,
101 const DDF* respDDF=nullptr
104 LoginEvent* newLoginEvent(const Application& application, const HTTPRequest& request) const;
105 ResolutionContext* resolveAttributes(
106 const Application& application,
107 const GenericRequest* request,
108 const saml2md::RoleDescriptor* issuer,
109 const XMLCh* protocol,
110 const saml2::NameID* nameid,
111 const saml2::AuthnStatement* statement,
112 const XMLCh* authncontext_class,
113 const XMLCh* authncontext_decl,
114 const vector<const Assertion*>* tokens=nullptr,
115 const vector<Attribute*>* inputAttributes=nullptr
120 #if defined (_MSC_VER)
121 #pragma warning( pop )
124 Handler* SHIBSP_DLLLOCAL ExternalAuthFactory(const pair<const DOMElement*,const char*>& p)
126 return new ExternalAuth(p.first, p.second);
132 static ostream& json_safe(ostream& os, const char* buf)
135 for (; *buf; ++buf) {
166 ExternalAuth::ExternalAuth(const DOMElement* e, const char* appId)
167 : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT".ExternalAuth"), "acl", "127.0.0.1 ::1")
169 setAddress("run::ExternalAuth");
172 pair<bool,long> ExternalAuth::run(SPRequest& request, bool isHandler) const
174 // Check ACL in base class.
175 pair<bool,long> ret = SecuredHandler::run(request, isHandler);
180 if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
181 // When out of process, we run natively and directly process the message, except that we
182 // have to indirect the request anyway in order to override the client address. This is
183 // the simplest way to get a delegated HTTPRequest object, and since this code path is
184 // not really one we expect to use, it's good enough.
185 vector<string> headers(1, "User-Agent");
186 headers.push_back("Accept");
187 headers.push_back("Accept-Language");
188 headers.push_back("Cookie");
189 DDF in = wrap(request, &headers);
191 scoped_ptr<HTTPRequest> fakedreq(getRequest(in));
192 return processMessage(request.getApplication(), *fakedreq, request, in);
195 // When not out of process, we remote all the message processing.
196 vector<string> headers(1, "User-Agent");
197 headers.push_back("Accept");
198 headers.push_back("Accept-Language");
199 headers.push_back("Cookie");
200 DDF out,in = wrap(request, &headers);
201 DDFJanitor jin(in), jout(out);
202 out=request.getServiceProvider().getListenerService()->send(in);
203 return unwrap(request, out);
206 catch (std::exception& ex) {
207 m_log.error("error while processing request: %s", ex.what());
208 istringstream msg("External Authentication Failed");
209 return make_pair(true, request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
213 void ExternalAuth::receive(DDF& in, ostream& out)
216 const char* aid = in["application_id"].string();
217 const Application* app = aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr;
219 // Something's horribly wrong.
220 m_log.error("couldn't find application (%s) for external authentication", aid ? aid : "(missing)");
221 throw ConfigurationException("Unable to locate application for external authentication, deleted?");
224 // Unpack the request.
225 scoped_ptr<HTTPRequest> req(getRequest(in));
227 // Wrap a response shim.
229 DDFJanitor jout(ret);
230 scoped_ptr<HTTPResponse> resp(getResponse(ret));
232 // Since we're remoted, the result should either be a throw, a false/0 return,
233 // which we just return as an empty structure, or a response/redirect,
234 // which we capture in the facade and send back.
236 processMessage(*app, *req, *resp, in, &ret);
238 catch (std::exception& ex) {
239 m_log.error("raising exception: %s", ex.what());
245 pair<bool,long> ExternalAuth::processMessage(
246 const Application& application, HTTPRequest& httpRequest, HTTPResponse& httpResponse, DDF& reqDDF, const DDF* respDDF
251 SessionCache* cache = application.getServiceProvider().getSessionCache();
252 MetadataProvider* m = application.getMetadataProvider(false);
255 scoped_ptr<TransactionLog::Event> event;
256 LoginEvent* login_event = nullptr;
257 if (SPConfig::getConfig().isEnabled(SPConfig::Logging)) {
258 event.reset(SPConfig::getConfig().EventManager.newPlugin(LOGIN_EVENT, nullptr));
259 login_event = dynamic_cast<LoginEvent*>(event.get());
261 login_event->m_app = &application;
263 m_log.warn("unable to audit event, log event object was of an incorrect type");
266 string ctype(httpRequest.getContentType());
267 if (ctype == "text/xml" || ctype == "application/samlassertion+xml") {
268 const char* body = httpRequest.getRequestBody();
270 throw FatalProfileException("Request body was empty.");
272 // Parse and bind the document into an XMLObject.
273 MemBufInputSource src(reinterpret_cast<const XMLByte*>(body), httpRequest.getContentLength(), "SAMLAssertion");
274 Wrapper4InputSource dsrc(&src, false);
275 DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(dsrc);
276 XercesJanitor<DOMDocument> janitor(doc);
277 auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
280 saml2::Assertion* token = dynamic_cast<saml2::Assertion*>(xmlObject.get());
282 throw FatalProfileException("Request body did not contain a SAML 2.0 assertion.");
283 else if (token->getAuthnStatements().empty())
284 throw FatalProfileException("Assertion in request did not contain an AuthnStatement.");
286 // We're not implementing a full SAML profile here, only a minimal one that ignores most
287 // security checking, conditions, etc. The caller is in full control here and we just consume
288 // what we're given. The only thing we're honoring is the authentication information we find
289 // and processing any attributes.
291 const XMLCh* protocol = nullptr;
292 pair<const EntityDescriptor*, const RoleDescriptor*> issuer = pair<const EntityDescriptor*, const RoleDescriptor*>(nullptr,nullptr);
293 if (m && token->getIssuer() && token->getIssuer()->getName()) {
294 MetadataProvider::Criteria mc;
295 mc.entityID_unicode = token->getIssuer()->getName();
296 mc.role = &IDPSSODescriptor::ELEMENT_QNAME;
297 mc.protocol = samlconstants::SAML20P_NS;
298 issuer = m->getEntityDescriptor(mc);
300 auto_ptr_char iname(token->getIssuer()->getName());
301 m_log.warn("no metadata found for issuer (%s)", iname.get());
303 else if (!issuer.second) {
304 auto_ptr_char iname(token->getIssuer()->getName());
305 m_log.warn("no IdP role found in metadata for issuer (%s)", iname.get());
307 protocol = mc.protocol;
310 const saml2::NameID* nameid = nullptr;
311 if (token->getSubject())
312 nameid = token->getSubject()->getNameID();
313 const AuthnStatement* ssoStatement = token->getAuthnStatements().front();
315 // authnskew allows rejection of SSO if AuthnInstant is too old.
316 const PropertySet* sessionProps = application.getPropertySet("Sessions");
317 pair<bool,unsigned int> authnskew = sessionProps ? sessionProps->getUnsignedInt("maxTimeSinceAuthn") : pair<bool,unsigned int>(false,0);
319 time_t now(time(nullptr));
320 if (ssoStatement->getAuthnInstant() &&
321 ssoStatement->getAuthnInstantEpoch() - XMLToolingConfig::getConfig().clock_skew_secs > now) {
322 throw FatalProfileException("The AuthnInstant was future-dated.");
324 else if (authnskew.first && authnskew.second && ssoStatement->getAuthnInstant() &&
325 ssoStatement->getAuthnInstantEpoch() <= now && (now - ssoStatement->getAuthnInstantEpoch() > authnskew.second)) {
326 throw FatalProfileException("The gap between now and the AuthnInstant exceeds the allowed limit.");
328 else if (authnskew.first && authnskew.second && ssoStatement->getAuthnInstant() == nullptr) {
329 throw FatalProfileException("No AuthnInstant was supplied, violating local policy.");
332 // Session expiration for SAML 2.0 is jointly IdP- and SP-driven.
333 time_t sessionExp = ssoStatement->getSessionNotOnOrAfter() ?
334 (ssoStatement->getSessionNotOnOrAfterEpoch() + XMLToolingConfig::getConfig().clock_skew_secs) : 0;
335 pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
336 if (!lifetime.first || lifetime.second == 0)
337 lifetime.second = 28800;
339 sessionExp = now + lifetime.second; // IdP says nothing, calulate based on SP.
341 sessionExp = min(sessionExp, now + lifetime.second); // Use the lowest.
343 const XMLCh* authncontext_class = nullptr;
344 const XMLCh* authncontext_decl = nullptr;
345 const AuthnContext* authnContext = ssoStatement->getAuthnContext();
347 authncontext_class = authnContext->getAuthnContextClassRef() ? authnContext->getAuthnContextClassRef()->getReference() : nullptr;
348 authncontext_decl = authnContext->getAuthnContextDeclRef() ? authnContext->getAuthnContextDeclRef()->getReference() : nullptr;
351 // Extract client address.
352 reqDDF.addmember("client_addr").string((const char*)nullptr);
353 if (ssoStatement->getSubjectLocality() && ssoStatement->getSubjectLocality()->getAddress()) {
354 auto_ptr_char addr(ssoStatement->getSubjectLocality()->getAddress());
356 reqDDF.getmember("client_addr").string(addr.get());
359 // The context will handle deleting attributes and tokens.
360 vector<const Assertion*> tokens(1, token);
361 scoped_ptr<ResolutionContext> ctx(
374 tokens.clear(); // don't store the original token in the session, since it was contrived
377 // Copy over any new tokens, but leave them in the context for cleanup.
378 tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
390 ssoStatement->getAuthnInstant() ? ssoStatement->getAuthnInstant()->getRawData() : nullptr,
391 ssoStatement->getSessionIndex(),
395 &ctx->getResolvedAttributes()
399 login_event->m_binding = "ExternalAuth/XML";
400 login_event->m_sessionID = session_id.c_str();
401 login_event->m_peer = issuer.first;
402 auto_ptr_char prot(protocol);
403 login_event->m_protocol = prot.get();
404 login_event->m_nameID = nameid;
405 login_event->m_saml2AuthnStatement = ssoStatement;
407 login_event->m_attributes = &ctx->getResolvedAttributes();
409 application.getServiceProvider().getTransactionLog()->write(*login_event);
411 catch (std::exception& ex) {
412 m_log.warn("exception auditing event: %s", ex.what());
416 else if (ctype == "application/x-www-form-urlencoded") {
417 auto_ptr_XMLCh protocol(httpRequest.getParameter("protocol"));
418 const char* param = httpRequest.getParameter("issuer");
419 pair<const EntityDescriptor*, const RoleDescriptor*> issuer = pair<const EntityDescriptor*, const RoleDescriptor*>(nullptr,nullptr);
420 if (m && param && *param) {
421 MetadataProvider::Criteria mc;
422 mc.entityID_ascii = param;
423 mc.role = &IDPSSODescriptor::ELEMENT_QNAME;
424 mc.protocol = protocol.get();
425 issuer = m->getEntityDescriptor(mc);
427 m_log.warn("no metadata found for issuer (%s)", param);
428 else if (!issuer.second)
429 m_log.warn("no IdP role found in metadata for issuer (%s)", param);
432 scoped_ptr<saml2::NameID> nameid;
433 param = httpRequest.getParameter("NameID");
434 if (param && *param) {
435 nameid.reset(saml2::NameIDBuilder::buildNameID());
436 auto_arrayptr<XMLCh> n(fromUTF8(param));
437 nameid->setName(n.get());
438 param = httpRequest.getParameter("Format");
439 if (param && param) {
440 auto_ptr_XMLCh f(param);
441 nameid->setFormat(f.get());
445 scoped_ptr<DateTime> authn_instant;
446 param = httpRequest.getParameter("AuthnInstant");
447 if (param && *param) {
448 auto_ptr_XMLCh d(param);
449 authn_instant.reset(new DateTime(d.get()));
450 authn_instant->parseDateTime();
453 auto_ptr_XMLCh session_index(httpRequest.getParameter("SessionIndex"));
454 auto_ptr_XMLCh authncontext_class(httpRequest.getParameter("AuthnContextClassRef"));
455 auto_ptr_XMLCh authncontext_decl(httpRequest.getParameter("AuthnContextDeclRef"));
457 time_t sessionExp = 0;
458 param = httpRequest.getParameter("lifetime");
460 sessionExp = atol(param);
462 sessionExp += time(nullptr);
465 const PropertySet* sessionProps = application.getPropertySet("Sessions");
466 pair<bool,unsigned int> lifetime = sessionProps ? sessionProps->getUnsignedInt("lifetime") : pair<bool,unsigned int>(true,28800);
467 if (!lifetime.first || lifetime.second == 0)
468 lifetime.second = 28800;
469 sessionExp = time(nullptr) + lifetime.second;
472 // Create simple attributes around whatever parameters are specified.
473 vector<Attribute*> resolvedAttributes;
474 param = httpRequest.getParameter("attributes");
475 if (param && *param) {
476 char_separator<char> sep(", ");
478 tokenizer< char_separator<char> > tokens(dup, sep);
480 for (tokenizer< char_separator<char> >::iterator t = tokens.begin(); t != tokens.end(); ++t) {
481 vector<const char*> vals;
482 if (httpRequest.getParameters(t->c_str(), vals)) {
483 vector<string> ids(1, *t);
484 auto_ptr<SimpleAttribute> attr(new SimpleAttribute(ids));
485 vector<string>& dest = attr->getValues();
486 for (vector<const char*>::const_iterator v = vals.begin(); v != vals.end(); ++v)
488 resolvedAttributes.push_back(attr.get());
493 catch (std::exception&) {
494 for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
499 // Get actual client address.
500 reqDDF.addmember("client_addr").string(httpRequest.getParameter("address"));
502 scoped_ptr<ResolutionContext> ctx(
510 authncontext_class.get(),
511 authncontext_decl.get(),
517 vector<const Assertion*> tokens;
519 // Copy over any new tokens, but leave them in the context for cleanup.
520 tokens.insert(tokens.end(), ctx->getResolvedAssertions().begin(), ctx->getResolvedAssertions().end());
532 authn_instant ? authn_instant->getRawData() : nullptr,
534 authncontext_class.get(),
535 authncontext_decl.get(),
537 &ctx->getResolvedAttributes()
541 login_event->m_binding = "ExternalAuth/POST";
542 login_event->m_sessionID = session_id.c_str();
543 login_event->m_peer = issuer.first;
544 login_event->m_protocol = httpRequest.getParameter("protocol");
545 login_event->m_nameID = nameid.get();
547 login_event->m_attributes = &ctx->getResolvedAttributes();
549 application.getServiceProvider().getTransactionLog()->write(*login_event);
551 catch (std::exception& ex) {
552 m_log.warn("exception auditing event: %s", ex.what());
557 throw FatalProfileException("Submission was not in a recognized SAML assertion or form-encoded format.");
560 const char* param = httpRequest.getParameter("RelayState");
561 string target(param ? param : "");
563 recoverRelayState(application, httpRequest, httpResponse, target);
565 catch (std::exception& ex) {
566 m_log.error("error recovering relay state: %s", ex.what());
571 string accept = httpRequest.getHeader("Accept");
572 if (accept.find("application/json") != string::npos) {
573 httpResponse.setContentType("application/json");
574 os << "{ \"SessionID\": "; json_safe(os, session_id.c_str());
575 bool firstCookie = true;
578 DDF hdrs = respDDF->getmember("headers");
580 while (hdr.isstring()) {
581 if (!strcmp(hdr.name(), "Set-Cookie")) {
583 os << ", \"Cookies\": [ ";
589 json_safe(os, hdr.string());
596 os << ", \"RelayState\": "; json_safe(os, target.c_str());
600 httpResponse.setContentType("text/xml");
601 static const XMLCh _ExternalAuth[] = UNICODE_LITERAL_12(E,x,t,e,r,n,a,l,A,u,t,h);
602 static const XMLCh _SessionID[] = UNICODE_LITERAL_9(S,e,s,s,i,o,n,I,D);
603 static const XMLCh _RelayState[] = UNICODE_LITERAL_10(R,e,l,a,y,S,t,a,t,e);
604 static const XMLCh _Cookie[] = UNICODE_LITERAL_6(C,o,o,k,i,e);
605 DOMDocument* retdoc = XMLToolingConfig::getConfig().getParser().newDocument();
606 XercesJanitor<DOMDocument> retjanitor(retdoc);
607 retdoc->appendChild(retdoc->createElement(_ExternalAuth));
608 auto_ptr_XMLCh wideid(session_id.c_str());
609 DOMElement* child = retdoc->createElement(_SessionID);
610 child->appendChild(retdoc->createTextNode(wideid.get()));
611 retdoc->getDocumentElement()->appendChild(child);
614 DDF hdrs = respDDF->getmember("headers");
616 while (hdr.isstring()) {
617 if (!strcmp(hdr.name(), "Set-Cookie")) {
618 child = retdoc->createElement(_Cookie);
619 auto_ptr_XMLCh wideval(hdr.string());
620 child->appendChild(retdoc->createTextNode(wideval.get()));
621 retdoc->getDocumentElement()->appendChild(child);
626 if (!target.empty()) {
627 auto_ptr_XMLCh widetar(target.c_str());
628 child = retdoc->createElement(_RelayState);
629 child->appendChild(retdoc->createTextNode(widetar.get()));
630 retdoc->getDocumentElement()->appendChild(child);
632 XMLHelper::serialize(retdoc->getDocumentElement(), os, true);
634 return make_pair(true, httpResponse.sendResponse(os));
636 return make_pair(false, 0L);
643 class SHIBSP_DLLLOCAL DummyContext : public ResolutionContext
646 DummyContext(const vector<Attribute*>& attributes) : m_attributes(attributes) {
649 virtual ~DummyContext() {
650 for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
653 vector<Attribute*>& getResolvedAttributes() {
656 vector<Assertion*>& getResolvedAssertions() {
661 vector<Attribute*> m_attributes;
662 static vector<Assertion*> m_tokens; // never any tokens, so just share an empty vector
666 vector<Assertion*> DummyContext::m_tokens;
668 ResolutionContext* ExternalAuth::resolveAttributes(
669 const Application& application,
670 const GenericRequest* request,
671 const RoleDescriptor* issuer,
672 const XMLCh* protocol,
673 const saml2::NameID* nameid,
674 const saml2::AuthnStatement* statement,
675 const XMLCh* authncontext_class,
676 const XMLCh* authncontext_decl,
677 const vector<const Assertion*>* tokens,
678 const vector<Attribute*>* inputAttributes
681 vector<Attribute*> resolvedAttributes;
683 resolvedAttributes = *inputAttributes;
685 // First we do the extraction of any pushed information, including from metadata.
686 AttributeExtractor* extractor = application.getAttributeExtractor();
688 Locker extlocker(extractor);
690 pair<bool,const char*> mprefix = application.getString("metadataAttributePrefix");
692 m_log.debug("extracting metadata-derived attributes...");
694 // We pass nullptr for "issuer" because the IdP isn't the one asserting metadata-based attributes.
695 extractor->extractAttributes(application, request, nullptr, *issuer, resolvedAttributes);
696 for (indirect_iterator<vector<Attribute*>::iterator> a = make_indirect_iterator(resolvedAttributes.begin());
697 a != make_indirect_iterator(resolvedAttributes.end()); ++a) {
698 vector<string>& ids = a->getAliases();
699 for (vector<string>::iterator id = ids.begin(); id != ids.end(); ++id)
700 *id = mprefix.second + *id;
703 catch (std::exception& ex) {
704 m_log.error("caught exception extracting attributes: %s", ex.what());
709 m_log.debug("extracting pushed attributes...");
713 extractor->extractAttributes(application, request, issuer, *nameid, resolvedAttributes);
715 catch (std::exception& ex) {
716 m_log.error("caught exception extracting attributes: %s", ex.what());
722 extractor->extractAttributes(application, request, issuer, *statement, resolvedAttributes);
724 catch (std::exception& ex) {
725 m_log.error("caught exception extracting attributes: %s", ex.what());
730 for (indirect_iterator<vector<const Assertion*>::const_iterator> t = make_indirect_iterator(tokens->begin());
731 t != make_indirect_iterator(tokens->end()); ++t) {
733 extractor->extractAttributes(application, request, issuer, *t, resolvedAttributes);
735 catch (std::exception& ex) {
736 m_log.error("caught exception extracting attributes: %s", ex.what());
741 AttributeFilter* filter = application.getAttributeFilter();
742 if (filter && !resolvedAttributes.empty()) {
743 BasicFilteringContext fc(application, resolvedAttributes, issuer, authncontext_class, authncontext_decl);
744 Locker filtlocker(filter);
746 filter->filterAttributes(fc, resolvedAttributes);
748 catch (std::exception& ex) {
749 m_log.error("caught exception filtering attributes: %s", ex.what());
750 m_log.error("dumping extracted attributes due to filtering exception");
751 for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
752 resolvedAttributes.clear();
757 m_log.warn("no AttributeExtractor plugin installed, check log during startup");
761 AttributeResolver* resolver = application.getAttributeResolver();
763 m_log.debug("resolving attributes...");
765 Locker locker(resolver);
766 auto_ptr<ResolutionContext> ctx(
767 resolver->createResolutionContext(
770 issuer ? dynamic_cast<const EntityDescriptor*>(issuer->getParent()) : nullptr,
779 resolver->resolveAttributes(*ctx);
780 // Copy over any pushed attributes.
781 while (!resolvedAttributes.empty()) {
782 ctx->getResolvedAttributes().push_back(resolvedAttributes.back());
783 resolvedAttributes.pop_back();
785 return ctx.release();
788 catch (std::exception& ex) {
789 m_log.error("attribute resolution failed: %s", ex.what());
792 if (!resolvedAttributes.empty()) {
794 return new DummyContext(resolvedAttributes);
797 for_each(resolvedAttributes.begin(), resolvedAttributes.end(), xmltooling::cleanup<shibsp::Attribute>());
803 LoginEvent* ExternalAuth::newLoginEvent(const Application& application, const HTTPRequest& request) const
805 if (!SPConfig::getConfig().isEnabled(SPConfig::Logging))
808 auto_ptr<TransactionLog::Event> event(SPConfig::getConfig().EventManager.newPlugin(LOGIN_EVENT, nullptr));
809 LoginEvent* login_event = dynamic_cast<LoginEvent*>(event.get());
811 login_event->m_request = &request;
812 login_event->m_app = &application;
813 login_event->m_binding = "ExternalAuth";
818 m_log.warn("unable to audit event, log event object was of an incorrect type");
821 catch (std::exception& ex) {
822 m_log.warn("exception auditing event: %s", ex.what());