X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fhandler%2Fimpl%2FAbstractHandler.cpp;h=e42ef5b6653fd488687d26d6269e8ea8e610075e;hb=a5b1914f888d2ac8992cc4985d65e9d727aa8df4;hp=cd9039c3300bd53651f5f1828f3b573fa871cda0;hpb=8cfb88bff921f1830d05045bf266b88f8d49abd7;p=shibboleth%2Fsp.git diff --git a/shibsp/handler/impl/AbstractHandler.cpp b/shibsp/handler/impl/AbstractHandler.cpp index cd9039c..e42ef5b 100644 --- a/shibsp/handler/impl/AbstractHandler.cpp +++ b/shibsp/handler/impl/AbstractHandler.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2001-2007 Internet2 + * Copyright 2001-2009 Internet2 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,16 @@ #include "handler/AbstractHandler.h" #include "handler/LogoutHandler.h" #include "remoting/ListenerService.h" +#include "util/CGIParser.h" #include "util/SPConstants.h" +#include "util/TemplateParameters.h" + +#include +#include +#include +#include +#include + #ifndef SHIBSP_LITE # include @@ -36,6 +45,8 @@ # include # include # include +# include +# include # include using namespace opensaml::saml2md; #else @@ -145,7 +156,7 @@ void AbstractHandler::checkError(const XMLObject* response, const saml2md::RoleD const saml1p::Status* status = r1->getStatus(); if (status) { const saml1p::StatusCode* sc = status->getStatusCode(); - const QName* code = sc ? sc->getValue() : NULL; + const xmltooling::QName* code = sc ? sc->getValue() : NULL; if (code && *code != saml1p::StatusCode::SUCCESS) { FatalProfileException ex("SAML response contained an error."); ex.addProperty("statusCode", code->toString().c_str()); @@ -305,7 +316,7 @@ void AbstractHandler::preserveRelayState(const Application& application, HTTPRes else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) { DDF out,in = DDF("set::RelayState").structure(); in.addmember("id").string(mech.second); - in.addmember("value").string(relayState.c_str()); + in.addmember("value").unsafe_string(relayState.c_str()); DDFJanitor jin(in),jout(out); out = application.getServiceProvider().getListenerService()->send(in); if (!out.isstring()) @@ -348,9 +359,7 @@ void AbstractHandler::recoverRelayState( relayState.erase(); } else { - Category::getInstance(SHIBSP_LOGCAT".Handler").error( - "Storage-backed RelayState with invalid StorageService ID (%s)", ssid.c_str() - ); + m_log.error("Storage-backed RelayState with invalid StorageService ID (%s)", ssid.c_str()); relayState.erase(); } #endif @@ -402,13 +411,244 @@ void AbstractHandler::recoverRelayState( relayState.erase(); } - // Check for "default" value. - if (relayState.empty() || relayState == "default") { + // Check for "default" value (or the old "cookie" value that might come from stale bookmarks). + if (relayState.empty() || relayState == "default" || relayState == "cookie") { pair homeURL=application.getString("homeURL"); - relayState=homeURL.first ? homeURL.second : "/"; + if (homeURL.first) + relayState=homeURL.second; + else { + // Compute a URL to the root of the site. + int port = request.getPort(); + const char* scheme = request.getScheme(); + relayState = string(scheme) + "://" + request.getHostname(); + if ((!strcmp(scheme,"http") && port!=80) || (!strcmp(scheme,"https") && port!=443)) { + ostringstream portstr; + portstr << port; + relayState += ":" + portstr.str(); + } + relayState += '/'; + } + } +} + +void AbstractHandler::preservePostData( + const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState + ) const +{ +#ifdef HAVE_STRCASECMP + if (strcasecmp(request.getMethod(), "POST")) return; +#else + if (stricmp(request.getMethod(), "POST")) return; +#endif + + // No specs mean no save. + const PropertySet* props=application.getPropertySet("Sessions"); + pair mech = props->getString("postData"); + if (!mech.first) { + m_log.info("postData property not supplied, form data will not be preserved across SSO"); + return; + } + + DDF postData = getPostData(application, request); + if (postData.isnull()) return; + + 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)); + } + + 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 a cookie with key info. + pair shib_cookie = getPostCookieNameProps(application, relayState); + postkey += shib_cookie.second; + response.setCookie(shib_cookie.first.c_str(), postkey.c_str()); + } + else { + postData.destroy(); + throw ConfigurationException("Unsupported postData mechanism ($1).", params(1,mech.second)); + } +} + +DDF AbstractHandler::recoverPostData( + const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState + ) const +{ + // First we need the post recovery cookie. + pair shib_cookie = getPostCookieNameProps(application, relayState); + const char* cookie = request.getCookie(shib_cookie.first.c_str()); + if (!cookie || !*cookie) + return DDF(); + + // 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 = cookie; + if (strstr(state, "ss:") == state) { + state += 3; + const char* key = strchr(state, ':'); + if (key) { + 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) { + 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 { + m_log.error("storage-backed PostData with invalid StorageService ID (%s)", ssid.c_str()); + } +#endif + } + else if (conf.isEnabled(SPConfig::InProcess)) { + DDF in = DDF("get::PostData").structure(); + DDFJanitor jin(in); + in.addmember("id").string(ssid.c_str()); + in.addmember("key").string(key); + 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."); + } + } + } + } + return DDF(); +} + +long AbstractHandler::sendPostResponse( + const Application& application, HTTPResponse& httpResponse, const char* url, DDF& postData + ) const +{ + const PropertySet* props=application.getPropertySet("Sessions"); + pair postTemplate = props->getString("postTemplate"); + if (!postTemplate.first) + throw ConfigurationException("Missing postTemplate property, unable to recreate form post."); + + 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, fname.c_str())); + TemplateParameters respParam; + respParam.m_map["action"] = url; + + // Load the parameters into objects for the template. + multimap& collection = respParam.m_collectionMap["PostedData"]; + DDF param = postData.first(); + while (param.isstring()) { + collection.insert(pair(param.name(), (param.string() ? param.string() : ""))); + param = postData.next(); } - if (relayState == "default") - relayState.empty(); + stringstream str; + XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, respParam); + + pair postExpire = props->getBool("postExpire"); + + httpResponse.setContentType("text/html"); + if (!postExpire.first || postExpire.second) { + httpResponse.setResponseHeader("Expires", "01-Jan-1997 12:00:00 GMT"); + httpResponse.setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate, private"); + httpResponse.setResponseHeader("Pragma", "no-cache"); + } + return httpResponse.sendResponse(str); +} + +pair AbstractHandler::getPostCookieNameProps(const Application& app, const char* relayState) const +{ + // Decorates the name of the cookie with the relay state key, if any. + // Doing so gives a better assurance that the recovered data really + // belongs to the relayed request. + pair shib_cookie=app.getCookieNameProps("_shibpost_"); + if (strstr(relayState, "cookie:") == relayState) { + shib_cookie.first = string("_shibpost_") + (relayState + 7); + } + else if (strstr(relayState, "ss:") == relayState) { + const char* pch = strchr(relayState + 3, ':'); + if (pch) + shib_cookie.first = string("_shibpost_") + (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 plimit = props->getUnsignedInt("postLimit"); + if (!plimit.first) + plimit.second = 1024 * 1024; + if (plimit.second == 0 || request.getContentLength() <= plimit.second) { + CGIParser cgi(request); + pair 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) { + if (!params.first->first.empty()) { + child = DDF(params.first->first.c_str()).unsafe_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 DDF(); }