X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fattribute%2Fresolver%2Fimpl%2FQueryAttributeResolver.cpp;h=05f63aa6816dab04425e85f995963d0b2dfe3ff2;hb=c51bfd77603cf0ddb0b5e374c35586a8435895d6;hp=c5a92f8ffa7fc0f056a2c9f5a57132ae62a03433;hpb=9f5b4fda51ddafe8f06d9f27aed34b2ff9c01f49;p=shibboleth%2Fcpp-sp.git diff --git a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp index c5a92f8..05f63aa 100644 --- a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp @@ -1,22 +1,26 @@ -/* - * Copyright 2001-2007 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 +/** + * Licensed to the University Corporation for Advanced Internet + * Development, Inc. (UCAID) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * UCAID licenses this file to you 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 * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ /** * QueryAttributeResolver.cpp - * + * * AttributeResolver based on SAML queries. */ @@ -24,28 +28,32 @@ #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" #include "attribute/resolver/AttributeResolver.h" #include "attribute/resolver/ResolutionContext.h" #include "binding/SOAPClient.h" +#include "metadata/MetadataProviderCriteria.h" +#include "security/SecurityPolicy.h" +#include "security/SecurityPolicyProvider.h" #include "util/SPConstants.h" -#include +#include +#include #include -#include #include #include #include -#include #include #include #include +#include #include -#include +#include #include +#include #include #include @@ -57,7 +65,7 @@ using namespace opensaml::saml2p; using namespace opensaml::saml2md; using namespace opensaml; using namespace xmltooling; -using namespace log4cpp; +using namespace boost; using namespace std; namespace shibsp { @@ -66,22 +74,22 @@ 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(NULL) { + : m_query(true), m_app(application), m_request(nullptr), m_session(&session), m_metadata(nullptr), m_entity(nullptr), m_nameid(nullptr) { m_protocol = XMLString::transcode(session.getProtocol()); m_class = XMLString::transcode(session.getAuthnContextClassRef()); m_decl = XMLString::transcode(session.getAuthnContextDeclRef()); } - + QueryContext( const Application& application, + const GenericRequest* request, 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), + const NameID* nameid=nullptr, + const XMLCh* authncontext_class=nullptr, + const XMLCh* authncontext_decl=nullptr, + const vector* tokens=nullptr + ) : m_query(true), m_app(application), m_request(request), m_session(nullptr), m_metadata(nullptr), m_entity(issuer), m_protocol(protocol), m_nameid(nameid), m_class(authncontext_class), m_decl(authncontext_decl) { if (tokens) { @@ -99,7 +107,7 @@ namespace shibsp { } } } - + ~QueryContext() { if (m_session) { XMLString::release((XMLCh**)&m_protocol); @@ -108,10 +116,10 @@ namespace shibsp { } 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()); } - + bool doQuery() const { return m_query; } @@ -119,17 +127,20 @@ namespace shibsp { const Application& getApplication() const { return m_app; } + const GenericRequest* getRequest() const { + return m_request; + } const EntityDescriptor* getEntityDescriptor() const { 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(MetadataProviderCriteria(m_app, m_session->getEntityID())).first; } } - return NULL; + return nullptr; } const XMLCh* getProtocol() const { return m_protocol; @@ -146,7 +157,7 @@ namespace shibsp { const Session* getSession() const { return m_session; } - multimap& getResolvedAttributes() { + vector& getResolvedAttributes() { return m_attributes; } vector& getResolvedAssertions() { @@ -156,6 +167,7 @@ namespace shibsp { private: bool m_query; const Application& m_app; + const GenericRequest* m_request; const Session* m_session; mutable MetadataProvider* m_metadata; mutable const EntityDescriptor* m_entity; @@ -163,33 +175,45 @@ namespace shibsp { const NameID* m_nameid; const XMLCh* m_class; const XMLCh* m_decl; - multimap m_attributes; + vector m_attributes; vector m_assertions; }; - + class SHIBSP_DLLLOCAL QueryResolver : public AttributeResolver { public: QueryResolver(const DOMElement* e); - ~QueryResolver() { - for_each(m_SAML1Designators.begin(), m_SAML1Designators.end(), xmltooling::cleanup()); - for_each(m_SAML2Designators.begin(), m_SAML2Designators.end(), xmltooling::cleanup()); - } + ~QueryResolver() {} Lockable* lock() {return this;} void unlock() {} - + + // deprecated method 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 NameID* nameid=nullptr, + const XMLCh* authncontext_class=nullptr, + const XMLCh* authncontext_decl=nullptr, + const vector* tokens=nullptr, + const vector* attributes=nullptr ) const { - return new QueryContext(application,issuer,protocol,nameid,authncontext_class,authncontext_decl,tokens,attributes); + return createResolutionContext(application, nullptr, issuer, protocol, nameid, authncontext_class, authncontext_decl, tokens); + } + + ResolutionContext* createResolutionContext( + const Application& application, + const GenericRequest* request, + const EntityDescriptor* issuer, + const XMLCh* protocol, + 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 QueryContext(application, request, issuer, protocol, nameid, authncontext_class, authncontext_decl, tokens); } ResolutionContext* createResolutionContext(const Application& application, const Session& session) const { @@ -203,36 +227,40 @@ 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; - vector m_SAML1Designators; - vector m_SAML2Designators; + string m_policyId; + bool m_subjectMatch; + ptr_vector m_SAML1Designators; + ptr_vector m_SAML2Designators; + vector m_exceptionId; }; AttributeResolver* SHIBSP_DLLLOCAL QueryResolverFactory(const DOMElement* const & e) { return new QueryResolver(e); } - -}; -void SHIBSP_API shibsp::registerAttributeResolvers() -{ - SPConfig::getConfig().AttributeResolverManager.registerFactory(QUERY_ATTRIBUTE_RESOLVER, QueryResolverFactory); -} + 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); +}; -QueryResolver::QueryResolver(const DOMElement* e) : m_log(Category::getInstance(SHIBSP_LOGCAT".AttributeResolver")) +QueryResolver::QueryResolver(const DOMElement* e) + : m_log(Category::getInstance(SHIBSP_LOGCAT ".AttributeResolver.Query")), + m_policyId(XMLHelper::getAttrString(e, nullptr, policyId)), + m_subjectMatch(XMLHelper::getAttrBool(e, false, subjectMatch)) { #ifdef _DEBUG xmltooling::NDC ndc("QueryResolver"); #endif - + DOMElement* child = XMLHelper::getFirstChildElement(e); while (child) { try { - if (XMLHelper::isNodeNamed(e, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME)) { + if (XMLHelper::isNodeNamed(child, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME)) { auto_ptr obj(saml2::AttributeBuilder::buildOneFromElement(child)); saml2::Attribute* down = dynamic_cast(obj.get()); if (down) { @@ -240,7 +268,7 @@ QueryResolver::QueryResolver(const DOMElement* e) : m_log(Category::getInstance( obj.release(); } } - else if (XMLHelper::isNodeNamed(e, samlconstants::SAML1P_NS, AttributeDesignator::LOCAL_NAME)) { + else if (XMLHelper::isNodeNamed(child, samlconstants::SAML1_NS, AttributeDesignator::LOCAL_NAME)) { auto_ptr obj(AttributeDesignatorBuilder::buildOneFromElement(child)); AttributeDesignator* down = dynamic_cast(obj.get()); if (down) { @@ -254,37 +282,49 @@ QueryResolver::QueryResolver(const DOMElement* e) : m_log(Category::getInstance( } 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"); #endif int version = XMLString::equals(ctx.getProtocol(), samlconstants::SAML11_PROTOCOL_ENUM) ? 1 : 0; - const AttributeAuthorityDescriptor* AA = ctx.getEntityDescriptor()->getAttributeAuthorityDescriptor(ctx.getProtocol()); + const AttributeAuthorityDescriptor* AA = + 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; } - shibsp::SecurityPolicy policy(ctx.getApplication()); + const Application& application = ctx.getApplication(); + const PropertySet* relyingParty = application.getRelyingParty(ctx.getEntityDescriptor()); + + // Locate policy key. + const char* policyId = m_policyId.empty() ? application.getString("policyId").second : m_policyId.c_str(); + + // Set up policy and SOAP client. + scoped_ptr policy( + application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, nullptr, policyId) + ); + policy->getAudiences().push_back(relyingParty->getXMLString("entityID").second); MetadataCredentialCriteria mcc(*AA); - shibsp::SOAPClient soaper(policy); - const PropertySet* policySettings = - ctx.getApplication().getServiceProvider().getPolicySettings(ctx.getApplication().getString("policyId").second); - pair signedAssertions = policySettings->getBool("signedAssertions"); + shibsp::SOAPClient soaper(*policy); auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP); - saml1p::Response* response=NULL; + auto_ptr response; const vector& endpoints=AA->getAttributeServices(); - for (vector::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) { + for (indirect_iterator::const_iterator> ep = make_indirect_iterator(endpoints.begin()); + !response.get() && ep != make_indirect_iterator(endpoints.end()); ++ep) { + if (!XMLString::equals(ep->getBinding(), binding.get()) || !ep->getLocation()) + continue; + auto_ptr_char loc(ep->getLocation()); try { - 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()); @@ -293,201 +333,356 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const subject->setNameIdentifier(nameid); saml1p::AttributeQuery* query = saml1p::AttributeQueryBuilder::buildAttributeQuery(); query->setSubject(subject); - query->setResource(issuer.get()); - for (vector::const_iterator ad = m_SAML1Designators.begin(); ad!=m_SAML1Designators.end(); ++ad) - query->getAttributeDesignators().push_back((*ad)->cloneAttributeDesignator()); + query->setResource(relyingParty->getXMLString("entityID").second); + for (ptr_vector::const_iterator ad = m_SAML1Designators.begin(); ad != m_SAML1Designators.end(); ++ad) { + auto_ptr adwrapper(ad->cloneAttributeDesignator()); + query->getAttributeDesignators().push_back(adwrapper.get()); + adwrapper.release(); + } Request* request = RequestBuilder::buildRequest(); request->setAttributeQuery(query); request->setMinorVersion(version); SAML1SOAPClient client(soaper, false); - client.sendSAML(request, mcc, loc.get()); - response = client.receiveSAML(); + client.sendSAML(request, application.getId(), mcc, loc.get()); + response.reset(client.receiveSAML()); } catch (exception& ex) { - m_log.error("exception making SAML query: %s", ex.what()); + m_log.error("exception during SAML query to %s: %s", loc.get(), ex.what()); soaper.reset(); } } - if (!response) { + if (!response.get()) { 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()==NULL || + 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.size()>1) + const vector& assertions = const_cast(response.get())->getAssertions(); + if (assertions.empty()) { + m_log.warn("response from attribute authority was empty"); + return; + } + 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("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 { - policy.evaluate(*newtoken); - if (!policy.isSecure()) + // 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); + + // 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)); - tokval.validateAssertion(*newtoken); } catch (exception& ex) { - m_log.error("assertion failed policy/validation: %s", ex.what()); - return true; + m_log.error("assertion failed policy validation: %s", ex.what()); + throw; } newtoken->detach(); - wrapper.release(); + response.release(); // detach blows away the Response ctx.getResolvedAssertions().push_back(newtoken); // 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()); + const vector& statements = const_cast(newtoken)->getAttributeStatements(); + for (indirect_iterator::const_iterator> s = make_indirect_iterator(statements.begin()); + s != make_indirect_iterator(statements.end()); ++s) { + if (m_subjectMatch) { + // Check for subject match. + const NameIdentifier* respName = s->getSubject() ? s->getSubject()->getNameIdentifier() : nullptr; + if (!respName || !XMLString::equals(respName->getName(), ctx.getNameID()->getName()) || + !XMLString::equals(respName->getFormat(), ctx.getNameID()->getFormat()) || + !XMLString::equals(respName->getNameQualifier(), ctx.getNameID()->getNameQualifier())) { + if (respName) + m_log.warnStream() << "ignoring AttributeStatement without strongly matching NameIdentifier in Subject: " << + *respName << logging::eol; + else + m_log.warn("ignoring AttributeStatement without NameIdentifier in Subject"); + continue; + } + } + extractor->extractAttributes(application, ctx.getRequest(), AA, *s, ctx.getResolvedAttributes()); + } } - AttributeFilter* filter = ctx.getApplication().getAttributeFilter(); + AttributeFilter* filter = application.getAttributeFilter(); if (filter) { - BasicFilteringContext fc(ctx.getApplication(), ctx.getResolvedAttributes(), AA, ctx.getClassRef(), ctx.getDeclRef()); + 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(); + throw; } - - return true; } -bool QueryResolver::SAML2Query(QueryContext& ctx) const +void QueryResolver::SAML2Query(QueryContext& ctx) const { #ifdef _DEBUG 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.warn("no SAML 2 AttributeAuthority role found in metadata"); - return false; + return; } - shibsp::SecurityPolicy policy(ctx.getApplication()); + const Application& application = ctx.getApplication(); + const PropertySet* relyingParty = application.getRelyingParty(ctx.getEntityDescriptor()); + 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(); + + // Set up policy and SOAP client. + scoped_ptr policy( + application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, nullptr, policyId) + ); + policy->getAudiences().push_back(relyingParty->getXMLString("entityID").second); MetadataCredentialCriteria mcc(*AA); - shibsp::SOAPClient soaper(policy); - const PropertySet* policySettings = - ctx.getApplication().getServiceProvider().getPolicySettings(ctx.getApplication().getString("policyId").second); - pair signedAssertions = policySettings->getBool("signedAssertions"); + shibsp::SOAPClient soaper(*policy); auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP); - saml2p::StatusResponseType* srt=NULL; + auto_ptr srt; const vector& endpoints=AA->getAttributeServices(); - for (vector::const_iterator ep=endpoints.begin(); !srt && ep!=endpoints.end(); ++ep) { + for (indirect_iterator::const_iterator> ep = make_indirect_iterator(endpoints.begin()); + !srt.get() && ep != make_indirect_iterator(endpoints.end()); ++ep) { + if (!XMLString::equals(ep->getBinding(), binding.get()) || !ep->getLocation()) + continue; + auto_ptr_char loc(ep->getLocation()); try { - 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()); + encrypted->encrypt( + *ctx.getNameID(), + *(application.getMetadataProvider()), + mcc, + false, + relyingParty->getXMLString("encryptionAlg").second + ); + subject->setEncryptedID(encrypted.get()); + encrypted.release(); + } + else { + auto_ptr namewrapper(ctx.getNameID()->cloneNameID()); + subject->setNameID(namewrapper.get()); + namewrapper.release(); + } + 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()); + for (ptr_vector::const_iterator ad = m_SAML2Designators.begin(); ad != m_SAML2Designators.end(); ++ad) { + auto_ptr adwrapper(ad->cloneAttribute()); + query->getAttributes().push_back(adwrapper.get()); + adwrapper.release(); + } SAML2SOAPClient client(soaper, false); - client.sendSAML(query, mcc, loc.get()); - srt = client.receiveSAML(); + client.sendSAML(query, application.getId(), mcc, loc.get()); + srt.reset(client.receiveSAML()); } catch (exception& ex) { - m_log.error("exception making SAML query: %s", ex.what()); + m_log.error("exception during SAML query to %s: %s", loc.get(), ex.what()); soaper.reset(); } } - if (!srt) { + if (!srt.get()) { 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."); } - saml2p::Response* response = dynamic_cast(srt); + + saml2p::Response* response = dynamic_cast(srt.get()); 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 returned a SAML error"); - return true; + throw FatalProfileException("Attribute authority returned a SAML error."); } + saml2::Assertion* newtoken = nullptr; + auto_ptr newtokenwrapper; const vector& assertions = const_cast(response)->getAssertions(); - if (assertions.size()>1) - m_log.warn("simple resolver only supports one assertion in the query response"); + if (assertions.empty()) { + // 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(response); - 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."); + } + + // With this flag on, we block unauthenticated ciphertext when decrypting, + // unless the protocol was authenticated. + pair authenticatedCipher = application.getBool("requireAuthenticatedEncryption"); + if (policy->isAuthenticated()) + authenticatedCipher.second = false; + + // Attempt to decrypt it. + try { + Locker credlocker(cr); + auto_ptr tokenwrapper( + encassertions.front()->decrypt( + *cr, relyingParty->getXMLString("entityID").second, &mcc, authenticatedCipher.first && authenticatedCipher.second + ) + ); + newtoken = dynamic_cast(tokenwrapper.get()); + if (newtoken) { + tokenwrapper.release(); + newtokenwrapper.reset(newtoken); + if (m_log.isDebugEnabled()) + m_log.debugStream() << "decrypted assertion: " << *newtoken << logging::eol; + } + } + catch (exception& ex) { + m_log.error("failed to decrypt assertion: %s", 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; + throw SecurityPolicyException("Rejected unsigned assertion based on local policy."); } try { - policy.evaluate(*newtoken); - if (!policy.isSecure()) + // 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); + + // 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)); - tokval.validateAssertion(*newtoken); + + if (m_subjectMatch) { + // Check for subject match. + auto_ptr nameIDwrapper; + 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) { + decryptedID.release(); + nameIDwrapper.reset(respName); + if (m_log.isDebugEnabled()) + m_log.debugStream() << "decrypted NameID: " << *respName << logging::eol; + } + } + } + } + + if (!respName || !XMLString::equals(respName->getName(), ctx.getNameID()->getName()) || + !XMLString::equals(respName->getFormat(), ctx.getNameID()->getFormat()) || + !XMLString::equals(respName->getNameQualifier(), ctx.getNameID()->getNameQualifier()) || + !XMLString::equals(respName->getSPNameQualifier(), ctx.getNameID()->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"); + return; + } + } } catch (exception& ex) { - m_log.error("assertion failed policy/validation: %s", ex.what()); - return true; + m_log.error("assertion failed policy validation: %s", ex.what()); + throw; } - newtoken->detach(); - wrapper.release(); + // If the token's embedded, detach it. + if (!newtokenwrapper.get()) { + newtoken->detach(); + srt.release(); // detach blows away the Response, so avoid a double free + newtokenwrapper.reset(newtoken); + } ctx.getResolvedAssertions().push_back(newtoken); + newtokenwrapper.release(); // 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, ctx.getRequest(), AA, *newtoken, ctx.getResolvedAttributes()); } - AttributeFilter* filter = ctx.getApplication().getAttributeFilter(); + AttributeFilter* filter = application.getAttributeFilter(); if (filter) { - BasicFilteringContext fc(ctx.getApplication(), ctx.getResolvedAttributes(), AA, ctx.getClassRef(), ctx.getDeclRef()); + 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(); + throw; } - - return true; } void QueryResolver::resolveAttributes(ResolutionContext& ctx) const @@ -502,19 +697,32 @@ 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); + 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.warn("can't attempt attribute query, either no NameID or no metadata to use"); } - 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); + } + catch (exception& ex) { + // Already logged. + if (!m_exceptionId.empty()) { + auto_ptr attr(new SimpleAttribute(m_exceptionId)); + attr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what())); + qctx.getResolvedAttributes().push_back(attr.get()); + attr.release(); } - else - m_log.warn("SSO protocol does not allow for attribute query"); } - else - m_log.warn("can't attempt attribute query, either no NameID or no metadata to use"); }