X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fhandler%2Fimpl%2FShib1SessionInitiator.cpp;h=e4ac2e8b15e975a2af64655688f49b471069c9c1;hb=75d3356afd20e997b6acdcc42cd4642d0a77cf99;hp=aeda3c7a0cad81a4207e9c942a8749ce8d89b56b;hpb=486ea6f0c638815c3a7556cbb12156e2f4b1943b;p=shibboleth%2Fsp.git diff --git a/shibsp/handler/impl/Shib1SessionInitiator.cpp b/shibsp/handler/impl/Shib1SessionInitiator.cpp index aeda3c7..e4ac2e8 100644 --- a/shibsp/handler/impl/Shib1SessionInitiator.cpp +++ b/shibsp/handler/impl/Shib1SessionInitiator.cpp @@ -1,6 +1,6 @@ /* - * Copyright 2001-2007 Internet2 - * + * Copyright 2001-2010 Internet2 + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,7 +16,7 @@ /** * Shib1SessionInitiator.cpp - * + * * Shibboleth 1.x AuthnRequest support. */ @@ -24,7 +24,6 @@ #include "Application.h" #include "exceptions.h" #include "ServiceProvider.h" -#include "SPRequest.h" #include "handler/AbstractHandler.h" #include "handler/RemotedHandler.h" #include "handler/SessionInitiator.h" @@ -34,6 +33,9 @@ # include "metadata/MetadataProviderCriteria.h" # include # include +# include +#else +# include "lite/SAMLConstants.h" #endif #include #include @@ -55,7 +57,7 @@ namespace shibsp { { public: Shib1SessionInitiator(const DOMElement* e, const char* appId) - : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.Shib1")), m_appId(appId) { + : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.Shib1"), nullptr, &m_remapper), m_appId(appId) { // If Location isn't set, defer address registration until the setParent call. pair loc = getString("Location"); if (loc.first) { @@ -64,17 +66,24 @@ namespace shibsp { } } virtual ~Shib1SessionInitiator() {} - + void setParent(const PropertySet* parent); void receive(DDF& in, ostream& out); + pair unwrap(SPRequest& request, DDF& out) const; pair run(SPRequest& request, string& entityID, bool isHandler=true) const; + const XMLCh* getProtocolFamily() const { + return samlconstants::SAML11_PROTOCOL_ENUM; + } + private: pair doRequest( const Application& application, + const HTTPRequest* httpRequest, HTTPResponse& httpResponse, const char* entityID, const char* acsLocation, + bool artifact, string& relayState ) const; string m_appId; @@ -107,66 +116,91 @@ void Shib1SessionInitiator::setParent(const PropertySet* parent) pair Shib1SessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const { // We have to know the IdP to function. - if (entityID.empty()) + if (entityID.empty() || !checkCompatibility(request, isHandler)) return make_pair(false,0L); string target; - const Handler* ACS=NULL; - const char* option; - const Application& app=request.getApplication(); + pair prop; + const Handler* ACS=nullptr; + const Application& app = request.getApplication(); if (isHandler) { - option=request.getParameter("acsIndex"); - if (option) { - ACS = app.getAssertionConsumerServiceByIndex(atoi(option)); + prop.second = request.getParameter("acsIndex"); + if (prop.second && *prop.second) { + ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second)); if (!ACS) - request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using default ACS location"); + request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using acsIndex property"); } - option = request.getParameter("target"); - if (option) - target = option; + prop = getString("target", request); + if (prop.first) + target = prop.second; // Since we're passing the ACS by value, we need to compute the return URL, // so we'll need the target resource for real. - recoverRelayState(request.getApplication(), request, request, target, false); + recoverRelayState(app, request, request, target, false); } else { - // 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(); + // Check for a hardwired target value in the map or handler. + prop = getString("target", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED); + if (prop.first) + target = prop.second; + else + target = request.getRequestURL(); } // Since we're not passing by index, we need to fully compute the return URL. if (!ACS) { - pair index = getUnsignedInt("defaultACSIndex"); + // Try fixed index property, or incoming binding set, or default, in order. + pair index = getUnsignedInt("acsIndex", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED); if (index.first) { ACS = app.getAssertionConsumerServiceByIndex(index.second); if (!ACS) - request.log(SPRequest::SPWarn, "invalid defaultACSIndex, using default ACS location"); + request.log(SPRequest::SPWarn, "invalid acsIndex property, using default ACS location"); } + /* + for (vector::const_iterator b = m_incomingBindings.begin(); !ACS && b != m_incomingBindings.end(); ++b) { + ACS = app.getAssertionConsumerServiceByBinding(b->c_str()); + if (ACS && !XMLString::equals(getProtocolFamily(), ACS->getProtocolFamily())) + ACS = nullptr; + } + */ if (!ACS) ACS = app.getDefaultAssertionConsumerService(); } + // Validate the ACS for use with this protocol. + if (ACS && !XMLString::equals(getProtocolFamily(), ACS->getProtocolFamily())) { + m_log.error("configured or requested ACS has non-SAML 1.x binding"); + throw ConfigurationException("Configured or requested ACS has non-SAML 1.x binding ($1).", params(1, ACS->getString("Binding").second)); + } + // Compute the ACS URL. We add the ACS location to the base handlerURL. - string ACSloc=request.getHandlerURL(target.c_str()); - pair loc=ACS ? ACS->getString("Location") : pair(false,NULL); - if (loc.first) ACSloc+=loc.second; + string ACSloc = request.getHandlerURL(target.c_str()); + prop = ACS ? ACS->getString("Location") : pair(false,nullptr); + if (prop.first) + ACSloc += prop.second; if (isHandler) { // We may already have RelayState set if we looped back here, - // but just in case target is a resource, we reset it back. - target.erase(); - option = request.getParameter("target"); - if (option) - target = option; + // but we've turned it back into a resource by this point, so if there's + // a target on the URL, reset to that value. + prop.second = request.getParameter("target"); + if (prop.second && *prop.second) + target = prop.second; } + // Is the in-bound binding artifact? + bool artifactInbound = ACS ? XMLString::equals(ACS->getString("Binding").second, samlconstants::SAML1_PROFILE_BROWSER_ARTIFACT) : false; + 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(), 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(), artifactInbound, target); + } // Remote the call. DDF out,in = DDF(m_address.c_str()).structure(); @@ -174,19 +208,31 @@ pair Shib1SessionInitiator::run(SPRequest& request, string& entityID, in.addmember("application_id").string(app.getId()); in.addmember("entity_id").string(entityID.c_str()); in.addmember("acsLocation").string(ACSloc.c_str()); + if (artifactInbound) + in.addmember("artifact").integer(1); if (!target.empty()) - in.addmember("RelayState").string(target.c_str()); + in.addmember("RelayState").unsafe_string(target.c_str()); - // 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 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. const char* aid=in["application_id"].string(); - const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL; + const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : nullptr; if (!app) { // Something's horribly wrong. m_log.error("couldn't find application (%s) to generate AuthnRequest", aid ? aid : "(missing)"); @@ -198,7 +244,7 @@ void Shib1SessionInitiator::receive(DDF& in, ostream& out) if (!entityID || !acsLocation) throw ConfigurationException("No entityID or acsLocation parameter supplied to remoted SessionInitiator."); - DDF ret(NULL); + DDF ret(nullptr); DDFJanitor jout(ret); // Wrap the outgoing object with a Response facade. @@ -209,15 +255,20 @@ void Shib1SessionInitiator::receive(DDF& in, ostream& out) // 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, relayState); + doRequest(*app, nullptr, *http.get(), entityID, acsLocation, (in["artifact"].integer() != 0), relayState); + if (!ret.isstruct()) + ret.structure(); + ret.addmember("RelayState").unsafe_string(relayState.c_str()); out << ret; } pair Shib1SessionInitiator::doRequest( const Application& app, + const HTTPRequest* httpRequest, HTTPResponse& httpResponse, const char* entityID, const char* acsLocation, + bool artifact, string& relayState ) const { @@ -232,11 +283,18 @@ pair Shib1SessionInitiator::doRequest( throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", entityID)); } else if (!entity.second) { - m_log.warn("unable to locate Shibboleth-aware identity provider role for provider (%s)", entityID); + m_log.log(getParent() ? Priority::INFO : Priority::WARN, "unable to locate Shibboleth-aware identity provider role for provider (%s)", entityID); if (getParent()) return make_pair(false,0L); throw MetadataException("Unable to locate Shibboleth-aware identity provider role for provider ($entityID)", namedparams(1, "entityID", entityID)); } + else if (artifact && !SPConfig::getConfig().getArtifactResolver()->isSupported(dynamic_cast(*entity.second))) { + m_log.warn("artifact profile selected for response, but identity provider lacks support"); + if (getParent()) + return make_pair(false,0L); + throw MetadataException("Identity provider ($entityID) lacks SAML artifact support.", namedparams(1, "entityID", entityID)); + } + const EndpointType* ep=EndpointManager( dynamic_cast(entity.second)->getSingleSignOnServices() ).getByBinding(shibspconstants::SHIB1_AUTHNREQUEST_PROFILE_URI); @@ -254,13 +312,18 @@ pair Shib1SessionInitiator::doRequest( relayState = "default"; char timebuf[16]; - sprintf(timebuf,"%lu",time(NULL)); + sprintf(timebuf,"%lu",time(nullptr)); const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder(); auto_ptr_char dest(ep->getLocation()); string req=string(dest.get()) + (strchr(dest.get(),'?') ? '&' : '?') + "shire=" + urlenc->encode(acsLocation) + "&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);