c6d9d0e4a91c6f0ae5d2a8dd52cfc34f10117363
[shibboleth/sp.git] / shib-target / shib-ccache.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50
51 /*
52  * shib-ccache.cpp -- SHAR Credential Cache
53  *
54  * Originally from mod_shib
55  * Modified by: Derek Atkins <derek@ihtfp.com>
56  *
57  * $Id$
58  */
59
60 #include "internal.h"
61
62 #if HAVE_UNISTD_H
63 # include <unistd.h>
64 #endif
65
66 #include <shib/shib-threads.h>
67
68 #include <log4cpp/Category.hh>
69
70 #include <sstream>
71 #include <stdexcept>
72
73 #ifdef HAVE_LIBDMALLOCXX
74 #include <dmalloc.h>
75 #endif
76
77 using namespace std;
78 using namespace log4cpp;
79 using namespace saml;
80 using namespace shibboleth;
81 using namespace shibtarget;
82
83 static const XMLCh cleanupInterval[] =
84 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
85   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
86 };
87 static const XMLCh cacheTimeout[] =
88 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
89   chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
90 };
91 static const XMLCh AAConnectTimeout[] =
92 { chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
93   chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
94 };
95 static const XMLCh AATimeout[] =
96 { chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
97
98 static const XMLCh defaultLifetime[] =
99 { chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
100   chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
101 };
102 static const XMLCh retryInterval[] =
103 { chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
104   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
105 };
106 static const XMLCh strictValidity[] =
107 { chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
108   chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
109 };
110 static const XMLCh propagateErrors[] =
111 { chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
112   chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
113 };
114
115 class InternalCCache;
116 class InternalCCacheEntry : public ISessionCacheEntry
117 {
118 public:
119   InternalCCacheEntry(
120     const char* id,
121     const IApplication* application,
122     SAMLAuthenticationStatement* s,
123     const char *client_addr,
124     SAMLResponse* r=NULL,
125     const IRoleDescriptor* source=NULL
126     );
127   ~InternalCCacheEntry();
128
129   void lock() { m_lock->lock(); }
130   void unlock() { m_lock->unlock(); }
131
132   bool isValid(time_t lifetime, time_t timeout) const;
133   const char* getClientAddress() const { return m_clientAddress.c_str(); }
134   const SAMLAuthenticationStatement* getAuthnStatement() const { return p_auth; }
135   Iterator<SAMLAssertion*> getAssertions();
136
137   void setCache(InternalCCache *cache) { m_cache = cache; }
138   time_t lastAccess() const { return m_lastAccess; }
139   
140   bool checkApplication(const IApplication* application) { return (m_application_id==application->getId()); }
141
142 private:
143   void populate();                  // wraps process of checking cache, and repopulating if need be
144   bool responseValid();             // checks validity of existing response
145   SAMLResponse* getNewResponse();   // wraps an actual query
146   
147   void filter(SAMLResponse* r, const IApplication* application, const IRoleDescriptor* source);
148   
149   string m_id;
150   string m_application_id;
151   string m_originSite;
152   string m_clientAddress;
153   time_t m_sessionCreated;
154   time_t m_responseCreated;
155   mutable time_t m_lastAccess;
156   time_t m_lastRetry;
157
158   const SAMLNameIdentifier* m_nameid;
159   SAMLAuthenticationStatement* p_auth;
160   SAMLResponse* m_response;
161   InternalCCache *m_cache;
162
163   log4cpp::Category* log;
164   Mutex* m_lock;
165 };
166
167 class InternalCCache : public ISessionCache
168 {
169 public:
170   InternalCCache(const DOMElement* e);
171   virtual ~InternalCCache();
172
173   void thread_init() {};
174   void thread_end() {};
175
176   string generateKey() const;
177   ISessionCacheEntry* find(const char* key, const IApplication* application);
178   void insert(
179     const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char *client_addr, SAMLResponse* r=NULL, const IRoleDescriptor* source=NULL
180     );
181   void remove(const char* key);
182
183   InternalCCacheEntry* findi(const char* key);
184   void  cleanup();
185
186 private:
187   const DOMElement* m_root;         // Only valid during initialization
188   RWLock *lock;
189   map<string,InternalCCacheEntry*> m_hashtable;
190
191   log4cpp::Category* log;
192
193   static void*  cleanup_fcn(void*); // XXX Assumed an InternalCCache
194   bool          shutdown;
195   CondWait*     shutdown_wait;
196   Thread*       cleanup_thread;
197   
198   // extracted config settings
199   unsigned int m_AATimeout,m_AAConnectTimeout;
200   unsigned int m_defaultLifetime,m_retryInterval;
201   bool m_strictValidity,m_propagateErrors;
202   friend class InternalCCacheEntry;
203 };
204
205 IPlugIn* MemoryCacheFactory(const DOMElement* e)
206 {
207     return new InternalCCache(e);
208 }
209
210 /******************************************************************************/
211 /* InternalCCache:  in memory session cache                                   */
212 /******************************************************************************/
213
214 InternalCCache::InternalCCache(const DOMElement* e)
215     : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
216         m_strictValidity(true), m_propagateErrors(false), lock(RWLock::create()),
217         log (&Category::getInstance("shibtarget.InternalCCache"))
218 {
219     const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
220     if (tag && *tag) {
221         m_AATimeout = XMLString::parseInt(tag);
222         if (!m_AATimeout)
223             m_AATimeout=30;
224     }
225
226     tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
227     if (tag && *tag) {
228         m_AAConnectTimeout = XMLString::parseInt(tag);
229         if (!m_AAConnectTimeout)
230             m_AAConnectTimeout=15;
231     }
232     
233     tag=m_root->getAttributeNS(NULL,defaultLifetime);
234     if (tag && *tag) {
235         m_defaultLifetime = XMLString::parseInt(tag);
236         if (!m_defaultLifetime)
237             m_defaultLifetime=1800;
238     }
239
240     tag=m_root->getAttributeNS(NULL,retryInterval);
241     if (tag && *tag) {
242         m_retryInterval = XMLString::parseInt(tag);
243         if (!m_retryInterval)
244             m_retryInterval=300;
245     }
246     
247     tag=m_root->getAttributeNS(NULL,strictValidity);
248     if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
249         m_strictValidity=false;
250         
251     tag=m_root->getAttributeNS(NULL,propagateErrors);
252     if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
253         m_propagateErrors=true;
254
255     shutdown_wait = CondWait::create();
256     shutdown = false;
257     cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
258 }
259
260 InternalCCache::~InternalCCache()
261 {
262   // Shut down the cleanup thread and let it know...
263   shutdown = true;
264   shutdown_wait->signal();
265   cleanup_thread->join(NULL);
266
267   for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
268     delete i->second;
269   delete lock;
270   delete shutdown_wait;
271 }
272
273 string InternalCCache::generateKey() const
274 {
275     SAMLIdentifier id;
276     auto_ptr_char c(id);
277     return c.get();
278 }
279
280 // assumes a lock is held..
281 InternalCCacheEntry* InternalCCache::findi(const char* key)
282 {
283   log->debug("findI: \"%s\"", key);
284
285   map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
286   if (i==m_hashtable.end()) {
287     log->debug("No Match found");
288     return NULL;
289   }
290   log->debug("Match Found.");
291
292   return i->second;
293 }
294
295 ISessionCacheEntry* InternalCCache::find(const char* key, const IApplication* application)
296 {
297   log->debug("Find: \"%s\"", key);
298   ReadLock rwlock(lock);
299
300   InternalCCacheEntry* entry = findi(key);
301   if (!entry)
302     return NULL;
303   else if (!entry->checkApplication(application)) {
304     log->crit("An application (%s) attempted to access another application's session!", application->getId());
305     return NULL;
306   }
307
308   // Lock the "database record" for the caller -- they have to unlock the item.
309   entry->lock();
310   return entry;
311 }
312
313 void InternalCCache::insert(
314     const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char* client_addr, SAMLResponse* r, const IRoleDescriptor* source
315     )
316 {
317   log->debug("caching new entry for application %s: \"%s\"", application->getId(), key);
318
319   InternalCCacheEntry* entry = new InternalCCacheEntry(key, application, s, client_addr, r, source);
320   entry->setCache(this);
321
322   lock->wrlock();
323   m_hashtable[key]=entry;
324   lock->unlock();
325 }
326
327 // remove the entry from the database and then destroy the cacheentry
328 void InternalCCache::remove(const char* key)
329 {
330   log->debug("removing cache entry \"key\"", key);
331
332   // lock the cache for writing, which means we know nobody is sitting in find()
333   lock->wrlock();
334
335   // grab the entry from the database.
336   ISessionCacheEntry* entry = findi(key);
337
338   if (!entry) {
339     lock->unlock();
340     return;
341   }
342
343   // ok, remove the entry and lock it
344   m_hashtable.erase(key);
345   dynamic_cast<InternalCCacheEntry*>(entry)->lock();
346   lock->unlock();
347
348   // we can release the entry lock because we know we're not in the cache anymore
349   entry->unlock();
350
351   // Now delete the entry
352   delete entry;
353 }
354
355 void InternalCCache::cleanup()
356 {
357   Mutex* mutex = Mutex::create();
358   saml::NDC ndc("InternalCCache::cleanup()");
359
360   int rerun_timer = 0;
361   int timeout_life = 0;
362
363   // Load our configuration details...
364   const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
365   if (tag && *tag)
366     rerun_timer = XMLString::parseInt(tag);
367
368   tag=m_root->getAttributeNS(NULL,cacheTimeout);
369   if (tag && *tag)
370     timeout_life = XMLString::parseInt(tag);
371   
372   if (rerun_timer <= 0)
373     rerun_timer = 300;        // rerun every 5 minutes
374
375   if (timeout_life <= 0)
376     timeout_life = 28800; // timeout after 8 hours
377
378   mutex->lock();
379
380   log->debug("Cleanup thread started...  Run every %d secs; timeout after %d secs",
381              rerun_timer, timeout_life);
382
383   while (shutdown == false) {
384     shutdown_wait->timedwait(mutex,rerun_timer);
385
386     if (shutdown == true)
387       break;
388
389     log->info("Cleanup thread running...");
390
391     // Ok, let's run through the cleanup process and clean out
392     // really old sessions.  This is a two-pass process.  The
393     // first pass is done holding a read-lock while we iterate over
394     // the database.  The second pass doesn't need a lock because
395     // the 'deletes' will lock the database.
396
397     // Pass 1: iterate over the map and find all entries that have not been
398     // used in X hours
399     vector<string> stale_keys;
400     time_t stale = time(NULL) - timeout_life;
401
402     lock->rdlock();
403     for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
404          i != m_hashtable.end(); i++)
405     {
406       // If the last access was BEFORE the stale timeout...
407       i->second->lock();
408       time_t last=i->second->lastAccess();
409       i->second->unlock();
410       if (last < stale)
411         stale_keys.push_back(i->first);
412     }
413     lock->unlock();
414
415     log->info("deleting %d old items.", stale_keys.size());
416
417     // Pass 2: walk through the list of stale entries and remove them from
418     // the database
419     for (vector<string>::iterator j = stale_keys.begin(); j != stale_keys.end(); j++) {
420       remove (j->c_str());
421       // Transaction Logging
422       STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
423       stc.getTransactionLog().infoStream() << "Purged expired session from memory (ID: " << j->c_str() << ")";
424       stc.releaseTransactionLog();
425     }
426   }
427
428   log->debug("Cleanup thread finished.");
429
430   mutex->unlock();
431   delete mutex;
432   Thread::exit(NULL);
433 }
434
435 void* InternalCCache::cleanup_fcn(void* cache_p)
436 {
437   InternalCCache* cache = reinterpret_cast<InternalCCache*>(cache_p);
438
439   // First, let's block all signals 
440   Thread::mask_all_signals();
441
442   // Now run the cleanup process.
443   cache->cleanup();
444   return NULL;
445 }
446
447 /******************************************************************************/
448 /* InternalCCacheEntry:  A Credential Cache Entry                             */
449 /******************************************************************************/
450
451 InternalCCacheEntry::InternalCCacheEntry(
452     const char* id,
453     const IApplication* application,
454     SAMLAuthenticationStatement *s,
455     const char* client_addr,
456     SAMLResponse* r,
457     const IRoleDescriptor* source
458     ) : m_response(r), m_responseCreated(r ? time(NULL) : 0), m_lastRetry(0),
459         log(&Category::getInstance("shibtarget::InternalCCacheEntry"))
460 {
461   if (!id || !s) {
462     log->error("NULL session ID or auth statement");
463     throw SAMLException("InternalCCacheEntry() passed an empty session ID or SAML Statement");
464   }
465
466   m_id=id;
467   m_application_id=application->getId();
468
469   m_nameid = s->getSubject()->getNameIdentifier();
470   auto_ptr_char d(m_nameid->getNameQualifier());
471   m_originSite = d.get();
472
473   m_clientAddress = client_addr;
474   m_sessionCreated = m_lastAccess = time(NULL);
475
476   // Save for later.
477   p_auth = s;
478   
479   // If pushing attributes, filter the response.
480   if (r)
481     filter(r, application, source);
482
483   m_lock = Mutex::create();
484
485   log->info("new session created (ID: %s)", id);
486   if (log->isDebugEnabled()) {
487       auto_ptr_char h(m_nameid->getName());
488       log->debug("Handle: \"%s\", Origin: \"%s\", Address: %s", h.get(), d.get(), client_addr);
489   }
490 }
491
492 InternalCCacheEntry::~InternalCCacheEntry()
493 {
494   log->debug("deleting session (ID: %s)", m_id.c_str());
495   delete m_response;
496   delete p_auth;
497   delete m_lock;
498 }
499
500 bool InternalCCacheEntry::isValid(time_t lifetime, time_t timeout) const
501 {
502 #ifdef _DEBUG
503   saml::NDC ndc("isValid");
504 #endif
505   
506   log->debug("testing session (ID: %s) (lifetime=%ld, timeout=%ld)",
507     m_id.c_str(), lifetime, timeout);
508     
509   time_t now=time(NULL);
510   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
511     log->info("session beyond lifetime (ID: %s)", m_id.c_str());
512     return false;
513   }
514
515   if (timeout > 0 && now-m_lastAccess >= timeout) {
516     log->info("session timed out (ID: %s)", m_id.c_str());
517     return false;
518   }
519   m_lastAccess=now;
520   return true;
521 }
522
523 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions()
524 {
525 #ifdef _DEBUG
526   saml::NDC ndc("getAssertions");
527 #endif
528   populate();
529   return (m_response) ? m_response->getAssertions() : EMPTY(SAMLAssertion*);
530 }
531
532 bool InternalCCacheEntry::responseValid()
533 {
534 #ifdef _DEBUG
535   saml::NDC ndc("responseValid");
536 #endif
537   log->debug("checking attribute data validity");
538   time_t now=time(NULL) - SAMLConfig::getConfig().clock_skew_secs;
539
540   int count = 0;
541   Iterator<SAMLAssertion*> iter = m_response->getAssertions();
542   while (iter.hasNext()) {
543     SAMLAssertion* assertion = iter.next();
544
545     log->debug("testing assertion...");
546
547     const SAMLDateTime* thistime = assertion->getNotOnOrAfter();
548
549     // If there is no time, then just continue and ignore this assertion.
550     if (!thistime)
551       continue;
552
553     count++;
554
555     if (now >= thistime->getEpoch()) {
556       log->debug("nope, not still valid");
557       return false;
558     }
559   }
560
561   // If we didn't find any assertions with times, then see if we're
562   // older than the default response lifetime.
563   if (!count) {
564       if ((now - m_responseCreated) > m_cache->m_defaultLifetime) {
565         log->debug("response is beyond default life, so it's invalid");
566         return false;
567       }
568   }
569   
570   log->debug("yep, response still valid");
571   return true;
572 }
573
574 void InternalCCacheEntry::populate()
575 {
576 #ifdef _DEBUG
577   saml::NDC ndc("populate");
578 #endif
579   log->debug("populating attributes for session (ID: %s)", m_id.c_str());
580
581   // Do we have any data cached?
582   if (m_response) {
583       // Can we use what we have?
584       if (responseValid())
585         return;
586       
587       // If we're being strict, dump what we have and reset timestamps.
588       if (m_cache->m_strictValidity) {
589         log->info("strictly enforcing attribute validity, dumping expired data");
590         delete m_response;
591         m_response=NULL;
592         m_responseCreated=0;
593         m_lastRetry=0; 
594       }
595   }
596
597   try {
598     // Transaction Logging
599     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
600     stc.getTransactionLog().infoStream() <<
601         "Making attribute query for session (ID: " <<
602             m_id <<
603         ") on (applicationId: " <<
604             m_application_id <<
605         ") for principal from (IdP: " <<
606             m_originSite <<
607         ")";
608     stc.releaseTransactionLog();
609
610     SAMLResponse* new_response=getNewResponse();
611     if (new_response) {
612         delete m_response;
613         m_response=new_response;
614         m_responseCreated=time(NULL);
615         m_lastRetry=0;
616         log->debug("fetched and stored new response");
617         stc.getTransactionLog().infoStream() <<  "Successful attribute query for session (ID: " << m_id << ")";
618         stc.releaseTransactionLog();
619     }
620   }
621   catch (SAMLException& e) {
622     if (typeid(e)==typeid(InvalidHandleException) || m_cache->m_propagateErrors)
623         throw;
624     log->warn("suppressed SAML exception caught while trying to fetch attributes");
625   }
626   catch (...) {
627     if (m_cache->m_propagateErrors)
628         throw;
629     log->warn("suppressed exception caught while trying to fetch attributes");
630   }
631 }
632
633 SAMLResponse* InternalCCacheEntry::getNewResponse()
634 {
635 #ifdef _DEBUG
636     saml::NDC ndc("getNewResponse");
637 #endif
638
639     // The retryInterval determines how often to poll an AA that might be down.
640     time_t now=time(NULL);
641     if ((now - m_lastRetry) < m_cache->m_retryInterval)
642         return NULL;
643     if (m_lastRetry)
644         log->debug("retry interval exceeded, so trying again");
645     m_lastRetry=now;
646     
647     log->info("trying to get new attributes for session (ID=%s)", m_id.c_str());
648
649     // Lookup application for session to get providerId and attributes to request.
650     IConfig* conf=ShibTargetConfig::getConfig().getINI();
651     Locker locker(conf);
652     const IApplication* application=conf->getApplication(m_application_id.c_str());
653     if (!application) {
654         log->crit("unable to locate application for session, deleted?");
655         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate application for session, deleted?");
656     }
657     pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
658     if (!providerID.first) {
659         log->crit("unable to determine ProviderID for application, not set?");
660         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to determine ProviderID for application, not set?");
661     }
662
663     // Get protocol signing policy.
664     pair<bool,bool> signRequest=application->getBool("signRequest");
665     pair<bool,bool> signedResponse=application->getBool("signedResponse");
666     
667     // Try this request. The binding wrapper class handles most of the details.
668     Metadata m(application->getMetadataProviders());
669     const IEntityDescriptor* site=m.lookup(m_nameid->getNameQualifier());
670     if (!site) {
671         log->error("unable to locate identity provider's metadata during attribute query");
672         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate identity provider's metadata during attribute query.");
673     }
674
675     // Try to locate an AA role.
676     const IAttributeAuthorityDescriptor* AA=site->getAttributeAuthorityDescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
677     if (!AA) {
678         log->error("unable to locate metadata for identity provider's Attribute Authority");
679         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate metadata for identity provider's Attribute Authority.",site);
680     }
681
682     SAMLResponse* response = NULL;
683     try {
684         // Build a SAML Request....
685         SAMLAttributeQuery* q=new SAMLAttributeQuery(
686             new SAMLSubject(static_cast<SAMLNameIdentifier*>(m_nameid->clone())),
687             providerID.second,
688             application->getAttributeDesignators().clone()
689             );
690         auto_ptr<SAMLRequest> req(new SAMLRequest(EMPTY(QName),q));
691         
692         // Sign it? Highly doubtful we'll ever use this, but just for fun...
693         if (signRequest.first && signRequest.second) {
694             Credentials creds(conf->getCredentialsProviders());
695             const ICredResolver* signingCred=creds.lookup(application->getSigningCred(site));
696             req->sign(SIGNATURE_RSA,signingCred->getKey(),signingCred->getCertificates());
697         }
698             
699         log->debug("trying to query an AA...");
700
701         SAMLConfig::SAMLBindingConfig bindconf;
702         bindconf.timeout=m_cache->m_AATimeout;
703         bindconf.conn_timeout=m_cache->m_AAConnectTimeout;
704         ShibBinding binding(application->getRevocationProviders(),application->getTrustProviders(),conf->getCredentialsProviders());
705         response=binding.send(*req,AA,application->getTLSCred(site),application->getAudiences(),p_auth->getBindings(),bindconf);
706
707         if (signedResponse.first && signedResponse.second && !response->isSigned()) {
708             delete response;
709             response=NULL;
710             log->error("unsigned response obtained, but we were told it must be signed.");
711         }
712         else {
713             // Run it through the filter. Note that we could end up with an empty response.
714             filter(response,application,AA);
715         }
716     }
717     catch (SAMLException& e) {
718         log->error("caught SAML exception during query to AA: %s", e.what());
719         if (typeid(e)==typeid(InvalidHandleException))
720             throw;
721         ostringstream os;
722         os << e;
723         throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), AA);
724     }
725     
726     // See if we got a response.
727     if (!response) {
728         log->error("no response obtained");
729         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to obtain attributes from user's identity provider.",AA);
730     }
731     return response;
732 }
733
734 void InternalCCacheEntry::filter(SAMLResponse* r, const IApplication* application, const IRoleDescriptor* source)
735 {
736     Trust t(application->getTrustProviders());
737     pair<bool,bool> signedAssertions=application->getBool("signedAssertions");
738
739     // Examine each new assertion...
740     Iterator<SAMLAssertion*> assertions=r->getAssertions();
741     for (unsigned long i=0; i < assertions.size();) {
742         try {
743             // Check signing policy.
744             if (signedAssertions.first && signedAssertions.second && !(assertions[i]->isSigned())) {
745                 log->warn("removing unsigned assertion from response, in accordance with signedAssertions policy");
746                 r->removeAssertion(i);
747                 continue;
748             }
749
750             // Check any conditions.
751             bool pruned=false;
752             Iterator<SAMLCondition*> conds=assertions[i]->getConditions();
753             while (conds.hasNext()) {
754                 SAMLAudienceRestrictionCondition* cond=dynamic_cast<SAMLAudienceRestrictionCondition*>(conds.next());
755                 if (!cond || !cond->eval(application->getAudiences())) {
756                     log->warn("assertion condition invalid, removing it");
757                     r->removeAssertion(i);
758                     pruned=true;
759                     break;
760                 }
761             }
762             if (pruned)
763                 continue;
764             
765             // Check token signature.
766             if (assertions[i]->isSigned() && !t.validate(application->getRevocationProviders(),source,*(assertions[i]))) {
767                 log->warn("signed assertion failed to validate, removing it");
768                 r->removeAssertion(i);
769                 continue;
770             }
771
772             // Finally, filter the content.
773             AAP::apply(application->getAAPProviders(),*(assertions[i]),source);
774             i++;
775         }
776         catch (SAMLException&) {
777             log->info("no statements remain after AAP, removing assertion");
778             r->removeAssertion(i);
779         }
780     }
781 }