X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2FServiceProvider.cpp;h=3ac5236055fb77aad116eaf417142f93b525331c;hb=HEAD;hp=66f125cbaa877a1eb07369b1d3ec66e458c69fca;hpb=66a7b1f30513115db03cd5bbe9b28fdbe7846cc0;p=shibboleth%2Fcpp-sp.git diff --git a/shibsp/ServiceProvider.cpp b/shibsp/ServiceProvider.cpp index 66f125c..3ac5236 100644 --- a/shibsp/ServiceProvider.cpp +++ b/shibsp/ServiceProvider.cpp @@ -1,22 +1,26 @@ -/* - * Copyright 2001-2007 Internet2 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at +/** + * Licensed to the University Corporation for Advanced Internet + * Development, Inc. (UCAID) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * UCAID licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the + * License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ /** * ServiceProvider.cpp - * + * * Interface to a Shibboleth ServiceProvider instance. */ @@ -33,68 +37,129 @@ #include #include +#include +#ifndef SHIBSP_LITE +# include +# include +#endif #include #include +#include #include #include using namespace shibsp; +using namespace xmltooling::logging; using namespace xmltooling; using namespace std; namespace shibsp { SHIBSP_DLLLOCAL PluginManager::Factory XMLServiceProviderFactory; - long SHIBSP_DLLLOCAL sendError(SPRequest& request, const Application* app, const char* page, TemplateParameters& tp, bool mayRedirect=false) + long SHIBSP_DLLLOCAL sendError( + Category& log, SPRequest& request, const Application* app, const char* page, TemplateParameters& tp, bool mayRedirect=true + ) { + // The properties we need can be set in the RequestMap, or the Errors element. + bool mderror = dynamic_cast(tp.getRichException())!=nullptr; + bool accesserror = (strcmp(page, "access")==0); + pair redirectErrors = pair(false,nullptr); + pair pathname = pair(false,nullptr); + + // Strictly for error handling, detect a nullptr application and point at the default. + if (!app) + app = request.getServiceProvider().getApplication(nullptr); + + const PropertySet* props=app->getPropertySet("Errors"); + + // First look for settings in the request map of the form pageError. + try { + RequestMapper::Settings settings = request.getRequestSettings(); + if (mderror) + pathname = settings.first->getString("metadataError"); + if (!pathname.first) { + string pagename(page); + pagename += "Error"; + pathname = settings.first->getString(pagename.c_str()); + } + if (mayRedirect) + redirectErrors = settings.first->getString("redirectErrors"); + } + catch (exception& ex) { + log.error(ex.what()); + } + + // Check for redirection on errors instead of template. if (mayRedirect) { - // Check for redirection on errors instead of template. - const PropertySet* sessions=app ? app->getPropertySet("Sessions") : NULL; - pair redirectErrors = sessions ? sessions->getString("redirectErrors") : pair(false,NULL); + if (!redirectErrors.first && props) + redirectErrors = props->getString("redirectErrors"); if (redirectErrors.first) { string loc(redirectErrors.second); + request.absolutize(loc); loc = loc + '?' + tp.toQueryString(); return request.sendRedirect(loc.c_str()); } } request.setContentType("text/html"); - request.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT"); - request.setResponseHeader("Cache-Control","private,no-store,no-cache"); - - const PropertySet* props=app ? app->getPropertySet("Errors") : NULL; - if (props) { - pair p=props->getString(page); - if (p.first) { - ifstream infile(p.second); - if (infile) { - tp.setPropertySet(props); - stringstream str; - XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp, tp.getRichException()); - return request.sendResponse(str); - } + request.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT"); + request.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0"); + + // Nothing in the request map, so check for a property named "page" in the Errors property set. + if (!pathname.first && props) { + if (mderror) + pathname=props->getString("metadata"); + if (!pathname.first) + pathname=props->getString(page); + } + + // If there's still no template to use, just use pageError.html unless it's an access issue. + string fname; + if (!pathname.first) { + if (!accesserror) { + fname = string(page) + "Error.html"; + pathname.second = fname.c_str(); } - else if (!strcmp(page,"access")) { - istringstream msg("Access Denied"); - return request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN); + } + else { + fname = pathname.second; + } + + // If we have a template to use, use it. + if (!fname.empty()) { + ifstream infile(XMLToolingConfig::getConfig().getPathResolver()->resolve(fname, PathResolver::XMLTOOLING_CFG_FILE).c_str()); + if (infile) { + tp.setPropertySet(props); + stringstream str; + XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp, tp.getRichException()); + return request.sendError(str); } } - - string errstr = string("sendError could not process error template (") + page + ")"; - request.log(SPRequest::SPError, errstr); + + // If we got here, then either it's an access error or a template failed. + if (accesserror) { + istringstream msg("Access Denied"); + return request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_FORBIDDEN); + } + + log.error("sendError could not process error template (%s)", pathname.second); istringstream msg("Internal Server Error. Please contact the site administrator."); return request.sendError(msg); } - + void SHIBSP_DLLLOCAL clearHeaders(SPRequest& request) { - request.clearHeader("Shib-Identity-Provider"); - request.clearHeader("Shib-Authentication-Method"); - request.clearHeader("Shib-AuthnContext-Class"); - request.clearHeader("Shib-AuthnContext-Decl"); - request.clearHeader("Shib-Attributes"); - request.clearHeader("Shib-Assertion-Count"); - //request.clearHeader("Shib-Application-ID"); handle inside app method - request.getApplication().clearAttributeHeaders(request); + const Application& app = request.getApplication(); + app.clearHeader(request, "Shib-Cookie-Name", "HTTP_SHIB_COOKIE_NAME"); + app.clearHeader(request, "Shib-Session-ID", "HTTP_SHIB_SESSION_ID"); + app.clearHeader(request, "Shib-Session-Index", "HTTP_SHIB_SESSION_INDEX"); + app.clearHeader(request, "Shib-Identity-Provider", "HTTP_SHIB_IDENTITY_PROVIDER"); + app.clearHeader(request, "Shib-Authentication-Method", "HTTP_SHIB_AUTHENTICATION_METHOD"); + app.clearHeader(request, "Shib-Authentication-Instant", "HTTP_SHIB_AUTHENTICATION_INSTANT"); + app.clearHeader(request, "Shib-AuthnContext-Class", "HTTP_SHIB_AUTHNCONTEXT_CLASS"); + app.clearHeader(request, "Shib-AuthnContext-Decl", "HTTP_SHIB_AUTHNCONTEXT_DECL"); + app.clearHeader(request, "Shib-Assertion-Count", "HTTP_SHIB_ASSERTION_COUNT"); + app.clearAttributeHeaders(request); + request.clearHeader("REMOTE_USER", "HTTP_REMOTE_USER"); } }; @@ -103,13 +168,63 @@ void SHIBSP_API shibsp::registerServiceProviders() SPConfig::getConfig().ServiceProviderManager.registerFactory(XML_SERVICE_PROVIDER, XMLServiceProviderFactory); } +ServiceProvider::ServiceProvider() +{ + m_authTypes.insert("shibboleth"); +} + +ServiceProvider::~ServiceProvider() +{ +} + +#ifndef SHIBSP_LITE +SecurityPolicyProvider* ServiceProvider::getSecurityPolicyProvider(bool required) const +{ + if (required) + throw ConfigurationException("No SecurityPolicyProvider available."); + return nullptr; +} +#endif + +Remoted* ServiceProvider::regListener(const char* address, Remoted* listener) +{ + Remoted* ret = nullptr; + map::const_iterator i = m_listenerMap.find(address); + if (i != m_listenerMap.end()) + ret = i->second; + m_listenerMap[address] = listener; + Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider").info("registered remoted message endpoint (%s)",address); + return ret; +} + +bool ServiceProvider::unregListener(const char* address, Remoted* current, Remoted* restore) +{ + map::const_iterator i = m_listenerMap.find(address); + if (i != m_listenerMap.end() && i->second == current) { + if (restore) + m_listenerMap[address] = restore; + else + m_listenerMap.erase(address); + Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider").info("unregistered remoted message endpoint (%s)",address); + return true; + } + return false; +} + +Remoted* ServiceProvider::lookupListener(const char *address) const +{ + map::const_iterator i = m_listenerMap.find(address); + return (i == m_listenerMap.end()) ? nullptr : i->second; +} + pair ServiceProvider::doAuthentication(SPRequest& request, bool handler) const { #ifdef _DEBUG xmltooling::NDC ndc("doAuthentication"); #endif + Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider"); - const Application* app=NULL; + const Application* app = nullptr; string targetURL = request.getRequestURL(); try { @@ -136,68 +251,90 @@ pair ServiceProvider::doAuthentication(SPRequest& request, bool handl else { TemplateParameters tp; tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "ssl", tp)); + return make_pair(true, sendError(log, request, app, "ssl", tp, false)); } } } - + const char* handlerURL=request.getHandlerURL(targetURL.c_str()); if (!handlerURL) throw ConfigurationException("Cannot determine handler from resource URL, check configuration."); // If the request URL contains the handler base URL for this application, either dispatch // directly (mainly Apache 2.0) or just pass back control. - if (strstr(targetURL.c_str(),handlerURL)) { + if (boost::contains(targetURL, handlerURL)) { if (handler) return doHandler(request); else return make_pair(true, request.returnOK()); } - // Three settings dictate how to proceed. + // These settings dictate how to proceed. pair authType = settings.first->getString("authType"); pair requireSession = settings.first->getBool("requireSession"); pair requireSessionWith = settings.first->getString("requireSessionWith"); + pair requireLogoutWith = settings.first->getString("requireLogoutWith"); - // If no session is required AND the AuthType (an Apache-derived concept) isn't shibboleth, + // If no session is required AND the AuthType (an Apache-derived concept) isn't recognized, // then we ignore this request and consider it unprotected. Apache might lie to us if // ShibBasicHijack is on, but that's up to it. if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first && -#ifdef HAVE_STRCASECMP - (!authType.first || strcasecmp(authType.second,"shibboleth"))) -#else - (!authType.first || _stricmp(authType.second,"shibboleth"))) -#endif - return make_pair(true,request.returnDecline()); + (!authType.first || m_authTypes.find(boost::to_lower_copy(string(authType.second))) == m_authTypes.end())) + return make_pair(true, request.returnDecline()); // Fix for secadv 20050901 clearHeaders(request); - Session* session = NULL; + Session* session = nullptr; try { - session = request.getSession(); + session = request.getSession(true, false, false); // don't cache it } catch (exception& e) { - request.log(SPRequest::SPWarn, string("error during session lookup: ") + e.what()); + log.warn("error during session lookup: %s", e.what()); // If it's not a retryable session failure, we throw to the outer handler for reporting. - if (dynamic_cast(&e)==NULL) + if (dynamic_cast(&e) == nullptr) throw; } - if (!session) { + Locker slocker(session, false); // pop existing lock on exit + if (session) { + // Check for logout interception. + if (requireLogoutWith.first) { + // Check for a completion parameter on the query string. + const char* qstr = request.getQueryString(); + if (!qstr || !strstr(qstr, "shiblogoutdone=1")) { + // First leg of circuit, so we redirect to the logout endpoint specified with this URL as a return location. + string selfurl = request.getRequestURL(); + if (qstr) + selfurl += '&'; + else + selfurl += '?'; + selfurl += "shiblogoutdone=1"; + string loc = requireLogoutWith.second; + request.absolutize(loc); + if (loc.find('?') != string::npos) + loc += '&'; + else + loc += '?'; + loc += "return=" + XMLToolingConfig::getConfig().getURLEncoder()->encode(selfurl.c_str()); + return make_pair(true, request.sendRedirect(loc.c_str())); + } + } + } + else { // No session. Maybe that's acceptable? if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first) - return make_pair(true,request.returnOK()); + return make_pair(true, request.returnOK()); // No session, but we require one. Initiate a new session using the indicated method. - const Handler* initiator=NULL; + const SessionInitiator* initiator=nullptr; if (requireSessionWith.first) { initiator=app->getSessionInitiatorById(requireSessionWith.second); - if (!initiator) + if (!initiator) { throw ConfigurationException( - "No session initiator found with id ($1), check requireSessionWith command.", - params(1,requireSessionWith.second) + "No session initiator found with id ($1), check requireSessionWith command.", params(1, requireSessionWith.second) ); + } } else { initiator=app->getDefaultSessionInitiator(); @@ -205,28 +342,27 @@ pair ServiceProvider::doAuthentication(SPRequest& request, bool handl throw ConfigurationException("No default session initiator found, check configuration."); } - return initiator->run(request,false); + // Dispatch to SessionInitiator. This MUST handle the request, or we want to fail here. + // Used to fall through into doExport, but this is a cleaner exit path. + pair ret = initiator->run(request, false); + if (ret.first) + return ret; + throw ConfigurationException("Session initiator did not handle request for a new session, check configuration."); } + request.setAuthType(authType.second); + // We're done. Everything is okay. Nothing to report. Nothing to do.. // Let the caller decide how to proceed. - request.log(SPRequest::SPDebug, "doAuthentication succeeded"); - return make_pair(false,0); + log.debug("doAuthentication succeeded"); + return make_pair(false,0L); } catch (exception& e) { + request.log(SPRequest::SPError, e.what()); TemplateParameters tp(&e); tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "session", tp)); - } -#ifndef _DEBUG - catch (...) { - TemplateParameters tp; - tp.m_map["errorType"] = "Unexpected Error"; - tp.m_map["errorText"] = "Caught an unknown exception."; - tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "session", tp)); + return make_pair(true, sendError(log, request, app, "session", tp)); } -#endif } pair ServiceProvider::doAuthorization(SPRequest& request) const @@ -234,8 +370,11 @@ pair ServiceProvider::doAuthorization(SPRequest& request) const #ifdef _DEBUG xmltooling::NDC ndc("doAuthorization"); #endif + Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider"); - const Application* app=NULL; + const Application* app = nullptr; + Session* session = nullptr; + Locker slocker; string targetURL = request.getRequestURL(); try { @@ -247,58 +386,53 @@ pair ServiceProvider::doAuthorization(SPRequest& request) const pair requireSession = settings.first->getBool("requireSession"); pair requireSessionWith = settings.first->getString("requireSessionWith"); - // If no session is required AND the AuthType (an Apache-derived concept) isn't shibboleth, + // If no session is required AND the AuthType (an Apache-derived concept) isn't recognized, // then we ignore this request and consider it unprotected. Apache might lie to us if // ShibBasicHijack is on, but that's up to it. if ((!requireSession.first || !requireSession.second) && !requireSessionWith.first && -#ifdef HAVE_STRCASECMP - (!authType.first || strcasecmp(authType.second,"shibboleth"))) -#else - (!authType.first || _stricmp(authType.second,"shibboleth"))) -#endif - return make_pair(true,request.returnDecline()); + (!authType.first || m_authTypes.find(boost::to_lower_copy(string(authType.second))) == m_authTypes.end())) + return make_pair(true, request.returnDecline()); // Do we have an access control plugin? if (settings.second) { - const Session* session = NULL; try { - session = request.getSession(false); + session = request.getSession(false, false, false); // ignore timeout and do not cache + if (session) + slocker.assign(session, false); // assign to lock popper } catch (exception& e) { - request.log(SPRequest::SPWarn, string("unable to obtain session to pass to access control provider: ") + e.what()); + log.warn("unable to obtain session to pass to access control provider: %s", e.what()); } - + Locker acllock(settings.second); - if (settings.second->authorized(request,session)) { - // Let the caller decide how to proceed. - request.log(SPRequest::SPDebug, "access control provider granted access"); - return make_pair(false,0); - } - else { - request.log(SPRequest::SPWarn, "access control provider denied access"); - TemplateParameters tp; - tp.m_map["requestURL"] = targetURL; - return make_pair(true,sendError(request, app, "access", tp)); + switch (settings.second->authorized(request, session)) { + case AccessControl::shib_acl_true: + log.debug("access control provider granted access"); + return make_pair(true, request.returnOK()); + + case AccessControl::shib_acl_false: + { + log.warn("access control provider denied access"); + TemplateParameters tp(nullptr, nullptr, session); + tp.m_map["requestURL"] = targetURL; + return make_pair(true, sendError(log, request, app, "access", tp, false)); + } + + default: + // Use the "DECLINE" interface to signal we don't know what to do. + return make_pair(true, request.returnDecline()); } - return make_pair(false,0); } - else - return make_pair(true,request.returnDecline()); + else { + return make_pair(true, request.returnDecline()); + } } catch (exception& e) { - TemplateParameters tp(&e); + request.log(SPRequest::SPError, e.what()); + TemplateParameters tp(&e, nullptr, session); tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "access", tp)); + return make_pair(true, sendError(log, request, app, "access", tp)); } -#ifndef _DEBUG - catch (...) { - TemplateParameters tp; - tp.m_map["errorType"] = "Unexpected Error"; - tp.m_map["errorText"] = "Caught an unknown exception."; - tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "access", tp)); - } -#endif } pair ServiceProvider::doExport(SPRequest& request, bool requireSession) const @@ -306,20 +440,24 @@ pair ServiceProvider::doExport(SPRequest& request, bool requireSessio #ifdef _DEBUG xmltooling::NDC ndc("doExport"); #endif + Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider"); - const Application* app=NULL; + const Application* app = nullptr; + Session* session = nullptr; + Locker slocker; string targetURL = request.getRequestURL(); try { RequestMapper::Settings settings = request.getRequestSettings(); app = &(request.getApplication()); - const Session* session = NULL; try { - session = request.getSession(false); + session = request.getSession(false, false, false); // ignore timeout and do not cache + if (session) + slocker.assign(session, false); // assign to lock popper } catch (exception& e) { - request.log(SPRequest::SPWarn, string("unable to obtain session to export to request: ") + e.what()); + log.warn("unable to obtain session to export to request: %s", e.what()); // If we have to have a session, then this is a fatal error. if (requireSession) throw; @@ -330,36 +468,64 @@ pair ServiceProvider::doExport(SPRequest& request, bool requireSessio if (requireSession) throw opensaml::RetryableProfileException("Unable to obtain session to export to request."); else - return make_pair(false,0); // just bail silently + return make_pair(false, 0L); // just bail silently } - - request.setHeader("Shib-Application-ID", app->getId()); - - // Export the IdP name and Authn method/context info. - const char* hval = session->getEntityID(); - if (hval) - request.setHeader("Shib-Identity-Provider", hval); - hval = session->getAuthnContextClassRef(); - if (hval) { - request.setHeader("Shib-Authentication-Method", hval); - request.setHeader("Shib-AuthnContext-Class", hval); + + pair enc = settings.first->getString("encoding"); + if (enc.first && strcmp(enc.second, "URL")) + throw ConfigurationException("Unsupported value for 'encoding' content setting ($1).", params(1,enc.second)); + + const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder(); + + app->setHeader(request, "Shib-Application-ID", app->getId()); + app->setHeader(request, "Shib-Session-ID", session->getID()); + + // Check for export of "standard" variables. + // A 3.0 release would switch this default to false and rely solely on the + // Assertion extractor plugin and ship out of the box with the same defaults. + pair stdvars = settings.first->getBool("exportStdVars"); + if (!stdvars.first || stdvars.second) { + const char* hval = session->getEntityID(); + if (hval) + app->setHeader(request, "Shib-Identity-Provider", hval); + hval = session->getAuthnInstant(); + if (hval) + app->setHeader(request, "Shib-Authentication-Instant", hval); + hval = session->getAuthnContextClassRef(); + if (hval) { + app->setHeader(request, "Shib-Authentication-Method", hval); + app->setHeader(request, "Shib-AuthnContext-Class", hval); + } + hval = session->getAuthnContextDeclRef(); + if (hval) + app->setHeader(request, "Shib-AuthnContext-Decl", hval); + hval = session->getSessionIndex(); + if (hval) + app->setHeader(request, "Shib-Session-Index", hval); + } + + // Check for export of algorithmically-derived portion of cookie names. + stdvars = settings.first->getBool("exportCookie"); + if (stdvars.first && stdvars.second) { + pair cookieprops = app->getCookieNameProps(nullptr); + app->setHeader(request, "Shib-Cookie-Name", cookieprops.first.c_str()); } - hval = session->getAuthnContextDeclRef(); - if (hval) - request.setHeader("Shib-AuthnContext-Decl", hval); - + // Maybe export the assertion keys. - pair exp=settings.first->getBool("exportAssertion"); + pair exp = settings.first->getBool("exportAssertion"); if (exp.first && exp.second) { - const PropertySet* sessions=app->getPropertySet("Sessions"); - pair exportLocation = sessions ? sessions->getString("exportLocation") : pair(false,NULL); + const PropertySet* sessions = app->getPropertySet("Sessions"); + pair exportLocation = sessions ? sessions->getString("exportLocation") : pair(false,nullptr); if (!exportLocation.first) - request.log(SPRequest::SPWarn, "can't export assertions without an exportLocation Sessions property"); + log.warn("can't export assertions without an exportLocation Sessions property"); else { - const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder(); string exportName = "Shib-Assertion-00"; - const char* handlerURL=request.getHandlerURL(targetURL.c_str()); - string baseURL = string(handlerURL) + exportLocation.second + "?key=" + session->getID() + "&ID="; + string baseURL; + if (!strncmp(exportLocation.second, "http", 4)) + baseURL = exportLocation.second; + else + baseURL = string(request.getHandlerURL(targetURL.c_str())) + exportLocation.second; + baseURL = baseURL + "?key=" + session->getID() + "&ID="; const vector& tokens = session->getAssertionIDs(); vector::size_type count = 0; for (vector::const_iterator tokenids = tokens.begin(); tokenids!=tokens.end(); ++tokenids) { @@ -367,61 +533,71 @@ pair ServiceProvider::doExport(SPRequest& request, bool requireSessio *(exportName.rbegin()) = '0' + (count%10); *(++exportName.rbegin()) = '0' + (count/10); string fullURL = baseURL + encoder->encode(*tokenids); - request.setHeader(exportName.c_str(), fullURL.c_str()); + app->setHeader(request, exportName.c_str(), fullURL.c_str()); } - request.setHeader("Shib-Assertion-Count", exportName.c_str() + 15); + app->setHeader(request, "Shib-Assertion-Count", exportName.c_str() + 15); } } // Export the attributes. - bool remoteUserSet = false; - const multimap& attributes = session->getAttributes(); - for (multimap::const_iterator a = attributes.begin(); a!=attributes.end(); ++a) { + const multimap& attributes = session->getIndexedAttributes(); + for (multimap::const_iterator a = attributes.begin(); a != attributes.end(); ++a) { + if (a->second->isInternal()) + continue; + string header(app->getSecureHeader(request, a->first.c_str())); const vector& vals = a->second->getSerializedValues(); - - // See if this needs to be set as the REMOTE_USER value. - if (!remoteUserSet && !vals.empty() && app->getRemoteUserAttributeIds().count(a->first)) { - request.setRemoteUser(vals.front().c_str()); - remoteUserSet = true; - } - - // Handle the normal export case. - string header(request.getSecureHeader(a->first.c_str())); - for (vector::const_iterator v = vals.begin(); v!=vals.end(); ++v) { + for (vector::const_iterator v = vals.begin(); v != vals.end(); ++v) { if (!header.empty()) header += ";"; - string::size_type pos = v->find_first_of(';',string::size_type(0)); - if (pos!=string::npos) { - string value(*v); - for (; pos != string::npos; pos = value.find_first_of(';',pos)) { - value.insert(pos, "\\"); - pos += 2; - } - header += value; - } - else { - header += (*v); + if (enc.first) { + // If URL-encoding, any semicolons will get escaped anyway. + header += encoder->encode(v->c_str()); + } + else { + string::size_type pos = v->find_first_of(';', string::size_type(0)); + if (pos != string::npos) { + string value(*v); + for (; pos != string::npos; pos = value.find_first_of(';', pos)) { + value.insert(pos, "\\"); + pos += 2; + } + header += value; + } + else { + header += (*v); + } + } + } + app->setHeader(request, a->first.c_str(), header.c_str()); + } + + // Check for REMOTE_USER. + bool remoteUserSet = false; + const vector& rmids = app->getRemoteUserAttributeIds(); + for (vector::const_iterator rmid = rmids.begin(); !remoteUserSet && rmid != rmids.end(); ++rmid) { + pair::const_iterator,multimap::const_iterator> matches = + attributes.equal_range(*rmid); + for (; matches.first != matches.second; ++matches.first) { + const vector& vals = matches.first->second->getSerializedValues(); + if (!vals.empty()) { + if (enc.first) + request.setRemoteUser(encoder->encode(vals.front().c_str()).c_str()); + else + request.setRemoteUser(vals.front().c_str()); + remoteUserSet = true; + break; } } - request.setHeader(a->first.c_str(), header.c_str()); } - - return make_pair(false,0); + + return make_pair(false,0L); } catch (exception& e) { - TemplateParameters tp(&e); + request.log(SPRequest::SPError, e.what()); + TemplateParameters tp(&e, nullptr, session); tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "rm", tp)); + return make_pair(true, sendError(log, request, app, "session", tp)); } -#ifndef _DEBUG - catch (...) { - TemplateParameters tp; - tp.m_map["errorType"] = "Unexpected Error"; - tp.m_map["errorText"] = "Caught an unknown exception."; - tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "rm", tp)); - } -#endif } pair ServiceProvider::doHandler(SPRequest& request) const @@ -429,71 +605,85 @@ pair ServiceProvider::doHandler(SPRequest& request) const #ifdef _DEBUG xmltooling::NDC ndc("doHandler"); #endif + Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider"); - const Application* app=NULL; + const Application* app = nullptr; string targetURL = request.getRequestURL(); try { RequestMapper::Settings settings = request.getRequestSettings(); app = &(request.getApplication()); - const char* handlerURL=request.getHandlerURL(targetURL.c_str()); + // If not SSL, check to see if we should block or redirect it. + if (!request.isSecure()) { + pair redirectToSSL = settings.first->getString("redirectToSSL"); + if (redirectToSSL.first) { +#ifdef HAVE_STRCASECMP + if (!strcasecmp("GET",request.getMethod()) || !strcasecmp("HEAD",request.getMethod())) { +#else + if (!stricmp("GET",request.getMethod()) || !stricmp("HEAD",request.getMethod())) { +#endif + // Compute the new target URL + string redirectURL = string("https://") + request.getHostname(); + if (strcmp(redirectToSSL.second,"443")) { + redirectURL = redirectURL + ':' + redirectToSSL.second; + } + redirectURL += request.getRequestURI(); + return make_pair(true, request.sendRedirect(redirectURL.c_str())); + } + else { + TemplateParameters tp; + tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); + return make_pair(true,sendError(log, request, app, "ssl", tp, false)); + } + } + } + + const char* handlerURL = request.getHandlerURL(targetURL.c_str()); if (!handlerURL) throw ConfigurationException("Cannot determine handler from resource URL, check configuration."); // Make sure we only process handler requests. - if (!strstr(targetURL.c_str(),handlerURL)) + if (!boost::contains(targetURL, handlerURL)) return make_pair(true, request.returnDecline()); - const PropertySet* sessionProps=app->getPropertySet("Sessions"); + const PropertySet* sessionProps = app->getPropertySet("Sessions"); if (!sessionProps) throw ConfigurationException("Unable to map request to application session settings, check configuration."); // Process incoming request. - pair handlerSSL=sessionProps->getBool("handlerSSL"); - + pair handlerSSL = sessionProps->getBool("handlerSSL"); + // Make sure this is SSL, if it should be if ((!handlerSSL.first || handlerSSL.second) && !request.isSecure()) throw opensaml::FatalProfileException("Blocked non-SSL access to Shibboleth handler."); // We dispatch based on our path info. We know the request URL begins with or equals the handler URL, // so the path info is the next character (or null). - const Handler* handler=app->getHandler(targetURL.c_str() + strlen(handlerURL)); + const Handler* handler = app->getHandler(targetURL.c_str() + strlen(handlerURL)); if (!handler) throw ConfigurationException("Shibboleth handler invoked at an unconfigured location."); - pair hret=handler->run(request); + pair hret = handler->run(request); // Did the handler run successfully? if (hret.first) return hret; - + throw ConfigurationException("Configured Shibboleth handler failed to process the request."); } - catch (opensaml::saml2md::MetadataException& e) { - TemplateParameters tp(&e); - tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - // See if a metadata error page is installed. - const PropertySet* props=app ? app->getPropertySet("Errors") : NULL; - if (props) { - pair p=props->getString("metadata"); - if (p.first) - return make_pair(true,sendError(request, app, "metadata", tp, true)); - } - return make_pair(true,sendError(request, app, "session", tp, true)); - } catch (exception& e) { - TemplateParameters tp(&e); - tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "session", tp, true)); - } -#ifndef _DEBUG - catch (...) { - TemplateParameters tp; - tp.m_map["errorType"] = "Unexpected Error"; - tp.m_map["errorText"] = "Caught an unknown exception."; - tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(request, app, "session", tp, true)); + request.log(SPRequest::SPError, e.what()); + Session* session = nullptr; + try { + session = request.getSession(false, true, false); // do not cache + } + catch (exception&) { + } + Locker slocker(session, false); // pop existing lock on exit + TemplateParameters tp(&e, nullptr, session); + tp.m_map["requestURL"] = targetURL.substr(0, targetURL.find('?')); + tp.m_request = &request; + return make_pair(true, sendError(log, request, app, "session", tp)); } -#endif }