938df67e9e58327bf7b3bc613ac5705a75b538f1
[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 char* getSerializedStatement() const { return m_statement.c_str(); }
135   const SAMLAuthenticationStatement* getStatement() const { return p_auth; }
136
137   Iterator<SAMLAssertion*> getAssertions();
138   void preFetch(int prefetch_window);
139
140   void setCache(InternalCCache *cache) { m_cache = cache; }
141   time_t lastAccess() const { return m_lastAccess; }
142   
143   bool checkApplication(const IApplication* application) { return (m_application_id==application->getId()); }
144
145 private:
146   bool responseValid(int slop);
147   void populate(int slop);
148   SAMLResponse* getNewResponse();
149   
150   string m_id;
151   string m_application_id;
152   string m_statement;
153   string m_originSite;
154   string m_clientAddress;
155   time_t m_sessionCreated;
156   time_t m_responseCreated;
157   mutable time_t m_lastAccess;
158   time_t m_lastRetry;
159
160   const SAMLNameIdentifier* m_nameid;
161   SAMLAuthenticationStatement* p_auth;
162   SAMLResponse* m_response;
163   InternalCCache *m_cache;
164
165   log4cpp::Category* log;
166   Mutex* m_lock;
167 };
168
169 class InternalCCache : public ISessionCache
170 {
171 public:
172   InternalCCache(const DOMElement* e);
173   virtual ~InternalCCache();
174
175   void thread_init() {};
176   void thread_end() {};
177
178   string generateKey() const;
179   ISessionCacheEntry* find(const char* key, const IApplication* application);
180   void insert(
181     const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char *client_addr, SAMLResponse* r=NULL, const IRoleDescriptor* source=NULL
182     );
183   void remove(const char* key);
184
185   InternalCCacheEntry* findi(const char* key);
186   void  cleanup();
187
188 private:
189   const DOMElement* m_root;         // Only valid during initialization
190   RWLock *lock;
191   map<string,InternalCCacheEntry*> m_hashtable;
192
193   log4cpp::Category* log;
194
195   static void*  cleanup_fcn(void*); // XXX Assumed an InternalCCache
196   bool          shutdown;
197   CondWait*     shutdown_wait;
198   Thread*       cleanup_thread;
199   
200   // extracted config settings
201   unsigned int m_AATimeout,m_AAConnectTimeout;
202   unsigned int m_defaultLifetime,m_retryInterval;
203   bool m_strictValidity,m_propagateErrors;
204   friend class InternalCCacheEntry;
205 };
206
207 IPlugIn* MemoryCacheFactory(const DOMElement* e)
208 {
209     return new InternalCCache(e);
210 }
211
212 /******************************************************************************/
213 /* InternalCCache:  in memory session cache                                   */
214 /******************************************************************************/
215
216 InternalCCache::InternalCCache(const DOMElement* e)
217     : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
218         m_strictValidity(true), m_propagateErrors(false), lock(RWLock::create()),
219         log (&Category::getInstance("shibtarget.InternalCCache"))
220 {
221     const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
222     if (tag && *tag) {
223         m_AATimeout = XMLString::parseInt(tag);
224         if (!m_AATimeout)
225             m_AATimeout=30;
226     }
227
228     tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
229     if (tag && *tag) {
230         m_AAConnectTimeout = XMLString::parseInt(tag);
231         if (!m_AAConnectTimeout)
232             m_AAConnectTimeout=15;
233     }
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, const IApplication* application, SAMLAuthenticationStatement *s, const char* client_addr, SAMLResponse* r, const IRoleDescriptor* source
455     ) : m_response(r), m_responseCreated(r ? time(NULL) : 0), m_lastRetry(0),
456         log(&Category::getInstance("shibtarget::InternalCCacheEntry"))
457 {
458   if (!id || !s) {
459     log->error("NULL session ID or auth statement");
460     throw SAMLException("InternalCCacheEntry() passed an empty session ID or SAML Statement");
461   }
462
463   m_id=id;
464   m_application_id=application->getId();
465
466   m_nameid = s->getSubject()->getNameIdentifier();
467   auto_ptr_char d(m_nameid->getNameQualifier());
468   m_originSite = d.get();
469
470   m_clientAddress = client_addr;
471   m_sessionCreated = m_lastAccess = time(NULL);
472
473   // Save for later.
474   p_auth = s;
475   
476   // Save the serialized version of the auth statement
477   ostringstream os;
478   os << *s;
479   m_statement = os.str();
480
481   if (r) {
482     // Run pushed data through the AAP. Note that we could end up with an empty response!
483     Iterator<SAMLAssertion*> assertions=r->getAssertions();
484     for (unsigned long i=0; i < assertions.size();) {
485         try {
486             AAP::apply(application->getAAPProviders(),*(assertions[i]),source);
487             i++;
488         }
489         catch (SAMLException&) {
490             log->info("no statements remain, removing assertion");
491             r->removeAssertion(i);
492         }
493     }
494   }
495
496   m_lock = Mutex::create();
497
498   log->info("new session created (ID: %s)", id);
499   if (log->isDebugEnabled()) {
500       auto_ptr_char h(m_nameid->getName());
501       log->debug("Handle: \"%s\", Origin: \"%s\", Address: %s", h.get(), d.get(), client_addr);
502   }
503 }
504
505 InternalCCacheEntry::~InternalCCacheEntry()
506 {
507   log->debug("deleting session (ID: %s)", m_id.c_str());
508   delete m_response;
509   delete p_auth;
510   delete m_lock;
511 }
512
513 bool InternalCCacheEntry::isValid(time_t lifetime, time_t timeout) const
514 {
515   saml::NDC ndc("isValid");
516   log->debug("testing session (ID: %s) (lifetime=%ld, timeout=%ld)", m_id.c_str(), lifetime, timeout);
517   time_t now=time(NULL);
518   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
519     log->debug("session beyond lifetime (ID: %s)", m_id.c_str());
520     return false;
521   }
522
523   if (timeout > 0 && now-m_lastAccess >= timeout) {
524     log->debug("session timed out (ID: %s)", m_id.c_str());
525     return false;
526   }
527   m_lastAccess=now;
528   return true;
529 }
530
531 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions()
532 {
533   saml::NDC ndc("getAssertions");
534   populate(0);
535   return (m_response) ? m_response->getAssertions() : EMPTY(SAMLAssertion*);
536 }
537
538 void InternalCCacheEntry::preFetch(int prefetch_window)
539 {
540   saml::NDC ndc("preFetch");
541   populate(prefetch_window);
542 }
543
544 bool InternalCCacheEntry::responseValid(int slop)
545 {
546   saml::NDC ndc("responseValid");
547
548   log->debug("checking AA response validity");
549
550   // This is awful, but the XMLDateTime class is truly horrible.
551   time_t now=time(NULL)+slop;
552 #ifdef WIN32
553   struct tm* ptime=gmtime(&now);
554 #else
555   struct tm res;
556   struct tm* ptime=gmtime_r(&now,&res);
557 #endif
558   char timebuf[32];
559   strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
560   auto_ptr_XMLCh timeptr(timebuf);
561   XMLDateTime curDateTime(timeptr.get());
562   curDateTime.parseDateTime();
563
564   int count = 0;
565   Iterator<SAMLAssertion*> iter = m_response->getAssertions();
566   while (iter.hasNext()) {
567     SAMLAssertion* assertion = iter.next();
568
569     log->debug("testing assertion...");
570
571     const XMLDateTime* thistime = assertion->getNotOnOrAfter();
572
573     // If there is no time, then just continue and ignore this assertion.
574     if (!thistime)
575       continue;
576
577     count++;
578     auto_ptr_char nowptr(curDateTime.getRawData());
579     auto_ptr_char assnptr(thistime->getRawData());
580
581     log->debug("comparing now (%s) to %s", nowptr.get(), assnptr.get());
582     int result=XMLDateTime::compareOrder(&curDateTime, thistime);
583
584     if (result != XMLDateTime::LESS_THAN) {
585       log->debug("nope, not still valid");
586       return false;
587     }
588   }
589
590   // If we didn't find any assertions with times, then see if we're
591   // older than the default response lifetime.
592   if (!count) {
593       if ((now - m_responseCreated) > m_cache->m_defaultLifetime) {
594         log->debug("response is beyond default life, so it's invalid");
595         return false;
596       }
597   }
598   
599   log->debug("yep, response still valid");
600   return true;
601 }
602
603 void InternalCCacheEntry::populate(int slop)
604 {
605   saml::NDC ndc("populate");
606   log->debug("populating attributes for session (ID: %s)", m_id.c_str());
607
608   // Do we have any data cached?
609   if (m_response) {
610       // Can we use what we have?
611       if (responseValid(slop))
612         return;
613       
614       // If we're being strict, dump what we have and reset timestamps.
615       if (m_cache->m_strictValidity) {
616         log->info("strictly enforcing attribute validity, dumping expired data");
617         delete m_response;
618         m_response=NULL;
619         m_responseCreated=0;
620         m_lastRetry=0; 
621       }
622   }
623
624   // Need to try and get a new response.
625
626   try {
627
628     // Transaction Logging
629     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
630     stc.getTransactionLog().infoStream() <<
631         "Making attribute query for session (ID: " <<
632             m_id <<
633         ") on (applicationId: " <<
634             m_application_id <<
635         ") for principal from (IdP: " <<
636             m_originSite <<
637         ")";
638     stc.releaseTransactionLog();
639
640     SAMLResponse* new_response=getNewResponse();
641     if (new_response) {
642         delete m_response;
643         m_response=new_response;
644         m_responseCreated=time(NULL);
645         m_lastRetry=0;
646         log->debug("fetched and stored new response");
647         stc.getTransactionLog().infoStream() <<  "Successful attribute query for session (ID: " << m_id << ")";
648         stc.releaseTransactionLog();
649     }
650   }
651   catch (SAMLException& e) {
652     if (typeid(e)==typeid(InvalidHandleException) || m_cache->m_propagateErrors)
653         throw;
654     log->warn("suppressed SAML exception caught while trying to fetch attributes");
655   }
656   catch (...) {
657     if (m_cache->m_propagateErrors)
658         throw;
659     log->warn("suppressed exception caught while trying to fetch attributes");
660   }
661 }
662
663 SAMLResponse* InternalCCacheEntry::getNewResponse()
664 {
665     saml::NDC ndc("getNewResponse");
666
667     // The retryInterval determines how often to poll an AA that might be down.
668     if ((time(NULL) - m_lastRetry) < m_cache->m_retryInterval)
669         return NULL;
670     if (m_lastRetry)
671         log->debug("retry interval exceeded, so trying again");
672     m_lastRetry=time(NULL);
673     
674     log->info("trying to get new attributes for session (ID=%s)", m_id.c_str());
675
676     // Lookup application for session to get providerId and attributes to request.
677     IConfig* conf=ShibTargetConfig::getConfig().getINI();
678     Locker locker(conf);
679     const IApplication* application=conf->getApplication(m_application_id.c_str());
680     if (!application) {
681         log->crit("unable to locate application for session, deleted?");
682         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate application for session, deleted?");
683     }
684     pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
685     if (!providerID.first) {
686         log->crit("unable to determine ProviderID for application, not set?");
687         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to determine ProviderID for application, not set?");
688     }
689
690     // Get signing policies.
691     pair<bool,bool> signRequest=application->getBool("signRequest");
692     pair<bool,bool> signedResponse=application->getBool("signedResponse");
693     pair<bool,bool> signedAssertions=application->getBool("signedAssertions");
694     
695     // Try this request. The binding wrapper class handles most of the details.
696     Metadata m(application->getMetadataProviders());
697     const IEntityDescriptor* site=m.lookup(m_nameid->getNameQualifier());
698     if (!site) {
699         log->error("unable to locate identity provider's metadata during attribute query");
700         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate identity provider's metadata during attribute query.");
701     }
702
703     // Try to locate an AA role.
704     const IAttributeAuthorityDescriptor* AA=site->getAttributeAuthorityDescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
705     if (!AA) {
706         log->error("unable to locate metadata for identity provider's Attribute Authority");
707         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate metadata for identity provider's Attribute Authority.",site);
708     }
709
710
711     SAMLResponse* response = NULL;
712     try {
713         // Build a SAML Request....
714         SAMLAttributeQuery* q=new SAMLAttributeQuery(
715             new SAMLSubject(static_cast<SAMLNameIdentifier*>(m_nameid->clone())),
716             providerID.second,
717             application->getAttributeDesignators().clone()
718             );
719         auto_ptr<SAMLRequest> req(new SAMLRequest(EMPTY(QName),q));
720         
721         // Sign it? Highly doubtful we'll ever use this, but just for fun...
722         if (signRequest.first && signRequest.second) {
723             Credentials creds(conf->getCredentialsProviders());
724             const ICredResolver* signingCred=creds.lookup(application->getSigningCred(site));
725             req->sign(SIGNATURE_RSA,signingCred->getKey(),signingCred->getCertificates());
726         }
727             
728         log->debug("trying to query an AA...");
729
730         SAMLConfig::SAMLBindingConfig bindconf;
731         bindconf.timeout=m_cache->m_AATimeout;
732         bindconf.conn_timeout=m_cache->m_AAConnectTimeout;
733         ShibBinding binding(application->getRevocationProviders(),application->getTrustProviders(),conf->getCredentialsProviders());
734         response=binding.send(*req,AA,application->getTLSCred(site),application->getAudiences(),p_auth->getBindings(),bindconf);
735     }
736     catch (SAMLException& e) {
737         log->error("caught SAML exception during query to AA: %s", e.what());
738         if (typeid(e)==typeid(InvalidHandleException))
739             throw;
740         ostringstream os;
741         os << e;
742         throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), AA);
743     }
744     
745     // See if we got a response.
746     if (!response) {
747         log->error("no response obtained");
748         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to obtain attributes from user's origin site.",AA);
749     }
750     else if (signedResponse.first && signedResponse.second && !response->isSigned()) {
751         delete response;
752         log->error("unsigned response obtained, but we were told it must be signed.");
753         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to obtain attributes from user's origin site.",AA);
754     }
755
756     // Run it through the AAP. Note that we could end up with an empty response!
757     Iterator<SAMLAssertion*> a=response->getAssertions();
758     for (unsigned long i=0; i < a.size();) {
759         try {
760             if (signedAssertions.first && signedAssertions.second && !(a[i]->isSigned())) {
761                 log->warn("removing unsigned assertion from response, in accordance with signedAssertions policy");
762                 response->removeAssertion(i);
763                 continue;
764             }
765             AAP::apply(application->getAAPProviders(),*(a[i]),AA);
766             i++;
767         }
768         catch (SAMLException&) {
769             log->info("no statements remain, removing assertion");
770             response->removeAssertion(i);
771         }
772     }
773
774     return response;
775 }