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  * ServiceProvider.cpp
23  *
24  * Interface to a Shibboleth ServiceProvider instance.
25  */
26 
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "AccessControl.h"
30 #include "Application.h"
31 #include "ServiceProvider.h"
32 #include "SessionCache.h"
33 #include "SPRequest.h"
34 #include "attribute/Attribute.h"
35 #include "handler/SessionInitiator.h"
36 #include "util/TemplateParameters.h"
37 
38 #include <fstream>
39 #include <sstream>
40 #include <boost/algorithm/string.hpp>
41 #include <boost/lexical_cast.hpp>
42 #ifndef SHIBSP_LITE
43 # include <saml/exceptions.h>
44 # include <saml/saml2/metadata/MetadataProvider.h>
45 #endif
46 #include <xmltooling/XMLToolingConfig.h>
47 #include <xmltooling/util/NDC.h>
48 #include <xmltooling/util/PathResolver.h>
49 #include <xmltooling/util/URLEncoder.h>
50 #include <xmltooling/util/XMLHelper.h>
51 
52 using namespace shibsp;
53 using namespace xmltooling::logging;
54 using namespace xmltooling;
55 using namespace std;
56 
57 namespace shibsp {
58     SHIBSP_DLLLOCAL PluginManager<ServiceProvider,string,const DOMElement*>::Factory XMLServiceProviderFactory;
59 
sendError(Category & log,SPRequest & request,const Application * app,const char * page,TemplateParameters & tp,bool mayRedirect=true)60     long SHIBSP_DLLLOCAL sendError(
61         Category& log, SPRequest& request, const Application* app, const char* page, TemplateParameters& tp, bool mayRedirect=true
62         )
63     {
64         // The properties we need can be set in the RequestMap, or the Errors element.
65         bool mderror = dynamic_cast<const opensaml::saml2md::MetadataException*>(tp.getRichException())!=nullptr;
66         bool accesserror = (strcmp(page, "access")==0);
67         pair<bool,const char*> redirectErrors = pair<bool,const char*>(false,nullptr);
68         pair<bool,const char*> pathname = pair<bool,const char*>(false,nullptr);
69 
70         // Strictly for error handling, detect a nullptr application and point at the default.
71         if (!app)
72             app = request.getServiceProvider().getApplication(nullptr);
73 
74         const PropertySet* props = app->getPropertySet("Errors");
75 
76         // If the externalParameters option isn't set, clear out the request field.
77         pair<bool,bool> externalParameters =
78                 props ? props->getBool("externalParameters") : pair<bool,bool>(false,false);
79         if (!externalParameters.first || !externalParameters.second) {
80             tp.m_request = nullptr;
81         }
82 
83         // Now look for settings in the request map of the form pageError.
84         try {
85             RequestMapper::Settings settings = request.getRequestSettings();
86             if (mderror)
87                 pathname = settings.first->getString("metadataError");
88             if (!pathname.first) {
89                 string pagename(page);
90                 pagename += "Error";
91                 pathname = settings.first->getString(pagename.c_str());
92             }
93             if (mayRedirect)
94                 redirectErrors = settings.first->getString("redirectErrors");
95         }
96         catch (const exception& ex) {
97             log.error(ex.what());
98         }
99 
100         // Check for redirection on errors instead of template.
101         if (mayRedirect) {
102             if (!redirectErrors.first && props)
103                 redirectErrors = props->getString("redirectErrors");
104             if (redirectErrors.first) {
105                 string loc(redirectErrors.second);
106                 request.absolutize(loc);
107                 loc = loc + '?' + tp.toQueryString();
108                 return request.sendRedirect(loc.c_str());
109             }
110         }
111 
112         request.setContentType("text/html");
113         request.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT");
114         request.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0");
115 
116         // Nothing in the request map, so check for a property named "page" in the Errors property set.
117         if (!pathname.first && props) {
118             if (mderror)
119                 pathname=props->getString("metadata");
120             if (!pathname.first)
121                 pathname=props->getString(page);
122         }
123 
124         // If there's still no template to use, just use pageError.html unless it's an access issue.
125         string fname;
126         if (!pathname.first) {
127             if (!accesserror) {
128                 fname = string(mderror ? "metadata" : page) + "Error.html";
129                 pathname.second = fname.c_str();
130             }
131         }
132         else {
133             fname = pathname.second;
134         }
135 
136         // If we have a template to use, use it.
137         if (!fname.empty()) {
138             ifstream infile(XMLToolingConfig::getConfig().getPathResolver()->resolve(fname, PathResolver::XMLTOOLING_CFG_FILE).c_str());
139             if (infile) {
140                 tp.setPropertySet(props);
141                 stringstream str;
142                 XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp, tp.getRichException());
143                 return request.sendError(str);
144             }
145         }
146 
147         // If we got here, then either it's an access error or a template failed.
148         if (accesserror) {
149             istringstream msg("Access Denied");
150             return request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN);
151         }
152 
153         log.error("sendError could not process error template (%s)", pathname.second);
154         istringstream msg("Internal Server Error. Please contact the site administrator.");
155         return request.sendError(msg);
156     }
157 
clearHeaders(SPRequest & request)158     void SHIBSP_DLLLOCAL clearHeaders(SPRequest& request) {
159         const Application& app = request.getApplication();
160         app.clearHeader(request, "Shib-Cookie-Name", "HTTP_SHIB_COOKIE_NAME");
161         app.clearHeader(request, "Shib-Session-ID", "HTTP_SHIB_SESSION_ID");
162         app.clearHeader(request, "Shib-Session-Index", "HTTP_SHIB_SESSION_INDEX");
163         app.clearHeader(request, "Shib-Session-Expires", "HTTP_SHIB_SESSION_EXPIRES");
164         app.clearHeader(request, "Shib-Session-Inactivity", "HTTP_SHIB_SESSION_INACTIVITY");
165         app.clearHeader(request, "Shib-Identity-Provider", "HTTP_SHIB_IDENTITY_PROVIDER");
166         app.clearHeader(request, "Shib-Authentication-Method", "HTTP_SHIB_AUTHENTICATION_METHOD");
167         app.clearHeader(request, "Shib-Authentication-Instant", "HTTP_SHIB_AUTHENTICATION_INSTANT");
168         app.clearHeader(request, "Shib-AuthnContext-Class", "HTTP_SHIB_AUTHNCONTEXT_CLASS");
169         app.clearHeader(request, "Shib-AuthnContext-Decl", "HTTP_SHIB_AUTHNCONTEXT_DECL");
170         app.clearHeader(request, "Shib-Assertion-Count", "HTTP_SHIB_ASSERTION_COUNT");
171         app.clearHeader(request, "Shib-Handler", "HTTP_SHIB_HANDLER");
172         app.clearAttributeHeaders(request);
173         request.clearHeader("REMOTE_USER", "HTTP_REMOTE_USER");
174     }
175 
exportAttributes(SPRequest & request,const Session * session,RequestMapper::Settings settings)176     void SHIBSP_DLLLOCAL exportAttributes(SPRequest& request, const Session* session, RequestMapper::Settings settings) {
177 
178         pair<bool,const char*> enc = settings.first->getString("encoding");
179         if (enc.first && strcmp(enc.second, "URL"))
180             throw ConfigurationException("Unsupported value for 'encoding' content setting ($1).", params(1,enc.second));
181 
182         const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder();
183 
184         // Default delimiter is semicolon but is now configurable.
185         pair<bool,const char*> delim = settings.first->getString("attributeValueDelimiter");
186         if (enc.first || !delim.first) {
187             delim.second = ";";
188         }
189         size_t delim_len = strlen(delim.second);
190 
191         pair<bool,bool> exportDups = settings.first->getBool("exportDuplicateValues");
192         const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
193 
194         // Default export strategy will include duplicates.
195         if (!exportDups.first || exportDups.second) {
196             for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
197                 if (a->second->isInternal())
198                     continue;
199                 string header(request.getApplication().getSecureHeader(request, a->first.c_str()));
200                 const vector<string>& vals = a->second->getSerializedValues();
201                 for (vector<string>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
202                     if (!header.empty())
203                         header += delim.second;
204                     if (enc.first) {
205                         // If URL-encoding, any semicolons will get escaped anyway.
206                         header += encoder->encode(v->c_str());
207                     }
208                     else {
209                         string::size_type pos = v->find(delim.second, string::size_type(0));
210                         if (pos != string::npos) {
211                             string value(*v);
212                             for (; pos != string::npos; pos = value.find(delim.second, pos)) {
213                                 value.insert(pos, "\\");
214                                 pos += delim_len + 1;
215                             }
216                             header += value;
217                         }
218                         else {
219                             header += (*v);
220                         }
221                     }
222                 }
223                 request.getApplication().setHeader(request, a->first.c_str(), header.c_str());
224             }
225         }
226         else {
227             // Capture values in a map of sets to check for duplicates on the fly.
228             map< string,set<string> > valueMap;
229             for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
230                 if (a->second->isInternal())
231                     continue;
232                 const vector<string>& vals = a->second->getSerializedValues();
233                 valueMap[a->first].insert(vals.begin(), vals.end());
234             }
235 
236             // Export the mapped sets to the headers.
237             for (map< string,set<string> >::const_iterator deduped = valueMap.begin(); deduped != valueMap.end(); ++deduped) {
238                 string header;
239                 for (set<string>::const_iterator v = deduped->second.begin(); v != deduped->second.end(); ++v) {
240                     if (!header.empty())
241                         header += delim.second;
242                     if (enc.first) {
243                         // If URL-encoding, any semicolons will get escaped anyway.
244                         header += encoder->encode(v->c_str());
245                     }
246                     else {
247                         string::size_type pos = v->find(delim.second, string::size_type(0));
248                         if (pos != string::npos) {
249                             string value(*v);
250                             for (; pos != string::npos; pos = value.find(delim.second, pos)) {
251                                 value.insert(pos, "\\");
252                                 pos += delim_len + 1;
253                             }
254                             header += value;
255                         }
256                         else {
257                             header += (*v);
258                         }
259                     }
260                 }
261                 request.getApplication().setHeader(request, deduped->first.c_str(), header.c_str());
262             }
263         }
264 
265         // Check for REMOTE_USER.
266         bool remoteUserSet = false;
267         const vector<string>& rmids = request.getApplication().getRemoteUserAttributeIds();
268         for (vector<string>::const_iterator rmid = rmids.begin(); !remoteUserSet && rmid != rmids.end(); ++rmid) {
269             pair<multimap<string,const Attribute*>::const_iterator,multimap<string,const Attribute*>::const_iterator> matches =
270                 attributes.equal_range(*rmid);
271             for (; matches.first != matches.second; ++matches.first) {
272                 const vector<string>& vals = matches.first->second->getSerializedValues();
273                 if (!vals.empty()) {
274                     if (enc.first)
275                         request.setRemoteUser(encoder->encode(vals.front().c_str()).c_str());
276                     else
277                         request.setRemoteUser(vals.front().c_str());
278                     remoteUserSet = true;
279                     break;
280                 }
281             }
282         }
283     }
284 };
285 
registerServiceProviders()286 void SHIBSP_API shibsp::registerServiceProviders()
287 {
288     SPConfig::getConfig().ServiceProviderManager.registerFactory(XML_SERVICE_PROVIDER, XMLServiceProviderFactory);
289 }
290 
ServiceProvider()291 ServiceProvider::ServiceProvider()
292 {
293     m_authTypes.insert("shibboleth");
294 }
295 
~ServiceProvider()296 ServiceProvider::~ServiceProvider()
297 {
298 }
299 
doAuthentication(SPRequest & request,bool handler) const300 pair<bool,long> ServiceProvider::doAuthentication(SPRequest& request, bool handler) const
301 {
302 #ifdef _DEBUG
303     xmltooling::NDC ndc("doAuthentication");
304 #endif
305     Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
306 
307     const Application* app = nullptr;
308     string targetURL = request.getRequestURL();
309 
310     try {
311         RequestMapper::Settings settings = request.getRequestSettings();
312         app = &(request.getApplication());
313 
314         string appid(string("[") + app->getId() + "]");
315         xmltooling::NDC ndc(appid.c_str());
316 
317         // If not SSL, check to see if we should block or redirect it.
318         if (!request.isSecure()) {
319             pair<bool,const char*> redirectToSSL = settings.first->getString("redirectToSSL");
320             if (redirectToSSL.first) {
321 #ifdef HAVE_STRCASECMP
322                 if (!strcasecmp("GET",request.getMethod()) || !strcasecmp("HEAD",request.getMethod())) {
323 #else
324                 if (!stricmp("GET",request.getMethod()) || !stricmp("HEAD",request.getMethod())) {
325 #endif
326                     // Compute the new target URL
327                     string redirectURL = string("https://") + request.getHostname();
328                     if (strcmp(redirectToSSL.second,"443")) {
329                         redirectURL = redirectURL + ':' + redirectToSSL.second;
330                     }
331                     redirectURL += request.getRequestURI();
332                     return make_pair(true, request.sendRedirect(redirectURL.c_str()));
333                 }
334                 else {
335                     TemplateParameters tp;
336                     tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
337                     return make_pair(true, sendError(log, request, app, "ssl", tp, false));
338                 }
339             }
340         }
341 
342         const char* handlerURL=request.getHandlerURL(targetURL.c_str());
343         if (!handlerURL)
344             throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
345 
346         // If the request URL contains the handler base URL for this application, either dispatch
347         // directly (mainly Apache 2.0) or just pass back control.
348         if (boost::contains(targetURL, handlerURL)) {
349             if (handler)
350                 return doHandler(request);
351             else
352                 return make_pair(true, request.returnOK());
353         }
354 
355         // These settings dictate how to proceed.
356         pair<bool,const char*> authType = settings.first->getString("authType");
357         pair<bool,bool> requireSession = settings.first->getBool("requireSession");
358         pair<bool,const char*> requireSessionWith = settings.first->getString("requireSessionWith");
359         pair<bool,const char*> requireLogoutWith = settings.first->getString("requireLogoutWith");
360 
361         // If no session is required AND the AuthType (an Apache-derived concept) isn't recognized,
362         // then we ignore this request and consider it unprotected. Apache might lie to us if
363         // ShibBasicHijack is on, but that's up to it.
364         if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first &&
365                 (!authType.first || m_authTypes.find(boost::to_lower_copy(string(authType.second))) == m_authTypes.end()))
366             return make_pair(true, request.returnDecline());
367 
368         // Fix for secadv 20050901
369         clearHeaders(request);
370 
371         Session* session = nullptr;
372         try {
373             session = request.getSession(true, false, false);   // don't cache it
374         }
375         catch (const exception& e) {
376             log.warn("error during session lookup: %s", e.what());
377             // If it's not a retryable session failure, we throw to the outer handler for reporting.
378             if (dynamic_cast<const opensaml::RetryableProfileException*>(&e) == nullptr)
379                 throw;
380         }
381 
382         Locker slocker(session, false); // pop existing lock on exit
383         if (session) {
384             // Check for logout interception.
385             if (requireLogoutWith.first) {
386                 // Check for a completion parameter on the query string.
387                 const char* qstr = request.getQueryString();
388                 if (!qstr || !strstr(qstr, "shiblogoutdone=1")) {
389                     // First leg of circuit, so we redirect to the logout endpoint specified with this URL as a return location.
390                     string selfurl = request.getRequestURL();
391                     if (qstr)
392                         selfurl += '&';
393                     else
394                         selfurl += '?';
395                     selfurl += "shiblogoutdone=1";
396                     string loc = requireLogoutWith.second;
397                     request.absolutize(loc);
398                     if (loc.find('?') != string::npos)
399                         loc += '&';
400                     else
401                         loc += '?';
402                     loc += "return=" + XMLToolingConfig::getConfig().getURLEncoder()->encode(selfurl.c_str());
403                     return make_pair(true, request.sendRedirect(loc.c_str()));
404                 }
405             }
406             app->setHeader(request, "Shib-Handler", handlerURL);
407         }
408         else {
409             // No session.  Maybe that's acceptable?
410             if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first) {
411                 app->setHeader(request, "Shib-Handler", handlerURL);
412                 return make_pair(true, request.returnOK());
413             }
414 
415             // No session, but we require one. Initiate a new session using the indicated method.
416             const SessionInitiator* initiator=nullptr;
417             if (requireSessionWith.first) {
418                 SPConfig::getConfig().deprecation().warn("requireSessionWith");
419                 initiator=app->getSessionInitiatorById(requireSessionWith.second);
420                 if (!initiator) {
421                     throw ConfigurationException(
422                         "No session initiator found with id ($1), check requireSessionWith setting.", params(1, requireSessionWith.second)
423                         );
424                 }
425             }
426             else {
427                 initiator=app->getDefaultSessionInitiator();
428                 if (!initiator)
429                     throw ConfigurationException("No default session initiator found, check configuration.");
430             }
431 
432             // Dispatch to SessionInitiator. This MUST handle the request, or we want to fail here.
433             // Used to fall through into doExport, but this is a cleaner exit path.
434             try {
435                 pair<bool, long> ret = initiator->run(request, false);
436                 if (ret.first)
437                     return ret;
438                 throw ConfigurationException("Session initiator did not handle request for a new session, check configuration.");
439             }
440             catch (XMLToolingException& ex) {
441                 if (!ex.getProperty("eventType") && initiator->getEventType())
442                     ex.addProperty("eventType", initiator->getEventType());
443                 throw;
444             }
445         }
446 
447         request.setAuthType(authType.second);
448 
449         // We're done.  Everything is okay.  Nothing to report.  Nothing to do..
450         // Let the caller decide how to proceed.
451         log.debug("doAuthentication succeeded");
452         return make_pair(false,0L);
453     }
454     catch (const exception& e) {
455         request.log(SPRequest::SPError, e.what());
456         TemplateParameters tp(&e);
457         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
458         return make_pair(true, sendError(log, request, app, "session", tp));
459     }
460 }
461 
462 pair<bool,long> ServiceProvider::doAuthorization(SPRequest& request) const
463 {
464 #ifdef _DEBUG
465     xmltooling::NDC ndc("doAuthorization");
466 #endif
467     Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
468 
469     const Application* app = nullptr;
470     Session* session = nullptr;
471     Locker slocker;
472     string targetURL = request.getRequestURL();
473 
474     try {
475         RequestMapper::Settings settings = request.getRequestSettings();
476         app = &(request.getApplication());
477 
478         string appid(string("[") + app->getId() + "]");
479         xmltooling::NDC ndc(appid.c_str());
480 
481         // Three settings dictate how to proceed.
482         pair<bool,const char*> authType = settings.first->getString("authType");
483         pair<bool,bool> requireSession = settings.first->getBool("requireSession");
484         pair<bool,const char*> requireSessionWith = settings.first->getString("requireSessionWith");
485 
486         // If no session is required AND the AuthType (an Apache-derived concept) isn't recognized,
487         // then we ignore this request and consider it unprotected. Apache might lie to us if
488         // ShibBasicHijack is on, but that's up to it.
489         if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first &&
490                 (!authType.first || m_authTypes.find(boost::to_lower_copy(string(authType.second))) == m_authTypes.end()))
491             return make_pair(true, request.returnDecline());
492 
493         // Do we have an access control plugin?
494         if (settings.second) {
495             try {
496                 session = request.getSession(false, false, false);  // ignore timeout and do not cache
497                 if (session)
498                     slocker.assign(session, false); // assign to lock popper
499             }
500             catch (const exception& e) {
501                 log.warn("unable to obtain session to pass to access control provider: %s", e.what());
502             }
503 
504             Locker acllock(settings.second);
505             switch (settings.second->authorized(request, session)) {
506                 case AccessControl::shib_acl_true:
507                     log.debug("access control provider granted access");
508                     return make_pair(true, request.returnOK());
509 
510                 case AccessControl::shib_acl_false:
511                 {
512                     log.warn("access control provider denied access");
513                     TemplateParameters tp(nullptr, nullptr, session);
514                     tp.m_map["requestURL"] = targetURL;
515                     return make_pair(true, sendError(log, request, app, "access", tp, false));
516                 }
517 
518                 default:
519                     // Use the "DECLINE" interface to signal we don't know what to do.
520                     return make_pair(true, request.returnDecline());
521             }
522         }
523         else {
524             return make_pair(true, request.returnDecline());
525         }
526     }
527     catch (const exception& e) {
528         request.log(SPRequest::SPError, e.what());
529         TemplateParameters tp(&e, nullptr, session);
530         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
531         return make_pair(true, sendError(log, request, app, "access", tp));
532     }
533 }
534 
535 pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSession) const
536 {
537 #ifdef _DEBUG
538     xmltooling::NDC ndc("doExport");
539 #endif
540     Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
541 
542     const Application* app = nullptr;
543     Session* session = nullptr;
544     Locker slocker;
545     string targetURL = request.getRequestURL();
546 
547     try {
548         RequestMapper::Settings settings = request.getRequestSettings();
549         app = &(request.getApplication());
550 
551         string appid(string("[") + app->getId() + "]");
552         xmltooling::NDC ndc(appid.c_str());
553 
554         try {
555             session = request.getSession(false, false, false);  // ignore timeout and do not cache
556             if (session)
557                 slocker.assign(session, false); // assign to lock popper
558         }
559         catch (const exception& e) {
560             log.warn("unable to obtain session to export to request: %s", e.what());
561         	// If we have to have a session, then this is a fatal error.
562         	if (requireSession)
563         		throw;
564         }
565 
566 		// Still no data?
567         if (!session) {
568         	if (requireSession)
569                 throw opensaml::RetryableProfileException("Unable to obtain session to export to request.");
570         	else
571         		return make_pair(false, 0L);	// just bail silently
572         }
573 
574         app->setHeader(request, "Shib-Application-ID", app->getId());
575         app->setHeader(request, "Shib-Session-ID", session->getID());
576 
577         const PropertySet* sessionProps = app->getPropertySet("Sessions");
578 
579         // Check for export of "standard" variables.
580         // A 3.0 release would switch this default to false and rely solely on the
581         // Assertion extractor plugin and ship out of the box with the same defaults.
582         pair<bool,bool> stdvars = settings.first->getBool("exportStdVars");
583         if (!stdvars.first || stdvars.second) {
584             const char* hval = session->getEntityID();
585             if (hval)
586                 app->setHeader(request, "Shib-Identity-Provider", hval);
587             hval = session->getAuthnInstant();
588             if (hval)
589                 app->setHeader(request, "Shib-Authentication-Instant", hval);
590             hval = session->getAuthnContextClassRef();
591             if (hval) {
592                 app->setHeader(request, "Shib-Authentication-Method", hval);
593                 app->setHeader(request, "Shib-AuthnContext-Class", hval);
594             }
595             hval = session->getAuthnContextDeclRef();
596             if (hval)
597                 app->setHeader(request, "Shib-AuthnContext-Decl", hval);
598             hval = session->getSessionIndex();
599             if (hval)
600                 app->setHeader(request, "Shib-Session-Index", hval);
601 
602             app->setHeader(request, "Shib-Session-Expires", boost::lexical_cast<string>(session->getExpiration()).c_str());
603             pair<bool,unsigned int> timeout = sessionProps ? sessionProps->getUnsignedInt("timeout") : pair<bool,unsigned int>(false, 0);
604             if (timeout.first && timeout.second > 0) {
605                 app->setHeader(request, "Shib-Session-Inactivity", boost::lexical_cast<string>(session->getLastAccess() + timeout.second).c_str());
606             }
607         }
608 
609         // Check for export of algorithmically-derived portion of cookie names.
610         stdvars = settings.first->getBool("exportCookie");
611         if (stdvars.first && stdvars.second) {
612             pair<string,const char*> cookieprops = app->getCookieNameProps(nullptr);
613             app->setHeader(request, "Shib-Cookie-Name", cookieprops.first.c_str());
614         }
615 
616         // Maybe export the assertion keys.
617         pair<bool,bool> exp = settings.first->getBool("exportAssertion");
618         if (exp.first && exp.second) {
619             pair<bool,const char*> exportLocation = sessionProps ? sessionProps->getString("exportLocation") : pair<bool,const char*>(false,nullptr);
620             if (!exportLocation.first)
621                 log.warn("can't export assertions without an exportLocation Sessions property");
622             else {
623                 string exportName = "Shib-Assertion-00";
624                 string baseURL;
625                 if (!strncmp(exportLocation.second, "http", 4))
626                     baseURL = exportLocation.second;
627                 else
628                     baseURL = string(request.getHandlerURL(targetURL.c_str())) + exportLocation.second;
629                 baseURL = baseURL + "?key=" + session->getID() + "&ID=";
630                 const vector<const char*>& tokens = session->getAssertionIDs();
631                 vector<const char*>::size_type count = 0;
632                 for (vector<const char*>::const_iterator tokenids = tokens.begin(); tokenids!=tokens.end(); ++tokenids) {
633                     count++;
634                     *(exportName.rbegin()) = '0' + (count%10);
635                     *(++exportName.rbegin()) = '0' + (count/10);
636                     string fullURL = baseURL + XMLToolingConfig::getConfig().getURLEncoder()->encode(*tokenids);
637                     app->setHeader(request, exportName.c_str(), fullURL.c_str());
638                 }
639                 app->setHeader(request, "Shib-Assertion-Count", exportName.c_str() + 15);
640             }
641         }
642 
643         // Export the attributes.
644         exportAttributes(request, session, settings);
645 
646         return make_pair(false,0L);
647     }
648     catch (const exception& e) {
649         request.log(SPRequest::SPError, e.what());
650         TemplateParameters tp(&e, nullptr, session);
651         tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
652         return make_pair(true, sendError(log, request, app, "session", tp));
653     }
654 }
655 
656 pair<bool,long> ServiceProvider::doHandler(SPRequest& request) const
657 {
658 #ifdef _DEBUG
659     xmltooling::NDC ndc("doHandler");
660 #endif
661     Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider");
662 
663     const Application* app = nullptr;
664     string targetURL = request.getRequestURL();
665 
666     try {
667         RequestMapper::Settings settings = request.getRequestSettings();
668         app = &(request.getApplication());
669 
670         string appid(string("[") + app->getId() + "]");
671         xmltooling::NDC ndc(appid.c_str());
672 
673         // If not SSL, check to see if we should block or redirect it.
674         if (!request.isSecure()) {
675             pair<bool,const char*> redirectToSSL = settings.first->getString("redirectToSSL");
676             if (redirectToSSL.first) {
677 #ifdef HAVE_STRCASECMP
678                 if (!strcasecmp("GET",request.getMethod()) || !strcasecmp("HEAD",request.getMethod())) {
679 #else
680                 if (!stricmp("GET",request.getMethod()) || !stricmp("HEAD",request.getMethod())) {
681 #endif
682                     // Compute the new target URL
683                     string redirectURL = string("https://") + request.getHostname();
684                     if (strcmp(redirectToSSL.second,"443")) {
685                         redirectURL = redirectURL + ':' + redirectToSSL.second;
686                     }
687                     redirectURL += request.getRequestURI();
688                     return make_pair(true, request.sendRedirect(redirectURL.c_str()));
689                 }
690                 else {
691                     TemplateParameters tp;
692                     tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
693                     return make_pair(true,sendError(log, request, app, "ssl", tp, false));
694                 }
695             }
696         }
697 
698         const char* handlerURL = request.getHandlerURL(targetURL.c_str());
699         if (!handlerURL)
700             throw ConfigurationException("Cannot determine handler from resource URL, check configuration.");
701 
702         // Make sure we only process handler requests.
703         if (!boost::contains(targetURL, handlerURL))
704             return make_pair(true, request.returnDecline());
705 
706         const PropertySet* sessionProps = app->getPropertySet("Sessions");
707         if (!sessionProps)
708             throw ConfigurationException("Unable to map request to application session settings, check configuration.");
709 
710         // Process incoming request.
711         pair<bool,bool> handlerSSL = sessionProps->getBool("handlerSSL");
712 
713         // Make sure this is SSL, if it should be
714         if ((!handlerSSL.first || handlerSSL.second) && !request.isSecure())
715             throw opensaml::FatalProfileException("Blocked non-SSL access to Shibboleth handler.");
716 
717         // We dispatch based on our path info. We know the request URL begins with or equals the handler URL,
718         // so the path info is the next character (or null).
719         const Handler* handler = app->getHandler(targetURL.c_str() + strlen(handlerURL));
720         if (!handler)
721             throw ConfigurationException("Shibboleth handler invoked at an unconfigured location.");
722 
723         try {
724             pair<bool, long> hret = handler->run(request);
725             // Did the handler run successfully?
726             if (hret.first)
727                 return hret;
728             throw ConfigurationException("Configured Shibboleth handler failed to process the request.");
729         }
730         catch (XMLToolingException& ex) {
731             if (!ex.getProperty("eventType") && handler->getEventType())
732                 ex.addProperty("eventType", handler->getEventType());
733             throw;
734         }
735     }
736     catch (const exception& e) {
737         request.log(SPRequest::SPError, e.what());
738         Session* session = nullptr;
739         try {
740             session = request.getSession(false, true, false);   // do not cache
741         }
742         catch (const exception&) {
743         }
744         Locker slocker(session, false); // pop existing lock on exit
745         TemplateParameters tp(&e, nullptr, session);
746         tp.m_map["requestURL"] = targetURL.substr(0, targetURL.find('?'));
747         tp.m_request = &request;
748         return make_pair(true, sendError(log, request, app, "session", tp));
749     }
750 }
751