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