Sync to SAMLRequest c'tor change.
[shibboleth/cpp-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     SAMLConfig::getConfig().timeout = m_AATimeout;
226
227     tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
228     if (tag && *tag) {
229         m_AAConnectTimeout = XMLString::parseInt(tag);
230         if (!m_AAConnectTimeout)
231             m_AAConnectTimeout=15;
232     }
233     SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
234     
235     tag=m_root->getAttributeNS(NULL,defaultLifetime);
236     if (tag && *tag) {
237         m_defaultLifetime = XMLString::parseInt(tag);
238         if (!m_defaultLifetime)
239             m_defaultLifetime=1800;
240     }
241
242     tag=m_root->getAttributeNS(NULL,retryInterval);
243     if (tag && *tag) {
244         m_retryInterval = XMLString::parseInt(tag);
245         if (!m_retryInterval)
246             m_retryInterval=300;
247     }
248     
249     tag=m_root->getAttributeNS(NULL,strictValidity);
250     if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
251         m_strictValidity=false;
252         
253     tag=m_root->getAttributeNS(NULL,propagateErrors);
254     if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
255         m_propagateErrors=true;
256
257     shutdown_wait = CondWait::create();
258     shutdown = false;
259     cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
260 }
261
262 InternalCCache::~InternalCCache()
263 {
264   // Shut down the cleanup thread and let it know...
265   shutdown = true;
266   shutdown_wait->signal();
267   cleanup_thread->join(NULL);
268
269   for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
270     delete i->second;
271   delete lock;
272   delete shutdown_wait;
273 }
274
275 string InternalCCache::generateKey() const
276 {
277     SAMLIdentifier id;
278     auto_ptr_char c(id);
279     return c.get();
280 }
281
282 // assumes a lock is held..
283 InternalCCacheEntry* InternalCCache::findi(const char* key)
284 {
285   log->debug("findI: \"%s\"", key);
286
287   map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
288   if (i==m_hashtable.end()) {
289     log->debug("No Match found");
290     return NULL;
291   }
292   log->debug("Match Found.");
293
294   return i->second;
295 }
296
297 ISessionCacheEntry* InternalCCache::find(const char* key, const IApplication* application)
298 {
299   log->debug("Find: \"%s\"", key);
300   ReadLock rwlock(lock);
301
302   InternalCCacheEntry* entry = findi(key);
303   if (!entry)
304     return NULL;
305   else if (!entry->checkApplication(application)) {
306     log->crit("An application (%s) attempted to access another application's session!", application->getId());
307     return NULL;
308   }
309
310   // Lock the "database record" for the caller -- they have to unlock the item.
311   entry->lock();
312   return entry;
313 }
314
315 void InternalCCache::insert(
316     const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char* client_addr, SAMLResponse* r, const IRoleDescriptor* source
317     )
318 {
319   log->debug("caching new entry for application %s: \"%s\"", application->getId(), key);
320
321   InternalCCacheEntry* entry = new InternalCCacheEntry(key, application, s, client_addr, r, source);
322   entry->setCache(this);
323
324   lock->wrlock();
325   m_hashtable[key]=entry;
326   lock->unlock();
327 }
328
329 // remove the entry from the database and then destroy the cacheentry
330 void InternalCCache::remove(const char* key)
331 {
332   log->debug("removing cache entry \"key\"", key);
333
334   // lock the cache for writing, which means we know nobody is sitting in find()
335   lock->wrlock();
336
337   // grab the entry from the database.
338   ISessionCacheEntry* entry = findi(key);
339
340   if (!entry) {
341     lock->unlock();
342     return;
343   }
344
345   // ok, remove the entry and lock it
346   m_hashtable.erase(key);
347   dynamic_cast<InternalCCacheEntry*>(entry)->lock();
348   lock->unlock();
349
350   // we can release the entry lock because we know we're not in the cache anymore
351   entry->unlock();
352
353   // Now delete the entry
354   delete entry;
355 }
356
357 void InternalCCache::cleanup()
358 {
359   Mutex* mutex = Mutex::create();
360   saml::NDC ndc("InternalCCache::cleanup()");
361
362   int rerun_timer = 0;
363   int timeout_life = 0;
364
365   // Load our configuration details...
366   const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
367   if (tag && *tag)
368     rerun_timer = XMLString::parseInt(tag);
369
370   tag=m_root->getAttributeNS(NULL,cacheTimeout);
371   if (tag && *tag)
372     timeout_life = XMLString::parseInt(tag);
373   
374   if (rerun_timer <= 0)
375     rerun_timer = 300;        // rerun every 5 minutes
376
377   if (timeout_life <= 0)
378     timeout_life = 28800; // timeout after 8 hours
379
380   mutex->lock();
381
382   log->debug("Cleanup thread started...  Run every %d secs; timeout after %d secs",
383              rerun_timer, timeout_life);
384
385   while (shutdown == false) {
386     shutdown_wait->timedwait(mutex,rerun_timer);
387
388     if (shutdown == true)
389       break;
390
391     log->info("Cleanup thread running...");
392
393     // Ok, let's run through the cleanup process and clean out
394     // really old sessions.  This is a two-pass process.  The
395     // first pass is done holding a read-lock while we iterate over
396     // the database.  The second pass doesn't need a lock because
397     // the 'deletes' will lock the database.
398
399     // Pass 1: iterate over the map and find all entries that have not been
400     // used in X hours
401     vector<string> stale_keys;
402     time_t stale = time(NULL) - timeout_life;
403
404     lock->rdlock();
405     for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
406          i != m_hashtable.end(); i++)
407     {
408       // If the last access was BEFORE the stale timeout...
409       i->second->lock();
410       time_t last=i->second->lastAccess();
411       i->second->unlock();
412       if (last < stale)
413         stale_keys.push_back(i->first);
414     }
415     lock->unlock();
416
417     log->info("deleting %d old items.", stale_keys.size());
418
419     // Pass 2: walk through the list of stale entries and remove them from
420     // the database
421     for (vector<string>::iterator j = stale_keys.begin(); j != stale_keys.end(); j++) {
422       remove (j->c_str());
423       // Transaction Logging
424       STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
425       stc.getTransactionLog().infoStream() << "Purged expired session from memory (ID: " << j->c_str() << ")";
426       stc.releaseTransactionLog();
427     }
428   }
429
430   log->debug("Cleanup thread finished.");
431
432   mutex->unlock();
433   delete mutex;
434   Thread::exit(NULL);
435 }
436
437 void* InternalCCache::cleanup_fcn(void* cache_p)
438 {
439   InternalCCache* cache = reinterpret_cast<InternalCCache*>(cache_p);
440
441   // First, let's block all signals 
442   Thread::mask_all_signals();
443
444   // Now run the cleanup process.
445   cache->cleanup();
446   return NULL;
447 }
448
449 /******************************************************************************/
450 /* InternalCCacheEntry:  A Credential Cache Entry                             */
451 /******************************************************************************/
452
453 InternalCCacheEntry::InternalCCacheEntry(
454     const char* id,
455     const IApplication* application,
456     SAMLAuthenticationStatement *s,
457     const char* client_addr,
458     SAMLResponse* r,
459     const IRoleDescriptor* source
460     ) : m_response(r), m_responseCreated(r ? time(NULL) : 0), m_lastRetry(0),
461         log(&Category::getInstance("shibtarget::InternalCCacheEntry"))
462 {
463   if (!id || !s) {
464     log->error("NULL session ID or auth statement");
465     throw SAMLException("InternalCCacheEntry() passed an empty session ID or SAML Statement");
466   }
467
468   m_id=id;
469   m_application_id=application->getId();
470
471   m_nameid = s->getSubject()->getNameIdentifier();
472   auto_ptr_char d(m_nameid->getNameQualifier());
473   m_originSite = d.get();
474
475   m_clientAddress = client_addr;
476   m_sessionCreated = m_lastAccess = time(NULL);
477
478   // Save for later.
479   p_auth = s;
480   
481   // If pushing attributes, filter the response.
482   if (r)
483     filter(r, application, source);
484
485   m_lock = Mutex::create();
486
487   log->info("new session created (ID: %s)", id);
488   if (log->isDebugEnabled()) {
489       auto_ptr_char h(m_nameid->getName());
490       log->debug("Handle: \"%s\", Origin: \"%s\", Address: %s", h.get(), d.get(), client_addr);
491   }
492 }
493
494 InternalCCacheEntry::~InternalCCacheEntry()
495 {
496   log->debug("deleting session (ID: %s)", m_id.c_str());
497   delete m_response;
498   delete p_auth;
499   delete m_lock;
500 }
501
502 bool InternalCCacheEntry::isValid(time_t lifetime, time_t timeout) const
503 {
504 #ifdef _DEBUG
505   saml::NDC ndc("isValid");
506 #endif
507   
508   log->debug("testing session (ID: %s) (lifetime=%ld, timeout=%ld)",
509     m_id.c_str(), lifetime, timeout);
510     
511   time_t now=time(NULL);
512   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
513     log->info("session beyond lifetime (ID: %s)", m_id.c_str());
514     return false;
515   }
516
517   if (timeout > 0 && now-m_lastAccess >= timeout) {
518     log->info("session timed out (ID: %s)", m_id.c_str());
519     return false;
520   }
521   m_lastAccess=now;
522   return true;
523 }
524
525 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions()
526 {
527 #ifdef _DEBUG
528   saml::NDC ndc("getAssertions");
529 #endif
530   populate();
531   return (m_response) ? m_response->getAssertions() : EMPTY(SAMLAssertion*);
532 }
533
534 bool InternalCCacheEntry::responseValid()
535 {
536 #ifdef _DEBUG
537   saml::NDC ndc("responseValid");
538 #endif
539   log->debug("checking attribute data validity");
540   time_t now=time(NULL) - SAMLConfig::getConfig().clock_skew_secs;
541
542   int count = 0;
543   Iterator<SAMLAssertion*> iter = m_response->getAssertions();
544   while (iter.hasNext()) {
545     SAMLAssertion* assertion = iter.next();
546
547     log->debug("testing assertion...");
548
549     const SAMLDateTime* thistime = assertion->getNotOnOrAfter();
550
551     // If there is no time, then just continue and ignore this assertion.
552     if (!thistime)
553       continue;
554
555     count++;
556
557     if (now >= thistime->getEpoch()) {
558       log->debug("nope, not still valid");
559       return false;
560     }
561   }
562
563   // If we didn't find any assertions with times, then see if we're
564   // older than the default response lifetime.
565   if (!count) {
566       if ((now - m_responseCreated) > m_cache->m_defaultLifetime) {
567         log->debug("response is beyond default life, so it's invalid");
568         return false;
569       }
570   }
571   
572   log->debug("yep, response still valid");
573   return true;
574 }
575
576 void InternalCCacheEntry::populate()
577 {
578 #ifdef _DEBUG
579   saml::NDC ndc("populate");
580 #endif
581   log->debug("populating attributes for session (ID: %s)", m_id.c_str());
582
583   // Do we have any data cached?
584   if (m_response) {
585       // Can we use what we have?
586       if (responseValid())
587         return;
588       
589       // If we're being strict, dump what we have and reset timestamps.
590       if (m_cache->m_strictValidity) {
591         log->info("strictly enforcing attribute validity, dumping expired data");
592         delete m_response;
593         m_response=NULL;
594         m_responseCreated=0;
595         m_lastRetry=0; 
596       }
597   }
598
599   try {
600     // Transaction Logging
601     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
602     stc.getTransactionLog().infoStream() <<
603         "Making attribute query for session (ID: " <<
604             m_id <<
605         ") on (applicationId: " <<
606             m_application_id <<
607         ") for principal from (IdP: " <<
608             m_originSite <<
609         ")";
610     stc.releaseTransactionLog();
611
612     SAMLResponse* new_response=getNewResponse();
613     if (new_response) {
614         delete m_response;
615         m_response=new_response;
616         m_responseCreated=time(NULL);
617         m_lastRetry=0;
618         log->debug("fetched and stored new response");
619         stc.getTransactionLog().infoStream() <<  "Successful attribute query for session (ID: " << m_id << ")";
620         stc.releaseTransactionLog();
621     }
622   }
623   catch (SAMLException& e) {
624     if (typeid(e)==typeid(InvalidHandleException) || m_cache->m_propagateErrors)
625         throw;
626     log->warn("suppressed SAML exception caught while trying to fetch attributes");
627   }
628   catch (...) {
629     if (m_cache->m_propagateErrors)
630         throw;
631     log->warn("suppressed exception caught while trying to fetch attributes");
632   }
633 }
634
635 SAMLResponse* InternalCCacheEntry::getNewResponse()
636 {
637 #ifdef _DEBUG
638     saml::NDC ndc("getNewResponse");
639 #endif
640
641     // The retryInterval determines how often to poll an AA that might be down.
642     time_t now=time(NULL);
643     if ((now - m_lastRetry) < m_cache->m_retryInterval)
644         return NULL;
645     if (m_lastRetry)
646         log->debug("retry interval exceeded, so trying again");
647     m_lastRetry=now;
648     
649     log->info("trying to get new attributes for session (ID=%s)", m_id.c_str());
650
651     // Lookup application for session to get providerId and attributes to request.
652     IConfig* conf=ShibTargetConfig::getConfig().getINI();
653     Locker locker(conf);
654     const IApplication* application=conf->getApplication(m_application_id.c_str());
655     if (!application) {
656         log->crit("unable to locate application for session, deleted?");
657         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate application for session, deleted?");
658     }
659     pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
660     if (!providerID.first) {
661         log->crit("unable to determine ProviderID for application, not set?");
662         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to determine ProviderID for application, not set?");
663     }
664
665     // Get protocol signing policy.
666     pair<bool,bool> signRequest=application->getBool("signRequest");
667     pair<bool,bool> signedResponse=application->getBool("signedResponse");
668     
669     // Try this request.
670     Metadata m(application->getMetadataProviders());
671     const IEntityDescriptor* site=m.lookup(m_nameid->getNameQualifier());
672     if (!site) {
673         log->error("unable to locate identity provider's metadata during attribute query");
674         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate identity provider's metadata during attribute query.");
675     }
676
677     // Try to locate an AA role.
678     const IAttributeAuthorityDescriptor* AA=site->getAttributeAuthorityDescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
679     if (!AA) {
680         log->error("unable to locate metadata for identity provider's Attribute Authority");
681         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate metadata for identity provider's Attribute Authority.",site);
682     }
683
684     SAMLResponse* response = NULL;
685     try {
686         // Build a SAML Request....
687         SAMLAttributeQuery* q=new SAMLAttributeQuery(
688             new SAMLSubject(static_cast<SAMLNameIdentifier*>(m_nameid->clone())),
689             providerID.second,
690             application->getAttributeDesignators().clone()
691             );
692         auto_ptr<SAMLRequest> req(new SAMLRequest(q,EMPTY(QName)));
693         
694         // Sign it? Highly doubtful we'll ever use this, but just for fun...
695         if (signRequest.first && signRequest.second) {
696             Credentials creds(conf->getCredentialsProviders());
697             const ICredResolver* signingCred=creds.lookup(application->getSigningCred(site));
698             req->sign(SIGNATURE_RSA,signingCred->getKey(),signingCred->getCertificates());
699         }
700             
701         log->debug("trying to query an AA...");
702
703
704         // Call context object
705         ShibHTTPHook::ShibHTTPHookCallContext ctx(application->getTLSCred(site),AA);
706         Trust t(application->getTrustProviders());
707         
708         // First try any bindings provided by caller. This is for compatibility with
709         // old releases. Metadata should be used going forward.
710         Iterator<SAMLAuthorityBinding*> bindings=p_auth->getBindings();
711         while (!response && bindings.hasNext()) {
712             SAMLAuthorityBinding* ab=bindings.next();
713             try {
714                 // Get a binding object for this protocol.
715                 SAMLBinding* binding = application->getBinding(ab->getBinding());
716                 if (!binding) {
717                     auto_ptr_char prot(ab->getBinding());
718                     log->warn("skipping binding on unsupported protocol (%s)", prot.get());
719                     continue;
720                 }
721                 auto_ptr<SAMLResponse> r(binding->send(ab->getLocation(), *(req.get()), &ctx));
722                 if (r->isSigned() && !t.validate(application->getRevocationProviders(),AA,*r))
723                     throw TrustException("CCacheEntry::getNewResponse() unable to verify signed response");
724                 response = r.release();
725             }
726             catch (SAMLException& e) {
727                 log->error("caught SAML exception during SAML attribute query: %s", e.what());
728                 // Check for shib:InvalidHandle error and propagate it out.
729                 Iterator<saml::QName> codes=e.getCodes();
730                 if (codes.size()>1) {
731                     const saml::QName& code=codes[1];
732                     if (!XMLString::compareString(code.getNamespaceURI(),shibboleth::Constants::SHIB_NS) &&
733                         !XMLString::compareString(code.getLocalName(), shibboleth::Constants::InvalidHandle)) {
734                         codes.reset();
735                         throw InvalidHandleException(codes,e.what());
736                     }
737                 }
738             }
739         }
740
741         // Now try metadata.
742         Iterator<const IEndpoint*> endpoints=AA->getAttributeServices()->getEndpoints();
743         while (!response && endpoints.hasNext()) {
744             const IEndpoint* ep=endpoints.next();
745             try {
746                 // Get a binding object for this protocol.
747                 SAMLBinding* binding = application->getBinding(ep->getBinding());
748                 if (!binding) {
749                     auto_ptr_char prot(ep->getBinding());
750                     log->warn("skipping binding on unsupported protocol (%s)", prot.get());
751                     continue;
752                 }
753                 auto_ptr<SAMLResponse> r(binding->send(ep->getLocation(), *(req.get()), &ctx));
754                 if (r->isSigned() && !t.validate(application->getRevocationProviders(),AA,*r))
755                     throw TrustException("CCacheEntry::getNewResponse() unable to verify signed response");
756                 response = r.release();
757             }
758             catch (SAMLException& e) {
759                 log->error("caught SAML exception during SAML attribute query: %s", e.what());
760                 // Check for shib:InvalidHandle error and propagate it out.
761                 Iterator<saml::QName> codes=e.getCodes();
762                 if (codes.size()>1) {
763                     const saml::QName& code=codes[1];
764                     if (!XMLString::compareString(code.getNamespaceURI(),shibboleth::Constants::SHIB_NS) &&
765                         !XMLString::compareString(code.getLocalName(), shibboleth::Constants::InvalidHandle)) {
766                         codes.reset();
767                         throw InvalidHandleException(codes,e.what());
768                     }
769                 }
770             }
771         }
772
773         if (signedResponse.first && signedResponse.second && !response->isSigned()) {
774             delete response;
775             response=NULL;
776             log->error("unsigned response obtained, but we were told it must be signed.");
777         }
778         else {
779             // Run it through the filter. Note that we could end up with an empty response.
780             filter(response,application,AA);
781         }
782     }
783     catch (SAMLException& e) {
784         log->error("caught SAML exception during query to AA: %s", e.what());
785         if (typeid(e)==typeid(InvalidHandleException))
786             throw;
787         ostringstream os;
788         os << e;
789         throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), AA);
790     }
791     
792     // See if we got a response.
793     if (!response) {
794         log->error("no response obtained");
795         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to obtain attributes from user's identity provider.",AA);
796     }
797     return response;
798 }
799
800 void InternalCCacheEntry::filter(SAMLResponse* r, const IApplication* application, const IRoleDescriptor* source)
801 {
802     Trust t(application->getTrustProviders());
803     pair<bool,bool> signedAssertions=application->getBool("signedAssertions");
804
805     // Examine each new assertion...
806     Iterator<SAMLAssertion*> assertions=r->getAssertions();
807     for (unsigned long i=0; i < assertions.size();) {
808         try {
809             // Check signing policy.
810             if (signedAssertions.first && signedAssertions.second && !(assertions[i]->isSigned())) {
811                 log->warn("removing unsigned assertion from response, in accordance with signedAssertions policy");
812                 r->removeAssertion(i);
813                 continue;
814             }
815
816             // Check any conditions.
817             bool pruned=false;
818             Iterator<SAMLCondition*> conds=assertions[i]->getConditions();
819             while (conds.hasNext()) {
820                 SAMLAudienceRestrictionCondition* cond=dynamic_cast<SAMLAudienceRestrictionCondition*>(conds.next());
821                 if (!cond || !cond->eval(application->getAudiences())) {
822                     log->warn("assertion condition invalid, removing it");
823                     r->removeAssertion(i);
824                     pruned=true;
825                     break;
826                 }
827             }
828             if (pruned)
829                 continue;
830             
831             // Check token signature.
832             if (assertions[i]->isSigned() && !t.validate(application->getRevocationProviders(),source,*(assertions[i]))) {
833                 log->warn("signed assertion failed to validate, removing it");
834                 r->removeAssertion(i);
835                 continue;
836             }
837
838             // Finally, filter the content.
839             AAP::apply(application->getAAPProviders(),*(assertions[i]),source);
840             i++;
841         }
842         catch (SAMLException&) {
843             log->info("no statements remain after AAP, removing assertion");
844             r->removeAssertion(i);
845         }
846     }
847 }