Merge commit '2.5.0' into moonshot-packaging-fixes
[shibboleth/sp.git] / shibsp / ServiceProvider.cpp
index ab6dc45..4fbf074 100644 (file)
@@ -37,6 +37,7 @@
 
 #include <fstream>
 #include <sstream>
+#include <boost/algorithm/string.hpp>
 #ifndef SHIBSP_LITE
 # include <saml/exceptions.h>
 # include <saml/saml2/metadata/MetadataProvider.h>
@@ -94,6 +95,7 @@ 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());
             }
@@ -147,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");
@@ -185,21 +188,21 @@ SecurityPolicyProvider* ServiceProvider::getSecurityPolicyProvider(bool required
 
 Remoted* ServiceProvider::regListener(const char* address, Remoted* listener)
 {
-    Remoted* ret=nullptr;
-    map<string,Remoted*>::const_iterator i=m_listenerMap.find(address);
-    if (i!=m_listenerMap.end())
-        ret=i->second;
-    m_listenerMap[address]=listener;
+    Remoted* ret = nullptr;
+    map<string,Remoted*>::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<string,Remoted*>::const_iterator i=m_listenerMap.find(address);
-    if (i!=m_listenerMap.end() && i->second==current) {
+    map<string,Remoted*>::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);
@@ -210,8 +213,8 @@ bool ServiceProvider::unregListener(const char* address, Remoted* current, Remot
 
 Remoted* ServiceProvider::lookupListener(const char *address) const
 {
-    map<string,Remoted*>::const_iterator i=m_listenerMap.find(address);
-    return (i==m_listenerMap.end()) ? nullptr : i->second;
+    map<string,Remoted*>::const_iterator i = m_listenerMap.find(address);
+    return (i == m_listenerMap.end()) ? nullptr : i->second;
 }
 
 pair<bool,long> ServiceProvider::doAuthentication(SPRequest& request, bool handler) const
@@ -221,7 +224,7 @@ pair<bool,long> ServiceProvider::doAuthentication(SPRequest& request, bool handl
 #endif
     Category& log = Category::getInstance(SHIBSP_LOGCAT".ServiceProvider");
 
-    const Application* app=nullptr;
+    const Application* app = nullptr;
     string targetURL = request.getRequestURL();
 
     try {
@@ -248,7 +251,7 @@ pair<bool,long> 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));
                 }
             }
         }
@@ -259,29 +262,24 @@ pair<bool,long> 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<bool,const char*> authType = settings.first->getString("authType");
         pair<bool,bool> requireSession = settings.first->getBool("requireSession");
         pair<bool,const char*> requireSessionWith = settings.first->getString("requireSessionWith");
-
-        string lcAuthType;
-        if (authType.first) {
-            while (*authType.second)
-                lcAuthType += tolower(*authType.second++);
-        }
+        pair<bool,const char*> requireLogoutWith = settings.first->getString("requireLogoutWith");
 
         // 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 &&
-                (!authType.first || m_authTypes.find(lcAuthType) == m_authTypes.end()))
+                (!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
@@ -294,14 +292,36 @@ pair<bool,long> ServiceProvider::doAuthentication(SPRequest& request, bool handl
         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<opensaml::RetryableProfileException*>(&e)==nullptr)
+            if (dynamic_cast<opensaml::RetryableProfileException*>(&e) == nullptr)
                 throw;
         }
 
-        if (!session) {
+        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 += '?';
+                    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;
@@ -309,7 +329,7 @@ pair<bool,long> 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)
                         );
                 }
             }
@@ -319,10 +339,15 @@ pair<bool,long> 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<bool,long> 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(lcAuthType.c_str());
+        request.setAuthType(authType.second);
 
         // We're done.  Everything is okay.  Nothing to report.  Nothing to do..
         // Let the caller decide how to proceed.
@@ -333,7 +358,7 @@ pair<bool,long> 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));
     }
 }
 
@@ -344,7 +369,7 @@ pair<bool,long> ServiceProvider::doAuthorization(SPRequest& request) const
 #endif
     Category& log = Category::getInstance(SHIBSP_LOGCAT".ServiceProvider");
 
-    const Application* app=nullptr;
+    const Application* app = nullptr;
     const Session* session = nullptr;
     string targetURL = request.getRequestURL();
 
@@ -357,17 +382,11 @@ pair<bool,long> ServiceProvider::doAuthorization(SPRequest& request) const
         pair<bool,bool> requireSession = settings.first->getBool("requireSession");
         pair<bool,const char*> requireSessionWith = settings.first->getString("requireSessionWith");
 
-        string lcAuthType;
-        if (authType.first) {
-            while (*authType.second)
-                lcAuthType += tolower(*authType.second++);
-        }
-
         // 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 &&
-                (!authType.first || m_authTypes.find(lcAuthType) == m_authTypes.end()))
+                (!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?
@@ -380,33 +399,33 @@ pair<bool,long> ServiceProvider::doAuthorization(SPRequest& request) const
             }
 
             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(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, 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));
     }
 }
 
@@ -417,7 +436,7 @@ pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSessio
 #endif
     Category& log = Category::getInstance(SHIBSP_LOGCAT".ServiceProvider");
 
-    const Application* app=nullptr;
+    const Application* app = nullptr;
     const Session* session = nullptr;
     string targetURL = request.getRequestURL();
 
@@ -440,7 +459,7 @@ pair<bool,long> 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<bool,const char*> enc = settings.first->getString("encoding");
@@ -476,10 +495,17 @@ pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSessio
                 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<string,const char*> cookieprops = app->getCookieNameProps(nullptr);
+            app->setHeader(request, "Shib-Cookie-Name", cookieprops.first.c_str());
+        }
+
         // Maybe export the assertion keys.
-        pair<bool,bool> exp=settings.first->getBool("exportAssertion");
+        pair<bool,bool> exp = settings.first->getBool("exportAssertion");
         if (exp.first && exp.second) {
-            const PropertySet* sessions=app->getPropertySet("Sessions");
+            const PropertySet* sessions = app->getPropertySet("Sessions");
             pair<bool,const char*> exportLocation = sessions ? sessions->getString("exportLocation") : pair<bool,const char*>(false,nullptr);
             if (!exportLocation.first)
                 log.warn("can't export assertions without an exportLocation Sessions property");
@@ -506,12 +532,12 @@ pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSessio
 
         // Export the attributes.
         const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
-        for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a!=attributes.end(); ++a) {
+        for (multimap<string,const Attribute*>::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<string>& vals = a->second->getSerializedValues();
-            for (vector<string>::const_iterator v = vals.begin(); v!=vals.end(); ++v) {
+            for (vector<string>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
                 if (!header.empty())
                     header += ";";
                                if (enc.first) {
@@ -519,10 +545,10 @@ pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSessio
                                        header += encoder->encode(v->c_str());
                                }
                                else {
-                                       string::size_type pos = v->find_first_of(';',string::size_type(0));
-                                       if (pos!=string::npos) {
+                                       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)) {
+                                               for (; pos != string::npos; pos = value.find_first_of(';', pos)) {
                                                        value.insert(pos, "\\");
                                                        pos += 2;
                                                }
@@ -561,7 +587,7 @@ pair<bool,long> ServiceProvider::doExport(SPRequest& request, bool requireSessio
         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(log, request, app, "session", tp));
+        return make_pair(true, sendError(log, request, app, "session", tp));
     }
 }
 
@@ -572,7 +598,7 @@ pair<bool,long> ServiceProvider::doHandler(SPRequest& request) const
 #endif
     Category& log = Category::getInstance(SHIBSP_LOGCAT".ServiceProvider");
 
-    const Application* app=nullptr;
+    const Application* app = nullptr;
     string targetURL = request.getRequestURL();
 
     try {
@@ -604,20 +630,20 @@ pair<bool,long> 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<bool,bool> handlerSSL=sessionProps->getBool("handlerSSL");
+        pair<bool,bool> handlerSSL = sessionProps->getBool("handlerSSL");
 
         // Make sure this is SSL, if it should be
         if ((!handlerSSL.first || handlerSSL.second) && !request.isSecure())
@@ -625,11 +651,11 @@ pair<bool,long> 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<bool,long> hret=handler->run(request);
+        pair<bool,long> hret = handler->run(request);
 
         // Did the handler run successfully?
         if (hret.first)
@@ -643,11 +669,11 @@ pair<bool,long> ServiceProvider::doHandler(SPRequest& request) const
         try {
             session = request.getSession(false, true);
         }
-        catch (exception& e2) {
+        catch (exception&) {
         }
         TemplateParameters tp(&e, nullptr, session);
-        tp.m_map["requestURL"] = targetURL.substr(0,targetURL.find('?'));
+        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));
     }
 }