Change license header.
[shibboleth/cpp-sp.git] / shibsp / attribute / resolver / impl / SimpleAggregationAttributeResolver.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  * SimpleAggregationAttributeResolver.cpp
23  *
24  * AttributeResolver based on SAML queries to third-party AA sources.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "Application.h"
30 #include "ServiceProvider.h"
31 #include "SessionCache.h"
32 #include "attribute/NameIDAttribute.h"
33 #include "attribute/SimpleAttribute.h"
34 #include "attribute/filtering/AttributeFilter.h"
35 #include "attribute/filtering/BasicFilteringContext.h"
36 #include "attribute/resolver/AttributeExtractor.h"
37 #include "attribute/resolver/AttributeResolver.h"
38 #include "attribute/resolver/ResolutionContext.h"
39 #include "binding/SOAPClient.h"
40 #include "metadata/MetadataProviderCriteria.h"
41 #include "security/SecurityPolicy.h"
42 #include "security/SecurityPolicyProvider.h"
43 #include "util/SPConstants.h"
44
45 #include <saml/exceptions.h>
46 #include <saml/SAMLConfig.h>
47 #include <saml/saml2/binding/SAML2SOAPClient.h>
48 #include <saml/saml2/core/Protocols.h>
49 #include <saml/saml2/metadata/Metadata.h>
50 #include <saml/saml2/metadata/MetadataCredentialCriteria.h>
51 #include <saml/saml2/metadata/MetadataProvider.h>
52 #include <xmltooling/XMLToolingConfig.h>
53 #include <xmltooling/security/TrustEngine.h>
54 #include <xmltooling/util/NDC.h>
55 #include <xmltooling/util/URLEncoder.h>
56 #include <xmltooling/util/XMLHelper.h>
57 #include <xercesc/util/XMLUniDefs.hpp>
58
59 using namespace shibsp;
60 using namespace opensaml::saml2;
61 using namespace opensaml::saml2p;
62 using namespace opensaml::saml2md;
63 using namespace opensaml;
64 using namespace xmltooling;
65 using namespace std;
66
67 namespace shibsp {
68
69     class SHIBSP_DLLLOCAL SimpleAggregationContext : public ResolutionContext
70     {
71     public:
72         SimpleAggregationContext(const Application& application, const Session& session)
73             : m_app(application),
74               m_session(&session),
75               m_nameid(nullptr),
76               m_entityid(nullptr),
77               m_class(XMLString::transcode(session.getAuthnContextClassRef())),
78               m_decl(XMLString::transcode(session.getAuthnContextDeclRef())),
79               m_inputTokens(nullptr),
80               m_inputAttributes(nullptr) {
81         }
82
83         SimpleAggregationContext(
84             const Application& application,
85             const NameID* nameid=nullptr,
86             const XMLCh* entityID=nullptr,
87             const XMLCh* authncontext_class=nullptr,
88             const XMLCh* authncontext_decl=nullptr,
89             const vector<const opensaml::Assertion*>* tokens=nullptr,
90             const vector<shibsp::Attribute*>* attributes=nullptr
91             ) : m_app(application),
92                 m_session(nullptr),
93                 m_nameid(nameid),
94                 m_entityid(entityID ? XMLString::transcode(entityID) : nullptr),
95                 m_class(const_cast<XMLCh*>(authncontext_class)),
96                 m_decl(const_cast<XMLCh*>(authncontext_decl)),
97                 m_inputTokens(tokens),
98                 m_inputAttributes(attributes) {
99         }
100
101         ~SimpleAggregationContext() {
102             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<shibsp::Attribute>());
103             for_each(m_assertions.begin(), m_assertions.end(), xmltooling::cleanup<opensaml::Assertion>());
104             if (m_session) {
105                 XMLString::release(&m_class);
106                 XMLString::release(&m_decl);
107             }
108             XMLString::release(&m_entityid);
109         }
110
111         const Application& getApplication() const {
112             return m_app;
113         }
114         const char* getEntityID() const {
115             return m_session ? m_session->getEntityID() : m_entityid;
116         }
117         const NameID* getNameID() const {
118             return m_session ? m_session->getNameID() : m_nameid;
119         }
120         const XMLCh* getClassRef() const {
121             return m_class;
122         }
123         const XMLCh* getDeclRef() const {
124             return m_decl;
125         }
126         const Session* getSession() const {
127             return m_session;
128         }
129         const vector<shibsp::Attribute*>* getInputAttributes() const {
130             return m_inputAttributes;
131         }
132         const vector<const opensaml::Assertion*>* getInputTokens() const {
133             return m_inputTokens;
134         }
135         vector<shibsp::Attribute*>& getResolvedAttributes() {
136             return m_attributes;
137         }
138         vector<opensaml::Assertion*>& getResolvedAssertions() {
139             return m_assertions;
140         }
141
142     private:
143         const Application& m_app;
144         const Session* m_session;
145         const NameID* m_nameid;
146         char* m_entityid;
147         XMLCh* m_class;
148         XMLCh* m_decl;
149         const vector<const opensaml::Assertion*>* m_inputTokens;
150         const vector<shibsp::Attribute*>* m_inputAttributes;
151         vector<shibsp::Attribute*> m_attributes;
152         vector<opensaml::Assertion*> m_assertions;
153     };
154
155     class SHIBSP_DLLLOCAL SimpleAggregationResolver : public AttributeResolver
156     {
157     public:
158         SimpleAggregationResolver(const DOMElement* e);
159         ~SimpleAggregationResolver() {
160             delete m_trust;
161             delete m_metadata;
162             for_each(m_designators.begin(), m_designators.end(), xmltooling::cleanup<saml2::Attribute>());
163         }
164
165         Lockable* lock() {return this;}
166         void unlock() {}
167
168         ResolutionContext* createResolutionContext(
169             const Application& application,
170             const EntityDescriptor* issuer,
171             const XMLCh* protocol,
172             const NameID* nameid=nullptr,
173             const XMLCh* authncontext_class=nullptr,
174             const XMLCh* authncontext_decl=nullptr,
175             const vector<const opensaml::Assertion*>* tokens=nullptr,
176             const vector<shibsp::Attribute*>* attributes=nullptr
177             ) const {
178             return new SimpleAggregationContext(
179                 application, nameid, (issuer ? issuer->getEntityID() : nullptr), authncontext_class, authncontext_decl, tokens, attributes
180                 );
181         }
182
183         ResolutionContext* createResolutionContext(const Application& application, const Session& session) const {
184             return new SimpleAggregationContext(application,session);
185         }
186
187         void resolveAttributes(ResolutionContext& ctx) const;
188
189         void getAttributeIds(vector<string>& attributes) const {
190             // Nothing to do, only the extractor would actually generate them.
191         }
192
193     private:
194         void doQuery(SimpleAggregationContext& ctx, const char* entityID, const NameID* name) const;
195
196         Category& m_log;
197         string m_policyId;
198         bool m_subjectMatch;
199         vector<string> m_attributeIds;
200         xstring m_format;
201         MetadataProvider* m_metadata;
202         TrustEngine* m_trust;
203         vector<saml2::Attribute*> m_designators;
204         vector< pair<string,bool> > m_sources;
205         vector<string> m_exceptionId;
206     };
207
208     AttributeResolver* SHIBSP_DLLLOCAL SimpleAggregationResolverFactory(const DOMElement* const & e)
209     {
210         return new SimpleAggregationResolver(e);
211     }
212
213     static const XMLCh attributeId[] =          UNICODE_LITERAL_11(a,t,t,r,i,b,u,t,e,I,d);
214     static const XMLCh Entity[] =               UNICODE_LITERAL_6(E,n,t,i,t,y);
215     static const XMLCh EntityReference[] =      UNICODE_LITERAL_15(E,n,t,i,t,y,R,e,f,e,r,e,n,c,e);
216     static const XMLCh exceptionId[] =          UNICODE_LITERAL_11(e,x,c,e,p,t,i,o,n,I,d);
217     static const XMLCh format[] =               UNICODE_LITERAL_6(f,o,r,m,a,t);
218     static const XMLCh _MetadataProvider[] =    UNICODE_LITERAL_16(M,e,t,a,d,a,t,a,P,r,o,v,i,d,e,r);
219     static const XMLCh policyId[] =             UNICODE_LITERAL_8(p,o,l,i,c,y,I,d);
220     static const XMLCh subjectMatch[] =         UNICODE_LITERAL_12(s,u,b,j,e,c,t,M,a,t,c,h);
221     static const XMLCh _TrustEngine[] =         UNICODE_LITERAL_11(T,r,u,s,t,E,n,g,i,n,e);
222     static const XMLCh _type[] =                UNICODE_LITERAL_4(t,y,p,e);
223 };
224
225 SimpleAggregationResolver::SimpleAggregationResolver(const DOMElement* e)
226     : m_log(Category::getInstance(SHIBSP_LOGCAT".AttributeResolver.SimpleAggregation")),
227         m_policyId(XMLHelper::getAttrString(e, nullptr, policyId)),
228         m_subjectMatch(XMLHelper::getAttrBool(e, false, subjectMatch)),
229         m_metadata(nullptr), m_trust(nullptr)
230 {
231 #ifdef _DEBUG
232     xmltooling::NDC ndc("SimpleAggregationResolver");
233 #endif
234
235     const XMLCh* aid = e ? e->getAttributeNS(nullptr, attributeId) : nullptr;
236     if (aid && *aid) {
237         char* dup = XMLString::transcode(aid);
238         char* pos;
239         char* start = dup;
240         while (start && *start) {
241             while (*start && isspace(*start))
242                 start++;
243             if (!*start)
244                 break;
245             pos = strchr(start,' ');
246             if (pos)
247                 *pos=0;
248             m_attributeIds.push_back(start);
249             start = pos ? pos+1 : nullptr;
250         }
251         XMLString::release(&dup);
252
253         aid = e->getAttributeNS(nullptr, format);
254         if (aid && *aid)
255             m_format = aid;
256     }
257
258     string exid(XMLHelper::getAttrString(e, nullptr, exceptionId));
259     if (!exid.empty())
260         m_exceptionId.push_back(exid);
261
262     DOMElement* child = XMLHelper::getFirstChildElement(e, _MetadataProvider);
263     if (child) {
264         string t(XMLHelper::getAttrString(child, nullptr, _type));
265         if (t.empty())
266             throw ConfigurationException("MetadataProvider element missing type attribute.");
267         m_log.info("building MetadataProvider of type %s...", t.c_str());
268         auto_ptr<MetadataProvider> mp(SAMLConfig::getConfig().MetadataProviderManager.newPlugin(t.c_str(), child));
269         mp->init();
270         m_metadata = mp.release();
271     }
272
273     child = XMLHelper::getFirstChildElement(e,  _TrustEngine);
274     if (child) {
275         try {
276             string t(XMLHelper::getAttrString(child, nullptr, _type));
277             if (t.empty())
278                 throw ConfigurationException("TrustEngine element missing type attribute.");
279             m_log.info("building TrustEngine of type %s...", t.c_str());
280             m_trust = XMLToolingConfig::getConfig().TrustEngineManager.newPlugin(t.c_str(), child);
281         }
282         catch (exception&) {
283             delete m_metadata;
284             throw;
285         }
286     }
287
288     child = XMLHelper::getFirstChildElement(e);
289     while (child) {
290         if (child->hasChildNodes() && XMLString::equals(child->getLocalName(), Entity)) {
291             aid = child->getFirstChild()->getNodeValue();
292             if (aid && *aid) {
293                 auto_ptr_char taid(aid);
294                 m_sources.push_back(pair<string,bool>(taid.get(),true));
295             }
296         }
297         else if (child->hasChildNodes() && XMLString::equals(child->getLocalName(), EntityReference)) {
298             aid = child->getFirstChild()->getNodeValue();
299             if (aid && *aid) {
300                 auto_ptr_char taid(aid);
301                 m_sources.push_back(pair<string,bool>(taid.get(),false));
302             }
303         }
304         else if (XMLHelper::isNodeNamed(child, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME)) {
305             try {
306                 auto_ptr<XMLObject> obj(saml2::AttributeBuilder::buildOneFromElement(child));
307                 saml2::Attribute* down = dynamic_cast<saml2::Attribute*>(obj.get());
308                 if (down) {
309                     m_designators.push_back(down);
310                     obj.release();
311                 }
312             }
313             catch (exception& ex) {
314                 m_log.error("exception loading attribute designator: %s", ex.what());
315             }
316         }
317         child = XMLHelper::getNextSiblingElement(child);
318     }
319 }
320
321 void SimpleAggregationResolver::doQuery(SimpleAggregationContext& ctx, const char* entityID, const NameID* name) const
322 {
323 #ifdef _DEBUG
324     xmltooling::NDC ndc("doQuery");
325 #endif
326     const Application& application = ctx.getApplication();
327     MetadataProviderCriteria mc(application, entityID, &AttributeAuthorityDescriptor::ELEMENT_QNAME, samlconstants::SAML20P_NS);
328     Locker mlocker(m_metadata);
329     const AttributeAuthorityDescriptor* AA=nullptr;
330     pair<const EntityDescriptor*,const RoleDescriptor*> mdresult =
331         (m_metadata ? m_metadata : application.getMetadataProvider())->getEntityDescriptor(mc);
332     if (!mdresult.first) {
333         m_log.warn("unable to locate metadata for provider (%s)", entityID);
334         return;
335     }
336     else if (!(AA=dynamic_cast<const AttributeAuthorityDescriptor*>(mdresult.second))) {
337         m_log.warn("no SAML 2 AttributeAuthority role found in metadata for (%s)", entityID);
338         return;
339     }
340
341     const PropertySet* relyingParty = application.getRelyingParty(mdresult.first);
342     pair<bool,bool> signedAssertions = relyingParty->getBool("requireSignedAssertions");
343     pair<bool,const char*> encryption = relyingParty->getString("encryption");
344
345     // Locate policy key.
346     const char* policyId = m_policyId.empty() ? application.getString("policyId").second : m_policyId.c_str();
347
348     // Set up policy and SOAP client.
349     auto_ptr<SecurityPolicy> policy(
350         application.getServiceProvider().getSecurityPolicyProvider()->createSecurityPolicy(application, nullptr, policyId)
351         );
352     if (m_metadata)
353         policy->setMetadataProvider(m_metadata);
354     if (m_trust)
355         policy->setTrustEngine(m_trust);
356     policy->getAudiences().push_back(relyingParty->getXMLString("entityID").second);
357
358     MetadataCredentialCriteria mcc(*AA);
359     shibsp::SOAPClient soaper(*policy.get());
360
361     auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);
362     saml2p::StatusResponseType* srt=nullptr;
363     const vector<AttributeService*>& endpoints=AA->getAttributeServices();
364     for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !srt && ep!=endpoints.end(); ++ep) {
365         if (!XMLString::equals((*ep)->getBinding(),binding.get())  || !(*ep)->getLocation())
366             continue;
367         auto_ptr_char loc((*ep)->getLocation());
368         try {
369             auto_ptr<saml2::Subject> subject(saml2::SubjectBuilder::buildSubject());
370
371             // Encrypt the NameID?
372             if (encryption.first && (!strcmp(encryption.second, "true") || !strcmp(encryption.second, "back"))) {
373                 auto_ptr<EncryptedID> encrypted(EncryptedIDBuilder::buildEncryptedID());
374                 encrypted->encrypt(
375                     *name,
376                     *(policy->getMetadataProvider()),
377                     mcc,
378                     false,
379                     relyingParty->getXMLString("encryptionAlg").second
380                     );
381                 subject->setEncryptedID(encrypted.release());
382             }
383             else {
384                 subject->setNameID(name->cloneNameID());
385             }
386
387             saml2p::AttributeQuery* query = saml2p::AttributeQueryBuilder::buildAttributeQuery();
388             query->setSubject(subject.release());
389             Issuer* iss = IssuerBuilder::buildIssuer();
390             iss->setName(relyingParty->getXMLString("entityID").second);
391             query->setIssuer(iss);
392             for (vector<saml2::Attribute*>::const_iterator ad = m_designators.begin(); ad!=m_designators.end(); ++ad)
393                 query->getAttributes().push_back((*ad)->cloneAttribute());
394
395             SAML2SOAPClient client(soaper, false);
396             client.sendSAML(query, application.getId(), mcc, loc.get());
397             srt = client.receiveSAML();
398         }
399         catch (exception& ex) {
400             m_log.error("exception during SAML query to %s: %s", loc.get(), ex.what());
401             soaper.reset();
402         }
403     }
404
405     if (!srt) {
406         m_log.error("unable to obtain a SAML response from attribute authority (%s)", entityID);
407         throw BindingException("Unable to obtain a SAML response from attribute authority.");
408     }
409
410     auto_ptr<saml2p::StatusResponseType> wrapper(srt);
411
412     saml2p::Response* response = dynamic_cast<saml2p::Response*>(srt);
413     if (!response) {
414         m_log.error("message was not a samlp:Response");
415         throw FatalProfileException("Attribute authority returned an unrecognized message.");
416     }
417     else if (!response->getStatus() || !response->getStatus()->getStatusCode() ||
418             !XMLString::equals(response->getStatus()->getStatusCode()->getValue(), saml2p::StatusCode::SUCCESS)) {
419         m_log.error("attribute authority (%s) returned a SAML error", entityID);
420         throw FatalProfileException("Attribute authority returned a SAML error.");
421     }
422
423     saml2::Assertion* newtoken = nullptr;
424     const vector<saml2::Assertion*>& assertions = const_cast<const saml2p::Response*>(response)->getAssertions();
425     if (assertions.empty()) {
426         // Check for encryption.
427         const vector<saml2::EncryptedAssertion*>& encassertions =
428             const_cast<const saml2p::Response*>(response)->getEncryptedAssertions();
429         if (encassertions.empty()) {
430             m_log.warn("response from attribute authority was empty");
431             return;
432         }
433         else if (encassertions.size() > 1) {
434             m_log.warn("simple resolver only supports one assertion in the query response");
435         }
436
437         CredentialResolver* cr=application.getCredentialResolver();
438         if (!cr) {
439             m_log.warn("found encrypted assertion, but no CredentialResolver was available");
440             throw FatalProfileException("Assertion was encrypted, but no decryption credentials are available.");
441         }
442
443         // Attempt to decrypt it.
444         try {
445             Locker credlocker(cr);
446             auto_ptr<XMLObject> tokenwrapper(encassertions.front()->decrypt(*cr, relyingParty->getXMLString("entityID").second, &mcc));
447             newtoken = dynamic_cast<saml2::Assertion*>(tokenwrapper.get());
448             if (newtoken) {
449                 tokenwrapper.release();
450                 if (m_log.isDebugEnabled())
451                     m_log.debugStream() << "decrypted Assertion: " << *newtoken << logging::eol;
452                 // Free the Response now, so we know this is a stand-alone token later.
453                 delete wrapper.release();
454             }
455         }
456         catch (exception& ex) {
457             m_log.error(ex.what());
458             throw;
459         }
460     }
461     else {
462         if (assertions.size() > 1)
463             m_log.warn("simple resolver only supports one assertion in the query response");
464         newtoken = assertions.front();
465     }
466
467     if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) {
468         m_log.error("assertion unsigned, rejecting it based on signedAssertions policy");
469         if (!wrapper.get())
470             delete newtoken;
471         throw SecurityPolicyException("Rejected unsigned assertion based on local policy.");
472     }
473
474     try {
475         // We're going to insist that the assertion issuer is the same as the peer.
476         // Reset the policy's message bits and extract them from the assertion.
477         policy->reset(true);
478         policy->setMessageID(newtoken->getID());
479         policy->setIssueInstant(newtoken->getIssueInstantEpoch());
480         policy->setIssuer(newtoken->getIssuer());
481         policy->evaluate(*newtoken);
482
483         // Now we can check the security status of the policy.
484         if (!policy->isAuthenticated())
485             throw SecurityPolicyException("Security of SAML 2.0 query result not established.");
486
487         if (m_subjectMatch) {
488             // Check for subject match.
489             bool ownedName = false;
490             NameID* respName = newtoken->getSubject() ? newtoken->getSubject()->getNameID() : nullptr;
491             if (!respName) {
492                 // Check for encryption.
493                 EncryptedID* encname = newtoken->getSubject() ? newtoken->getSubject()->getEncryptedID() : nullptr;
494                 if (encname) {
495                     CredentialResolver* cr=application.getCredentialResolver();
496                     if (!cr)
497                         m_log.warn("found EncryptedID, but no CredentialResolver was available");
498                     else {
499                         Locker credlocker(cr);
500                         auto_ptr<XMLObject> decryptedID(encname->decrypt(*cr, relyingParty->getXMLString("entityID").second, &mcc));
501                         respName = dynamic_cast<NameID*>(decryptedID.get());
502                         if (respName) {
503                             ownedName = true;
504                             decryptedID.release();
505                             if (m_log.isDebugEnabled())
506                                 m_log.debugStream() << "decrypted NameID: " << *respName << logging::eol;
507                         }
508                     }
509                 }
510             }
511
512             auto_ptr<NameID> nameIDwrapper(ownedName ? respName : nullptr);
513
514             if (!respName || !XMLString::equals(respName->getName(), name->getName()) ||
515                 !XMLString::equals(respName->getFormat(), name->getFormat()) ||
516                 !XMLString::equals(respName->getNameQualifier(), name->getNameQualifier()) ||
517                 !XMLString::equals(respName->getSPNameQualifier(), name->getSPNameQualifier())) {
518                 if (respName)
519                     m_log.warnStream() << "ignoring Assertion without strongly matching NameID in Subject: " <<
520                         *respName << logging::eol;
521                 else
522                     m_log.warn("ignoring Assertion without NameID in Subject");
523                 if (!wrapper.get())
524                     delete newtoken;
525                 return;
526             }
527         }
528     }
529     catch (exception& ex) {
530         m_log.error("assertion failed policy validation: %s", ex.what());
531         if (!wrapper.get())
532             delete newtoken;
533         throw;
534     }
535
536     if (wrapper.get()) {
537         newtoken->detach();
538         wrapper.release();  // detach blows away the Response
539     }
540     ctx.getResolvedAssertions().push_back(newtoken);
541
542     // Finally, extract and filter the result.
543     try {
544         AttributeExtractor* extractor = application.getAttributeExtractor();
545         if (extractor) {
546             Locker extlocker(extractor);
547             extractor->extractAttributes(application, AA, *newtoken, ctx.getResolvedAttributes());
548         }
549
550         AttributeFilter* filter = application.getAttributeFilter();
551         if (filter) {
552             BasicFilteringContext fc(application, ctx.getResolvedAttributes(), AA, ctx.getClassRef(), ctx.getDeclRef());
553             Locker filtlocker(filter);
554             filter->filterAttributes(fc, ctx.getResolvedAttributes());
555         }
556     }
557     catch (exception& ex) {
558         m_log.error("caught exception extracting/filtering attributes from query result: %s", ex.what());
559         for_each(ctx.getResolvedAttributes().begin(), ctx.getResolvedAttributes().end(), xmltooling::cleanup<shibsp::Attribute>());
560         ctx.getResolvedAttributes().clear();
561         throw;
562     }
563 }
564
565 void SimpleAggregationResolver::resolveAttributes(ResolutionContext& ctx) const
566 {
567 #ifdef _DEBUG
568     xmltooling::NDC ndc("resolveAttributes");
569 #endif
570
571     SimpleAggregationContext& qctx = dynamic_cast<SimpleAggregationContext&>(ctx);
572
573     // First we manufacture the appropriate NameID to use.
574     NameID* n=nullptr;
575     for (vector<string>::const_iterator a = m_attributeIds.begin(); !n && a != m_attributeIds.end(); ++a) {
576         const Attribute* attr=nullptr;
577         if (qctx.getSession()) {
578             // Input attributes should be available via multimap.
579             pair<multimap<string,const Attribute*>::const_iterator, multimap<string,const Attribute*>::const_iterator> range =
580                 qctx.getSession()->getIndexedAttributes().equal_range(*a);
581             for (; !attr && range.first != range.second; ++range.first) {
582                 if (range.first->second->valueCount() > 0)
583                     attr = range.first->second;
584             }
585         }
586         else if (qctx.getInputAttributes()) {
587             // Have to loop over unindexed set.
588             const vector<Attribute*>* matches = qctx.getInputAttributes();
589             for (vector<Attribute*>::const_iterator match = matches->begin(); !attr && match != matches->end(); ++match) {
590                 if (*a == (*match)->getId() && (*match)->valueCount() > 0)
591                     attr = *match;
592             }
593         }
594
595         if (attr) {
596             m_log.debug("using input attribute (%s) as identifier for queries", attr->getId());
597             n = NameIDBuilder::buildNameID();
598             const NameIDAttribute* down = dynamic_cast<const NameIDAttribute*>(attr);
599             if (down) {
600                 // We can create a NameID directly from the source material.
601                 const NameIDAttribute::Value& v = down->getValues().front();
602                 XMLCh* val = fromUTF8(v.m_Name.c_str());
603                 n->setName(val);
604                 delete[] val;
605                 if (!v.m_Format.empty()) {
606                     val = fromUTF8(v.m_Format.c_str());
607                     n->setFormat(val);
608                     delete[] val;
609                 }
610                 if (!v.m_NameQualifier.empty()) {
611                     val = fromUTF8(v.m_NameQualifier.c_str());
612                     n->setNameQualifier(val);
613                     delete[] val;
614                 }
615                 if (!v.m_SPNameQualifier.empty()) {
616                     val = fromUTF8(v.m_SPNameQualifier.c_str());
617                     n->setSPNameQualifier(val);
618                     delete[] val;
619                 }
620                 if (!v.m_SPProvidedID.empty()) {
621                     val = fromUTF8(v.m_SPProvidedID.c_str());
622                     n->setSPProvidedID(val);
623                     delete[] val;
624                 }
625             }
626             else {
627                 // We have to mock up the NameID.
628                 XMLCh* val = fromUTF8(attr->getSerializedValues().front().c_str());
629                 n->setName(val);
630                 delete[] val;
631                 if (!m_format.empty())
632                     n->setFormat(m_format.c_str());
633             }
634         }
635     }
636
637     if (!n) {
638         if (qctx.getNameID() && m_attributeIds.empty()) {
639             m_log.debug("using authenticated NameID as identifier for queries");
640         }
641         else {
642             m_log.warn("unable to resolve attributes, no suitable query identifier found");
643             return;
644         }
645     }
646
647     auto_ptr<NameID> wrapper(n);
648
649     set<string> history;
650
651     // Put initial IdP into history to prevent extra query.
652     if (qctx.getEntityID())
653         history.insert(qctx.getEntityID());
654
655     // Prepare to track exceptions.
656     SimpleAttribute* exceptAttr = nullptr;
657     if (!m_exceptionId.empty()) {
658         exceptAttr = new SimpleAttribute(m_exceptionId);
659     }
660     auto_ptr<Attribute> exceptWrapper(exceptAttr);
661
662     // We have a master loop over all the possible sources of material.
663     for (vector< pair<string,bool> >::const_iterator source = m_sources.begin(); source != m_sources.end(); ++source) {
664         if (source->second) {
665             // A literal entityID to query.
666             if (history.count(source->first) == 0) {
667                 m_log.debug("issuing SAML query to (%s)", source->first.c_str());
668                 try {
669                     doQuery(qctx, source->first.c_str(), n ? n : qctx.getNameID());
670                 }
671                 catch (exception& ex) {
672                     if (exceptAttr)
673                         exceptAttr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what()));
674                 }
675                 history.insert(source->first);
676             }
677             else {
678                 m_log.debug("skipping previously queried attribute source (%s)", source->first.c_str());
679             }
680         }
681         else {
682             m_log.debug("using attribute sources referenced in attribute (%s)", source->first.c_str());
683             if (qctx.getSession()) {
684                 // Input attributes should be available via multimap.
685                 pair<multimap<string,const Attribute*>::const_iterator, multimap<string,const Attribute*>::const_iterator> range =
686                     qctx.getSession()->getIndexedAttributes().equal_range(source->first);
687                 for (; range.first != range.second; ++range.first) {
688                     const vector<string>& links = range.first->second->getSerializedValues();
689                     for (vector<string>::const_iterator link = links.begin(); link != links.end(); ++link) {
690                         if (history.count(*link) == 0) {
691                             m_log.debug("issuing SAML query to (%s)", link->c_str());
692                             try {
693                                 doQuery(qctx, link->c_str(), n ? n : qctx.getNameID());
694                             }
695                             catch (exception& ex) {
696                                 if (exceptAttr)
697                                     exceptAttr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what()));
698                             }
699                             history.insert(*link);
700                         }
701                         else {
702                             m_log.debug("skipping previously queried attribute source (%s)", link->c_str());
703                         }
704                     }
705                 }
706             }
707             else if (qctx.getInputAttributes()) {
708                 // Have to loop over unindexed set.
709                 const vector<Attribute*>* matches = qctx.getInputAttributes();
710                 for (vector<Attribute*>::const_iterator match = matches->begin(); match != matches->end(); ++match) {
711                     if (source->first == (*match)->getId()) {
712                         const vector<string>& links = (*match)->getSerializedValues();
713                         for (vector<string>::const_iterator link = links.begin(); link != links.end(); ++link) {
714                             if (history.count(*link) == 0) {
715                                 m_log.debug("issuing SAML query to (%s)", link->c_str());
716                                 try {
717                                     doQuery(qctx, link->c_str(), n ? n : qctx.getNameID());
718                                 }
719                                 catch (exception& ex) {
720                                     if (exceptAttr)
721                                         exceptAttr->getValues().push_back(XMLToolingConfig::getConfig().getURLEncoder()->encode(ex.what()));
722                                 }
723                                 history.insert(*link);
724                             }
725                             else {
726                                 m_log.debug("skipping previously queried attribute source (%s)", link->c_str());
727                             }
728                         }
729                     }
730                 }
731             }
732         }
733     }
734
735     if (exceptAttr) {
736         qctx.getResolvedAttributes().push_back(exceptWrapper.release());
737     }
738 }