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