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