SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-sp.git] / shibsp / attribute / resolver / impl / QueryAttributeResolver.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * QueryAttributeResolver.cpp
23  *
24  * AttributeResolver based on SAML queries.
25  */
26
27 #include "internal.h"
28 #include "Application.h"
29 #include "ServiceProvider.h"
30 #include "SessionCache.h"
31 #include "attribute/SimpleAttribute.h"
32 #include "attribute/filtering/AttributeFilter.h"
33 #include "attribute/filtering/BasicFilteringContext.h"
34 #include "attribute/resolver/AttributeExtractor.h"
35 #include "attribute/resolver/AttributeResolver.h"
36 #include "attribute/resolver/ResolutionContext.h"
37 #include "binding/SOAPClient.h"
38 #include "metadata/MetadataProviderCriteria.h"
39 #include "security/SecurityPolicy.h"
40 #include "security/SecurityPolicyProvider.h"
41 #include "util/SPConstants.h"
42
43 #include <boost/iterator/indirect_iterator.hpp>
44 #include <boost/ptr_container/ptr_vector.hpp>
45 #include <saml/exceptions.h>
46 #include <saml/saml1/binding/SAML1SOAPClient.h>
47 #include <saml/saml1/core/Assertions.h>
48 #include <saml/saml1/core/Protocols.h>
49 #include <saml/saml2/binding/SAML2SOAPClient.h>
50 #include <saml/saml2/core/Protocols.h>
51 #include <saml/saml2/metadata/Metadata.h>
52 #include <saml/saml2/metadata/MetadataCredentialCriteria.h>
53 #include <saml/saml2/metadata/MetadataProvider.h>
54 #include <xmltooling/XMLToolingConfig.h>
55 #include <xmltooling/util/NDC.h>
56 #include <xmltooling/util/URLEncoder.h>
57 #include <xmltooling/util/XMLHelper.h>
58 #include <xercesc/util/XMLUniDefs.hpp>
59
60 using namespace shibsp;
61 using namespace opensaml::saml1;
62 using namespace opensaml::saml1p;
63 using namespace opensaml::saml2;
64 using namespace opensaml::saml2p;
65 using namespace opensaml::saml2md;
66 using namespace opensaml;
67 using namespace xmltooling;
68 using namespace boost;
69 using namespace std;
70
71 namespace shibsp {
72
73     class SHIBSP_DLLLOCAL QueryContext : public ResolutionContext
74     {
75     public:
76         QueryContext(const Application& application, const Session& session)
77                 : m_query(true), m_app(application), m_request(nullptr), m_session(&session), m_metadata(nullptr), m_entity(nullptr), m_nameid(nullptr) {
78             m_protocol = XMLString::transcode(session.getProtocol());
79             m_class = XMLString::transcode(session.getAuthnContextClassRef());
80             m_decl = XMLString::transcode(session.getAuthnContextDeclRef());
81         }
82
83         QueryContext(
84             const Application& application,
85             const GenericRequest* request,
86             const EntityDescriptor* issuer,
87             const XMLCh* protocol,
88             const NameID* nameid=nullptr,
89             const XMLCh* authncontext_class=nullptr,
90             const XMLCh* authncontext_decl=nullptr,
91             const vector<const opensaml::Assertion*>* tokens=nullptr
92             ) : m_query(true), m_app(application), m_request(request), m_session(nullptr), m_metadata(nullptr), m_entity(issuer),
93                 m_protocol(protocol), m_nameid(nameid), m_class(authncontext_class), m_decl(authncontext_decl) {
94
95             if (tokens) {
96                 for (vector<const opensaml::Assertion*>::const_iterator t = tokens->begin(); t!=tokens->end(); ++t) {
97                     const saml2::Assertion* token2 = dynamic_cast<const saml2::Assertion*>(*t);
98                     if (token2 && !token2->getAttributeStatements().empty()) {
99                         m_query = false;
100                     }
101                     else {
102                         const saml1::Assertion* token1 = dynamic_cast<const saml1::Assertion*>(*t);
103                         if (token1 && !token1->getAttributeStatements().empty()) {
104                             m_query = false;
105                         }
106                     }
107                 }
108             }
109         }
110
111         ~QueryContext() {
112             if (m_session) {
113                 XMLString::release((XMLCh**)&m_protocol);
114                 XMLString::release((XMLCh**)&m_class);
115                 XMLString::release((XMLCh**)&m_decl);
116             }
117             if (m_metadata)
118                 m_metadata->unlock();
119             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<shibsp::Attribute>());
120             for_each(m_assertions.begin(), m_assertions.end(), xmltooling::cleanup<opensaml::Assertion>());
121         }
122
123         bool doQuery() const {
124             return m_query;
125         }
126
127         const Application& getApplication() const {
128             return m_app;
129         }
130         const GenericRequest* getRequest() const {
131             return m_request;
132         }
133         const EntityDescriptor* getEntityDescriptor() const {
134             if (m_entity)
135                 return m_entity;
136             if (m_session && m_session->getEntityID()) {
137                 m_metadata = m_app.getMetadataProvider(false);
138                 if (m_metadata) {
139                     m_metadata->lock();
140                     return m_entity = m_metadata->getEntityDescriptor(MetadataProviderCriteria(m_app, m_session->getEntityID())).first;
141                 }
142             }
143             return nullptr;
144         }
145         const XMLCh* getProtocol() const {
146             return m_protocol;
147         }
148         const NameID* getNameID() const {
149             return m_session ? m_session->getNameID() : m_nameid;
150         }
151         const XMLCh* getClassRef() const {
152             return m_class;
153         }
154         const XMLCh* getDeclRef() const {
155             return m_decl;
156         }
157         const Session* getSession() const {
158             return m_session;
159         }
160         vector<shibsp::Attribute*>& getResolvedAttributes() {
161             return m_attributes;
162         }
163         vector<opensaml::Assertion*>& getResolvedAssertions() {
164             return m_assertions;
165         }
166
167     private:
168         bool m_query;
169         const Application& m_app;
170         const GenericRequest* m_request;
171         const Session* m_session;
172         mutable MetadataProvider* m_metadata;
173         mutable const EntityDescriptor* m_entity;
174         const XMLCh* m_protocol;
175         const NameID* m_nameid;
176         const XMLCh* m_class;
177         const XMLCh* m_decl;
178         vector<shibsp::Attribute*> m_attributes;
179         vector<opensaml::Assertion*> m_assertions;
180     };
181
182     class SHIBSP_DLLLOCAL QueryResolver : public AttributeResolver
183     {
184     public:
185         QueryResolver(const DOMElement* e);
186         ~QueryResolver() {}
187
188         Lockable* lock() {return this;}
189         void unlock() {}
190
191         // deprecated method
192         ResolutionContext* createResolutionContext(
193             const Application& application,
194             const EntityDescriptor* issuer,
195             const XMLCh* protocol,
196             const NameID* nameid=nullptr,
197             const XMLCh* authncontext_class=nullptr,
198             const XMLCh* authncontext_decl=nullptr,
199             const vector<const opensaml::Assertion*>* tokens=nullptr,
200             const vector<shibsp::Attribute*>* attributes=nullptr
201             ) const {
202             return createResolutionContext(application, nullptr, issuer, protocol, nameid, authncontext_class, authncontext_decl, tokens);
203         }
204
205         ResolutionContext* createResolutionContext(
206             const Application& application,
207             const GenericRequest* request,
208             const EntityDescriptor* issuer,
209             const XMLCh* protocol,
210             const NameID* nameid=nullptr,
211             const XMLCh* authncontext_class=nullptr,
212             const XMLCh* authncontext_decl=nullptr,
213             const vector<const opensaml::Assertion*>* tokens=nullptr,
214             const vector<shibsp::Attribute*>* attributes=nullptr
215             ) const {
216             return new QueryContext(application, request, issuer, protocol, nameid, authncontext_class, authncontext_decl, tokens);
217         }
218
219         ResolutionContext* createResolutionContext(const Application& application, const Session& session) const {
220             return new QueryContext(application,session);
221         }
222
223         void resolveAttributes(ResolutionContext& ctx) const;
224
225         void getAttributeIds(vector<string>& attributes) const {
226             // Nothing to do, only the extractor would actually generate them.
227         }
228
229     private:
230         void SAML1Query(QueryContext& ctx) const;
231         void SAML2Query(QueryContext& ctx) const;
232
233         Category& m_log;
234         string m_policyId;
235         bool m_subjectMatch;
236         ptr_vector<AttributeDesignator> m_SAML1Designators;
237         ptr_vector<saml2::Attribute> m_SAML2Designators;
238         vector<string> m_exceptionId;
239     };
240
241     AttributeResolver* SHIBSP_DLLLOCAL QueryResolverFactory(const DOMElement* const & e)
242     {
243         return new QueryResolver(e);
244     }
245
246     static const XMLCh exceptionId[] =  UNICODE_LITERAL_11(e,x,c,e,p,t,i,o,n,I,d);
247     static const XMLCh policyId[] =     UNICODE_LITERAL_8(p,o,l,i,c,y,I,d);
248     static const XMLCh subjectMatch[] = UNICODE_LITERAL_12(s,u,b,j,e,c,t,M,a,t,c,h);
249 };
250
251 QueryResolver::QueryResolver(const DOMElement* e)
252     : m_log(Category::getInstance(SHIBSP_LOGCAT ".AttributeResolver.Query")),
253         m_policyId(XMLHelper::getAttrString(e, nullptr, policyId)),
254         m_subjectMatch(XMLHelper::getAttrBool(e, false, subjectMatch))
255 {
256 #ifdef _DEBUG
257     xmltooling::NDC ndc("QueryResolver");
258 #endif
259
260     DOMElement* child = XMLHelper::getFirstChildElement(e);
261     while (child) {
262         try {
263             if (XMLHelper::isNodeNamed(child, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME)) {
264                 auto_ptr<XMLObject> obj(saml2::AttributeBuilder::buildOneFromElement(child));
265                 saml2::Attribute* down = dynamic_cast<saml2::Attribute*>(obj.get());
266                 if (down) {
267                     m_SAML2Designators.push_back(down);
268                     obj.release();
269                 }
270             }
271             else if (XMLHelper::isNodeNamed(child, samlconstants::SAML1_NS, AttributeDesignator::LOCAL_NAME)) {
272                 auto_ptr<XMLObject> obj(AttributeDesignatorBuilder::buildOneFromElement(child));
273                 AttributeDesignator* down = dynamic_cast<AttributeDesignator*>(obj.get());
274                 if (down) {
275                     m_SAML1Designators.push_back(down);
276                     obj.release();
277                 }
278             }
279         }
280         catch (exception& ex) {
281             m_log.error("exception loading attribute designator: %s", ex.what());
282         }
283         child = XMLHelper::getNextSiblingElement(child);
284     }
285
286     string exid(XMLHelper::getAttrString(e, nullptr, exceptionId));
287     if (!exid.empty())
288         m_exceptionId.push_back(exid);
289 }
290
291 void QueryResolver::SAML1Query(QueryContext& ctx) const
292 {
293 #ifdef _DEBUG
294     xmltooling::NDC ndc("query");
295 #endif
296
297     int version = XMLString::equals(ctx.getProtocol(), samlconstants::SAML11_PROTOCOL_ENUM) ? 1 : 0;
298     const AttributeAuthorityDescriptor* AA =
299         find_if(ctx.getEntityDescriptor()->getAttributeAuthorityDescriptors(), isValidForProtocol(ctx.getProtocol()));
300     if (!AA) {
301         m_log.warn("no SAML 1.%d AttributeAuthority role found in metadata", version);
302         return;
303     }
304
305     const Application& application = ctx.getApplication();
306     const PropertySet* relyingParty = application.getRelyingParty(ctx.getEntityDescriptor());
307
308     // Locate policy key.
309     const char* policyId = m_policyId.empty() ? application.getString("policyId").second : m_policyId.c_str();
310
311     // Set up policy and SOAP client.
312     scoped_ptr<SecurityPolicy> policy(
313         application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, nullptr, policyId)
314         );
315     policy->getAudiences().push_back(relyingParty->getXMLString("entityID").second);
316     MetadataCredentialCriteria mcc(*AA);
317     shibsp::SOAPClient soaper(*policy);
318
319     auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP);
320     auto_ptr<saml1p::Response> response;
321     const vector<AttributeService*>& endpoints=AA->getAttributeServices();
322     for (indirect_iterator<vector<AttributeService*>::const_iterator> ep = make_indirect_iterator(endpoints.begin());
323             !response.get() && ep != make_indirect_iterator(endpoints.end()); ++ep) {
324         if (!XMLString::equals(ep->getBinding(), binding.get()) || !ep->getLocation())
325             continue;
326         auto_ptr_char loc(ep->getLocation());
327         try {
328             NameIdentifier* nameid = NameIdentifierBuilder::buildNameIdentifier();
329             nameid->setName(ctx.getNameID()->getName());
330             nameid->setFormat(ctx.getNameID()->getFormat());
331             nameid->setNameQualifier(ctx.getNameID()->getNameQualifier());
332             saml1::Subject* subject = saml1::SubjectBuilder::buildSubject();
333             subject->setNameIdentifier(nameid);
334             saml1p::AttributeQuery* query = saml1p::AttributeQueryBuilder::buildAttributeQuery();
335             query->setSubject(subject);
336             query->setResource(relyingParty->getXMLString("entityID").second);
337             for (ptr_vector<AttributeDesignator>::const_iterator ad = m_SAML1Designators.begin(); ad != m_SAML1Designators.end(); ++ad) {
338                 auto_ptr<AttributeDesignator> adwrapper(ad->cloneAttributeDesignator());
339                 query->getAttributeDesignators().push_back(adwrapper.get());
340                 adwrapper.release();
341             }
342             Request* request = RequestBuilder::buildRequest();
343             request->setAttributeQuery(query);
344             request->setMinorVersion(version);
345
346             SAML1SOAPClient client(soaper, false);
347             client.sendSAML(request, application.getId(), mcc, loc.get());
348             response.reset(client.receiveSAML());
349         }
350         catch (exception& ex) {
351             m_log.error("exception during SAML query to %s: %s", loc.get(), ex.what());
352             soaper.reset();
353         }
354     }
355
356     if (!response.get()) {
357         m_log.error("unable to obtain a SAML response from attribute authority");
358         throw BindingException("Unable to obtain a SAML response from attribute authority.");
359     }
360     else if (!response->getStatus() || !response->getStatus()->getStatusCode() || response->getStatus()->getStatusCode()->getValue()==nullptr ||
361             *(response->getStatus()->getStatusCode()->getValue()) != saml1p::StatusCode::SUCCESS) {
362         m_log.error("attribute authority returned a SAML error");
363         throw FatalProfileException("Attribute authority returned a SAML error.");
364     }
365
366     const vector<saml1::Assertion*>& assertions = const_cast<const saml1p::Response*>(response.get())->getAssertions();
367     if (assertions.empty()) {
368         m_log.warn("response from attribute authority was empty");
369         return;
370     }
371     else if (assertions.size() > 1) {
372         m_log.warn("simple resolver only supports one assertion in the query response");
373     }
374
375     saml1::Assertion* newtoken = assertions.front();
376
377     pair<bool,bool> signedAssertions = relyingParty->getBool("requireSignedAssertions");
378     if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) {
379         m_log.error("assertion unsigned, rejecting it based on signedAssertions policy");
380         throw SecurityPolicyException("Rejected unsigned assertion based on local policy.");
381     }
382
383     try {
384         // We're going to insist that the assertion issuer is the same as the peer.
385         // Reset the policy's message bits and extract them from the assertion.
386         policy->reset(true);
387         policy->setMessageID(newtoken->getAssertionID());
388         policy->setIssueInstant(newtoken->getIssueInstantEpoch());
389         policy->setIssuer(newtoken->getIssuer());
390         policy->evaluate(*newtoken);
391
392         // Now we can check the security status of the policy.
393         if (!policy->isAuthenticated())
394             throw SecurityPolicyException("Security of SAML 1.x query result not established.");
395     }
396     catch (exception& ex) {
397         m_log.error("assertion failed policy validation: %s", ex.what());
398         throw;
399     }
400
401     newtoken->detach();
402     response.release();  // detach blows away the Response
403     ctx.getResolvedAssertions().push_back(newtoken);
404
405     // Finally, extract and filter the result.
406     try {
407         AttributeExtractor* extractor = application.getAttributeExtractor();
408         if (extractor) {
409             Locker extlocker(extractor);
410             const vector<saml1::AttributeStatement*>& statements = const_cast<const saml1::Assertion*>(newtoken)->getAttributeStatements();
411             for (indirect_iterator<vector<saml1::AttributeStatement*>::const_iterator> s = make_indirect_iterator(statements.begin());
412                     s != make_indirect_iterator(statements.end()); ++s) {
413                 if (m_subjectMatch) {
414                     // Check for subject match.
415                     const NameIdentifier* respName = s->getSubject() ? s->getSubject()->getNameIdentifier() : nullptr;
416                     if (!respName || !XMLString::equals(respName->getName(), ctx.getNameID()->getName()) ||
417                         !XMLString::equals(respName->getFormat(), ctx.getNameID()->getFormat()) ||
418                         !XMLString::equals(respName->getNameQualifier(), ctx.getNameID()->getNameQualifier())) {
419                         if (respName)
420                             m_log.warnStream() << "ignoring AttributeStatement without strongly matching NameIdentifier in Subject: " <<
421                                 *respName << logging::eol;
422                         else
423                             m_log.warn("ignoring AttributeStatement without NameIdentifier in Subject");
424                         continue;
425                     }
426                 }
427                 extractor->extractAttributes(application, ctx.getRequest(), AA, *s, ctx.getResolvedAttributes());
428             }
429         }
430
431         AttributeFilter* filter = application.getAttributeFilter();
432         if (filter) {
433             BasicFilteringContext fc(application, ctx.getResolvedAttributes(), AA, ctx.getClassRef(), ctx.getDeclRef());
434             Locker filtlocker(filter);
435             filter->filterAttributes(fc, ctx.getResolvedAttributes());
436         }
437     }
438     catch (exception& ex) {
439         m_log.error("caught exception extracting/filtering attributes from query result: %s", ex.what());
440         for_each(ctx.getResolvedAttributes().begin(), ctx.getResolvedAttributes().end(), xmltooling::cleanup<shibsp::Attribute>());
441         ctx.getResolvedAttributes().clear();
442         throw;
443     }
444 }
445
446 void QueryResolver::SAML2Query(QueryContext& ctx) const
447 {
448 #ifdef _DEBUG
449     xmltooling::NDC ndc("query");
450 #endif
451
452     const AttributeAuthorityDescriptor* AA =
453         find_if(ctx.getEntityDescriptor()->getAttributeAuthorityDescriptors(), isValidForProtocol(samlconstants::SAML20P_NS));
454     if (!AA) {
455         m_log.warn("no SAML 2 AttributeAuthority role found in metadata");
456         return;
457     }
458
459     const Application& application = ctx.getApplication();
460     const PropertySet* relyingParty = application.getRelyingParty(ctx.getEntityDescriptor());
461     pair<bool,bool> signedAssertions = relyingParty->getBool("requireSignedAssertions");
462     pair<bool,const char*> encryption = relyingParty->getString("encryption");
463
464     // Locate policy key.
465     const char* policyId = m_policyId.empty() ? application.getString("policyId").second : m_policyId.c_str();
466
467     // Set up policy and SOAP client.
468     scoped_ptr<SecurityPolicy> policy(
469         application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, nullptr, policyId)
470         );
471     policy->getAudiences().push_back(relyingParty->getXMLString("entityID").second);
472     MetadataCredentialCriteria mcc(*AA);
473     shibsp::SOAPClient soaper(*policy);
474
475     auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);
476     auto_ptr<saml2p::StatusResponseType> srt;
477     const vector<AttributeService*>& endpoints=AA->getAttributeServices();
478     for (indirect_iterator<vector<AttributeService*>::const_iterator> ep = make_indirect_iterator(endpoints.begin());
479             !srt.get() && ep != make_indirect_iterator(endpoints.end()); ++ep) {
480         if (!XMLString::equals(ep->getBinding(), binding.get())  || !ep->getLocation())
481             continue;
482         auto_ptr_char loc(ep->getLocation());
483         try {
484             auto_ptr<saml2::Subject> subject(saml2::SubjectBuilder::buildSubject());
485
486             // Encrypt the NameID?
487             if (encryption.first && (!strcmp(encryption.second, "true") || !strcmp(encryption.second, "back"))) {
488                 auto_ptr<EncryptedID> encrypted(EncryptedIDBuilder::buildEncryptedID());
489                 encrypted->encrypt(
490                     *ctx.getNameID(),
491                     *(application.getMetadataProvider()),
492                     mcc,
493                     false,
494                     relyingParty->getXMLString("encryptionAlg").second
495                     );
496                 subject->setEncryptedID(encrypted.get());
497                 encrypted.release();
498             }
499             else {
500                 auto_ptr<NameID> namewrapper(ctx.getNameID()->cloneNameID());
501                 subject->setNameID(namewrapper.get());
502                 namewrapper.release();
503             }
504
505             saml2p::AttributeQuery* query = saml2p::AttributeQueryBuilder::buildAttributeQuery();
506             query->setSubject(subject.release());
507             Issuer* iss = IssuerBuilder::buildIssuer();
508             iss->setName(relyingParty->getXMLString("entityID").second);
509             query->setIssuer(iss);
510             for (ptr_vector<saml2::Attribute>::const_iterator ad = m_SAML2Designators.begin(); ad != m_SAML2Designators.end(); ++ad) {
511                 auto_ptr<saml2::Attribute> adwrapper(ad->cloneAttribute());
512                 query->getAttributes().push_back(adwrapper.get());
513                 adwrapper.release();
514             }
515
516             SAML2SOAPClient client(soaper, false);
517             client.sendSAML(query, application.getId(), mcc, loc.get());
518             srt.reset(client.receiveSAML());
519         }
520         catch (exception& ex) {
521             m_log.error("exception during SAML query to %s: %s", loc.get(), ex.what());
522             soaper.reset();
523         }
524     }
525
526     if (!srt.get()) {
527         m_log.error("unable to obtain a SAML response from attribute authority");
528         throw BindingException("Unable to obtain a SAML response from attribute authority.");
529     }
530
531     saml2p::Response* response = dynamic_cast<saml2p::Response*>(srt.get());
532     if (!response) {
533         m_log.error("message was not a samlp:Response");
534         throw FatalProfileException("Attribute authority returned an unrecognized message.");
535     }
536     else if (!response->getStatus() || !response->getStatus()->getStatusCode() ||
537             !XMLString::equals(response->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) {
538         m_log.error("attribute authority returned a SAML error");
539         throw FatalProfileException("Attribute authority returned a SAML error.");
540     }
541
542     saml2::Assertion* newtoken = nullptr;
543     auto_ptr<saml2::Assertion> newtokenwrapper;
544     const vector<saml2::Assertion*>& assertions = const_cast<const saml2p::Response*>(response)->getAssertions();
545     if (assertions.empty()) {
546         // Check for encryption.
547         const vector<saml2::EncryptedAssertion*>& encassertions = const_cast<const saml2p::Response*>(response)->getEncryptedAssertions();
548         if (encassertions.empty()) {
549             m_log.warn("response from attribute authority was empty");
550             return;
551         }
552         else if (encassertions.size() > 1) {
553             m_log.warn("simple resolver only supports one assertion in the query response");
554         }
555
556         CredentialResolver* cr = application.getCredentialResolver();
557         if (!cr) {
558             m_log.warn("found encrypted assertion, but no CredentialResolver was available");
559             throw FatalProfileException("Assertion was encrypted, but no decryption credentials are available.");
560         }
561
562         // With this flag on, we block unauthenticated ciphertext when decrypting,
563         // unless the protocol was authenticated.
564         pair<bool,bool> authenticatedCipher = application.getBool("requireAuthenticatedEncryption");
565         if (policy->isAuthenticated())
566             authenticatedCipher.second = false;
567
568         // Attempt to decrypt it.
569         try {
570             Locker credlocker(cr);
571             auto_ptr<XMLObject> tokenwrapper(
572                 encassertions.front()->decrypt(
573                     *cr, relyingParty->getXMLString("entityID").second, &mcc, authenticatedCipher.first && authenticatedCipher.second
574                     )
575                 );
576             newtoken = dynamic_cast<saml2::Assertion*>(tokenwrapper.get());
577             if (newtoken) {
578                 tokenwrapper.release();
579                 newtokenwrapper.reset(newtoken);
580                 if (m_log.isDebugEnabled())
581                     m_log.debugStream() << "decrypted assertion: " << *newtoken << logging::eol;
582             }
583         }
584         catch (exception& ex) {
585             m_log.error("failed to decrypt assertion: %s", ex.what());
586             throw;
587         }
588     }
589     else {
590         if (assertions.size() > 1)
591             m_log.warn("simple resolver only supports one assertion in the query response");
592         newtoken = assertions.front();
593     }
594
595     if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) {
596         m_log.error("assertion unsigned, rejecting it based on signedAssertions policy");
597         throw SecurityPolicyException("Rejected unsigned assertion based on local policy.");
598     }
599
600     try {
601         // We're going to insist that the assertion issuer is the same as the peer.
602         // Reset the policy's message bits and extract them from the assertion.
603         policy->reset(true);
604         policy->setMessageID(newtoken->getID());
605         policy->setIssueInstant(newtoken->getIssueInstantEpoch());
606         policy->setIssuer(newtoken->getIssuer());
607         policy->evaluate(*newtoken);
608
609         // Now we can check the security status of the policy.
610         if (!policy->isAuthenticated())
611             throw SecurityPolicyException("Security of SAML 2.0 query result not established.");
612
613         if (m_subjectMatch) {
614             // Check for subject match.
615             auto_ptr<NameID> nameIDwrapper;
616             NameID* respName = newtoken->getSubject() ? newtoken->getSubject()->getNameID() : nullptr;
617             if (!respName) {
618                 // Check for encryption.
619                 EncryptedID* encname = newtoken->getSubject() ? newtoken->getSubject()->getEncryptedID() : nullptr;
620                 if (encname) {
621                     CredentialResolver* cr=application.getCredentialResolver();
622                     if (!cr)
623                         m_log.warn("found EncryptedID, but no CredentialResolver was available");
624                     else {
625                         Locker credlocker(cr);
626                         auto_ptr<XMLObject> decryptedID(encname->decrypt(*cr, relyingParty->getXMLString("entityID").second, &mcc));
627                         respName = dynamic_cast<NameID*>(decryptedID.get());
628                         if (respName) {
629                             decryptedID.release();
630                             nameIDwrapper.reset(respName);
631                             if (m_log.isDebugEnabled())
632                                 m_log.debugStream() << "decrypted NameID: " << *respName << logging::eol;
633                         }
634                     }
635                 }
636             }
637
638             if (!respName || !XMLString::equals(respName->getName(), ctx.getNameID()->getName()) ||
639                 !XMLString::equals(respName->getFormat(), ctx.getNameID()->getFormat()) ||
640                 !XMLString::equals(respName->getNameQualifier(), ctx.getNameID()->getNameQualifier()) ||
641                 !XMLString::equals(respName->getSPNameQualifier(), ctx.getNameID()->getSPNameQualifier())) {
642                 if (respName)
643                     m_log.warnStream() << "ignoring Assertion without strongly matching NameID in Subject: " <<
644                         *respName << logging::eol;
645                 else
646                     m_log.warn("ignoring Assertion without NameID in Subject");
647                 return;
648             }
649         }
650     }
651     catch (exception& ex) {
652         m_log.error("assertion failed policy validation: %s", ex.what());
653         throw;
654     }
655
656     // If the token's embedded, detach it.
657     if (!newtokenwrapper.get()) {
658         newtoken->detach();
659         srt.release();  // detach blows away the Response, so avoid a double free
660         newtokenwrapper.reset(newtoken);
661     }
662     ctx.getResolvedAssertions().push_back(newtoken);
663     newtokenwrapper.release();
664
665     // Finally, extract and filter the result.
666     try {
667         AttributeExtractor* extractor = application.getAttributeExtractor();
668         if (extractor) {
669             Locker extlocker(extractor);
670             extractor->extractAttributes(application, ctx.getRequest(), AA, *newtoken, ctx.getResolvedAttributes());
671         }
672
673         AttributeFilter* filter = application.getAttributeFilter();
674         if (filter) {
675             BasicFilteringContext fc(application, ctx.getResolvedAttributes(), AA, ctx.getClassRef(), ctx.getDeclRef());
676             Locker filtlocker(filter);
677             filter->filterAttributes(fc, ctx.getResolvedAttributes());
678         }
679     }
680     catch (exception& ex) {
681         m_log.error("caught exception extracting/filtering attributes from query result: %s", ex.what());
682         for_each(ctx.getResolvedAttributes().begin(), ctx.getResolvedAttributes().end(), xmltooling::cleanup<shibsp::Attribute>());
683         ctx.getResolvedAttributes().clear();
684         throw;
685     }
686 }
687
688 void QueryResolver::resolveAttributes(ResolutionContext& ctx) const
689 {
690 #ifdef _DEBUG
691     xmltooling::NDC ndc("resolveAttributes");
692 #endif
693
694     QueryContext& qctx = dynamic_cast<QueryContext&>(ctx);
695     if (!qctx.doQuery()) {
696         m_log.debug("found AttributeStatement in input to new session, skipping query");
697         return;
698     }
699
700     try {
701         if (qctx.getNameID() && qctx.getEntityDescriptor()) {
702             if (XMLString::equals(qctx.getProtocol(), samlconstants::SAML20P_NS)) {
703                 m_log.debug("attempting SAML 2.0 attribute query");
704                 SAML2Query(qctx);
705             }
706             else if (XMLString::equals(qctx.getProtocol(), samlconstants::SAML11_PROTOCOL_ENUM) ||
707                     XMLString::equals(qctx.getProtocol(), samlconstants::SAML10_PROTOCOL_ENUM)) {
708                 m_log.debug("attempting SAML 1.x attribute query");
709                 SAML1Query(qctx);
710             }
711             else {
712                 m_log.info("SSO protocol does not allow for attribute query");
713             }
714         }
715         else {
716             m_log.warn("can't attempt attribute query, either no NameID or no metadata to use");
717         }
718     }
719     catch (exception& ex) {
720         // Already logged.
721         if (!m_exceptionId.empty()) {
722             auto_ptr<SimpleAttribute> attr(new SimpleAttribute(m_exceptionId));
723             attr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what()));
724             qctx.getResolvedAttributes().push_back(attr.get());
725             attr.release();
726         }
727     }
728 }