https://issues.shibboleth.net/jira/browse/SSPCPP-605
[shibboleth/sp.git] / shibsp / handler / impl / AbstractHandler.cpp
index c837429..37db704 100644 (file)
@@ -75,18 +75,20 @@ using namespace boost;
 using namespace std;
 
 namespace shibsp {
-
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML1ConsumerFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2ConsumerFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2ArtifactResolutionFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2LogoutFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2NameIDMgmtFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AssertionLookupFactory;
+    SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AttributeCheckerFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory DiscoveryFeedFactory;
+    SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory ExternalAuthFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory MetadataGeneratorFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory StatusHandlerFactory;
     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SessionHandlerFactory;
 
+
     void SHIBSP_DLLLOCAL generateRandomHex(std::string& buf, unsigned int len) {
         static char DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
         int r;
@@ -102,56 +104,6 @@ namespace shibsp {
             buf += (DIGITS[0x0F & b2]);
         }
     }
-
-    void SHIBSP_DLLLOCAL limitRelayState(
-        Category& log, const Application& application, const HTTPRequest& httpRequest, const char* relayState
-        ) {
-        const PropertySet* sessionProps = application.getPropertySet("Sessions");
-        if (sessionProps) {
-            pair<bool,const char*> relayStateLimit = sessionProps->getString("relayStateLimit");
-            if (relayStateLimit.first && strcmp(relayStateLimit.second, "none")) {
-                vector<string> whitelist;
-                if (!strcmp(relayStateLimit.second, "exact")) {
-                    // Scheme and hostname have to match.
-                    if (!strcmp(httpRequest.getScheme(), "https") && httpRequest.getPort() == 443) {
-                        whitelist.push_back(string("https://") + httpRequest.getHostname() + '/');
-                    }
-                    else if (!strcmp(httpRequest.getScheme(), "http") && httpRequest.getPort() == 80) {
-                        whitelist.push_back(string("http://") + httpRequest.getHostname() + '/');
-                    }
-                    whitelist.push_back(
-                        string(httpRequest.getScheme()) + "://" + httpRequest.getHostname() + ':' + lexical_cast<string>(httpRequest.getPort()) + '/'
-                        );
-                }
-                else if (!strcmp(relayStateLimit.second, "host")) {
-                    // Allow any scheme or port.
-                    whitelist.push_back(string("https://") + httpRequest.getHostname() + '/');
-                    whitelist.push_back(string("http://") + httpRequest.getHostname() + '/');
-                    whitelist.push_back(string("https://") + httpRequest.getHostname() + ':');
-                    whitelist.push_back(string("http://") + httpRequest.getHostname() + ':');
-                }
-                else if (!strcmp(relayStateLimit.second, "whitelist")) {
-                    // Literal set of comparisons to use.
-                    pair<bool,const char*> whitelistval = sessionProps->getString("relayStateWhitelist");
-                    if (whitelistval.first) {
-                        string dup(whitelistval.second);
-                        split(whitelist, dup, is_space(), algorithm::token_compress_on);
-                    }
-                }
-                else {
-                    log.warn("unrecognized relayStateLimit policy (%s), blocked redirect to (%s)", relayStateLimit.second, relayState);
-                    throw opensaml::SecurityPolicyException("Unrecognized relayStateLimit setting.");
-                }
-
-                static bool (*startsWithI)(const char*,const char*) = XMLString::startsWithI;
-                if (find_if(whitelist.begin(), whitelist.end(),
-                        boost::bind(startsWithI, relayState, boost::bind(&string::c_str, _1))) == whitelist.end()) {
-                    log.warn("relayStateLimit policy (%s), blocked redirect to (%s)", relayStateLimit.second, relayState);
-                    throw opensaml::SecurityPolicyException("Blocked unacceptable redirect location.");
-                }
-            }
-        }
-    }
 };
 
 void SHIBSP_API shibsp::registerHandlers()
@@ -171,7 +123,9 @@ void SHIBSP_API shibsp::registerHandlers()
     conf.ArtifactResolutionServiceManager.registerFactory(SAML20_BINDING_SOAP, SAML2ArtifactResolutionFactory);
 
     conf.HandlerManager.registerFactory(SAML20_BINDING_URI, AssertionLookupFactory);
+    conf.HandlerManager.registerFactory(ATTR_CHECKER_HANDLER, AttributeCheckerFactory);
     conf.HandlerManager.registerFactory(DISCOVERY_FEED_HANDLER, DiscoveryFeedFactory);
+    conf.HandlerManager.registerFactory(EXTERNAL_AUTH_HANDLER, ExternalAuthFactory);
     conf.HandlerManager.registerFactory(METADATA_GENERATOR_HANDLER, MetadataGeneratorFactory);
     conf.HandlerManager.registerFactory(STATUS_HANDLER, StatusHandlerFactory);
     conf.HandlerManager.registerFactory(SESSION_HANDLER, SessionHandlerFactory);
@@ -223,6 +177,54 @@ void Handler::log(SPRequest::SPLogLevel level, const string& msg) const
         );
 }
 
+void Handler::cleanRelayState(
+    const Application& application, const xmltooling::HTTPRequest& request, xmltooling::HTTPResponse& response
+    ) const
+{
+    // Only cookie-based relay state requires cleaning.
+    pair<bool,const char*> mech = getString("relayState");
+    if (!mech.first) {
+        // Check for setting on Sessions element.
+        const PropertySet* sessionprop = application.getPropertySet("Sessions");
+        if (sessionprop)
+            mech = sessionprop->getString("relayState");
+    }
+    if (!mech.first || !mech.second || strncmp(mech.second, "cookie", 6))
+        return;
+    
+    int maxCookies = 25,purgedCookies = 0;
+    mech.second += 6;
+    if (*mech.second == ':' && isdigit(*(++mech.second))) {
+        maxCookies = atoi(mech.second);
+        if (maxCookies == 0)
+            maxCookies = 25;
+    }
+
+    string exp;
+
+    // Walk the list of cookies backwards by name.
+    const map<string,string>& cookies = request.getCookies();
+    for (map<string,string>::const_reverse_iterator i = cookies.rbegin(); i != cookies.rend(); ++i) {
+        // Process relay state cookies only.
+        if (starts_with(i->first, "_shibstate_")) {
+            if (maxCookies > 0) {
+                // Keep it, but count it against the limit.
+                --maxCookies;
+            }
+            else {
+                // We're over the limit, so everything here and older gets cleaned up.
+                if (exp.empty())
+                    exp = string(application.getCookieNameProps("_shibstate_").second) + "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
+                response.setCookie(i->first.c_str(), exp.c_str());
+                ++purgedCookies;
+            }
+        }
+    }
+
+    if (purgedCookies > 0)
+        log(SPRequest::SPDebug, string("purged ") + lexical_cast<string>(purgedCookies) + " stale relay state cookie(s) from client");
+}
+
 void Handler::preserveRelayState(const Application& application, HTTPResponse& response, string& relayState) const
 {
     // The empty string implies no state to deal with.
@@ -240,23 +242,23 @@ void Handler::preserveRelayState(const Application& application, HTTPResponse& r
     if (!mech.first || !mech.second || !*mech.second)
         return;
 
-    if (!strcmp(mech.second, "cookie")) {
+    if (!strncmp(mech.second, "cookie", 6)) {
         // Here we store the state in a cookie and send a fixed
         // value so we can recognize it on the way back.
-        if (relayState.find("cookie:") != 0) {
-            const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
-            pair<string,const char*> shib_cookie=application.getCookieNameProps("_shibstate_");
-            string stateval = urlenc->encode(relayState.c_str()) + shib_cookie.second;
+        if (relayState.find("cookie:") != 0 && relayState.find("ss:") != 0) {
+            pair<string,const char*> shib_cookie = application.getCookieNameProps("_shibstate_");
+            string stateval = XMLToolingConfig::getConfig().getURLEncoder()->encode(relayState.c_str()) + shib_cookie.second;
             // Generate a random key for the cookie name instead of the fixed name.
             string rsKey;
-            generateRandomHex(rsKey, 5);
+            generateRandomHex(rsKey, 4);
+            rsKey = lexical_cast<string>(time(nullptr)) + '_' + rsKey;
             shib_cookie.first = "_shibstate_" + rsKey;
             response.setCookie(shib_cookie.first.c_str(), stateval.c_str());
             relayState = "cookie:" + rsKey;
         }
     }
-    else if (strstr(mech.second,"ss:")==mech.second) {
-        if (relayState.find("ss:") != 0) {
+    else if (!strncmp(mech.second, "ss:", 3)) {
+        if (relayState.find("cookie:") != 0 && relayState.find("ss:") != 0) {
             mech.second+=3;
             if (*mech.second) {
                 if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
@@ -300,8 +302,9 @@ void Handler::preserveRelayState(const Application& application, HTTPResponse& r
             }
         }
     }
-    else
+    else {
         throw ConfigurationException("Unsupported relayState mechanism ($1).", params(1,mech.second));
+    }
 }
 
 void Handler::recoverRelayState(
@@ -312,7 +315,7 @@ void Handler::recoverRelayState(
 
     // Look for StorageService-backed state of the form "ss:SSID:key".
     const char* state = relayState.c_str();
-    if (strstr(state,"ss:")==state) {
+    if (strstr(state,"ss:") == state) {
         state += 3;
         const char* key = strchr(state,':');
         if (key) {
@@ -369,8 +372,9 @@ void Handler::recoverRelayState(
         }
     }
 
-    // Look for cookie-backed state of the form "cookie:key".
-    if (strstr(state,"cookie:")==state) {
+    // Look for cookie-backed state of the form "cookie:timestamp_key".
+    state = relayState.c_str();
+    if (strstr(state,"cookie:") == state) {
         state += 7;
         if (*state) {
             // Pull the value from the "relay state" cookie.
@@ -379,11 +383,10 @@ void Handler::recoverRelayState(
             state = request.getCookie(relay_cookie.first.c_str());
             if (state && *state) {
                 // URL-decode the value.
-                char* rscopy=strdup(state);
+                char* rscopy = strdup(state);
                 XMLToolingConfig::getConfig().getURLEncoder()->decode(rscopy);
                 relayState = rscopy;
                 free(rscopy);
-
                 if (clear) {
                     string exp(relay_cookie.second);
                     exp += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
@@ -446,7 +449,7 @@ void AbstractHandler::checkError(const XMLObject* response, const saml2md::RoleD
             const saml2p::StatusCode* sc = status->getStatusCode();
             const XMLCh* code = sc ? sc->getValue() : nullptr;
             if (code && !XMLString::equals(code,saml2p::StatusCode::SUCCESS)) {
-                FatalProfileException ex("SAML response contained an error.");
+                FatalProfileException ex("SAML response reported an IdP error.");
                 annotateException(&ex, role, status);   // throws it
             }
         }
@@ -459,7 +462,7 @@ void AbstractHandler::checkError(const XMLObject* response, const saml2md::RoleD
             const saml1p::StatusCode* sc = status->getStatusCode();
             const xmltooling::QName* code = sc ? sc->getValue() : nullptr;
             if (code && *code != saml1p::StatusCode::SUCCESS) {
-                FatalProfileException ex("SAML response contained an error.");
+                FatalProfileException ex("SAML response reported an IdP error.");
                 annotateException(&ex, role, status);   // throws it
             }
         }
@@ -501,11 +504,10 @@ long AbstractHandler::sendMessage(
     const EntityDescriptor* entity = role ? dynamic_cast<const EntityDescriptor*>(role->getParent()) : nullptr;
     const PropertySet* relyingParty = application.getRelyingParty(entity);
     pair<bool,const char*> flag = signIfPossible ? make_pair(true,(const char*)"true") : relyingParty->getString("signing");
-    if (role && flag.first &&
-        (!strcmp(flag.second, "true") ||
-            (encoder.isUserAgentPresent() && !strcmp(flag.second, "front")) ||
-            (!encoder.isUserAgentPresent() && !strcmp(flag.second, "back")))) {
-        CredentialResolver* credResolver=application.getCredentialResolver();
+    if (flag.first && (!strcmp(flag.second, "true") ||
+                        (encoder.isUserAgentPresent() && !strcmp(flag.second, "front")) ||
+                        (!encoder.isUserAgentPresent() && !strcmp(flag.second, "back")))) {
+        CredentialResolver* credResolver = application.getCredentialResolver();
         if (credResolver) {
             Locker credLocker(credResolver);
             const Credential* cred = nullptr;