Rework POST handling to avoid remoting data if handler doesn't run.
authorScott Cantor <cantor.2@osu.edu>
Tue, 10 Mar 2009 01:48:22 +0000 (01:48 +0000)
committerScott Cantor <cantor.2@osu.edu>
Tue, 10 Mar 2009 01:48:22 +0000 (01:48 +0000)
Alter POST preserving APIs, and use built-in CGI parser and URL decoder.

12 files changed:
adfs/adfs.cpp
configs/Makefile.am
shibsp/handler/AbstractHandler.h
shibsp/handler/impl/AbstractHandler.cpp
shibsp/handler/impl/AssertionConsumerService.cpp
shibsp/handler/impl/SAML2SessionInitiator.cpp
shibsp/handler/impl/SAMLDSSessionInitiator.cpp
shibsp/handler/impl/Shib1SessionInitiator.cpp
shibsp/handler/impl/WAYFSessionInitiator.cpp
shibsp/impl/XMLServiceProvider.cpp
shibsp/util/CGIParser.cpp
shibsp/util/CGIParser.h

index f6fae28..4795b4c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2005 Internet2
+ *  Copyright 2001-2009 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -135,11 +135,13 @@ namespace {
         }
 
         void receive(DDF& in, ostream& out);
+        pair<bool,long> unwrap(SPRequest& request, DDF& out) const;
         pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
 
     private:
         pair<bool,long> doRequest(
             const Application& application,
+            const HTTPRequest* httpRequest,
             HTTPResponse& httpResponse,
             const char* entityID,
             const char* acsLocation,
@@ -393,8 +395,12 @@ pair<bool,long> ADFSSessionInitiator::run(SPRequest& request, string& entityID,
 
     m_log.debug("attempting to initiate session using ADFS with provider (%s)", entityID.c_str());
 
-    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
-        return doRequest(app, request, entityID.c_str(), ACSloc.c_str(), (acClass.first ? acClass.second : NULL), target);
+    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+        // Out of process means the POST data via the request can be exposed directly to the private method.
+        // The method will handle POST preservation if necessary *before* issuing the response, but only if
+        // it dispatches to an IdP.
+        return doRequest(app, &request, request, entityID.c_str(), ACSloc.c_str(), (acClass.first ? acClass.second : NULL), target);
+    }
 
     // Remote the call.
     DDF out,in = DDF(m_address.c_str()).structure();
@@ -412,6 +418,16 @@ pair<bool,long> ADFSSessionInitiator::run(SPRequest& request, string& entityID,
     return unwrap(request, out);
 }
 
+pair<bool,long> ADFSSessionInitiator::unwrap(SPRequest& request, DDF& out) const
+{
+    // See if there's any response to send back.
+    if (!out["redirect"].isnull() || !out["response"].isnull()) {
+        // If so, we're responsible for handling the POST data, probably by dropping a cookie.
+        preservePostData(request.getApplication(), request, request, out["RelayState"].string());
+    }
+    return RemotedHandler::unwrap(request, out);
+}
+
 void ADFSSessionInitiator::receive(DDF& in, ostream& out)
 {
     // Find application.
@@ -439,12 +455,16 @@ void ADFSSessionInitiator::receive(DDF& in, ostream& out)
     // Since we're remoted, the result should either be a throw, which we pass on,
     // a false/0 return, which we just return as an empty structure, or a response/redirect,
     // which we capture in the facade and send back.
-    doRequest(*app, *http.get(), entityID, acsLocation, in["authnContextClassRef"].string(), relayState);
+    doRequest(*app, NULL, *http.get(), entityID, acsLocation, in["authnContextClassRef"].string(), relayState);
+    if (!ret.isstruct())
+        ret.structure();
+    ret.addmember("RelayState").string(relayState.c_str());
     out << ret;
 }
 
 pair<bool,long> ADFSSessionInitiator::doRequest(
     const Application& app,
+    const HTTPRequest* httpRequest,
     HTTPResponse& httpResponse,
     const char* entityID,
     const char* acsLocation,
@@ -501,6 +521,11 @@ pair<bool,long> ADFSSessionInitiator::doRequest(
     if (!relayState.empty())
         req += "&wctx=" + urlenc->encode(relayState.c_str());
 
+    if (httpRequest) {
+        // If the request object is available, we're responsible for the POST data.
+        preservePostData(app, *httpRequest, httpResponse, relayState.c_str());
+    }
+
     return make_pair(true, httpResponse.sendRedirect(req.c_str()));
 #else
     return make_pair(false,0L);
index 5efa446..39a75f2 100644 (file)
@@ -42,6 +42,7 @@ CONFIGFILES = \
        metadataError.html \
        bindingTemplate.html \
        discoveryTemplate.html \
+    postTemplate.html \
        localLogout.html \
        globalLogout.html \
        sslError.html
index aee17dd..20a480b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2007 Internet2
+ *  Copyright 2001-2009 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
 #define __shibsp_abshandler_h__
 
 #include <shibsp/handler/Handler.h>
+#include <shibsp/remoting/ddf.h>
 #include <shibsp/util/DOMPropertySet.h>
 
 #ifndef SHIBSP_LITE
@@ -156,64 +157,54 @@ namespace shibsp {
             ) const;
         
         /**
-         * Implements storage service and cookie mechanism to preserve PostData,
-         *
-         * <p>If a supported mechanism can be identified, the input parameter will be
-         * replaced with a suitable state key.
+         * Implements a mechanism to preserve form post data.
          *
          * @param application   the associated Application
+         * @param request       incoming HTTP request
          * @param response      outgoing HTTP response
-         * @param postData      Posted data to preserve
-         * @param relayState    relayState ( with key value )
+         * @param relayState    relay state information attached to current sequence, if any
          */
         virtual void preservePostData(
-            const Application& application, xmltooling::HTTPResponse& response, std::string& postData, std::string& relayState
+            const Application& application,
+            const xmltooling::HTTPRequest& request,
+            xmltooling::HTTPResponse& response,
+            const char* relayState
             ) const;
 
         /**
-         * Implements storage service and cookie mechanism to recover PostData,
+         * Implements storage service and cookie mechanism to recover PostData.
          *
-         * <p>If a supported mechanism can be identified, the input parameter will be
-         * replaced with the recovered state information.
+         * <p>If a supported mechanism can be identified, the return value will be
+         * the recovered state information.
          *
          * @param application   the associated Application
          * @param request       incoming HTTP request
          * @param response      outgoing HTTP response
-         * @param postData      recovered posted data token supplied with message
-         * @param relayState    relayState ( with key value )
+         * @param relayState    relay state information attached to current sequence, if any
+         * @return  recovered form post data associated with request as a DDF list of string members
          */
-        virtual void recoverPostData(
+        virtual DDF recoverPostData(
             const Application& application,
             const xmltooling::HTTPRequest& request,
             xmltooling::HTTPResponse& response,
-            std::string& postData,
-            std::string& relayState
+            const char* relayState
             ) const;
 
         /**
-         * URL decodes a string inplace
-         * 
-         * @param in            the encoded string
-         */
-        virtual void urlDecode(std::string &in) const;
-
-        /**
-         * Get the data posted with a request
-         * 
-         * @param request       incoming HTTP request
-         */
-        virtual std::string getPostData(SPRequest& request) const;
-
-        /**
-         * Post a redirect response with post data
+         * Post a redirect response with post data.
          * 
          * @param application   the associated Application
          * @param response      outgoing HTTP response
          * @param request       incoming HTTP request
          * @param url           action url for the form
-         * @param postData      posted data to load into the form
+         * @param postData      list of parameters to load into the form, as DDF string members
          */
-        virtual long sendPostResponse(const Application& application, xmltooling::HTTPResponse& httpResponse, std::string& url, std::string& postData) const;
+        virtual long sendPostResponse(
+            const Application& application,
+            xmltooling::HTTPResponse& httpResponse,
+            const char* url,
+            DDF& postData
+            ) const;
 
         /** Logging object. */
         xmltooling::logging::Category& m_log;
@@ -223,6 +214,10 @@ namespace shibsp {
 
     public:
         virtual ~AbstractHandler() {}
+
+    private:
+        std::pair<std::string,const char*> getPostCookieNameProps(const Application& app, const char* relayState) const;
+        DDF getPostData(const Application& application, const xmltooling::HTTPRequest& request) const;
     };
 
 #if defined (_MSC_VER)
index 5763b21..c3d3f67 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2007 Internet2
+ *  Copyright 2001-2009 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
 #include "handler/AbstractHandler.h"
 #include "handler/LogoutHandler.h"
 #include "remoting/ListenerService.h"
+#include "util/CGIParser.h"
 #include "util/SPConstants.h"
 #include "util/TemplateParameters.h"
 
@@ -358,9 +359,7 @@ void AbstractHandler::recoverRelayState(
                             relayState.erase();
                     }
                     else {
-                        Category::getInstance(SHIBSP_LOGCAT".Handler").error(
-                            "Storage-backed RelayState with invalid StorageService ID (%s)", ssid.c_str()
-                            );
+                        m_log.error("Storage-backed RelayState with invalid StorageService ID (%s)", ssid.c_str());
                         relayState.erase();
                     }
 #endif
@@ -419,245 +418,222 @@ void AbstractHandler::recoverRelayState(
     }
 }
 
-// postdata tools
-
-inline int hex2int(const char& hex)
-{
-   return ((hex>='0'&&hex<='9')?(hex-'0'):(std::toupper(hex)-'A'+10));
-}
-
-// remove the '+' and '%xx' from a string
-void AbstractHandler::urlDecode(string &in) const
-{
-    const char *cin(in.c_str());
-    string out;
-
-    for (; *cin; cin++) {
-       if(*cin!='%' || !std::isxdigit(cin[1]) || !std::isxdigit(cin[2])) {
-          // same char or space
-          if(*cin=='+') out += ' ';
-          else out += *cin;
-       } else {
-          // convert hex char
-          out += static_cast<char>(hex2int(cin[1])*16+hex2int(cin[2]));
-          cin += 2;
-       }
-    }
-    in = out;
-}
-
-// get posted data from a request (limit should be parameter?)
-#define MAX_POST_DATA (1024*1024)
-string AbstractHandler::getPostData(SPRequest& request) const
-{
-    string contentType = request.getContentType();
-    string postData;
-    if (contentType.compare("application/x-www-form-urlencoded")==0) {
-       if (request.getContentLength()<MAX_POST_DATA) {
-          postData = request.getRequestBody();
-          m_log.debug("processed %d bytes of posted data", postData.length());
-       } else {
-          m_log.debug("ignoring %d bytes of posted data", request.getContentLength());
-       }
-    } else {
-       m_log.warn("ignoring '%s' posted data.", contentType.c_str());
-    }
-    urlDecode(postData);
-    return (postData);
-}
-
-
-// postdata name is base + relaystate key
-inline string postCookieName(string& relayState)
-{
-    string name = "_shibpost_";
-    string::size_type rp = string::npos;
-    if (relayState.find("cookie:")==0) rp = 6;
-    else if (relayState.find("ss:")==0) rp = relayState.find(':', 3);
-    if (rp!=string::npos) name += relayState.substr(rp+1);
-    return (name);
-}
-
 /* Save posted data to a storage service.
    We may not have to key the postdata cookie to the relay state,
    but doing so gives a better assurance that the recovered data really belongs to the relayed request. */
-
-void AbstractHandler::preservePostData(const Application& application, HTTPResponse& response,  string& postData, string& relayState) const
+void AbstractHandler::preservePostData(
+    const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState
+    ) const
 {
-    if (postData.empty()) return;
+    if (strcmp(request.getMethod(), "POST"))
+        return;
 
-    // No specs mean no save
+    // No specs mean no save.
     const PropertySet* props=application.getPropertySet("Sessions");
     pair<bool,const char*> mech = props->getString("postData");
-    pair<bool,const char*> mecht = props->getString("postTemplate");
-    if (!mech.first || !mech.second || !*mech.second || !mecht.first || !mecht.second || !*mecht.second) {
-        m_log.warn("post data save requires postData and postTemplate specification.");
+    if (!mech.first) {
+        m_log.info("postData property not supplied, form data will not be preserved across SSO");
         return;
     }
 
-    if (strstr(mech.second,"ss:")==mech.second) {
-            mech.second+=3;
-            if (*mech.second) {
-                if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
-#ifndef SHIBSP_LITE
-                    StorageService* storage = application.getServiceProvider().getStorageService(mech.second);
-                    if (storage) {
+    DDF postData = getPostData(application, request);
+    if (postData.isnull())
+        return;
 
-                        // Use a random key
-                        string rsKey;
-                        SAMLConfig::getConfig().generateRandomBytes(rsKey,20);
-                        rsKey = SAMLArtifact::toHex(rsKey);
+    if (strstr(mech.second,"ss:") == mech.second) {
+        mech.second+=3;
+        if (!*mech.second) {
+            postData.destroy();
+            throw ConfigurationException("Unsupported postData mechanism ($1).", params(1, mech.second - 3));
+        }
 
-                        if (!storage->createString("PostData", rsKey.c_str(), postData.c_str(), time(NULL) + 600))
-                            throw IOException("Attempted to insert duplicate storage key.");
-                        postData = string(mech.second-3) + ':' + rsKey;
-                    }
-                    else {
-                        m_log.error("Storage-backed PostData with invalid StorageService ID (%s)", mech.second);
-                        postData.erase();
-                    }
-#endif
-                }
-                else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
-                    DDF out,in = DDF("set::PostData").structure();
-                    in.addmember("id").string(mech.second);
-                    in.addmember("value").string(postData.c_str());
-                    DDFJanitor jin(in),jout(out);
-                    out = application.getServiceProvider().getListenerService()->send(in);
-                    if (!out.isstring())
-                        throw IOException("StorageService-backed PostData mechanism did not return a state key.");
-                    postData = string(mech.second-3) + ':' + out.string();
-                }
+        string postkey;
+        if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+            DDFJanitor postjan(postData);
+#ifndef SHIBSP_LITE
+            StorageService* storage = application.getServiceProvider().getStorageService(mech.second);
+            if (storage) {
+                // Use a random key
+                string rsKey;
+                SAMLConfig::getConfig().generateRandomBytes(rsKey,20);
+                rsKey = SAMLArtifact::toHex(rsKey);
+                ostringstream out;
+                out << postData;
+                if (!storage->createString("PostData", rsKey.c_str(), out.str().c_str(), time(NULL) + 600))
+                    throw IOException("Attempted to insert duplicate storage key.");
+                postkey = string(mech.second-3) + ':' + rsKey;
             }
+            else {
+                m_log.error("storage-backed PostData mechanism with invalid StorageService ID (%s)", mech.second);
+            }
+#endif
+        }
+        else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
+            DDF out,in = DDF("set::PostData").structure();
+            DDFJanitor jin(in),jout(out);
+            in.addmember("id").string(mech.second);
+            in.add(postData);
+            out = application.getServiceProvider().getListenerService()->send(in);
+            if (!out.isstring())
+                throw IOException("StorageService-backed PostData mechanism did not return a state key.");
+            postkey = string(mech.second-3) + ':' + out.string();
+        }
 
-            // set cookie with ss key info
-            string ckname = postCookieName(relayState);
-            string ckval = postData + ";path=/; secure";
-            response.setCookie(ckname.c_str(),ckval.c_str());
-            m_log.debug("post cookie, name=%s, value=%s", ckname.c_str(), ckval.c_str());
-
+        // Set a cookie with key info.
+        pair<string,const char*> shib_cookie = getPostCookieNameProps(application, relayState);
+        postkey += shib_cookie.second;
+        response.setCookie(shib_cookie.first.c_str(), postkey.c_str());
     }
-    else
+    else {
+        postData.destroy();
         throw ConfigurationException("Unsupported postData mechanism ($1).", params(1,mech.second));
+    }
 }
 
-void AbstractHandler::recoverPostData(
-    const Application& application, const HTTPRequest& request, HTTPResponse& response, string& postData, string& relayState
+DDF AbstractHandler::recoverPostData(
+    const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState
     ) const
 {
-    SPConfig& conf = SPConfig::getConfig();
+    // First we need the post recovery cookie.
+    pair<string,const char*> shib_cookie = getPostCookieNameProps(application, relayState);
+    const char* cookie = request.getCookie(shib_cookie.first.c_str());
+    if (!cookie || !*cookie)
+        return DDF();
 
-    /** get ss key from our cookie **/
-
-    string ckname = postCookieName(relayState);
-    const char *ck = request.getCookie(ckname.c_str());
-    if (ck && *ck) {
-       postData = ck;
-       m_log.debug("found post cookie, name=%s, value=%s", ckname.c_str(), postData.c_str());
-       if (true ) {
-          response.setCookie(ckname.c_str(), ";path=/; expires=Mon, 01 Jan 2001 00:00:00 GMT");
-       }
-    } else {
-       m_log.debug("data cookie (%s) not found", ckname.c_str());
-       return;
-    }
+    // Clear the cookie.
+    string exp(shib_cookie.second);
+    exp += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
+    response.setCookie(shib_cookie.first.c_str(), exp.c_str());
 
     // Look for StorageService-backed state of the form "ss:SSID:key".
-    const char* state = postData.c_str();
-    if (strstr(state,"ss:")==state) {
+    const char* state = cookie;
+    if (strstr(state, "ss:") == state) {
         state += 3;
-        const char* key = strchr(state,':');
+        const char* key = strchr(state, ':');
         if (key) {
-            string ssid = postData.substr(3, key - state);
+            string ssid = string(cookie).substr(3, key - state);
             key++;
-
             if (!ssid.empty() && *key) {
+                SPConfig& conf = SPConfig::getConfig();
                 if (conf.isEnabled(SPConfig::OutOfProcess)) {
 #ifndef SHIBSP_LITE
                     StorageService* storage = conf.getServiceProvider()->getStorageService(ssid.c_str());
                     if (storage) {
-                        ssid = key;
-                        if (storage->readString("PostData",ssid.c_str(),&postData)>0) {
-                            if (true )
-                                storage->deleteString("PostData",ssid.c_str());
-                            return;
+                        if (storage->readString("PostData", key, &ssid) > 0) {
+                            storage->deleteString("PostData", key);
+                            istringstream inret(ssid);
+                            DDF ret;
+                            inret >> ret;
+                            return ret;
+                        }
+                        else {
+                            m_log.error("failed to recover form post data using key (%s)", key);
                         }
-                        else
-                            postData.erase();
                     }
                     else {
-                        Category::getInstance(SHIBSP_LOGCAT".Handler").error(
-                            "Storage-backed PostData with invalid StorageService ID (%s)", ssid.c_str()
-                            );
-                        postData.erase();
+                        m_log.error("storage-backed PostData with invalid StorageService ID (%s)", ssid.c_str());
                     }
 #endif
                 }
                 else if (conf.isEnabled(SPConfig::InProcess)) {
-                    DDF out,in = DDF("get::PostData").structure();
+                    DDF in = DDF("get::PostData").structure();
+                    DDFJanitor jin(in);
                     in.addmember("id").string(ssid.c_str());
                     in.addmember("key").string(key);
-                    DDFJanitor jin(in),jout(out);
-                    out = application.getServiceProvider().getListenerService()->send(in);
-                    if (!out.isstring()) {
-                        m_log.error("StorageService-backed PostData mechanism did not return a state value.");
-                        postData.erase();
-                    }
-                    else {
-                        postData = out.string();
-                        return;
-                    }
+                    DDF out = application.getServiceProvider().getListenerService()->send(in);
+                    if (out.islist())
+                        return out;
+                    out.destroy();
+                    m_log.error("storageService-backed PostData mechanism did not return preserved data.");
                 }
             }
         }
     }
-
-    if (postData == "default")
-        postData.empty();
+    return DDF();
 }
 
-/* Send recovered data via template to browser for re-post */
-
-long AbstractHandler::sendPostResponse(const Application& application, HTTPResponse& httpResponse, string& url, string& postData) const
+long AbstractHandler::sendPostResponse(
+    const Application& application, HTTPResponse& httpResponse, const char* url, DDF& postData
+    ) const
 {
-    httpResponse.setContentType("text/html");
-    httpResponse.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
-    httpResponse.setResponseHeader("Cache-Control","private,no-store,no-cache");
-
     const PropertySet* props=application.getPropertySet("Sessions");
-    const char* postTemplate =  props->getString("postTemplate").second;
-    m_log.debug("send post, template=%s", postTemplate);
+    pair<bool,const char*> postTemplate = props->getString("postTemplate");
+    if (!postTemplate.first)
+        throw ConfigurationException("Missing postTemplate property, unable to recreate form post.");
 
-    string fname(postTemplate);
+    string fname(postTemplate.second);
     ifstream infile(XMLToolingConfig::getConfig().getPathResolver()->resolve(fname, PathResolver::XMLTOOLING_CFG_FILE).c_str());
     if (!infile)
-          throw ConfigurationException("Unable to access HTML template ($1).", params(1, postTemplate));
+        throw ConfigurationException("Unable to access HTML template ($1).", params(1, fname.c_str()));
     TemplateParameters respParam;
-    respParam.m_map["action"] = url.c_str();
-
-    // parse the posted data into form elements
-    vector<xmltooling::TemplateEngine::TemplateParameters> dataParams;
-    string::size_type startPos = 0;
-    for (int n=0;;n++) {
-       string::size_type ampPos = postData.find("&", startPos);
-       if (ampPos==string::npos) ampPos = postData.length();
-
-       string::size_type eqPos = postData.find("=", startPos);
-       if (eqPos>=ampPos) eqPos = ampPos - 1;
-
-       // add these name/values to the vector
-       dataParams.resize(n+1);
-       TemplateParameters *xp = static_cast<TemplateParameters*>(&dataParams.at(n));
-       xp->m_map["_name_"] = postData.substr(startPos,eqPos-startPos).c_str();
-       xp->m_map["_value_"] = postData.substr(eqPos+1,ampPos-eqPos-1).c_str();
-       if (ampPos==postData.length()) break;
-       startPos = ampPos+1;
-    } 
+    respParam.m_map["action"] = url;
+
+    // Load the parameters into objects for the template.
+    // TODO: rework the TemplateEngine to require only a single TemplateParameters object
+    vector<TemplateEngine::TemplateParameters> dataParams;
+
+    DDF param = postData.first();
+    while (param.isstring()) {
+        dataParams.push_back(TemplateEngine::TemplateParameters());
+        TemplateEngine::TemplateParameters& xp = dataParams.back();
+        xp.m_map["_name_"] = param.name();
+        xp.m_map["_value_"] = param.string();
+        param = postData.next();
+    }
     respParam.m_collectionMap["PostedData"] = dataParams;
 
     stringstream str;
     XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, respParam);
-    return (httpResponse.sendResponse(str));
+
+    httpResponse.setContentType("text/html");
+    httpResponse.setResponseHeader("Expires", "01-Jan-1997 12:00:00 GMT");
+    httpResponse.setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate, private");\r
+    httpResponse.setResponseHeader("Pragma", "no-cache");\r
+    return httpResponse.sendResponse(str);
 }
 
+// Decorates the name of the cookie with the relay state key, if any.
+pair<string,const char*> AbstractHandler::getPostCookieNameProps(const Application& app, const char* relayState) const
+{
+    pair<string,const char*> shib_cookie=app.getCookieNameProps("_shibpost_");
+    if (strstr(relayState, "cookie:") == relayState) {
+        shib_cookie.first += (relayState + 7);
+    }
+    else if (strstr(relayState, "ss:") == relayState) {
+        const char* pch = strchr(relayState + 3, ':');
+        if (pch)
+            shib_cookie.first += (pch + 1);
+    }
+    return shib_cookie;
+}
 
+DDF AbstractHandler::getPostData(const Application& application, const HTTPRequest& request) const
+{
+    string contentType = request.getContentType();
+    if (contentType.compare("application/x-www-form-urlencoded") == 0) {
+        const PropertySet* props=application.getPropertySet("Sessions");
+        pair<bool,unsigned int> plimit = props->getUnsignedInt("postLimit");
+        if (!plimit.first)
+            plimit.second = 1024 * 1024;
+        if (plimit.second == 0 || request.getContentLength() <= plimit.second) {
+            CGIParser cgi(request);
+            pair<CGIParser::walker,CGIParser::walker> params = cgi.getParameters(NULL);
+            if (params.first == params.second)
+                return DDF();
+            DDF child;
+            DDF ret = DDF("parameters").list();
+            for (; params.first != params.second; ++params.first) {
+                child = DDF(params.first->first.c_str()).string(params.first->second);
+                ret.add(child);
+            }
+            return ret;
+        }
+        else {
+            m_log.warn("POST limit exceeded, ignoring %d bytes of posted data", request.getContentLength());
+        }
+    }
+    else {
+        m_log.info("ignoring POST data with non-standard encoding (%s)", contentType.c_str());
+    }
+    return NULL;
+}
index 8a4dae1..99a21da 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2007 Internet2
+ *  Copyright 2001-2009 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -147,18 +147,16 @@ pair<bool,long> AssertionConsumerService::processMessage(
     Locker metadataLocker(application.getMetadataProvider());
 
     // Create the policy.
-    shibsp::SecurityPolicy policy(application, &m_role, validate.first && validate.second);
+    shibsp::SecurityPolicy policy(application, &m_role, validate.first && validate.second, policyId.second);
 
     string relayState;
-    const char* m_template =  getString("postTemplate").second;
-
     try {
         // Decode the message and process it in a protocol-specific way.
         auto_ptr<XMLObject> msg(m_decoder->decode(relayState, httpRequest, policy));
         if (!msg.get())
             throw BindingException("Failed to decode an SSO protocol response.");
-        string postData;
-        recoverPostData(application, httpRequest, httpResponse, postData, relayState);
+        DDF postData = recoverPostData(application, httpRequest, httpResponse, relayState.c_str());
+        DDFJanitor postjan(postData);
         recoverRelayState(application, httpRequest, httpResponse, relayState);
         implementProtocol(application, httpRequest, httpResponse, policy, settings, *msg.get());
 
@@ -169,12 +167,14 @@ pair<bool,long> AssertionConsumerService::processMessage(
             maintainHistory(application, httpRequest, httpResponse, issuer.get());
 
         // Now redirect to the state value. By now, it should be set to *something* usable.
-        if (postData.empty()) {
-          m_log.debug("ACS returning via redirect to: %s", relayState.c_str());
-          return make_pair(true, httpResponse.sendRedirect(relayState.c_str()));
-        } else {
-          m_log.debug("ACS returning via post to: %s", relayState.c_str());
-          return make_pair(true,sendPostResponse(application, httpResponse, relayState, postData));
+        // First check for POST data.
+        if (!postData.islist()) {
+            m_log.debug("ACS returning via redirect to: %s", relayState.c_str());
+            return make_pair(true, httpResponse.sendRedirect(relayState.c_str()));
+        }
+        else {
+            m_log.debug("ACS returning via POST to: %s", relayState.c_str());
+            return make_pair(true, sendPostResponse(application, httpResponse, relayState.c_str(), postData));
         }
     }
     catch (XMLToolingException& ex) {
index d058604..3bfcf36 100644 (file)
@@ -73,11 +73,13 @@ namespace shibsp {
 
         void setParent(const PropertySet* parent);
         void receive(DDF& in, ostream& out);
+        pair<bool,long> unwrap(SPRequest& request, DDF& out) const;
         pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
 
     private:
         pair<bool,long> doRequest(
             const Application& application,
+            const HTTPRequest* httpRequest,
             HTTPResponse& httpResponse,
             const char* entityID,
             const XMLCh* acsIndex,
@@ -88,8 +90,7 @@ namespace shibsp {
             bool forceAuthn,
             const char* authnContextClassRef,
             const char* authnContextComparison,
-            string& relayState,
-            string& postData
+            string& relayState
             ) const;
 
         string m_appId;
@@ -251,7 +252,6 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
             target = option;
 
         // Always need to recover target URL to compute handler below.
-        // don't think we need to recover post data here though
         recoverRelayState(request.getApplication(), request, request, target, false);
 
         pair<bool,bool> flag;
@@ -288,7 +288,6 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
         // We're running as a "virtual handler" from within the filter.
         // The target resource is the current one and everything else is defaulted.
         target=request.getRequestURL();
-        postData = getPostData(request);
         const PropertySet* settings = request.getRequestSettings().first;
 
         pair<bool,bool> flag = settings->getBool("isPassive");
@@ -367,15 +366,14 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
             }
 
             return doRequest(
-                app, request, entityID.c_str(),
+                app, &request, request, entityID.c_str(),
                 ix.second,
                 ACS ? XMLString::equals(ACS->getString("Binding").second, samlconstants::SAML20_BINDING_HTTP_ARTIFACT) : false,
                 NULL, NULL,
                 isPassive, forceAuthn,
                 acClass.first ? acClass.second : NULL,
                 acComp.first ? acComp.second : NULL,
-                target,
-                postData
+                target
                 );
         }
 
@@ -394,15 +392,14 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
         }
 
         return doRequest(
-            app, request, entityID.c_str(),
+            app, &request, request, entityID.c_str(),
             NULL,
             ACS ? XMLString::equals(ACS->getString("Binding").second, samlconstants::SAML20_BINDING_HTTP_ARTIFACT) : false,
             ACSloc.c_str(), ACS ? ACS->getXMLString("Binding").second : NULL,
             isPassive, forceAuthn,
             acClass.first ? acClass.second : NULL,
             acComp.first ? acComp.second : NULL,
-            target,
-            postData
+            target
             );
     }
 
@@ -462,16 +459,21 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
     if (!target.empty())
         in.addmember("RelayState").string(target.c_str());
 
-    if (!postData.empty()) {
-        in.addmember("PostData").string(postData.c_str());
-        m_log.debug("saml2SI remoting %d posted bytes", postData.length());
-    }
-
     // Remote the processing.
     out = request.getServiceProvider().getListenerService()->send(in);
     return unwrap(request, out);
 }
 
+pair<bool,long> SAML2SessionInitiator::unwrap(SPRequest& request, DDF& out) const
+{
+    // See if there's any response to send back.
+    if (!out["redirect"].isnull() || !out["response"].isnull()) {
+        // If so, we're responsible for handling the POST data, probably by dropping a cookie.
+        preservePostData(request.getApplication(), request, request, out["RelayState"].string());
+    }
+    return RemotedHandler::unwrap(request, out);
+}
+
 void SAML2SessionInitiator::receive(DDF& in, ostream& out)
 {
     // Find application.
@@ -499,15 +501,17 @@ void SAML2SessionInitiator::receive(DDF& in, ostream& out)
     // a false/0 return, which we just return as an empty structure, or a response/redirect,
     // which we capture in the facade and send back.
     doRequest(
-        *app, *http.get(), in["entity_id"].string(),
+        *app, NULL, *http.get(), in["entity_id"].string(),
         index.get(),
         (in["artifact"].integer() != 0),
         in["acsLocation"].string(), bind.get(),
         in["isPassive"].integer()==1, in["forceAuthn"].integer()==1,
         in["authnContextClassRef"].string(), in["authnContextComparison"].string(),
-        relayState,
-        postData
+        relayState
         );
+    if (!ret.isstruct())
+        ret.structure();
+    ret.addmember("RelayState").string(relayState.c_str());
     out << ret;
 }
 
@@ -525,6 +529,7 @@ namespace {
 
 pair<bool,long> SAML2SessionInitiator::doRequest(
     const Application& app,
+    const HTTPRequest* httpRequest,
     HTTPResponse& httpResponse,
     const char* entityID,
     const XMLCh* acsIndex,
@@ -535,8 +540,7 @@ pair<bool,long> SAML2SessionInitiator::doRequest(
     bool forceAuthn,
     const char* authnContextClassRef,
     const char* authnContextComparison,
-    string& relayState,
-    string& postData
+    string& relayState
     ) const
 {
 #ifndef SHIBSP_LITE
@@ -599,7 +603,6 @@ pair<bool,long> SAML2SessionInitiator::doRequest(
     }
 
     preserveRelayState(app, httpResponse, relayState);
-    preservePostData(app, httpResponse, postData, relayState);
 
     auto_ptr<AuthnRequest> req(m_requestTemplate ? m_requestTemplate->cloneAuthnRequest() : AuthnRequestBuilder::buildAuthnRequest());
     if (m_requestTemplate) {
@@ -677,6 +680,11 @@ pair<bool,long> SAML2SessionInitiator::doRequest(
 
     auto_ptr_char dest(ep ? ep->getLocation() : NULL);
 
+    if (httpRequest) {
+        // If the request object is available, we're responsible for the POST data.
+        preservePostData(app, *httpRequest, httpResponse, relayState.c_str());
+    }
+
     long ret = sendMessage(
         *encoder, req.get(), relayState.c_str(), dest.get(), role, app, httpResponse, role ? role->WantAuthnRequestsSigned() : false
         );
index 02e935b..5ff0878 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2007 Internet2
+ *  Copyright 2001-2009 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -166,8 +166,8 @@ pair<bool,long> SAMLDSSessionInitiator::run(SPRequest& request, string& entityID
             target = option;
     }
     preserveRelayState(request.getApplication(), request, target);
-    string postData = getPostData(request);
-    preservePostData(request.getApplication(), request, postData, target);
+    if (!isHandler)
+        preservePostData(request.getApplication(), request, request, target.c_str());
 
     const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
     if (isHandler) {
index 2a0a700..417cbcd 100644 (file)
@@ -67,17 +67,18 @@ namespace shibsp {
 
         void setParent(const PropertySet* parent);
         void receive(DDF& in, ostream& out);
+        pair<bool,long> unwrap(SPRequest& request, DDF& out) const;
         pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
 
     private:
         pair<bool,long> doRequest(
             const Application& application,
+            const HTTPRequest* httpRequest,
             HTTPResponse& httpResponse,
             const char* entityID,
             const char* acsLocation,
             bool artifact,
-            string& relayState,
-            string& postData
+            string& relayState
             ) const;
         string m_appId;
     };
@@ -138,7 +139,6 @@ pair<bool,long> Shib1SessionInitiator::run(SPRequest& request, string& entityID,
         // We're running as a "virtual handler" from within the filter.
         // The target resource is the current one and everything else is defaulted.
         target=request.getRequestURL();
-        postData = getPostData(request);
     }
 
     // Since we're not passing by index, we need to fully compute the return URL.
@@ -172,8 +172,12 @@ pair<bool,long> Shib1SessionInitiator::run(SPRequest& request, string& entityID,
 
     m_log.debug("attempting to initiate session using Shibboleth with provider (%s)", entityID.c_str());
 
-    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
-        return doRequest(app, request, entityID.c_str(), ACSloc.c_str(), artifactInbound, target, postData);
+    if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
+        // Out of process means the POST data via the request can be exposed directly to the private method.
+        // The method will handle POST preservation if necessary *before* issuing the response, but only if
+        // it dispatches to an IdP.
+        return doRequest(app, &request, request, entityID.c_str(), ACSloc.c_str(), artifactInbound, target);
+    }
 
     // Remote the call.
     DDF out,in = DDF(m_address.c_str()).structure();
@@ -186,16 +190,21 @@ pair<bool,long> Shib1SessionInitiator::run(SPRequest& request, string& entityID,
     if (!target.empty())
         in.addmember("RelayState").string(target.c_str());
 
-    if (!postData.empty()) {
-        in.addmember("PostData").string(postData.c_str());
-        m_log.debug("shib1SI remoting %d posted bytes", postData.length());
-    }
-
-    // Remote the processing.
+    // Remote the processing. Our unwrap method will handle POST data if necessary.
     out = request.getServiceProvider().getListenerService()->send(in);
     return unwrap(request, out);
 }
 
+pair<bool,long> Shib1SessionInitiator::unwrap(SPRequest& request, DDF& out) const
+{
+    // See if there's any response to send back.
+    if (!out["redirect"].isnull() || !out["response"].isnull()) {
+        // If so, we're responsible for handling the POST data, probably by dropping a cookie.
+        preservePostData(request.getApplication(), request, request, out["RelayState"].string());
+    }
+    return RemotedHandler::unwrap(request, out);
+}
+
 void Shib1SessionInitiator::receive(DDF& in, ostream& out)
 {
     // Find application.
@@ -219,23 +228,25 @@ void Shib1SessionInitiator::receive(DDF& in, ostream& out)
     auto_ptr<HTTPResponse> http(getResponse(ret));
 
     string relayState(in["RelayState"].string() ? in["RelayState"].string() : "");
-    string postData(in["PostData"].string() ? in["PostData"].string() : "");
 
     // Since we're remoted, the result should either be a throw, which we pass on,
     // a false/0 return, which we just return as an empty structure, or a response/redirect,
     // which we capture in the facade and send back.
-    doRequest(*app, *http.get(), entityID, acsLocation, (in["artifact"].integer() != 0), relayState, postData);
+    doRequest(*app, NULL, *http.get(), entityID, acsLocation, (in["artifact"].integer() != 0), relayState);
+    if (!ret.isstruct())
+        ret.structure();
+    ret.addmember("RelayState").string(relayState.c_str());
     out << ret;
 }
 
 pair<bool,long> Shib1SessionInitiator::doRequest(
     const Application& app,
+    const HTTPRequest* httpRequest,
     HTTPResponse& httpResponse,
     const char* entityID,
     const char* acsLocation,
     bool artifact,
-    string& relayState,
-    string& postData
+    string& relayState
     ) const
 {
 #ifndef SHIBSP_LITE
@@ -272,7 +283,6 @@ pair<bool,long> Shib1SessionInitiator::doRequest(
     }
 
     preserveRelayState(app, httpResponse, relayState);
-    preservePostData(app, httpResponse, postData, relayState);
 
     // Shib 1.x requires a target value.
     if (relayState.empty())
@@ -286,6 +296,11 @@ pair<bool,long> Shib1SessionInitiator::doRequest(
         "&time=" + timebuf + "&target=" + urlenc->encode(relayState.c_str()) +
         "&providerId=" + urlenc->encode(app.getRelyingParty(entity.first)->getString("entityID").second);
 
+    if (httpRequest) {
+        // If the request object is available, we're responsible for the POST data.
+        preservePostData(app, *httpRequest, httpResponse, relayState.c_str());
+    }
+
     return make_pair(true, httpResponse.sendRedirect(req.c_str()));
 #else
     return make_pair(false,0L);
index dd67a28..af9b71a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2007 Internet2
+ *  Copyright 2001-2009 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -131,8 +131,8 @@ pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID,
             target = option;
     }
     preserveRelayState(request.getApplication(), request, target);
-    postData = getPostData(request);
-    preservePostData(request.getApplication(), request, postData, target);
+    if (!isHandler)
+        preservePostData(request.getApplication(), request, request, target.c_str());
 
     // WAYF requires a target value.
     if (target.empty())
index 305b005..26f36af 100644 (file)
@@ -1632,8 +1632,8 @@ void XMLConfig::receive(DDF& in, ostream& out)
         string postData;
         StorageService* storage = getStorageService(id);
         if (storage) {
-            if (storage->readString("PostData",key,&postData)>0) {
-               storage->deleteString("PostData",key);
+            if (storage->readString("PostData",key,&postData) > 0) {
+                storage->deleteString("PostData",key);
             }
         }
         else {
@@ -1641,16 +1641,20 @@ void XMLConfig::receive(DDF& in, ostream& out)
                 "Storage-backed PostData with invalid StorageService ID (%s)", id
                 );
         }
-
-        // Repack for return to caller.
-        DDF ret=DDF(NULL).string(postData.c_str());
-        DDFJanitor jret(ret);
-        out << ret;
+        // If the data's empty, we'll send nothing back.
+        // If not, we don't need to round trip it, just send back the serialized DDF list.
+        if (postData.empty()) {
+            DDF ret(NULL);
+            DDFJanitor jret(ret);
+            out << ret;
+        }
+        else {
+            out << postData;
+        }
     }
     else if (!strcmp(in.name(), "set::PostData")) {
         const char* id = in["id"].string();
-        const char* value = in["value"].string();
-        if (!id || !value)
+        if (!id || !in["parameters"].islist())
             throw ListenerException("Required parameters missing for PostData creation.");
 
         string rsKey;
@@ -1658,7 +1662,9 @@ void XMLConfig::receive(DDF& in, ostream& out)
         if (storage) {
             SAMLConfig::getConfig().generateRandomBytes(rsKey,20);
             rsKey = SAMLArtifact::toHex(rsKey);
-            storage->createString("PostData", rsKey.c_str(), value, time(NULL) + 600);
+            ostringstream params;
+            params << in["parameters"];
+            storage->createString("PostData", rsKey.c_str(), params.str().c_str(), time(NULL) + 600);
         }
         else {
             Category::getInstance(SHIBSP_LOGCAT".ServiceProvider").error(
@@ -1671,7 +1677,6 @@ void XMLConfig::receive(DDF& in, ostream& out)
         DDFJanitor jret(ret);
         out << ret;
     }
-
 }
 #endif
 
index 1011351..d1cba90 100644 (file)
@@ -61,7 +61,9 @@ CGIParser::~CGIParser()
 
 pair<CGIParser::walker,CGIParser::walker> CGIParser::getParameters(const char* name) const
 {
-    return kvp_map.equal_range(name);
+    if (name)
+        return kvp_map.equal_range(name);
+    return make_pair(kvp_map.begin(), kvp_map.end());
 }
 
 /* Parsing routines modified from NCSA source. */
index b76953d..c0e3371 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2007 Internet2
+ *  Copyright 2001-2009 Internet2
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ namespace shibsp {
         /**
          * Returns a pair of bounded iterators around the values of a parameter.
          * 
-         * @param name  name of parameter
+         * @param name  name of parameter, or NULL to return all parameters
          * @return  a pair of multimap iterators surrounding the matching value(s)
          */
         std::pair<walker,walker> getParameters(const char* name) const;