X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=shibsp%2Fattribute%2Fresolver%2Fimpl%2FQueryAttributeResolver.cpp;h=05f63aa6816dab04425e85f995963d0b2dfe3ff2;hb=c51bfd77603cf0ddb0b5e374c35586a8435895d6;hp=e6e231ecad10378c7b8da8770c0cc98b98a29cf3;hpb=b2574d7bc4161294be2de59d97deaa500391fad9;p=shibboleth%2Fcpp-sp.git diff --git a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp index e6e231e..05f63aa 100644 --- a/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp +++ b/shibsp/attribute/resolver/impl/QueryAttributeResolver.cpp @@ -1,17 +1,21 @@ -/* - * Copyright 2001-2007 Internet2 +/** + * 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. * - * 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 + * 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. */ /** @@ -24,7 +28,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" @@ -32,20 +36,24 @@ #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 @@ -57,6 +65,7 @@ using namespace opensaml::saml2p; using namespace opensaml::saml2md; using namespace opensaml; using namespace xmltooling; +using namespace boost; using namespace std; namespace shibsp { @@ -65,7 +74,7 @@ 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()); @@ -73,14 +82,14 @@ namespace shibsp { QueryContext( const Application& application, + const GenericRequest* request, 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 - ) : 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) { @@ -118,6 +127,9 @@ 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; @@ -128,7 +140,7 @@ namespace shibsp { return m_entity = m_metadata->getEntityDescriptor(MetadataProviderCriteria(m_app, m_session->getEntityID())).first; } } - return NULL; + return nullptr; } const XMLCh* getProtocol() const { return m_protocol; @@ -155,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; @@ -170,25 +183,37 @@ namespace shibsp { { 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=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 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 { @@ -202,12 +227,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; - 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) @@ -215,9 +243,15 @@ 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); }; -QueryResolver::QueryResolver(const DOMElement* e) : m_log(Category::getInstance(SHIBSP_LOGCAT".AttributeResolver.Query")) +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"); @@ -226,7 +260,7 @@ QueryResolver::QueryResolver(const DOMElement* e) : m_log(Category::getInstance( 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) { @@ -234,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) { @@ -248,9 +282,13 @@ 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"); @@ -261,22 +299,31 @@ 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(); const PropertySet* relyingParty = application.getRelyingParty(ctx.getEntityDescriptor()); - shibsp::SecurityPolicy policy(application); + + // 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); + 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) { - if (!XMLString::equals((*ep)->getBinding(),binding.get())) + 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()); + auto_ptr_char loc(ep->getLocation()); try { NameIdentifier* nameid = NameIdentifierBuilder::buildNameIdentifier(); nameid->setName(ctx.getNameID()->getName()); @@ -287,15 +334,18 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const saml1p::AttributeQuery* query = saml1p::AttributeQueryBuilder::buildAttributeQuery(); query->setSubject(subject); 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()); + 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, application.getId(), mcc, loc.get()); - response = client.receiveSAML(); + response.reset(client.receiveSAML()); } catch (exception& ex) { m_log.error("exception during SAML query to %s: %s", loc.get(), ex.what()); @@ -303,59 +353,53 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const } } - 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(); + const vector& assertions = const_cast(response.get())->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(); 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 { // 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); + 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()) + if (!policy->isAuthenticated()) throw SecurityPolicyException("Security of SAML 1.x query result not established."); - - // Lastly, check it over. - saml1::AssertionValidator tokval(relyingParty->getXMLString("entityID").second, application.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. @@ -363,7 +407,25 @@ bool QueryResolver::SAML1Query(QueryContext& ctx) const AttributeExtractor* extractor = application.getAttributeExtractor(); if (extractor) { Locker extlocker(extractor); - extractor->extractAttributes(application, 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 = application.getAttributeFilter(); @@ -377,12 +439,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"); @@ -392,32 +453,39 @@ 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(); - shibsp::SecurityPolicy policy(application); - MetadataCredentialCriteria mcc(*AA); - shibsp::SOAPClient soaper(policy); - 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); + 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) { - if (!XMLString::equals((*ep)->getBinding(),binding.get())) + 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()); + auto_ptr_char loc(ep->getLocation()); try { 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()), @@ -425,10 +493,13 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const false, relyingParty->getXMLString("encryptionAlg").second ); - subject->setEncryptedID(encrypted.release()); + subject->setEncryptedID(encrypted.get()); + encrypted.release(); } else { - subject->setNameID(ctx.getNameID()->cloneNameID()); + auto_ptr namewrapper(ctx.getNameID()->cloneNameID()); + subject->setNameID(namewrapper.get()); + namewrapper.release(); } saml2p::AttributeQuery* query = saml2p::AttributeQueryBuilder::buildAttributeQuery(); @@ -436,12 +507,15 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const Issuer* iss = IssuerBuilder::buildIssuer(); 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, application.getId(), mcc, loc.get()); - srt = client.receiveSAML(); + srt.reset(client.receiveSAML()); } catch (exception& ex) { m_log.error("exception during SAML query to %s: %s", loc.get(), ex.what()); @@ -449,72 +523,151 @@ bool QueryResolver::SAML2Query(QueryContext& ctx) const } } - 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.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"); + // 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"); + } + + 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; - auto_ptr wrapper(srt); - saml2::Assertion* newtoken = assertions.front(); + // 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 { // 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."); - // Lastly, check it over. - saml2::AssertionValidator tokval(relyingParty->getXMLString("entityID").second, application.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 = application.getAttributeExtractor(); if (extractor) { Locker extlocker(extractor); - extractor->extractAttributes(application, AA, *newtoken, ctx.getResolvedAttributes()); + extractor->extractAttributes(application, ctx.getRequest(), AA, *newtoken, ctx.getResolvedAttributes()); } AttributeFilter* filter = application.getAttributeFilter(); @@ -528,9 +681,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 @@ -545,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"); }