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