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