X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2FServiceProvider.cpp;h=3ac5236055fb77aad116eaf417142f93b525331c;hb=c51bfd77603cf0ddb0b5e374c35586a8435895d6;hp=30e4423cc304d269532f262da2605717dc76aca5;hpb=89cd55d63b109670f615c5adc941d93f0872be52;p=shibboleth%2Fcpp-sp.git diff --git a/shibsp/ServiceProvider.cpp b/shibsp/ServiceProvider.cpp index 30e4423..3ac5236 100644 --- a/shibsp/ServiceProvider.cpp +++ b/shibsp/ServiceProvider.cpp @@ -1,17 +1,21 @@ -/* - * Copyright 2001-2010 Internet2 +/** + * 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. * - * 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 + * 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. */ /** @@ -33,6 +37,7 @@ #include #include +#include #ifndef SHIBSP_LITE # include # include @@ -90,14 +95,15 @@ namespace shibsp { 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"); + 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) { @@ -143,6 +149,7 @@ namespace shibsp { void SHIBSP_DLLLOCAL clearHeaders(SPRequest& 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"); @@ -163,6 +170,7 @@ void SHIBSP_API shibsp::registerServiceProviders() ServiceProvider::ServiceProvider() { + m_authTypes.insert("shibboleth"); } ServiceProvider::~ServiceProvider() @@ -180,24 +188,24 @@ SecurityPolicyProvider* ServiceProvider::getSecurityPolicyProvider(bool required 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); + 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) { + map::const_iterator i = m_listenerMap.find(address); + if (i != m_listenerMap.end() && i->second == current) { if (restore) - m_listenerMap[address]=restore; + m_listenerMap[address] = restore; else m_listenerMap.erase(address); - Category::getInstance(SHIBSP_LOGCAT".ServiceProvider").info("unregistered remoted message endpoint (%s)",address); + Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider").info("unregistered remoted message endpoint (%s)",address); return true; } return false; @@ -205,8 +213,8 @@ bool ServiceProvider::unregListener(const char* address, Remoted* current, Remot Remoted* ServiceProvider::lookupListener(const char *address) const { - map::const_iterator i=m_listenerMap.find(address); - return (i==m_listenerMap.end()) ? nullptr : i->second; + map::const_iterator i = m_listenerMap.find(address); + return (i == m_listenerMap.end()) ? nullptr : i->second; } pair ServiceProvider::doAuthentication(SPRequest& request, bool handler) const @@ -214,9 +222,9 @@ pair ServiceProvider::doAuthentication(SPRequest& request, bool handl #ifdef _DEBUG xmltooling::NDC ndc("doAuthentication"); #endif - Category& log = Category::getInstance(SHIBSP_LOGCAT".ServiceProvider"); + Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider"); - const Application* app=nullptr; + const Application* app = nullptr; string targetURL = request.getRequestURL(); try { @@ -243,7 +251,7 @@ pair ServiceProvider::doAuthentication(SPRequest& request, bool handl else { TemplateParameters tp; tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(log, request, app, "ssl", tp, false)); + return make_pair(true, sendError(log, request, app, "ssl", tp, false)); } } } @@ -254,47 +262,69 @@ pair ServiceProvider::doAuthentication(SPRequest& request, bool handl // 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 = nullptr; try { - session = request.getSession(); + session = request.getSession(true, false, false); // don't cache it } catch (exception& e) { 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)==nullptr) + 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 SessionInitiator* initiator=nullptr; @@ -302,7 +332,7 @@ pair ServiceProvider::doAuthentication(SPRequest& request, bool handl initiator=app->getSessionInitiatorById(requireSessionWith.second); 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) ); } } @@ -312,10 +342,15 @@ 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("shibboleth"); + request.setAuthType(authType.second); // We're done. Everything is okay. Nothing to report. Nothing to do.. // Let the caller decide how to proceed. @@ -326,7 +361,7 @@ pair ServiceProvider::doAuthentication(SPRequest& request, bool handl request.log(SPRequest::SPError, e.what()); TemplateParameters tp(&e); tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(log, request, app, "session", tp)); + return make_pair(true, sendError(log, request, app, "session", tp)); } } @@ -335,9 +370,11 @@ pair ServiceProvider::doAuthorization(SPRequest& request) const #ifdef _DEBUG xmltooling::NDC ndc("doAuthorization"); #endif - Category& log = Category::getInstance(SHIBSP_LOGCAT".ServiceProvider"); + Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider"); - const Application* app=nullptr; + const Application* app = nullptr; + Session* session = nullptr; + Locker slocker; string targetURL = request.getRequestURL(); try { @@ -349,55 +386,52 @@ 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 = nullptr; 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) { log.warn("unable to obtain session to pass to access control provider: %s", e.what()); } Locker acllock(settings.second); - switch (settings.second->authorized(request,session)) { + switch (settings.second->authorized(request, session)) { case AccessControl::shib_acl_true: log.debug("access control provider granted access"); - return make_pair(true,request.returnOK()); + return make_pair(true, request.returnOK()); case AccessControl::shib_acl_false: { log.warn("access control provider denied access"); - TemplateParameters tp; + TemplateParameters tp(nullptr, nullptr, session); tp.m_map["requestURL"] = targetURL; - return make_pair(true,sendError(log, request, app, "access", tp, false)); + 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(true, request.returnDecline()); } } else { - return make_pair(true,request.returnDecline()); + return make_pair(true, request.returnDecline()); } } catch (exception& e) { request.log(SPRequest::SPError, e.what()); - TemplateParameters tp(&e); + TemplateParameters tp(&e, nullptr, session); tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(log, request, app, "access", tp)); + return make_pair(true, sendError(log, request, app, "access", tp)); } } @@ -406,18 +440,21 @@ pair ServiceProvider::doExport(SPRequest& request, bool requireSessio #ifdef _DEBUG xmltooling::NDC ndc("doExport"); #endif - Category& log = Category::getInstance(SHIBSP_LOGCAT".ServiceProvider"); + Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider"); - const Application* app=nullptr; + 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 = nullptr; 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) { log.warn("unable to obtain session to export to request: %s", e.what()); @@ -431,40 +468,57 @@ 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,0L); // just bail silently + return make_pair(false, 0L); // just bail silently } + 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()); - // Export the IdP name and Authn method/context info. - 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); + // 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) - app->setHeader(request, "Shib-AuthnContext-Decl", hval); - hval = session->getSessionIndex(); - if (hval) - app->setHeader(request, "Shib-Session-Index", 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"); + const PropertySet* sessions = app->getPropertySet("Sessions"); pair exportLocation = sessions ? sessions->getString("exportLocation") : pair(false,nullptr); if (!exportLocation.first) log.warn("can't export assertions without an exportLocation Sessions property"); else { - const URLEncoder* encoder = XMLToolingConfig::getConfig().getURLEncoder(); string exportName = "Shib-Assertion-00"; string baseURL; if (!strncmp(exportLocation.second, "http", 4)) @@ -487,26 +541,32 @@ pair ServiceProvider::doExport(SPRequest& request, bool requireSessio // Export the attributes. const multimap& attributes = session->getIndexedAttributes(); - for (multimap::const_iterator a = attributes.begin(); a!=attributes.end(); ++a) { + 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(); - 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()); } @@ -520,7 +580,10 @@ pair ServiceProvider::doExport(SPRequest& request, bool requireSessio for (; matches.first != matches.second; ++matches.first) { const vector& vals = matches.first->second->getSerializedValues(); if (!vals.empty()) { - request.setRemoteUser(vals.front().c_str()); + if (enc.first) + request.setRemoteUser(encoder->encode(vals.front().c_str()).c_str()); + else + request.setRemoteUser(vals.front().c_str()); remoteUserSet = true; break; } @@ -531,9 +594,9 @@ pair ServiceProvider::doExport(SPRequest& request, bool requireSessio } catch (exception& e) { request.log(SPRequest::SPError, e.what()); - TemplateParameters tp(&e); + TemplateParameters tp(&e, nullptr, session); tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); - return make_pair(true,sendError(log, request, app, "session", tp)); + return make_pair(true, sendError(log, request, app, "session", tp)); } } @@ -542,9 +605,9 @@ pair ServiceProvider::doHandler(SPRequest& request) const #ifdef _DEBUG xmltooling::NDC ndc("doHandler"); #endif - Category& log = Category::getInstance(SHIBSP_LOGCAT".ServiceProvider"); + Category& log = Category::getInstance(SHIBSP_LOGCAT ".ServiceProvider"); - const Application* app=nullptr; + const Application* app = nullptr; string targetURL = request.getRequestURL(); try { @@ -576,20 +639,20 @@ pair ServiceProvider::doHandler(SPRequest& request) const } } - const char* handlerURL=request.getHandlerURL(targetURL.c_str()); + 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()) @@ -597,11 +660,11 @@ pair ServiceProvider::doHandler(SPRequest& request) const // 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) @@ -611,9 +674,16 @@ pair ServiceProvider::doHandler(SPRequest& request) const } catch (exception& e) { request.log(SPRequest::SPError, e.what()); - TemplateParameters tp(&e); - tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?')); + 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)); + return make_pair(true, sendError(log, request, app, "session", tp)); } }