From c3d778ed20d2f2dedac5bd6db12fc7fbb8ce7734 Mon Sep 17 00:00:00 2001 From: Scott Cantor Date: Mon, 25 Oct 2010 01:34:28 +0000 Subject: [PATCH] https://issues.shibboleth.net/jira/browse/SSPCPP-313 --- .../resolver/impl/QueryAttributeResolver.cpp | 102 ++++++++++++--------- .../impl/SimpleAggregationAttributeResolver.cpp | 81 +++++++++++----- 2 files changed, 113 insertions(+), 70 deletions(-) diff --git a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp index 0c6a453..85a1d88 100644 --- a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp @@ -24,7 +24,7 @@ #include "Application.h" #include "ServiceProvider.h" #include "SessionCache.h" -#include "attribute/Attribute.h" +#include "attribute/SimpleAttribute.h" #include "attribute/filtering/AttributeFilter.h" #include "attribute/filtering/BasicFilteringContext.h" #include "attribute/resolver/AttributeExtractor.h" @@ -45,7 +45,9 @@ #include #include #include +#include #include +#include #include #include @@ -201,14 +203,15 @@ namespace shibsp { } private: - bool SAML1Query(QueryContext& ctx) const; - bool SAML2Query(QueryContext& ctx) const; + void SAML1Query(QueryContext& ctx) const; + void SAML2Query(QueryContext& ctx) const; Category& m_log; string m_policyId; bool m_subjectMatch; vector m_SAML1Designators; vector m_SAML2Designators; + vector m_exceptionId; }; AttributeResolver* SHIBSP_DLLLOCAL QueryResolverFactory(const DOMElement* const & e) @@ -216,6 +219,7 @@ namespace shibsp { return new QueryResolver(e); } + static const XMLCh exceptionId[] = UNICODE_LITERAL_11(e,x,c,e,p,t,i,o,n,I,d); 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); }; @@ -254,9 +258,13 @@ QueryResolver::QueryResolver(const DOMElement* e) } child = XMLHelper::getNextSiblingElement(child); } + + string exid(XMLHelper::getAttrString(e, nullptr, exceptionId)); + if (!exid.empty()) + m_exceptionId.push_back(exid); } -bool QueryResolver::SAML1Query(QueryContext& ctx) const +void QueryResolver::SAML1Query(QueryContext& ctx) const { #ifdef _DEBUG xmltooling::NDC ndc("query"); @@ -267,7 +275,7 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const find_if(ctx.getEntityDescriptor()->getAttributeAuthorityDescriptors(), isValidForProtocol(ctx.getProtocol())); if (!AA) { m_log.warn("no SAML 1.%d AttributeAuthority role found in metadata", version); - return false; + return; } const Application& application = ctx.getApplication(); @@ -319,23 +327,24 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const if (!response) { m_log.error("unable to obtain a SAML response from attribute authority"); - return false; + throw BindingException("Unable to obtain a SAML response from attribute authority."); } else if (!response->getStatus() || !response->getStatus()->getStatusCode() || response->getStatus()->getStatusCode()->getValue()==nullptr || *(response->getStatus()->getStatusCode()->getValue()) != saml1p::StatusCode::SUCCESS) { delete response; m_log.error("attribute authority returned a SAML error"); - return true; + throw FatalProfileException("Attribute authority returned a SAML error."); } const vector& assertions = const_cast(response)->getAssertions(); if (assertions.empty()) { delete response; m_log.warn("response from attribute authority was empty"); - return true; + return; } - else if (assertions.size()>1) + else if (assertions.size()>1) { m_log.warn("simple resolver only supports one assertion in the query response"); + } auto_ptr wrapper(response); saml1::Assertion* newtoken = assertions.front(); @@ -343,7 +352,7 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const pair signedAssertions = relyingParty->getBool("requireSignedAssertions"); if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) { m_log.error("assertion unsigned, rejecting it based on signedAssertions policy"); - return true; + throw SecurityPolicyException("Rejected unsigned assertion based on local policy."); } try { @@ -361,7 +370,7 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const } catch (exception& ex) { m_log.error("assertion failed policy validation: %s", ex.what()); - return true; + throw; } newtoken->detach(); @@ -404,12 +413,11 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const 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; } -bool QueryResolver::SAML2Query(QueryContext& ctx) const +void QueryResolver::SAML2Query(QueryContext& ctx) const { #ifdef _DEBUG xmltooling::NDC ndc("query"); @@ -419,7 +427,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const find_if(ctx.getEntityDescriptor()->getAttributeAuthorityDescriptors(), isValidForProtocol(samlconstants::SAML20P_NS)); if (!AA) { m_log.warn("no SAML 2 AttributeAuthority role found in metadata"); - return false; + return; } const Application& application = ctx.getApplication(); @@ -484,7 +492,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const if (!srt) { m_log.error("unable to obtain a SAML response from attribute authority"); - return false; + throw BindingException("Unable to obtain a SAML response from attribute authority."); } auto_ptr wrapper(srt); @@ -492,12 +500,12 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const saml2p::Response* response = dynamic_cast(srt); if (!response) { 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)) { m_log.error("attribute authority returned a SAML error"); - return true; + throw FatalProfileException("Attribute authority returned a SAML error."); } saml2::Assertion* newtoken = nullptr; @@ -507,7 +515,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const const vector& encassertions = const_cast(response)->getEncryptedAssertions(); if (encassertions.empty()) { m_log.warn("response from attribute authority was empty"); - return true; + return; } else if (encassertions.size() > 1) { m_log.warn("simple resolver only supports one assertion in the query response"); @@ -516,7 +524,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const CredentialResolver* cr=application.getCredentialResolver(); if (!cr) { m_log.warn("found encrypted assertion, but no CredentialResolver was available"); - return true; + throw FatalProfileException("Assertion was encrypted, but no decryption credentials are available."); } // Attempt to decrypt it. @@ -528,18 +536,13 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const 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()); - } - if (newtoken) { - // Free the Response now, so we know this is a stand-alone token later. - delete wrapper.release(); - } - else { - // Nothing decrypted, should already be logged. - return true; + throw; } } else { @@ -552,7 +555,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const m_log.error("assertion unsigned, rejecting it based on signedAssertions policy"); if (!wrapper.get()) delete newtoken; - return true; + throw SecurityPolicyException("Rejected unsigned assertion based on local policy."); } try { @@ -606,7 +609,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const m_log.warn("ignoring Assertion without NameID in Subject"); if (!wrapper.get()) delete newtoken; - return true; + return; } } } @@ -614,7 +617,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const m_log.error("assertion failed policy validation: %s", ex.what()); if (!wrapper.get()) delete newtoken; - return true; + throw; } if (wrapper.get()) { @@ -642,9 +645,8 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const 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 QueryResolver::resolveAttributes(ResolutionContext& ctx) const @@ -659,21 +661,31 @@ void QueryResolver::resolveAttributes(ResolutionContext& ctx) const return; } - if (qctx.getNameID() && qctx.getEntityDescriptor()) { - if (XMLString::equals(qctx.getProtocol(), samlconstants::SAML20P_NS)) { - m_log.debug("attempting SAML 2.0 attribute query"); - SAML2Query(qctx); - } - else if (XMLString::equals(qctx.getProtocol(), samlconstants::SAML11_PROTOCOL_ENUM) || - XMLString::equals(qctx.getProtocol(), samlconstants::SAML10_PROTOCOL_ENUM)) { - m_log.debug("attempting SAML 1.x attribute query"); - SAML1Query(qctx); + try { + if (qctx.getNameID() && qctx.getEntityDescriptor()) { + if (XMLString::equals(qctx.getProtocol(), samlconstants::SAML20P_NS)) { + m_log.debug("attempting SAML 2.0 attribute query"); + SAML2Query(qctx); + } + else if (XMLString::equals(qctx.getProtocol(), samlconstants::SAML11_PROTOCOL_ENUM) || + XMLString::equals(qctx.getProtocol(), samlconstants::SAML10_PROTOCOL_ENUM)) { + m_log.debug("attempting SAML 1.x attribute query"); + SAML1Query(qctx); + } + else { + m_log.info("SSO protocol does not allow for attribute query"); + } } else { - m_log.info("SSO protocol does not allow for attribute query"); + m_log.warn("can't attempt attribute query, either no NameID or no metadata to use"); } } - else { - m_log.warn("can't attempt attribute query, either no NameID or no metadata to use"); + catch (exception& ex) { + // Already logged. + if (!m_exceptionId.empty()) { + SimpleAttribute* attr = new SimpleAttribute(m_exceptionId); + attr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what())); + qctx.getResolvedAttributes().push_back(attr); + } } } diff --git a/shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp b/shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp index 2d27a30..573e6ec 100644 --- a/shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/SimpleAggregationAttributeResolver.cpp @@ -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" @@ -47,6 +48,7 @@ #include #include #include +#include #include #include @@ -185,7 +187,7 @@ 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; @@ -196,6 +198,7 @@ namespace shibsp { TrustEngine* m_trust; vector m_designators; vector< pair > m_sources; + vector m_exceptionId; }; AttributeResolver* SHIBSP_DLLLOCAL SimpleAggregationResolverFactory(const DOMElement* const & e) @@ -206,6 +209,7 @@ 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); @@ -247,6 +251,10 @@ SimpleAggregationResolver::SimpleAggregationResolver(const DOMElement* e) 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) { string t(XMLHelper::getAttrString(child, nullptr, _type)); @@ -306,7 +314,7 @@ SimpleAggregationResolver::SimpleAggregationResolver(const DOMElement* e) } } -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"); @@ -319,11 +327,11 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha (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); @@ -392,7 +400,7 @@ 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); @@ -400,12 +408,12 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha saml2p::Response* response = dynamic_cast(srt); if (!response) { 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)) { 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; @@ -416,7 +424,7 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha const_cast(response)->getEncryptedAssertions(); if (encassertions.empty()) { m_log.warn("response from attribute authority was empty"); - return true; + return; } else if (encassertions.size() > 1) { m_log.warn("simple resolver only supports one assertion in the query response"); @@ -425,7 +433,7 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha CredentialResolver* cr=application.getCredentialResolver(); if (!cr) { m_log.warn("found encrypted assertion, but no CredentialResolver was available"); - return true; + throw FatalProfileException("Assertion was encrypted, but no decryption credentials are available."); } // Attempt to decrypt it. @@ -437,18 +445,13 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha 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()); - } - if (newtoken) { - // Free the Response now, so we know this is a stand-alone token later. - delete wrapper.release(); - } - else { - // Nothing decrypted, should already be logged. - return true; + throw; } } else { @@ -461,7 +464,7 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha m_log.error("assertion unsigned, rejecting it based on signedAssertions policy"); if (!wrapper.get()) delete newtoken; - return true; + throw SecurityPolicyException("Rejected unsigned assertion based on local policy."); } try { @@ -515,7 +518,7 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha m_log.warn("ignoring Assertion without NameID in Subject"); if (!wrapper.get()) delete newtoken; - return true; + return; } } } @@ -523,7 +526,7 @@ bool SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const cha m_log.error("assertion failed policy validation: %s", ex.what()); if (!wrapper.get()) delete newtoken; - return true; + throw; } if (wrapper.get()) { @@ -551,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 @@ -646,13 +648,26 @@ void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const 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 { @@ -670,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 { @@ -688,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 { @@ -700,4 +727,8 @@ void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const } } } + + if (exceptAttr) { + qctx.getResolvedAttributes().push_back(exceptWrapper.release()); + } } -- 2.1.4