Adding capability to save and restore posted data through login loop
authorJim Fox <fox@washington.edu>
Fri, 6 Mar 2009 23:35:20 +0000 (23:35 +0000)
committerJim Fox <fox@washington.edu>
Fri, 6 Mar 2009 23:35:20 +0000 (23:35 +0000)
doxygen.m4
schemas/shibboleth-2.0-native-sp-config.xsd
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

index e4688de..e9c56c2 100644 (file)
@@ -78,7 +78,7 @@ AC_DEFUN([DX_REQUIRE_PROG], [
 AC_PATH_TOOL([$1], [$2])
 if test "$DX_FLAG_[]DX_CURRENT_FEATURE$$1" = 1; then
     AC_MSG_WARN([$2 not found - will not DX_CURRENT_DESCRIPTION])
-    AC_SUBST([DX_FLAG_[]DX_CURRENT_FEATURE], 0)
+    AC_SUBST([DX_FLAG_]DX_CURRENT_FEATURE, 0)
 fi
 ])
 
@@ -101,7 +101,7 @@ test "$DX_FLAG_$1" = "$2" \
 # ----------------------------------------------------------
 # Turn off the DX_CURRENT_FEATURE if the required feature is off.
 AC_DEFUN([DX_CLEAR_DEPEND], [
-test "$DX_FLAG_$1" = "$2" || AC_SUBST([DX_FLAG_[]DX_CURRENT_FEATURE], 0)
+test "$DX_FLAG_$1" = "$2" || AC_SUBST([DX_FLAG_]DX_CURRENT_FEATURE, 0)
 ])
 
 # DX_FEATURE_ARG(FEATURE, DESCRIPTION,
index 6845cd8..1f87eff 100644 (file)
                    <attribute name="maxTimeSinceAuthn" type="unsignedInt"/>\r
                        <attribute name="checkAddress" type="boolean"/>\r
                        <attribute name="consistentAddress" type="boolean"/>\r
+                       <attribute name="postData" type="conf:string"/>\r
+                       <attribute name="postTemplate" type="conf:string"/>\r
                        <anyAttribute namespace="##other" processContents="lax"/>\r
                </complexType>\r
        </element>\r
index 140fa11..aee17dd 100644 (file)
@@ -155,6 +155,66 @@ namespace shibsp {
             bool clear=true
             ) 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.
+         *
+         * @param application   the associated Application
+         * @param response      outgoing HTTP response
+         * @param postData      Posted data to preserve
+         * @param relayState    relayState ( with key value )
+         */
+        virtual void preservePostData(
+            const Application& application, xmltooling::HTTPResponse& response, std::string& postData, std::string& relayState
+            ) const;
+
+        /**
+         * 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.
+         *
+         * @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 )
+         */
+        virtual void recoverPostData(
+            const Application& application,
+            const xmltooling::HTTPRequest& request,
+            xmltooling::HTTPResponse& response,
+            std::string& postData,
+            std::string& 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
+         * 
+         * @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
+         */
+        virtual long sendPostResponse(const Application& application, xmltooling::HTTPResponse& httpResponse, std::string& url, std::string& postData) const;
+
         /** Logging object. */
         xmltooling::logging::Category& m_log;
         
index 3b3c0dc..5763b21 100644 (file)
 #include "handler/LogoutHandler.h"
 #include "remoting/ListenerService.h"
 #include "util/SPConstants.h"
+#include "util/TemplateParameters.h"
+
+#include <vector>
+#include <fstream>
+#include <xmltooling/XMLToolingConfig.h>
+#include <xmltooling/util/PathResolver.h>
+#include <xmltooling/util/URLEncoder.h>
+
 
 #ifndef SHIBSP_LITE
 # include <saml/saml1/core/Protocols.h>
@@ -36,6 +44,8 @@
 # include <saml/saml2/metadata/Metadata.h>
 # include <saml/saml2/metadata/MetadataCredentialCriteria.h>
 # include <saml/util/SAMLConstants.h>
+# include <saml/SAMLConfig.h>
+# include <saml/binding/SAMLArtifact.h>
 # include <xmltooling/util/StorageService.h>
 using namespace opensaml::saml2md;
 #else
@@ -408,3 +418,246 @@ void AbstractHandler::recoverRelayState(
         relayState=homeURL.first ? homeURL.second : "/";
     }
 }
+
+// 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
+{
+    if (postData.empty()) return;
+
+    // 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.");
+        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) {
+
+                        // Use a random key
+                        string rsKey;
+                        SAMLConfig::getConfig().generateRandomBytes(rsKey,20);
+                        rsKey = SAMLArtifact::toHex(rsKey);
+
+                        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();
+                }
+            }
+
+            // 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());
+
+    }
+    else
+        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
+    ) const
+{
+    SPConfig& conf = SPConfig::getConfig();
+
+    /** 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;
+    }
+
+    // Look for StorageService-backed state of the form "ss:SSID:key".
+    const char* state = postData.c_str();
+    if (strstr(state,"ss:")==state) {
+        state += 3;
+        const char* key = strchr(state,':');
+        if (key) {
+            string ssid = postData.substr(3, key - state);
+            key++;
+
+            if (!ssid.empty() && *key) {
+                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;
+                        }
+                        else
+                            postData.erase();
+                    }
+                    else {
+                        Category::getInstance(SHIBSP_LOGCAT".Handler").error(
+                            "Storage-backed PostData with invalid StorageService ID (%s)", ssid.c_str()
+                            );
+                        postData.erase();
+                    }
+#endif
+                }
+                else if (conf.isEnabled(SPConfig::InProcess)) {
+                    DDF out,in = DDF("get::PostData").structure();
+                    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;
+                    }
+                }
+            }
+        }
+    }
+
+    if (postData == "default")
+        postData.empty();
+}
+
+/* Send recovered data via template to browser for re-post */
+
+long AbstractHandler::sendPostResponse(const Application& application, HTTPResponse& httpResponse, string& url, string& 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);
+
+    string fname(postTemplate);
+    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));
+    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_collectionMap["PostedData"] = dataParams;
+
+    stringstream str;
+    XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, respParam);
+    return (httpResponse.sendResponse(str));
+}
+
+
index efa036d..8a4dae1 100644 (file)
@@ -150,12 +150,15 @@ pair<bool,long> AssertionConsumerService::processMessage(
     shibsp::SecurityPolicy policy(application, &m_role, validate.first && validate.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);
         recoverRelayState(application, httpRequest, httpResponse, relayState);
         implementProtocol(application, httpRequest, httpResponse, policy, settings, *msg.get());
 
@@ -166,7 +169,13 @@ 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.
-        return make_pair(true, httpResponse.sendRedirect(relayState.c_str()));
+        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));
+        }
     }
     catch (XMLToolingException& ex) {
         // Check for isPassive error condition.
index 1a44d2e..d058604 100644 (file)
@@ -88,7 +88,8 @@ namespace shibsp {
             bool forceAuthn,
             const char* authnContextClassRef,
             const char* authnContextComparison,
-            string& relayState
+            string& relayState,
+            string& postData
             ) const;
 
         string m_appId;
@@ -222,6 +223,7 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
         return make_pair(false,0L);
 
     string target;
+    string postData;
     const Handler* ACS=NULL;
     const char* option;
     pair<bool,const char*> acClass;
@@ -249,6 +251,7 @@ 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;
@@ -285,6 +288,7 @@ 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");
@@ -370,7 +374,8 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
                 isPassive, forceAuthn,
                 acClass.first ? acClass.second : NULL,
                 acComp.first ? acComp.second : NULL,
-                target
+                target,
+                postData
                 );
         }
 
@@ -396,7 +401,8 @@ pair<bool,long> SAML2SessionInitiator::run(SPRequest& request, string& entityID,
             isPassive, forceAuthn,
             acClass.first ? acClass.second : NULL,
             acComp.first ? acComp.second : NULL,
-            target
+            target,
+            postData
             );
     }
 
@@ -456,6 +462,11 @@ 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);
@@ -482,6 +493,7 @@ void SAML2SessionInitiator::receive(DDF& in, ostream& out)
     auto_ptr_XMLCh bind(in["acsBinding"].string());
 
     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,
@@ -493,7 +505,8 @@ void SAML2SessionInitiator::receive(DDF& in, ostream& out)
         in["acsLocation"].string(), bind.get(),
         in["isPassive"].integer()==1, in["forceAuthn"].integer()==1,
         in["authnContextClassRef"].string(), in["authnContextComparison"].string(),
-        relayState
+        relayState,
+        postData
         );
     out << ret;
 }
@@ -522,7 +535,8 @@ pair<bool,long> SAML2SessionInitiator::doRequest(
     bool forceAuthn,
     const char* authnContextClassRef,
     const char* authnContextComparison,
-    string& relayState
+    string& relayState,
+    string& postData
     ) const
 {
 #ifndef SHIBSP_LITE
@@ -585,6 +599,7 @@ 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) {
index 9545420..02e935b 100644 (file)
@@ -166,6 +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);
 
     const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
     if (isHandler) {
index 0aed575..2a0a700 100644 (file)
@@ -76,7 +76,8 @@ namespace shibsp {
             const char* entityID,
             const char* acsLocation,
             bool artifact,
-            string& relayState
+            string& relayState,
+            string& postData
             ) const;
         string m_appId;
     };
@@ -112,6 +113,7 @@ pair<bool,long> Shib1SessionInitiator::run(SPRequest& request, string& entityID,
         return make_pair(false,0L);
 
     string target;
+    string postData;
     const Handler* ACS=NULL;
     const char* option;
     const Application& app=request.getApplication();
@@ -136,6 +138,7 @@ 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.
@@ -170,7 +173,7 @@ 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);
+        return doRequest(app, request, entityID.c_str(), ACSloc.c_str(), artifactInbound, target, postData);
 
     // Remote the call.
     DDF out,in = DDF(m_address.c_str()).structure();
@@ -183,6 +186,11 @@ 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.
     out = request.getServiceProvider().getListenerService()->send(in);
     return unwrap(request, out);
@@ -211,11 +219,12 @@ 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);
+    doRequest(*app, *http.get(), entityID, acsLocation, (in["artifact"].integer() != 0), relayState, postData);
     out << ret;
 }
 
@@ -225,7 +234,8 @@ pair<bool,long> Shib1SessionInitiator::doRequest(
     const char* entityID,
     const char* acsLocation,
     bool artifact,
-    string& relayState
+    string& relayState,
+    string& postData
     ) const
 {
 #ifndef SHIBSP_LITE
@@ -262,6 +272,7 @@ pair<bool,long> Shib1SessionInitiator::doRequest(
     }
 
     preserveRelayState(app, httpResponse, relayState);
+    preservePostData(app, httpResponse, postData, relayState);
 
     // Shib 1.x requires a target value.
     if (relayState.empty())
index 19510ec..dd67a28 100644 (file)
@@ -80,6 +80,7 @@ pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID,
         return make_pair(false,0L);
 
     string target;
+    string postData;
     const char* option;
     const Handler* ACS=NULL;
     const Application& app=request.getApplication();
@@ -130,6 +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);
 
     // WAYF requires a target value.
     if (target.empty())
index 989c348..305b005 100644 (file)
@@ -1389,6 +1389,8 @@ XMLConfigImpl::XMLConfigImpl(const DOMElement* e, bool first, const XMLConfig* o
             if (m_outer->m_listener && conf.isEnabled(SPConfig::OutOfProcess) && !conf.isEnabled(SPConfig::InProcess)) {
                 m_outer->m_listener->regListener("set::RelayState", const_cast<XMLConfig*>(m_outer));
                 m_outer->m_listener->regListener("get::RelayState", const_cast<XMLConfig*>(m_outer));
+                m_outer->m_listener->regListener("set::PostData", const_cast<XMLConfig*>(m_outer));
+                m_outer->m_listener->regListener("get::PostData", const_cast<XMLConfig*>(m_outer));
             }
 #endif
 
@@ -1621,6 +1623,55 @@ void XMLConfig::receive(DDF& in, ostream& out)
         DDFJanitor jret(ret);
         out << ret;
     }
+    else if (!strcmp(in.name(), "get::PostData")) {
+        const char* id = in["id"].string();
+        const char* key = in["key"].string();
+        if (!id || !key)
+            throw ListenerException("Required parameters missing for PostData recovery.");
+
+        string postData;
+        StorageService* storage = getStorageService(id);
+        if (storage) {
+            if (storage->readString("PostData",key,&postData)>0) {
+               storage->deleteString("PostData",key);
+            }
+        }
+        else {
+            Category::getInstance(SHIBSP_LOGCAT".ServiceProvider").error(
+                "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;
+    }
+    else if (!strcmp(in.name(), "set::PostData")) {
+        const char* id = in["id"].string();
+        const char* value = in["value"].string();
+        if (!id || !value)
+            throw ListenerException("Required parameters missing for PostData creation.");
+
+        string rsKey;
+        StorageService* storage = getStorageService(id);
+        if (storage) {
+            SAMLConfig::getConfig().generateRandomBytes(rsKey,20);
+            rsKey = SAMLArtifact::toHex(rsKey);
+            storage->createString("PostData", rsKey.c_str(), value, time(NULL) + 600);
+        }
+        else {
+            Category::getInstance(SHIBSP_LOGCAT".ServiceProvider").error(
+                "Storage-backed PostData with invalid StorageService ID (%s)", id
+                );
+        }
+
+        // Repack for return to caller.
+        DDF ret=DDF(NULL).string(rsKey.c_str());
+        DDFJanitor jret(ret);
+        out << ret;
+    }
+
 }
 #endif