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