X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fattribute%2Fresolver%2Fimpl%2FQueryAttributeResolver.cpp;h=2b36e995a60defe788d1c3c9640f35186b2bbc5e;hb=392d1448deb48beb75f219532ac248b4776f16db;hp=b87375737218a2bd46a691bfb32315e51f3b0a32;hpb=fa1832aa42cb8a1f9461e31c9f105bd65a1981e1;p=shibboleth%2Fsp.git diff --git a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp index b873757..2b36e99 100644 --- a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp @@ -33,7 +33,6 @@ #include "binding/SOAPClient.h" #include "util/SPConstants.h" -#include #include #include #include @@ -57,7 +56,6 @@ using namespace opensaml::saml2p; using namespace opensaml::saml2md; using namespace opensaml; using namespace xmltooling; -using namespace log4cpp; using namespace std; namespace shibsp { @@ -66,16 +64,23 @@ namespace shibsp { { public: QueryContext(const Application& application, const Session& session) - : m_query(true), m_app(application), m_session(&session), m_metadata(NULL), m_entity(NULL), m_nameid(session.getNameID()) { + : m_query(true), m_app(application), m_session(&session), m_metadata(NULL), m_entity(NULL), m_nameid(NULL) { + m_protocol = XMLString::transcode(session.getProtocol()); + m_class = XMLString::transcode(session.getAuthnContextClassRef()); + m_decl = XMLString::transcode(session.getAuthnContextDeclRef()); } QueryContext( const Application& application, const EntityDescriptor* issuer, + const XMLCh* protocol, const NameID* nameid, + const XMLCh* authncontext_class=NULL, + const XMLCh* authncontext_decl=NULL, const vector* tokens=NULL, - const multimap* attributes=NULL - ) : m_query(true), m_app(application), m_session(NULL), m_metadata(NULL), m_entity(issuer), m_nameid(nameid) { + const vector* attributes=NULL + ) : m_query(true), m_app(application), m_session(NULL), m_metadata(NULL), m_entity(issuer), + m_protocol(protocol), m_nameid(nameid), m_class(authncontext_class), m_decl(authncontext_decl) { if (tokens) { for (vector::const_iterator t = tokens->begin(); t!=tokens->end(); ++t) { @@ -94,9 +99,14 @@ namespace shibsp { } ~QueryContext() { + if (m_session) { + XMLString::release((XMLCh**)&m_protocol); + XMLString::release((XMLCh**)&m_class); + XMLString::release((XMLCh**)&m_decl); + } if (m_metadata) m_metadata->unlock(); - for_each(m_attributes.begin(), m_attributes.end(), cleanup_pair()); + for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup()); for_each(m_assertions.begin(), m_assertions.end(), xmltooling::cleanup()); } @@ -111,21 +121,30 @@ namespace shibsp { if (m_entity) return m_entity; if (m_session && m_session->getEntityID()) { - m_metadata = m_app.getMetadataProvider(); + m_metadata = m_app.getMetadataProvider(false); if (m_metadata) { m_metadata->lock(); - return m_entity = m_metadata->getEntityDescriptor(m_session->getEntityID()); + return m_entity = m_metadata->getEntityDescriptor(MetadataProvider::Criteria(m_session->getEntityID())).first; } } return NULL; } + const XMLCh* getProtocol() const { + return m_protocol; + } const NameID* getNameID() const { - return m_nameid; + return m_session ? m_session->getNameID() : m_nameid; + } + const XMLCh* getClassRef() const { + return m_class; + } + const XMLCh* getDeclRef() const { + return m_decl; } const Session* getSession() const { return m_session; - } - multimap& getResolvedAttributes() { + } + vector& getResolvedAttributes() { return m_attributes; } vector& getResolvedAssertions() { @@ -138,8 +157,11 @@ namespace shibsp { const Session* m_session; mutable MetadataProvider* m_metadata; mutable const EntityDescriptor* m_entity; + const XMLCh* m_protocol; const NameID* m_nameid; - multimap m_attributes; + const XMLCh* m_class; + const XMLCh* m_decl; + vector m_attributes; vector m_assertions; }; @@ -158,11 +180,14 @@ namespace shibsp { ResolutionContext* createResolutionContext( const Application& application, const EntityDescriptor* issuer, + const XMLCh* protocol, const NameID* nameid, + const XMLCh* authncontext_class=NULL, + const XMLCh* authncontext_decl=NULL, const vector* tokens=NULL, - const multimap* attributes=NULL + const vector* attributes=NULL ) const { - return new QueryContext(application,issuer,nameid,tokens,attributes); + return new QueryContext(application,issuer,protocol,nameid,authncontext_class,authncontext_decl,tokens,attributes); } ResolutionContext* createResolutionContext(const Application& application, const Session& session) const { @@ -171,6 +196,10 @@ namespace shibsp { void resolveAttributes(ResolutionContext& ctx) const; + void getAttributeIds(vector& attributes) const { + // Nothing to do, only the extractor would actually generate them. + } + private: bool SAML1Query(QueryContext& ctx) const; bool SAML2Query(QueryContext& ctx) const; @@ -231,23 +260,19 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const xmltooling::NDC ndc("query"); #endif - int version = 1; - const AttributeAuthorityDescriptor* AA = ctx.getEntityDescriptor()->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM); + int version = XMLString::equals(ctx.getProtocol(), samlconstants::SAML11_PROTOCOL_ENUM) ? 1 : 0; + const AttributeAuthorityDescriptor* AA = + find_if(ctx.getEntityDescriptor()->getAttributeAuthorityDescriptors(), isValidForProtocol(ctx.getProtocol())); if (!AA) { - AA = ctx.getEntityDescriptor()->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM); - version = 0; - } - if (!AA) { - m_log.info("no SAML 1.x AttributeAuthority role found in metadata"); + m_log.warn("no SAML 1.%d AttributeAuthority role found in metadata", version); return false; } - shibsp::SecurityPolicy policy(ctx.getApplication()); + const Application& application = ctx.getApplication(); + const PropertySet* relyingParty = application.getRelyingParty(ctx.getEntityDescriptor()); + shibsp::SecurityPolicy policy(application); MetadataCredentialCriteria mcc(*AA); shibsp::SOAPClient soaper(policy); - const PropertySet* policySettings = - ctx.getApplication().getServiceProvider().getPolicySettings(ctx.getApplication().getString("policyId").second); - pair signedAssertions = policySettings->getBool("signedAssertions"); auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP); saml1p::Response* response=NULL; @@ -257,7 +282,6 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const if (!XMLString::equals((*ep)->getBinding(),binding.get())) continue; auto_ptr_char loc((*ep)->getLocation()); - auto_ptr_XMLCh issuer(ctx.getApplication().getString("entityID").second); NameIdentifier* nameid = NameIdentifierBuilder::buildNameIdentifier(); nameid->setName(ctx.getNameID()->getName()); nameid->setFormat(ctx.getNameID()->getFormat()); @@ -266,15 +290,15 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const subject->setNameIdentifier(nameid); saml1p::AttributeQuery* query = saml1p::AttributeQueryBuilder::buildAttributeQuery(); query->setSubject(subject); - query->setResource(issuer.get()); + query->setResource(relyingParty->getXMLString("entityID").second); for (vector::const_iterator ad = m_SAML1Designators.begin(); ad!=m_SAML1Designators.end(); ++ad) query->getAttributeDesignators().push_back((*ad)->cloneAttributeDesignator()); Request* request = RequestBuilder::buildRequest(); request->setAttributeQuery(query); request->setMinorVersion(version); - SAML1SOAPClient client(soaper); - client.sendSAML(request, mcc, loc.get()); + SAML1SOAPClient client(soaper, false); + client.sendSAML(request, application.getId(), mcc, loc.get()); response = client.receiveSAML(); } catch (exception& ex) { @@ -284,27 +308,49 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const } if (!response) { - m_log.error("unable to successfully query for attributes"); + m_log.error("unable to obtain a SAML response from attribute authority"); return false; } + else if (!response->getStatus() || !response->getStatus()->getStatusCode() || response->getStatus()->getStatusCode()->getValue()==NULL || + *(response->getStatus()->getStatusCode()->getValue()) != saml1p::StatusCode::SUCCESS) { + delete response; + m_log.error("attribute authority returned a SAML error"); + return true; + } const vector& assertions = const_cast(response)->getAssertions(); - if (assertions.size()>1) + if (assertions.empty()) { + delete response; + m_log.warn("response from attribute authority was empty"); + return true; + } + 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(); + pair signedAssertions = relyingParty->getBool("signedAssertions"); if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) { m_log.error("assertion unsigned, rejecting it based on signedAssertions policy"); return true; } 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->getAssertionID()); + policy.setIssueInstant(newtoken->getIssueInstantEpoch()); + policy.setIssuer(newtoken->getIssuer()); policy.evaluate(*newtoken); - if (!policy.isSecure()) + + // Now we can check the security status of the policy. + if (!policy.isAuthenticated()) throw SecurityPolicyException("Security of SAML 1.x query result not established."); - saml1::AssertionValidator tokval(ctx.getApplication().getAudiences(), time(NULL)); + + // Lastly, check it over. + saml1::AssertionValidator tokval(relyingParty->getXMLString("entityID").second, application.getAudiences(), time(NULL)); tokval.validateAssertion(*newtoken); } catch (exception& ex) { @@ -318,22 +364,22 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const // Finally, extract and filter the result. try { - AttributeExtractor* extractor = ctx.getApplication().getAttributeExtractor(); + AttributeExtractor* extractor = application.getAttributeExtractor(); if (extractor) { Locker extlocker(extractor); - extractor->extractAttributes(ctx.getApplication(), AA, *newtoken, ctx.getResolvedAttributes()); + extractor->extractAttributes(application, AA, *newtoken, ctx.getResolvedAttributes()); } - AttributeFilter* filter = ctx.getApplication().getAttributeFilter(); + AttributeFilter* filter = application.getAttributeFilter(); if (filter) { - BasicFilteringContext fc(ctx.getApplication(), AA); + BasicFilteringContext fc(application, ctx.getResolvedAttributes(), AA, ctx.getClassRef(), ctx.getDeclRef()); Locker filtlocker(filter); filter->filterAttributes(fc, ctx.getResolvedAttributes()); } } catch (exception& ex) { m_log.error("caught exception extracting/filtering attributes from query result: %s", ex.what()); - for_each(ctx.getResolvedAttributes().begin(), ctx.getResolvedAttributes().end(), cleanup_pair()); + for_each(ctx.getResolvedAttributes().begin(), ctx.getResolvedAttributes().end(), xmltooling::cleanup()); ctx.getResolvedAttributes().clear(); } @@ -346,18 +392,21 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const xmltooling::NDC ndc("query"); #endif - const AttributeAuthorityDescriptor* AA = ctx.getEntityDescriptor()->getAttributeAuthorityDescriptor(samlconstants::SAML20P_NS); + const AttributeAuthorityDescriptor* AA = + find_if(ctx.getEntityDescriptor()->getAttributeAuthorityDescriptors(), isValidForProtocol(samlconstants::SAML20P_NS)); if (!AA) { - m_log.info("no SAML 2 AttributeAuthority role found in metadata"); + m_log.warn("no SAML 2 AttributeAuthority role found in metadata"); return false; } - shibsp::SecurityPolicy policy(ctx.getApplication()); + const Application& application = ctx.getApplication(); + shibsp::SecurityPolicy policy(application); MetadataCredentialCriteria mcc(*AA); shibsp::SOAPClient soaper(policy); - const PropertySet* policySettings = - ctx.getApplication().getServiceProvider().getPolicySettings(ctx.getApplication().getString("policyId").second); - pair signedAssertions = policySettings->getBool("signedAssertions"); + + const PropertySet* relyingParty = application.getRelyingParty(ctx.getEntityDescriptor()); + pair signedAssertions = relyingParty->getBool("signedAssertions"); + pair encryption = relyingParty->getString("encryption"); auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP); saml2p::StatusResponseType* srt=NULL; @@ -367,19 +416,35 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const if (!XMLString::equals((*ep)->getBinding(),binding.get())) continue; auto_ptr_char loc((*ep)->getLocation()); - auto_ptr_XMLCh issuer(ctx.getApplication().getString("entityID").second); - saml2::Subject* subject = saml2::SubjectBuilder::buildSubject(); - subject->setNameID(ctx.getNameID()->cloneNameID()); + auto_ptr subject(saml2::SubjectBuilder::buildSubject()); + + // Encrypt the NameID? + if (encryption.first && (!strcmp(encryption.second, "true") || !strcmp(encryption.second, "back"))) { + auto_ptr encrypted(EncryptedIDBuilder::buildEncryptedID()); + MetadataCredentialCriteria mcc(*AA); + encrypted->encrypt( + *ctx.getNameID(), + *(application.getMetadataProvider()), + mcc, + false, + relyingParty->getXMLString("encryptionAlg").second + ); + subject->setEncryptedID(encrypted.release()); + } + else { + subject->setNameID(ctx.getNameID()->cloneNameID()); + } + saml2p::AttributeQuery* query = saml2p::AttributeQueryBuilder::buildAttributeQuery(); - query->setSubject(subject); + query->setSubject(subject.release()); Issuer* iss = IssuerBuilder::buildIssuer(); - iss->setName(issuer.get()); + iss->setName(relyingParty->getXMLString("entityID").second); query->setIssuer(iss); for (vector::const_iterator ad = m_SAML2Designators.begin(); ad!=m_SAML2Designators.end(); ++ad) query->getAttributes().push_back((*ad)->cloneAttribute()); - SAML2SOAPClient client(soaper); - client.sendSAML(query, mcc, loc.get()); + SAML2SOAPClient client(soaper, false); + client.sendSAML(query, application.getId(), mcc, loc.get()); srt = client.receiveSAML(); } catch (exception& ex) { @@ -389,7 +454,7 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const } if (!srt) { - m_log.error("unable to successfully query for attributes"); + m_log.error("unable to obtain a SAML response from attribute authority"); return false; } saml2p::Response* response = dynamic_cast(srt); @@ -398,12 +463,23 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const m_log.error("message was not a samlp:Response"); return true; } + else if (!response->getStatus() || !response->getStatus()->getStatusCode() || + !XMLString::equals(response->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) { + delete srt; + m_log.error("attribute authority returned a SAML error"); + return true; + } const vector& assertions = const_cast(response)->getAssertions(); - if (assertions.size()>1) + if (assertions.empty()) { + delete srt; + m_log.warn("response from attribute authority was empty"); + return true; + } + else if (assertions.size()>1) m_log.warn("simple resolver only supports one assertion in the query response"); - auto_ptr wrapper(response); + auto_ptr wrapper(srt); saml2::Assertion* newtoken = assertions.front(); if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) { @@ -412,10 +488,20 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const } 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); - if (!policy.isSecure()) + + // Now we can check the security status of the policy. + if (!policy.isAuthenticated()) throw SecurityPolicyException("Security of SAML 2.0 query result not established."); - saml2::AssertionValidator tokval(ctx.getApplication().getAudiences(), time(NULL)); + + // Lastly, check it over. + saml2::AssertionValidator tokval(relyingParty->getXMLString("entityID").second, application.getAudiences(), time(NULL)); tokval.validateAssertion(*newtoken); } catch (exception& ex) { @@ -429,22 +515,22 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const // Finally, extract and filter the result. try { - AttributeExtractor* extractor = ctx.getApplication().getAttributeExtractor(); + AttributeExtractor* extractor = application.getAttributeExtractor(); if (extractor) { Locker extlocker(extractor); - extractor->extractAttributes(ctx.getApplication(), AA, *newtoken, ctx.getResolvedAttributes()); + extractor->extractAttributes(application, AA, *newtoken, ctx.getResolvedAttributes()); } - AttributeFilter* filter = ctx.getApplication().getAttributeFilter(); + AttributeFilter* filter = application.getAttributeFilter(); if (filter) { - BasicFilteringContext fc(ctx.getApplication(), AA); + BasicFilteringContext fc(application, ctx.getResolvedAttributes(), AA, ctx.getClassRef(), ctx.getDeclRef()); Locker filtlocker(filter); filter->filterAttributes(fc, ctx.getResolvedAttributes()); } } catch (exception& ex) { m_log.error("caught exception extracting/filtering attributes from query result: %s", ex.what()); - for_each(ctx.getResolvedAttributes().begin(), ctx.getResolvedAttributes().end(), cleanup_pair()); + for_each(ctx.getResolvedAttributes().begin(), ctx.getResolvedAttributes().end(), xmltooling::cleanup()); ctx.getResolvedAttributes().clear(); } @@ -464,11 +550,18 @@ void QueryResolver::resolveAttributes(ResolutionContext& ctx) const } if (qctx.getNameID() && qctx.getEntityDescriptor()) { - m_log.debug("attempting SAML 2.0 attribute query"); - if (!SAML2Query(qctx)) { + 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.warn("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"); }