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
])
# ----------------------------------------------------------
# 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,
<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
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;
#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>
# 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
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));
+}
+
+
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());
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.
bool forceAuthn,
const char* authnContextClassRef,
const char* authnContextComparison,
- string& relayState
+ string& relayState,
+ string& postData
) const;
string m_appId;
return make_pair(false,0L);
string target;
+ string postData;
const Handler* ACS=NULL;
const char* option;
pair<bool,const char*> acClass;
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;
// 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");
isPassive, forceAuthn,
acClass.first ? acClass.second : NULL,
acComp.first ? acComp.second : NULL,
- target
+ target,
+ postData
);
}
isPassive, forceAuthn,
acClass.first ? acClass.second : NULL,
acComp.first ? acComp.second : NULL,
- target
+ target,
+ postData
);
}
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);
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,
in["acsLocation"].string(), bind.get(),
in["isPassive"].integer()==1, in["forceAuthn"].integer()==1,
in["authnContextClassRef"].string(), in["authnContextComparison"].string(),
- relayState
+ relayState,
+ postData
);
out << ret;
}
bool forceAuthn,
const char* authnContextClassRef,
const char* authnContextComparison,
- string& relayState
+ string& relayState,
+ string& postData
) const
{
#ifndef SHIBSP_LITE
}
preserveRelayState(app, httpResponse, relayState);
+ preservePostData(app, httpResponse, postData, relayState);
auto_ptr<AuthnRequest> req(m_requestTemplate ? m_requestTemplate->cloneAuthnRequest() : AuthnRequestBuilder::buildAuthnRequest());
if (m_requestTemplate) {
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) {
const char* entityID,
const char* acsLocation,
bool artifact,
- string& relayState
+ string& relayState,
+ string& postData
) const;
string m_appId;
};
return make_pair(false,0L);
string target;
+ string postData;
const Handler* ACS=NULL;
const char* option;
const Application& app=request.getApplication();
// 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.
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();
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);
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;
}
const char* entityID,
const char* acsLocation,
bool artifact,
- string& relayState
+ string& relayState,
+ string& postData
) const
{
#ifndef SHIBSP_LITE
}
preserveRelayState(app, httpResponse, relayState);
+ preservePostData(app, httpResponse, postData, relayState);
// Shib 1.x requires a target value.
if (relayState.empty())
return make_pair(false,0L);
string target;
+ string postData;
const char* option;
const Handler* ACS=NULL;
const Application& app=request.getApplication();
target = option;
}
preserveRelayState(request.getApplication(), request, target);
+ postData = getPostData(request);
+ preservePostData(request.getApplication(), request, postData, target);
// WAYF requires a target value.
if (target.empty())
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
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