From a7c32e2e05638e9daa5e8e999d051a3a84c13b9a Mon Sep 17 00:00:00 2001 From: Jim Fox Date: Fri, 6 Mar 2009 23:35:20 +0000 Subject: [PATCH] Adding capability to save and restore posted data through login loop --- doxygen.m4 | 4 +- schemas/shibboleth-2.0-native-sp-config.xsd | 2 + shibsp/handler/AbstractHandler.h | 60 ++++++ shibsp/handler/impl/AbstractHandler.cpp | 253 +++++++++++++++++++++++ shibsp/handler/impl/AssertionConsumerService.cpp | 11 +- shibsp/handler/impl/SAML2SessionInitiator.cpp | 25 ++- shibsp/handler/impl/SAMLDSSessionInitiator.cpp | 2 + shibsp/handler/impl/Shib1SessionInitiator.cpp | 19 +- shibsp/handler/impl/WAYFSessionInitiator.cpp | 3 + shibsp/impl/XMLServiceProvider.cpp | 51 +++++ 10 files changed, 418 insertions(+), 12 deletions(-) diff --git a/doxygen.m4 b/doxygen.m4 index e4688de..e9c56c2 100644 --- a/doxygen.m4 +++ b/doxygen.m4 @@ -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, diff --git a/schemas/shibboleth-2.0-native-sp-config.xsd b/schemas/shibboleth-2.0-native-sp-config.xsd index 6845cd8..1f87eff 100644 --- a/schemas/shibboleth-2.0-native-sp-config.xsd +++ b/schemas/shibboleth-2.0-native-sp-config.xsd @@ -553,6 +553,8 @@ + + diff --git a/shibsp/handler/AbstractHandler.h b/shibsp/handler/AbstractHandler.h index 140fa11..aee17dd 100644 --- a/shibsp/handler/AbstractHandler.h +++ b/shibsp/handler/AbstractHandler.h @@ -155,6 +155,66 @@ namespace shibsp { bool clear=true ) const; + /** + * Implements storage service and cookie mechanism to preserve PostData, + * + *

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, + * + *

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; diff --git a/shibsp/handler/impl/AbstractHandler.cpp b/shibsp/handler/impl/AbstractHandler.cpp index 3b3c0dc..5763b21 100644 --- a/shibsp/handler/impl/AbstractHandler.cpp +++ b/shibsp/handler/impl/AbstractHandler.cpp @@ -29,6 +29,14 @@ #include "handler/LogoutHandler.h" #include "remoting/ListenerService.h" #include "util/SPConstants.h" +#include "util/TemplateParameters.h" + +#include +#include +#include +#include +#include + #ifndef SHIBSP_LITE # include @@ -36,6 +44,8 @@ # include # include # include +# include +# include # include 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(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() mech = props->getString("postData"); + pair 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 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(&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)); +} + + diff --git a/shibsp/handler/impl/AssertionConsumerService.cpp b/shibsp/handler/impl/AssertionConsumerService.cpp index efa036d..8a4dae1 100644 --- a/shibsp/handler/impl/AssertionConsumerService.cpp +++ b/shibsp/handler/impl/AssertionConsumerService.cpp @@ -150,12 +150,15 @@ pair 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 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 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. diff --git a/shibsp/handler/impl/SAML2SessionInitiator.cpp b/shibsp/handler/impl/SAML2SessionInitiator.cpp index 1a44d2e..d058604 100644 --- a/shibsp/handler/impl/SAML2SessionInitiator.cpp +++ b/shibsp/handler/impl/SAML2SessionInitiator.cpp @@ -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 SAML2SessionInitiator::run(SPRequest& request, string& entityID, return make_pair(false,0L); string target; + string postData; const Handler* ACS=NULL; const char* option; pair acClass; @@ -249,6 +251,7 @@ pair 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 flag; @@ -285,6 +288,7 @@ pair 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 flag = settings->getBool("isPassive"); @@ -370,7 +374,8 @@ pair 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 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 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 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 SAML2SessionInitiator::doRequest( } preserveRelayState(app, httpResponse, relayState); + preservePostData(app, httpResponse, postData, relayState); auto_ptr req(m_requestTemplate ? m_requestTemplate->cloneAuthnRequest() : AuthnRequestBuilder::buildAuthnRequest()); if (m_requestTemplate) { diff --git a/shibsp/handler/impl/SAMLDSSessionInitiator.cpp b/shibsp/handler/impl/SAMLDSSessionInitiator.cpp index 9545420..02e935b 100644 --- a/shibsp/handler/impl/SAMLDSSessionInitiator.cpp +++ b/shibsp/handler/impl/SAMLDSSessionInitiator.cpp @@ -166,6 +166,8 @@ pair 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) { diff --git a/shibsp/handler/impl/Shib1SessionInitiator.cpp b/shibsp/handler/impl/Shib1SessionInitiator.cpp index 0aed575..2a0a700 100644 --- a/shibsp/handler/impl/Shib1SessionInitiator.cpp +++ b/shibsp/handler/impl/Shib1SessionInitiator.cpp @@ -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 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 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 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 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 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 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 Shib1SessionInitiator::doRequest( } preserveRelayState(app, httpResponse, relayState); + preservePostData(app, httpResponse, postData, relayState); // Shib 1.x requires a target value. if (relayState.empty()) diff --git a/shibsp/handler/impl/WAYFSessionInitiator.cpp b/shibsp/handler/impl/WAYFSessionInitiator.cpp index 19510ec..dd67a28 100644 --- a/shibsp/handler/impl/WAYFSessionInitiator.cpp +++ b/shibsp/handler/impl/WAYFSessionInitiator.cpp @@ -80,6 +80,7 @@ pair 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 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()) diff --git a/shibsp/impl/XMLServiceProvider.cpp b/shibsp/impl/XMLServiceProvider.cpp index 989c348..305b005 100644 --- a/shibsp/impl/XMLServiceProvider.cpp +++ b/shibsp/impl/XMLServiceProvider.cpp @@ -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(m_outer)); m_outer->m_listener->regListener("get::RelayState", const_cast(m_outer)); + m_outer->m_listener->regListener("set::PostData", const_cast(m_outer)); + m_outer->m_listener->regListener("get::PostData", const_cast(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 -- 2.1.4