X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fattribute%2Fresolver%2Fimpl%2FSimpleAggregationAttributeResolver.cpp;h=573e6ec0c7729b51c0fa95e00ac076571bdfe7c8;hb=a0af8ed86ab481af4605daf5cf837fdaa4544bdd;hp=3be90bc1dc788bccccc8e5f337410307d371a821;hpb=a1f37f881774adaa587a95bf725cb15f93fb9e6d;p=shibboleth%2Fsp.git diff --git a/shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp b/shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp index 3be90bc..573e6ec 100644 --- a/shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2009 Internet2 + * Copyright 2009-2010 Internet2 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ #include "ServiceProvider.h" #include "SessionCache.h" #include "attribute/NameIDAttribute.h" +#include "attribute/SimpleAttribute.h" #include "attribute/filtering/AttributeFilter.h" #include "attribute/filtering/BasicFilteringContext.h" #include "attribute/resolver/AttributeExtractor.h" @@ -34,6 +35,7 @@ #include "binding/SOAPClient.h" #include "metadata/MetadataProviderCriteria.h" #include "security/SecurityPolicy.h" +#include "security/SecurityPolicyProvider.h" #include "util/SPConstants.h" #include @@ -41,10 +43,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -64,23 +68,26 @@ namespace shibsp { SimpleAggregationContext(const Application& application, const Session& session) : m_app(application), m_session(&session), - m_nameid(NULL), + m_nameid(nullptr), + m_entityid(nullptr), m_class(XMLString::transcode(session.getAuthnContextClassRef())), m_decl(XMLString::transcode(session.getAuthnContextDeclRef())), - m_inputTokens(NULL), - m_inputAttributes(NULL) { + m_inputTokens(nullptr), + m_inputAttributes(nullptr) { } SimpleAggregationContext( const Application& application, - const NameID* nameid=NULL, - const XMLCh* authncontext_class=NULL, - const XMLCh* authncontext_decl=NULL, - const vector* tokens=NULL, - const vector* attributes=NULL + const NameID* nameid=nullptr, + const XMLCh* entityID=nullptr, + const XMLCh* authncontext_class=nullptr, + const XMLCh* authncontext_decl=nullptr, + const vector* tokens=nullptr, + const vector* attributes=nullptr ) : m_app(application), - m_session(NULL), + m_session(nullptr), m_nameid(nameid), + m_entityid(entityID ? XMLString::transcode(entityID) : nullptr), m_class(const_cast(authncontext_class)), m_decl(const_cast(authncontext_decl)), m_inputTokens(tokens), @@ -94,11 +101,15 @@ namespace shibsp { XMLString::release(&m_class); XMLString::release(&m_decl); } + XMLString::release(&m_entityid); } const Application& getApplication() const { return m_app; } + const char* getEntityID() const { + return m_session ? m_session->getEntityID() : m_entityid; + } const NameID* getNameID() const { return m_session ? m_session->getNameID() : m_nameid; } @@ -128,6 +139,7 @@ namespace shibsp { const Application& m_app; const Session* m_session; const NameID* m_nameid; + char* m_entityid; XMLCh* m_class; XMLCh* m_decl; const vector* m_inputTokens; @@ -153,13 +165,15 @@ namespace shibsp { const Application& application, const EntityDescriptor* issuer, const XMLCh* protocol, - const NameID* nameid=NULL, - const XMLCh* authncontext_class=NULL, - const XMLCh* authncontext_decl=NULL, - const vector* tokens=NULL, - const vector* attributes=NULL + const NameID* nameid=nullptr, + const XMLCh* authncontext_class=nullptr, + const XMLCh* authncontext_decl=nullptr, + const vector* tokens=nullptr, + const vector* attributes=nullptr ) const { - return new SimpleAggregationContext(application,nameid,authncontext_class,authncontext_decl,tokens,attributes); + return new SimpleAggregationContext( + application, nameid, (issuer ? issuer->getEntityID() : nullptr), authncontext_class, authncontext_decl, tokens, attributes + ); } ResolutionContext* createResolutionContext(const Application& application, const Session& session) const { @@ -173,16 +187,18 @@ namespace shibsp { } private: - bool doQuery(SimpleAggregationContext& ctx, const char* entityID, const NameID* name) const; + void doQuery(SimpleAggregationContext& ctx, const char* entityID, const NameID* name) const; Category& m_log; string m_policyId; + bool m_subjectMatch; vector m_attributeIds; xstring m_format; MetadataProvider* m_metadata; TrustEngine* m_trust; vector m_designators; vector< pair > m_sources; + vector m_exceptionId; }; AttributeResolver* SHIBSP_DLLLOCAL SimpleAggregationResolverFactory(const DOMElement* const & e) @@ -193,29 +209,28 @@ namespace shibsp { static const XMLCh attributeId[] = UNICODE_LITERAL_11(a,t,t,r,i,b,u,t,e,I,d); static const XMLCh Entity[] = UNICODE_LITERAL_6(E,n,t,i,t,y); static const XMLCh EntityReference[] = UNICODE_LITERAL_15(E,n,t,i,t,y,R,e,f,e,r,e,n,c,e); + static const XMLCh exceptionId[] = UNICODE_LITERAL_11(e,x,c,e,p,t,i,o,n,I,d); static const XMLCh format[] = UNICODE_LITERAL_6(f,o,r,m,a,t); static const XMLCh _MetadataProvider[] = UNICODE_LITERAL_16(M,e,t,a,d,a,t,a,P,r,o,v,i,d,e,r); static const XMLCh policyId[] = UNICODE_LITERAL_8(p,o,l,i,c,y,I,d); + static const XMLCh subjectMatch[] = UNICODE_LITERAL_12(s,u,b,j,e,c,t,M,a,t,c,h); static const XMLCh _TrustEngine[] = UNICODE_LITERAL_11(T,r,u,s,t,E,n,g,i,n,e); static const XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e); }; SimpleAggregationResolver::SimpleAggregationResolver(const DOMElement* e) - : m_log(Category::getInstance(SHIBSP_LOGCAT".AttributeResolver.SimpleAggregation")), m_metadata(NULL), m_trust(NULL) + : m_log(Category::getInstance(SHIBSP_LOGCAT".AttributeResolver.SimpleAggregation")), + m_policyId(XMLHelper::getAttrString(e, nullptr, policyId)), + m_subjectMatch(XMLHelper::getAttrBool(e, false, subjectMatch)), + m_metadata(nullptr), m_trust(nullptr) { #ifdef _DEBUG xmltooling::NDC ndc("SimpleAggregationResolver"); #endif - const XMLCh* pid = e ? e->getAttributeNS(NULL, policyId) : NULL; - if (pid && *pid) { - auto_ptr_char temp(pid); - m_policyId = temp.get(); - } - - pid = e ? e->getAttributeNS(NULL, attributeId) : NULL; - if (pid && *pid) { - char* dup = XMLString::transcode(pid); + const XMLCh* aid = e ? e->getAttributeNS(nullptr, attributeId) : nullptr; + if (aid && *aid) { + char* dup = XMLString::transcode(aid); char* pos; char* start = dup; while (start && *start) { @@ -227,23 +242,26 @@ SimpleAggregationResolver::SimpleAggregationResolver(const DOMElement* e) if (pos) *pos=0; m_attributeIds.push_back(start); - start = pos ? pos+1 : NULL; + start = pos ? pos+1 : nullptr; } XMLString::release(&dup); - pid = e->getAttributeNS(NULL, format); - if (pid && *pid) - m_format = pid; - + aid = e->getAttributeNS(nullptr, format); + if (aid && *aid) + m_format = aid; } + string exid(XMLHelper::getAttrString(e, nullptr, exceptionId)); + if (!exid.empty()) + m_exceptionId.push_back(exid); + DOMElement* child = XMLHelper::getFirstChildElement(e, _MetadataProvider); if (child) { - auto_ptr_char type(child->getAttributeNS(NULL, _type)); - if (!type.get() || !*type.get()) + string t(XMLHelper::getAttrString(child, nullptr, _type)); + if (t.empty()) throw ConfigurationException("MetadataProvider element missing type attribute."); - m_log.info("building MetadataProvider of type %s...", type.get()); - auto_ptr mp(SAMLConfig::getConfig().MetadataProviderManager.newPlugin(type.get(), child)); + m_log.info("building MetadataProvider of type %s...", t.c_str()); + auto_ptr mp(SAMLConfig::getConfig().MetadataProviderManager.newPlugin(t.c_str(), child)); mp->init(); m_metadata = mp.release(); } @@ -251,11 +269,11 @@ SimpleAggregationResolver::SimpleAggregationResolver(const DOMElement* e) child = XMLHelper::getFirstChildElement(e, _TrustEngine); if (child) { try { - auto_ptr_char type(child->getAttributeNS(NULL, _type)); - if (!type.get() || !*type.get()) + string t(XMLHelper::getAttrString(child, nullptr, _type)); + if (t.empty()) throw ConfigurationException("TrustEngine element missing type attribute."); - m_log.info("building TrustEngine of type %s...", type.get()); - m_trust = XMLToolingConfig::getConfig().TrustEngineManager.newPlugin(type.get(), child); + m_log.info("building TrustEngine of type %s...", t.c_str()); + m_trust = XMLToolingConfig::getConfig().TrustEngineManager.newPlugin(t.c_str(), child); } catch (exception&) { delete m_metadata; @@ -266,17 +284,17 @@ SimpleAggregationResolver::SimpleAggregationResolver(const DOMElement* e) child = XMLHelper::getFirstChildElement(e); while (child) { if (child->hasChildNodes() && XMLString::equals(child->getLocalName(), Entity)) { - pid = child->getFirstChild()->getNodeValue(); - if (pid && *pid) { - auto_ptr_char tpid(pid); - m_sources.push_back(pair(tpid.get(),true)); + aid = child->getFirstChild()->getNodeValue(); + if (aid && *aid) { + auto_ptr_char taid(aid); + m_sources.push_back(pair(taid.get(),true)); } } else if (child->hasChildNodes() && XMLString::equals(child->getLocalName(), EntityReference)) { - pid = child->getFirstChild()->getNodeValue(); - if (pid && *pid) { - auto_ptr_char tpid(pid); - m_sources.push_back(pair(tpid.get(),false)); + aid = child->getFirstChild()->getNodeValue(); + if (aid && *aid) { + auto_ptr_char taid(aid); + m_sources.push_back(pair(taid.get(),false)); } } else if (XMLHelper::isNodeNamed(child, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME)) { @@ -294,10 +312,9 @@ SimpleAggregationResolver::SimpleAggregationResolver(const DOMElement* e) } child = XMLHelper::getNextSiblingElement(child); } - } -bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const char* entityID, const NameID* name) const +void SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const char* entityID, const NameID* name) const { #ifdef _DEBUG xmltooling::NDC ndc("doQuery"); @@ -305,42 +322,40 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha const Application& application = ctx.getApplication(); MetadataProviderCriteria mc(application, entityID, &AttributeAuthorityDescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS); Locker mlocker(m_metadata); - const AttributeAuthorityDescriptor* AA=NULL; + const AttributeAuthorityDescriptor* AA=nullptr; pair mdresult = (m_metadata ? m_metadata : application.getMetadataProvider())->getEntityDescriptor(mc); if (!mdresult.first) { m_log.warn("unable to locate metadata for provider (%s)", entityID); - return false; + return; } else if (!(AA=dynamic_cast(mdresult.second))) { m_log.warn("no SAML 2 AttributeAuthority role found in metadata for (%s)", entityID); - return false; + return; } const PropertySet* relyingParty = application.getRelyingParty(mdresult.first); + pair signedAssertions = relyingParty->getBool("requireSignedAssertions"); + pair encryption = relyingParty->getString("encryption"); // Locate policy key. const char* policyId = m_policyId.empty() ? application.getString("policyId").second : m_policyId.c_str(); - // Access policy properties. - const PropertySet* settings = application.getServiceProvider().getPolicySettings(policyId); - pair validate = settings->getBool("validate"); - - pair signedAssertions = relyingParty->getBool("requireSignedAssertions"); - pair encryption = relyingParty->getString("encryption"); - - shibsp::SecurityPolicy policy(application, NULL, validate.first && validate.second, policyId); + // Set up policy and SOAP client. + auto_ptr policy( + application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, nullptr, policyId) + ); if (m_metadata) - policy.setMetadataProvider(m_metadata); + policy->setMetadataProvider(m_metadata); if (m_trust) - policy.setTrustEngine(m_trust); - policy.getAudiences().push_back(relyingParty->getXMLString("entityID").second); + policy->setTrustEngine(m_trust); + policy->getAudiences().push_back(relyingParty->getXMLString("entityID").second); MetadataCredentialCriteria mcc(*AA); - shibsp::SOAPClient soaper(policy); + shibsp::SOAPClient soaper(*policy.get()); auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP); - saml2p::StatusResponseType* srt=NULL; + saml2p::StatusResponseType* srt=nullptr; const vector& endpoints=AA->getAttributeServices(); for (vector::const_iterator ep=endpoints.begin(); !srt && ep!=endpoints.end(); ++ep) { if (!XMLString::equals((*ep)->getBinding(),binding.get()) || !(*ep)->getLocation()) @@ -352,10 +367,9 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha // Encrypt the NameID? if (encryption.first && (!strcmp(encryption.second, "true") || !strcmp(encryption.second, "back"))) { auto_ptr encrypted(EncryptedIDBuilder::buildEncryptedID()); - MetadataCredentialCriteria mcc(*AA); encrypted->encrypt( *name, - *(policy.getMetadataProvider()), + *(policy->getMetadataProvider()), mcc, false, relyingParty->getXMLString("encryptionAlg").second @@ -386,58 +400,139 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha if (!srt) { m_log.error("unable to obtain a SAML response from attribute authority (%s)", entityID); - return false; + throw BindingException("Unable to obtain a SAML response from attribute authority."); } + + auto_ptr wrapper(srt); + saml2p::Response* response = dynamic_cast(srt); if (!response) { - delete srt; m_log.error("message was not a samlp:Response"); - return true; + throw FatalProfileException("Attribute authority returned an unrecognized message."); } else if (!response->getStatus() || !response->getStatus()->getStatusCode() || !XMLString::equals(response->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) { - delete srt; m_log.error("attribute authority (%s) returned a SAML error", entityID); - return true; + throw FatalProfileException("Attribute authority returned a SAML error."); } + saml2::Assertion* newtoken = nullptr; const vector& assertions = const_cast(response)->getAssertions(); if (assertions.empty()) { - delete srt; - m_log.warn("response from attribute authority (%s) was empty", entityID); - return true; - } - else if (assertions.size()>1) - m_log.warn("resolver only supports one assertion in the query response"); + // Check for encryption. + const vector& encassertions = + const_cast(response)->getEncryptedAssertions(); + if (encassertions.empty()) { + m_log.warn("response from attribute authority was empty"); + return; + } + else if (encassertions.size() > 1) { + m_log.warn("simple resolver only supports one assertion in the query response"); + } - auto_ptr wrapper(srt); - saml2::Assertion* newtoken = assertions.front(); + CredentialResolver* cr=application.getCredentialResolver(); + if (!cr) { + m_log.warn("found encrypted assertion, but no CredentialResolver was available"); + throw FatalProfileException("Assertion was encrypted, but no decryption credentials are available."); + } + + // Attempt to decrypt it. + try { + Locker credlocker(cr); + auto_ptr tokenwrapper(encassertions.front()->decrypt(*cr, relyingParty->getXMLString("entityID").second, &mcc)); + newtoken = dynamic_cast(tokenwrapper.get()); + if (newtoken) { + tokenwrapper.release(); + if (m_log.isDebugEnabled()) + m_log.debugStream() << "decrypted Assertion: " << *newtoken << logging::eol; + // Free the Response now, so we know this is a stand-alone token later. + delete wrapper.release(); + } + } + catch (exception& ex) { + m_log.error(ex.what()); + throw; + } + } + else { + if (assertions.size() > 1) + m_log.warn("simple resolver only supports one assertion in the query response"); + newtoken = assertions.front(); + } if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) { m_log.error("assertion unsigned, rejecting it based on signedAssertions policy"); - return true; + if (!wrapper.get()) + delete newtoken; + throw SecurityPolicyException("Rejected unsigned assertion based on local policy."); } try { // We're going to insist that the assertion issuer is the same as the peer. // Reset the policy's message bits and extract them from the assertion. - policy.reset(true); - policy.setMessageID(newtoken->getID()); - policy.setIssueInstant(newtoken->getIssueInstantEpoch()); - policy.setIssuer(newtoken->getIssuer()); - policy.evaluate(*newtoken); + policy->reset(true); + policy->setMessageID(newtoken->getID()); + policy->setIssueInstant(newtoken->getIssueInstantEpoch()); + policy->setIssuer(newtoken->getIssuer()); + policy->evaluate(*newtoken); // Now we can check the security status of the policy. - if (!policy.isAuthenticated()) + if (!policy->isAuthenticated()) throw SecurityPolicyException("Security of SAML 2.0 query result not established."); + + if (m_subjectMatch) { + // Check for subject match. + bool ownedName = false; + NameID* respName = newtoken->getSubject() ? newtoken->getSubject()->getNameID() : nullptr; + if (!respName) { + // Check for encryption. + EncryptedID* encname = newtoken->getSubject() ? newtoken->getSubject()->getEncryptedID() : nullptr; + if (encname) { + CredentialResolver* cr=application.getCredentialResolver(); + if (!cr) + m_log.warn("found EncryptedID, but no CredentialResolver was available"); + else { + Locker credlocker(cr); + auto_ptr decryptedID(encname->decrypt(*cr, relyingParty->getXMLString("entityID").second, &mcc)); + respName = dynamic_cast(decryptedID.get()); + if (respName) { + ownedName = true; + decryptedID.release(); + if (m_log.isDebugEnabled()) + m_log.debugStream() << "decrypted NameID: " << *respName << logging::eol; + } + } + } + } + + auto_ptr nameIDwrapper(ownedName ? respName : nullptr); + + if (!respName || !XMLString::equals(respName->getName(), name->getName()) || + !XMLString::equals(respName->getFormat(), name->getFormat()) || + !XMLString::equals(respName->getNameQualifier(), name->getNameQualifier()) || + !XMLString::equals(respName->getSPNameQualifier(), name->getSPNameQualifier())) { + if (respName) + m_log.warnStream() << "ignoring Assertion without strongly matching NameID in Subject: " << + *respName << logging::eol; + else + m_log.warn("ignoring Assertion without NameID in Subject"); + if (!wrapper.get()) + delete newtoken; + return; + } + } } catch (exception& ex) { m_log.error("assertion failed policy validation: %s", ex.what()); - return true; + if (!wrapper.get()) + delete newtoken; + throw; } - newtoken->detach(); - wrapper.release(); + if (wrapper.get()) { + newtoken->detach(); + wrapper.release(); // detach blows away the Response + } ctx.getResolvedAssertions().push_back(newtoken); // Finally, extract and filter the result. @@ -459,9 +554,8 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha m_log.error("caught exception extracting/filtering attributes from query result: %s", ex.what()); for_each(ctx.getResolvedAttributes().begin(), ctx.getResolvedAttributes().end(), xmltooling::cleanup()); ctx.getResolvedAttributes().clear(); + throw; } - - return true; } void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const @@ -473,9 +567,9 @@ void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const SimpleAggregationContext& qctx = dynamic_cast(ctx); // First we manufacture the appropriate NameID to use. - NameID* n=NULL; + NameID* n=nullptr; for (vector::const_iterator a = m_attributeIds.begin(); !n && a != m_attributeIds.end(); ++a) { - const Attribute* attr=NULL; + const Attribute* attr=nullptr; if (qctx.getSession()) { // Input attributes should be available via multimap. pair::const_iterator, multimap::const_iterator> range = @@ -550,13 +644,30 @@ void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const set history; + // Put initial IdP into history to prevent extra query. + if (qctx.getEntityID()) + history.insert(qctx.getEntityID()); + + // Prepare to track exceptions. + SimpleAttribute* exceptAttr = nullptr; + if (!m_exceptionId.empty()) { + exceptAttr = new SimpleAttribute(m_exceptionId); + } + auto_ptr exceptWrapper(exceptAttr); + // We have a master loop over all the possible sources of material. for (vector< pair >::const_iterator source = m_sources.begin(); source != m_sources.end(); ++source) { if (source->second) { // A literal entityID to query. if (history.count(source->first) == 0) { m_log.debug("issuing SAML query to (%s)", source->first.c_str()); - doQuery(qctx, source->first.c_str(), n ? n : qctx.getNameID()); + try { + doQuery(qctx, source->first.c_str(), n ? n : qctx.getNameID()); + } + catch (exception& ex) { + if (exceptAttr) + exceptAttr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what())); + } history.insert(source->first); } else { @@ -574,7 +685,13 @@ void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const for (vector::const_iterator link = links.begin(); link != links.end(); ++link) { if (history.count(*link) == 0) { m_log.debug("issuing SAML query to (%s)", link->c_str()); - doQuery(qctx, link->c_str(), n ? n : qctx.getNameID()); + try { + doQuery(qctx, link->c_str(), n ? n : qctx.getNameID()); + } + catch (exception& ex) { + if (exceptAttr) + exceptAttr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what())); + } history.insert(*link); } else { @@ -592,7 +709,13 @@ void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const for (vector::const_iterator link = links.begin(); link != links.end(); ++link) { if (history.count(*link) == 0) { m_log.debug("issuing SAML query to (%s)", link->c_str()); - doQuery(qctx, link->c_str(), n ? n : qctx.getNameID()); + try { + doQuery(qctx, link->c_str(), n ? n : qctx.getNameID()); + } + catch (exception& ex) { + if (exceptAttr) + exceptAttr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what())); + } history.insert(*link); } else { @@ -604,4 +727,8 @@ void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const } } } + + if (exceptAttr) { + qctx.getResolvedAttributes().push_back(exceptWrapper.release()); + } }