X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fhandler%2Fimpl%2FSAML2SessionInitiator.cpp;h=6556f0a48422b2f7c00567053e5699a94fbdbfa0;hb=db3dd0f8110b3daa89123ef9994dc86121a92b31;hp=5f9e87fc885e9b1d4c197dd2abd2e816f71748ee;hpb=0e5e6defe8898a673db2784125e66591236e58cc;p=shibboleth%2Fsp.git diff --git a/shibsp/handler/impl/SAML2SessionInitiator.cpp b/shibsp/handler/impl/SAML2SessionInitiator.cpp index 5f9e87f..6556f0a 100644 --- a/shibsp/handler/impl/SAML2SessionInitiator.cpp +++ b/shibsp/handler/impl/SAML2SessionInitiator.cpp @@ -39,12 +39,13 @@ using namespace opensaml::saml2; using namespace opensaml::saml2p; using namespace opensaml::saml2md; +#else +#include #endif using namespace shibsp; using namespace opensaml; using namespace xmltooling; -using namespace log4cpp; using namespace std; namespace shibsp { @@ -88,11 +89,16 @@ namespace shibsp { ) const; string m_appId; + auto_ptr_char m_paosNS,m_ecpNS; + auto_ptr_XMLCh m_paosBinding; #ifndef SHIBSP_LITE XMLCh* m_outgoing; vector m_bindings; map m_encoders; + MessageEncoder* m_ecp; AuthnRequest* m_requestTemplate; +#else + bool m_ecp; #endif }; @@ -108,28 +114,47 @@ namespace shibsp { }; SAML2SessionInitiator::SAML2SessionInitiator(const DOMElement* e, const char* appId) - : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator")), m_appId(appId) + : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.SAML2")), m_appId(appId), + m_paosNS(samlconstants::PAOS_NS), m_ecpNS(samlconstants::SAML20ECP_NS), m_paosBinding(samlconstants::SAML20_BINDING_PAOS) { -#ifndef SHIBSP_LITE + static const XMLCh ECP[] = UNICODE_LITERAL_3(E,C,P); + const XMLCh* flag = e ? e->getAttributeNS(NULL,ECP) : NULL; +#ifdef SHIBSP_LITE + m_ecp = (flag && (*flag == chLatin_t || *flag == chDigit_1)); +#else m_outgoing=NULL; + m_ecp = NULL; m_requestTemplate=NULL; + if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) { // Check for a template AuthnRequest to build from. DOMElement* child = XMLHelper::getFirstChildElement(e, samlconstants::SAML20P_NS, AuthnRequest::LOCAL_NAME); if (child) m_requestTemplate = dynamic_cast(AuthnRequestBuilder::buildOneFromElement(child)); + // If directed, build an ECP encoder. + if (flag && (*flag == chLatin_t || *flag == chDigit_1)) { + try { + m_ecp = SAMLConfig::getConfig().MessageEncoderManager.newPlugin( + samlconstants::SAML20_BINDING_PAOS, pair(e,NULL) + ); + } + catch (exception& ex) { + m_log.error("error building PAOS/ECP MessageEncoder: %s", ex.what()); + } + } + // Handle outgoing binding setup. pair outgoing = getXMLString("outgoingBindings"); if (outgoing.first) { m_outgoing = XMLString::replicate(outgoing.second); + XMLString::trim(m_outgoing); } else { // No override, so we'll install a default binding precedence. string prec = string(samlconstants::SAML20_BINDING_HTTP_REDIRECT) + ' ' + samlconstants::SAML20_BINDING_HTTP_POST + ' ' + samlconstants::SAML20_BINDING_HTTP_POST_SIMPLESIGN + ' ' + samlconstants::SAML20_BINDING_HTTP_ARTIFACT; m_outgoing = XMLString::transcode(prec.c_str()); - XMLString::trim(m_outgoing); } int pos; @@ -141,7 +166,9 @@ SAML2SessionInitiator::SAML2SessionInitiator(const DOMElement* e, const char* ap m_bindings.push_back(start); try { auto_ptr_char b(start); - MessageEncoder * encoder = SAMLConfig::getConfig().MessageEncoderManager.newPlugin(b.get(),e); + MessageEncoder * encoder = SAMLConfig::getConfig().MessageEncoderManager.newPlugin( + b.get(),pair(e,NULL) + ); m_encoders[start] = encoder; m_log.debug("supporting outgoing binding (%s)", b.get()); } @@ -179,9 +206,17 @@ void SAML2SessionInitiator::setParent(const PropertySet* parent) pair SAML2SessionInitiator::run(SPRequest& request, const char* entityID, bool isHandler) const { - // We have to know the IdP to function. - if (!entityID || !*entityID) - return make_pair(false,0); + // First check for ECP support, since that doesn't require an IdP to be known. + bool ECP = false; + if (m_ecp && request.getHeader("Accept").find("application/vnd.paos+xml") != string::npos) { + string PAOS = request.getHeader("PAOS"); + if (PAOS.find(m_paosNS.get()) != string::npos && PAOS.find(m_ecpNS.get()) != string::npos) + ECP = true; + } + + // We have to know the IdP to function unless this is ECP. + if (!ECP && (!entityID || !*entityID)) + return make_pair(false,0L); string target; const Handler* ACS=NULL; @@ -190,14 +225,20 @@ pair SAML2SessionInitiator::run(SPRequest& request, const char* entit pair acComp; bool isPassive=false,forceAuthn=false; const Application& app=request.getApplication(); - pair acsByIndex = getBool("acsByIndex"); + + // ECP means the ACS will be by value no matter what. + pair acsByIndex = ECP ? make_pair(true,false) : getBool("acsByIndex"); if (isHandler) { option=request.getParameter("acsIndex"); if (option) { ACS = app.getAssertionConsumerServiceByIndex(atoi(option)); if (!ACS) - throw ConfigurationException("AssertionConsumerService with index ($1) not found, check configuration.", params(1,option)); + request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using default ACS location"); + else if (ECP && !XMLString::equals(ACS->getString("Binding").second, samlconstants::SAML20_BINDING_PAOS)) { + request.log(SPRequest::SPWarn, "acsIndex in request referenced a non-PAOS ACS, using default ACS location"); + ACS = NULL; + } } option = request.getParameter("target"); @@ -206,20 +247,38 @@ pair SAML2SessionInitiator::run(SPRequest& request, const char* entit if (acsByIndex.first && !acsByIndex.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, target, false); + recoverRelayState(request.getApplication(), request, request, target, false); } + pair flag; option = request.getParameter("isPassive"); - isPassive = (option && (*option=='1' || *option=='t')); + if (option) { + isPassive = (*option=='1' || *option=='t'); + } + else { + flag = getBool("isPassive"); + isPassive = (flag.first && flag.second); + } if (!isPassive) { option = request.getParameter("forceAuthn"); - forceAuthn = (option && (*option=='1' || *option=='t')); + if (option) { + forceAuthn = (*option=='1' || *option=='t'); + } + else { + flag = getBool("forceAuthn"); + forceAuthn = (flag.first && flag.second); + } } - acClass.second = request.getParameter("authnContextClassRef"); - acClass.first = (acClass.second!=NULL); - acComp.second = request.getParameter("authnContextComparison"); - acComp.first = (acComp.second!=NULL); + if (acClass.second = request.getParameter("authnContextClassRef")) + acClass.first = true; + else + acClass = getString("authnContextClassRef"); + + if (acComp.second = request.getParameter("authnContextComparison")) + acComp.first = true; + else + acComp = getString("authnContextComparison"); } else { // We're running as a "virtual handler" from within the filter. @@ -228,25 +287,55 @@ pair SAML2SessionInitiator::run(SPRequest& request, const char* entit const PropertySet* settings = request.getRequestSettings().first; pair flag = settings->getBool("isPassive"); + if (!flag.first) + flag = getBool("isPassive"); isPassive = flag.first && flag.second; if (!isPassive) { flag = settings->getBool("forceAuthn"); + if (!flag.first) + flag = getBool("forceAuthn"); forceAuthn = flag.first && flag.second; } acClass = settings->getString("authnContextClassRef"); + if (!acClass.first) + acClass = getString("authnContextClassRef"); acComp = settings->getString("authnContextComparison"); + if (!acComp.first) + acComp = getString("authnContextComparison"); } - m_log.debug("attempting to initiate session using SAML 2.0 with provider (%s)", entityID); + if (ECP) + m_log.debug("attempting to initiate session using SAML 2.0 Enhanced Client Profile"); + else + m_log.debug("attempting to initiate session using SAML 2.0 with provider (%s)", entityID); + + if (!ACS) { + if (ECP) { + const vector& handlers = app.getAssertionConsumerServicesByBinding(m_paosBinding.get()); + if (handlers.empty()) + throw ConfigurationException("Unable to locate PAOS response endpoint."); + ACS = handlers.front(); + } + else { + pair index = getUnsignedInt("defaultACSIndex"); + if (index.first) { + ACS = app.getAssertionConsumerServiceByIndex(index.second); + if (!ACS) + request.log(SPRequest::SPWarn, "invalid defaultACSIndex, using default ACS location"); + } + if (!ACS) + ACS = app.getDefaultAssertionConsumerService(); + } + } - // To invoke the request builder, the key requirement is to figure out how and whether + // To invoke the request builder, the key requirement is to figure out how // to express the ACS, by index or value, and if by value, where. SPConfig& conf = SPConfig::getConfig(); if (conf.isEnabled(SPConfig::OutOfProcess)) { if (!acsByIndex.first || acsByIndex.second) { - // Pass by Index. This also allows for defaulting it entirely and sending nothing. + // Pass by Index. 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. @@ -266,9 +355,6 @@ pair SAML2SessionInitiator::run(SPRequest& request, const char* entit } // Since we're not passing by index, we need to fully compute the return URL and binding. - if (!ACS) - ACS = app.getDefaultAssertionConsumerService(); - // 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); @@ -297,7 +383,8 @@ pair SAML2SessionInitiator::run(SPRequest& request, const char* entit DDF out,in = DDF(m_address.c_str()).structure(); DDFJanitor jin(in), jout(out); in.addmember("application_id").string(app.getId()); - in.addmember("entity_id").string(entityID); + if (entityID) + in.addmember("entity_id").string(entityID); if (isPassive) in.addmember("isPassive").integer(1); else if (forceAuthn) @@ -312,9 +399,6 @@ pair SAML2SessionInitiator::run(SPRequest& request, const char* entit } else { // Since we're not passing by index, we need to fully compute the return URL and binding. - if (!ACS) - ACS = app.getDefaultAssertionConsumerService(); - // 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); @@ -351,10 +435,6 @@ void SAML2SessionInitiator::receive(DDF& in, ostream& out) throw ConfigurationException("Unable to locate application for new session, deleted?"); } - const char* entityID = in["entity_id"].string(); - if (!entityID) - throw ConfigurationException("No entityID parameter supplied to remoted SessionInitiator."); - DDF ret(NULL); DDFJanitor jout(ret); @@ -370,7 +450,7 @@ void SAML2SessionInitiator::receive(DDF& in, ostream& out) // 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, + *app, *http.get(), in["entity_id"].string(), index.get(), in["acsLocation"].string(), bind.get(), in["isPassive"].integer()==1, in["forceAuthn"].integer()==1, in["authnContextClassRef"].string(), in["authnContextComparison"].string(), @@ -379,6 +459,18 @@ void SAML2SessionInitiator::receive(DDF& in, ostream& out) out << ret; } +#ifndef SHIBSP_LITE +namespace { + class _sameIdP : public binary_function + { + public: + bool operator()(const IDPEntry* entry, const XMLCh* entityID) const { + return entry ? XMLString::equals(entry->getProviderID(), entityID) : false; + } + }; +}; +#endif + pair SAML2SessionInitiator::doRequest( const Application& app, HTTPResponse& httpResponse, @@ -394,36 +486,56 @@ pair SAML2SessionInitiator::doRequest( ) const { #ifndef SHIBSP_LITE - // Use metadata to locate the IdP's SSO service. + bool ECP = XMLString::equals(acsBinding, m_paosBinding.get()); + + pair entity = pair(NULL,NULL); + const IDPSSODescriptor* role = NULL; + const EndpointType* ep = NULL; + const MessageEncoder* encoder = NULL; + + // We won't need this for ECP, but safety dictates we get the lock here. MetadataProvider* m=app.getMetadataProvider(); Locker locker(m); - const EntityDescriptor* entity=m->getEntityDescriptor(entityID); - if (!entity) { - m_log.error("unable to locate metadata for provider (%s)", entityID); - throw MetadataException("Unable to locate metadata for identity provider ($entityID)", - namedparams(1, "entityID", entityID)); - } - const IDPSSODescriptor* role=entity->getIDPSSODescriptor(samlconstants::SAML20P_NS); - if (!role) { - m_log.error("unable to locate SAML 2.0 identity provider role for provider (%s)", entityID); - return make_pair(false,0); + + if (ECP) { + encoder = m_ecp; + if (!encoder) { + m_log.error("MessageEncoder for PAOS binding not available"); + return make_pair(false,0L); + } } + else { + // Use metadata to locate the IdP's SSO service. + MetadataProvider::Criteria mc(entityID, &IDPSSODescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS); + entity=m->getEntityDescriptor(mc); + if (!entity.first) { + m_log.warn("unable to locate metadata for provider (%s)", entityID); + throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", entityID)); + } + else if (!entity.second) { + m_log.warn("unable to locate SAML 2.0 identity provider role for provider (%s)", entityID); + if (getParent()) + return make_pair(false,0L); + throw MetadataException("Unable to locate SAML 2.0 identity provider role for provider ($entityID)", namedparams(1, "entityID", entityID)); + } - // Loop over the supportable outgoing bindings. - const EndpointType* ep=NULL; - const MessageEncoder* encoder=NULL; - vector::const_iterator b; - for (b = m_bindings.begin(); b!=m_bindings.end(); ++b) { - if (ep=EndpointManager(role->getSingleSignOnServices()).getByBinding(*b)) { - map::const_iterator enc = m_encoders.find(*b); - if (enc!=m_encoders.end()) - encoder = enc->second; - break; + // Loop over the supportable outgoing bindings. + role = dynamic_cast(entity.second); + vector::const_iterator b; + for (b = m_bindings.begin(); b!=m_bindings.end(); ++b) { + if (ep=EndpointManager(role->getSingleSignOnServices()).getByBinding(*b)) { + map::const_iterator enc = m_encoders.find(*b); + if (enc!=m_encoders.end()) + encoder = enc->second; + break; + } + } + if (!ep || !encoder) { + m_log.warn("unable to locate compatible SSO service for provider (%s)", entityID); + if (getParent()) + return make_pair(false,0L); + throw MetadataException("Unable to locate compatible SSO service for provider ($entityID)", namedparams(1, "entityID", entityID)); } - } - if (!ep || !encoder) { - m_log.error("unable to locate compatible SSO service for provider (%s)", entityID); - return make_pair(false,0); } preserveRelayState(app, httpResponse, relayState); @@ -435,7 +547,8 @@ pair SAML2SessionInitiator::doRequest( req->setIssueInstant(time(NULL)); } - req->setDestination(ep->getLocation()); + if (ep) + req->setDestination(ep->getLocation()); if (acsIndex && *acsIndex) req->setAssertionConsumerServiceIndex(acsIndex); if (acsLocation) { @@ -451,7 +564,7 @@ pair SAML2SessionInitiator::doRequest( if (!req->getIssuer()) { Issuer* issuer = IssuerBuilder::buildIssuer(); req->setIssuer(issuer); - issuer->setName(app.getXMLString("entityID").second); + issuer->setName(app.getRelyingParty(entity.first)->getXMLString("entityID").second); } if (!req->getNameIDPolicy()) { NameIDPolicy* namepol = NameIDPolicyBuilder::buildNameIDPolicy(); @@ -478,52 +591,34 @@ pair SAML2SessionInitiator::doRequest( } } - auto_ptr_char dest(ep->getLocation()); - - // Check for signing. - const PropertySet* relyingParty = app.getRelyingParty(entity); - pair flag = relyingParty->getBool("signRequests"); - if ((flag.first && flag.second) || role->WantAuthnRequestsSigned()) { - CredentialResolver* credResolver=app.getCredentialResolver(); - if (credResolver) { - Locker credLocker(credResolver); - // Fill in criteria to use. - MetadataCredentialCriteria mcc(*role); - mcc.setUsage(CredentialCriteria::SIGNING_CREDENTIAL); - pair keyName = relyingParty->getString("keyName"); - if (keyName.first) - mcc.getKeyNames().insert(keyName.second); - pair sigalg = relyingParty->getXMLString("signatureAlg"); - if (sigalg.first) - mcc.setXMLAlgorithm(sigalg.second); - const Credential* cred = credResolver->resolve(&mcc); - if (cred) { - // Signed request. - long ret = encoder->encode( - httpResponse, - req.get(), - dest.get(), - entity, - relayState.c_str(), - &app, - cred, - sigalg.second, - relyingParty->getXMLString("digestAlg").second - ); - req.release(); // freed by encoder - return make_pair(true,ret); - } - else { - m_log.warn("no signing credential resolved, leaving AuthnRequest unsigned"); - } + if (ECP && entityID) { + auto_ptr_XMLCh wideid(entityID); + Scoping* scoping = req->getScoping(); + if (!scoping) { + scoping = ScopingBuilder::buildScoping(); + req->setScoping(scoping); + } + IDPList* idplist = scoping->getIDPList(); + if (!idplist) { + idplist = IDPListBuilder::buildIDPList(); + scoping->setIDPList(idplist); + } + VectorOf(IDPEntry) entries = idplist->getIDPEntrys(); + if (find_if(entries, bind2nd(_sameIdP(), wideid.get())) == NULL) { + IDPEntry* entry = IDPEntryBuilder::buildIDPEntry(); + entry->setProviderID(wideid.get()); + entries.push_back(entry); } } - // Unsigned request. - long ret = encoder->encode(httpResponse, req.get(), dest.get(), entity, relayState.c_str(), &app); + auto_ptr_char dest(ep ? ep->getLocation() : NULL); + + long ret = sendMessage( + *encoder, req.get(), relayState.c_str(), dest.get(), role, app, httpResponse, role ? role->WantAuthnRequestsSigned() : false + ); req.release(); // freed by encoder return make_pair(true,ret); #else - return make_pair(false,0); + return make_pair(false,0L); #endif }