Alter POST preserving APIs, and use built-in CGI parser and URL decoder.
/*
- * 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.
}
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,
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();
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.
// 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,
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);
metadataError.html \
bindingTemplate.html \
discoveryTemplate.html \
+ postTemplate.html \
localLogout.html \
globalLogout.html \
sslError.html
/*
- * 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.
#define __shibsp_abshandler_h__
#include <shibsp/handler/Handler.h>
+#include <shibsp/remoting/ddf.h>
#include <shibsp/util/DOMPropertySet.h>
#ifndef SHIBSP_LITE
) 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;
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)
/*
- * 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.
#include "handler/AbstractHandler.h"
#include "handler/LogoutHandler.h"
#include "remoting/ListenerService.h"
+#include "util/CGIParser.h"
#include "util/SPConstants.h"
#include "util/TemplateParameters.h"
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
}
}
-// 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;
+}
/*
- * 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.
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());
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) {
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,
bool forceAuthn,
const char* authnContextClassRef,
const char* authnContextComparison,
- string& relayState,
- string& postData
+ string& relayState
) const;
string m_appId;
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");
}
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
);
}
}
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
);
}
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.
// 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;
}
pair<bool,long> SAML2SessionInitiator::doRequest(
const Application& app,
+ const HTTPRequest* httpRequest,
HTTPResponse& httpResponse,
const char* entityID,
const XMLCh* acsIndex,
bool forceAuthn,
const char* authnContextClassRef,
const char* authnContextComparison,
- string& relayState,
- string& postData
+ string& relayState
) 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) {
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
);
/*
- * 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.
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) {
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;
};
// 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, 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();
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.
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
}
preserveRelayState(app, httpResponse, relayState);
- preservePostData(app, httpResponse, postData, relayState);
// Shib 1.x requires a target value.
if (relayState.empty())
"&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);
/*
- * 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.
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())
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 {
"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;
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(
DDFJanitor jret(ret);
out << ret;
}
-
}
#endif
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. */
/*
- * 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.
/**
* 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;