Fixed additional warnings.
[shibboleth/cpp-sp.git] / shib-target / shib-ccache.cpp
1 /*
2  *  Copyright 2001-2005 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * shib-ccache.cpp -- SHAR Credential Cache
19  *
20  * Originally from mod_shib
21  * Modified by: Derek Atkins <derek@ihtfp.com>
22  *
23  * $Id$
24  */
25
26 #include "internal.h"
27
28 #if HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31
32 #include <shib/shib-threads.h>
33
34 #include <log4cpp/Category.hh>
35
36 #include <sstream>
37 #include <stdexcept>
38
39 #ifdef HAVE_LIBDMALLOCXX
40 #include <dmalloc.h>
41 #endif
42
43 using namespace std;
44 using namespace log4cpp;
45 using namespace saml;
46 using namespace shibboleth;
47 using namespace shibtarget;
48
49 static const XMLCh cleanupInterval[] =
50 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
51   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
52 };
53 static const XMLCh cacheTimeout[] =
54 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
55   chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
56 };
57 static const XMLCh AAConnectTimeout[] =
58 { chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
59   chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
60 };
61 static const XMLCh AATimeout[] =
62 { chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
63
64 static const XMLCh defaultLifetime[] =
65 { chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
66   chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
67 };
68 static const XMLCh retryInterval[] =
69 { chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
70   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
71 };
72 static const XMLCh strictValidity[] =
73 { chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
74   chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
75 };
76 static const XMLCh propagateErrors[] =
77 { chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
78   chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
79 };
80
81 class InternalCCache;
82 class InternalCCacheEntry : public ISessionCacheEntry
83 {
84 public:
85   InternalCCacheEntry(
86     InternalCCache* cache,
87     const char* key,
88     const IApplication* application,
89     const char* client_addr,
90     ShibProfile profile,
91     const char* providerId,
92     SAMLAuthenticationStatement* s,
93     SAMLResponse* r=NULL,
94     const IRoleDescriptor* source=NULL,
95     time_t created=0,
96     time_t accessed=0
97     );
98   ~InternalCCacheEntry();
99
100   void lock() { m_lock->lock(); }
101   void unlock() { m_lock->unlock(); }
102
103   bool isValid(time_t lifetime, time_t timeout) const;
104   const char* getClientAddress() const { return m_clientAddress.c_str(); }
105   ShibProfile getProfile() const { return m_profile; }
106   const char* getProviderId() const { return m_provider_id.c_str(); }
107   const SAMLAuthenticationStatement* getAuthnStatement() const { return m_auth_statement; }
108   CachedResponse getResponse();
109
110   time_t lastAccess() const { return m_lastAccess; }
111   
112   bool checkApplication(const IApplication* application) { return (m_application_id==application->getId()); }
113
114 private:
115   void populate();                  // wraps process of checking cache, and repopulating if need be
116   bool responseValid();             // checks validity of existing response
117   pair<SAMLResponse*,SAMLResponse*> getNewResponse();   // wraps an actual query
118   
119   SAMLResponse* filter(SAMLResponse* r, const IApplication* application, const IRoleDescriptor* source);
120   
121   string m_id;
122   string m_application_id;
123   string m_provider_id;
124   string m_clientAddress;
125   time_t m_sessionCreated;
126   time_t m_responseCreated;
127   mutable time_t m_lastAccess;
128   time_t m_lastRetry;
129
130   ShibProfile m_profile;
131   SAMLAuthenticationStatement* m_auth_statement;
132   SAMLResponse* m_response_pre;
133   SAMLResponse* m_response_post;
134   InternalCCache *m_cache;
135
136   Mutex* m_lock;
137 };
138
139 class InternalCCache : public ISessionCache
140 {
141 public:
142   InternalCCache(const DOMElement* e);
143   virtual ~InternalCCache();
144
145   void thread_init() {};
146   void thread_end() {};
147
148   string generateKey() const;
149   ISessionCacheEntry* find(const char* key, const IApplication* application);
150   void insert(
151     const char* key,
152     const IApplication* application,
153     const char* client_addr,
154     ShibProfile profile,
155     const char* providerId,
156     SAMLAuthenticationStatement* s,
157     SAMLResponse* r=NULL,
158     const IRoleDescriptor* source=NULL,
159     time_t created=0,
160     time_t accessed=0
161     );
162   void remove(const char* key);
163
164   InternalCCacheEntry* findi(const char* key);
165   void  cleanup();
166
167 private:
168   const DOMElement* m_root;         // Only valid during initialization
169   RWLock *lock;
170   map<string,InternalCCacheEntry*> m_hashtable;
171
172   log4cpp::Category* log;
173
174   static void*  cleanup_fcn(void*); // Assumes an InternalCCache
175   bool          shutdown;
176   CondWait*     shutdown_wait;
177   Thread*       cleanup_thread;
178   
179   // extracted config settings
180   unsigned int m_AATimeout,m_AAConnectTimeout;
181   unsigned int m_defaultLifetime,m_retryInterval;
182   bool m_strictValidity,m_propagateErrors;
183   friend class InternalCCacheEntry;
184 };
185
186 IPlugIn* MemoryCacheFactory(const DOMElement* e)
187 {
188     return new InternalCCache(e);
189 }
190
191 /******************************************************************************/
192 /* InternalCCache:  in memory session cache                                   */
193 /******************************************************************************/
194
195 InternalCCache::InternalCCache(const DOMElement* e)
196     : m_root(e), lock(RWLock::create()), log(&Category::getInstance(SHIBT_LOGCAT".SessionCache")),
197         m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
198         m_strictValidity(true), m_propagateErrors(false) 
199         
200 {
201     const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
202     if (tag && *tag) {
203         m_AATimeout = XMLString::parseInt(tag);
204         if (!m_AATimeout)
205             m_AATimeout=30;
206     }
207     SAMLConfig::getConfig().timeout = m_AATimeout;
208
209     tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
210     if (tag && *tag) {
211         m_AAConnectTimeout = XMLString::parseInt(tag);
212         if (!m_AAConnectTimeout)
213             m_AAConnectTimeout=15;
214     }
215     SAMLConfig::getConfig().conn_timeout = m_AAConnectTimeout;
216     
217     tag=m_root->getAttributeNS(NULL,defaultLifetime);
218     if (tag && *tag) {
219         m_defaultLifetime = XMLString::parseInt(tag);
220         if (!m_defaultLifetime)
221             m_defaultLifetime=1800;
222     }
223
224     tag=m_root->getAttributeNS(NULL,retryInterval);
225     if (tag && *tag) {
226         m_retryInterval = XMLString::parseInt(tag);
227         if (!m_retryInterval)
228             m_retryInterval=300;
229     }
230     
231     tag=m_root->getAttributeNS(NULL,strictValidity);
232     if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
233         m_strictValidity=false;
234         
235     tag=m_root->getAttributeNS(NULL,propagateErrors);
236     if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
237         m_propagateErrors=true;
238
239     shutdown_wait = CondWait::create();
240     shutdown = false;
241     cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
242 }
243
244 InternalCCache::~InternalCCache()
245 {
246   // Shut down the cleanup thread and let it know...
247   shutdown = true;
248   shutdown_wait->signal();
249   cleanup_thread->join(NULL);
250
251   for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
252     delete i->second;
253   delete lock;
254   delete shutdown_wait;
255 }
256
257 string InternalCCache::generateKey() const
258 {
259     SAMLIdentifier id;
260     auto_ptr_char c(id);
261     return c.get();
262 }
263
264 // assumes a lock is held..
265 InternalCCacheEntry* InternalCCache::findi(const char* key)
266 {
267   map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
268   if (i==m_hashtable.end()) {
269     log->debug("No match found");
270     return NULL;
271   }
272   log->debug("Match found");
273
274   return i->second;
275 }
276
277 ISessionCacheEntry* InternalCCache::find(const char* key, const IApplication* application)
278 {
279   log->debug("searching memory cache for key (%s)", key);
280   ReadLock rwlock(lock);
281
282   InternalCCacheEntry* entry = findi(key);
283   if (!entry)
284     return NULL;
285   else if (!entry->checkApplication(application)) {
286     log->crit("An application (%s) attempted to access another application's session!", application->getId());
287     return NULL;
288   }
289
290   // Lock the "database record" for the caller -- they have to unlock the item.
291   entry->lock();
292   return entry;
293 }
294
295 void InternalCCache::insert(
296     const char* key,
297     const IApplication* application,
298     const char* client_addr,
299     ShibProfile profile,
300     const char* providerId,
301     SAMLAuthenticationStatement* s,
302     SAMLResponse* r,
303     const IRoleDescriptor* source,
304     time_t created,
305     time_t accessed
306     )
307 {
308   log->debug("caching new entry for application %s: \"%s\"", application->getId(), key);
309
310   InternalCCacheEntry* entry = new InternalCCacheEntry(
311     this,
312     key,
313     application,
314     client_addr,
315     profile,
316     providerId,
317     s,
318     r,
319     source,
320     created,
321     accessed
322     );
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 with key (%s)", 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 #ifdef _DEBUG
360   saml::NDC ndc("cleanup()");
361 #endif
362
363   int rerun_timer = 0;
364   int timeout_life = 0;
365   Mutex* mutex = Mutex::create();
366   
367   // Load our configuration details...
368   const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
369   if (tag && *tag)
370     rerun_timer = XMLString::parseInt(tag);
371
372   tag=m_root->getAttributeNS(NULL,cacheTimeout);
373   if (tag && *tag)
374     timeout_life = XMLString::parseInt(tag);
375   
376   if (rerun_timer <= 0)
377     rerun_timer = 300;        // rerun every 5 minutes
378
379   if (timeout_life <= 0)
380     timeout_life = 28800; // timeout after 8 hours
381
382   mutex->lock();
383
384   log->info("Cleanup thread started...  Run every %d secs; timeout after %d secs",
385              rerun_timer, timeout_life);
386
387   while (shutdown == false) {
388     shutdown_wait->timedwait(mutex,rerun_timer);
389
390     if (shutdown == true)
391       break;
392
393     log->debug("Cleanup thread running...");
394
395     // Ok, let's run through the cleanup process and clean out
396     // really old sessions.  This is a two-pass process.  The
397     // first pass is done holding a read-lock while we iterate over
398     // the database.  The second pass doesn't need a lock because
399     // the 'deletes' will lock the database.
400
401     // Pass 1: iterate over the map and find all entries that have not been
402     // used in X hours
403     vector<string> stale_keys;
404     time_t stale = time(NULL) - timeout_life;
405
406     lock->rdlock();
407     for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
408          i != m_hashtable.end(); i++)
409     {
410       // If the last access was BEFORE the stale timeout...
411       i->second->lock();
412       time_t last=i->second->lastAccess();
413       i->second->unlock();
414       if (last < stale)
415         stale_keys.push_back(i->first);
416     }
417     lock->unlock();
418
419     log->info("deleting %d old items.", stale_keys.size());
420
421     // Pass 2: walk through the list of stale entries and remove them from
422     // the database
423     for (vector<string>::iterator j = stale_keys.begin(); j != stale_keys.end(); j++) {
424       remove (j->c_str());
425       // Transaction Logging
426       STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
427       stc.getTransactionLog().infoStream() << "Purged expired session from memory (ID: " << j->c_str() << ")";
428       stc.releaseTransactionLog();
429     }
430   }
431
432   log->info("Cleanup thread finished.");
433
434   mutex->unlock();
435   delete mutex;
436   Thread::exit(NULL);
437 }
438
439 void* InternalCCache::cleanup_fcn(void* cache_p)
440 {
441   InternalCCache* cache = reinterpret_cast<InternalCCache*>(cache_p);
442
443   // First, let's block all signals 
444   Thread::mask_all_signals();
445
446   // Now run the cleanup process.
447   cache->cleanup();
448   return NULL;
449 }
450
451 /******************************************************************************/
452 /* InternalCCacheEntry:  A Credential Cache Entry                             */
453 /******************************************************************************/
454
455 InternalCCacheEntry::InternalCCacheEntry(
456     InternalCCache* cache,
457     const char* key,
458     const IApplication* application,
459     const char* client_addr,
460     ShibProfile profile,
461     const char* providerId,
462     SAMLAuthenticationStatement* s,
463     SAMLResponse* r,
464     const IRoleDescriptor* source,
465     time_t created,
466     time_t accessed
467     ) : m_application_id(application->getId()), m_responseCreated(r ? time(NULL) : 0), m_lastRetry(0),
468         m_profile(profile), m_auth_statement(s), m_response_pre(r), m_response_post(NULL), m_cache(cache) 
469 {
470   if (!cache || !key || !s || !client_addr || !providerId) {
471     if (cache) cache->log->error("missing required cache entry details");
472     throw SAMLException("InternalCCacheEntry() missing required cache entry details");
473   }
474
475   m_id=key;
476   m_clientAddress = client_addr;
477   m_provider_id = providerId;
478   m_sessionCreated = (created==0) ? time(NULL) : created;
479   m_lastAccess = (accessed==0) ? time(NULL) : accessed;
480
481   // If pushing attributes, filter the response.
482   if (r) {
483     m_cache->log->debug("filtering pushed attribute information");
484     m_response_post=filter(r, application, source);
485   }
486
487   m_lock = Mutex::create();
488
489   m_cache->log->info("new session created with session ID (%s)", key);
490   if (m_cache->log->isDebugEnabled()) {
491       auto_ptr_char h(s->getSubject()->getNameIdentifier()->getName());
492       m_cache->log->debug("NameID (%s), IdP (%s), Address (%s)", h.get(), providerId, client_addr);
493   }
494 }
495
496 InternalCCacheEntry::~InternalCCacheEntry()
497 {
498   m_cache->log->debug("deleting session (ID: %s)", m_id.c_str());
499   delete m_response_pre;
500   delete m_response_post;
501   delete m_auth_statement;
502   delete m_lock;
503 }
504
505 bool InternalCCacheEntry::isValid(time_t lifetime, time_t timeout) const
506 {
507 #ifdef _DEBUG
508   saml::NDC ndc("isValid");
509 #endif
510   
511   m_cache->log->debug("testing session (ID: %s) (lifetime=%ld, timeout=%ld)",
512     m_id.c_str(), lifetime, timeout);
513     
514   time_t now=time(NULL);
515   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
516     m_cache->log->info("session beyond lifetime (ID: %s)", m_id.c_str());
517     return false;
518   }
519
520   if (timeout > 0 && now-m_lastAccess >= timeout) {
521     m_cache->log->info("session timed out (ID: %s)", m_id.c_str());
522     return false;
523   }
524   m_lastAccess=now;
525   return true;
526 }
527
528 ISessionCacheEntry::CachedResponse InternalCCacheEntry::getResponse()
529 {
530 #ifdef _DEBUG
531     saml::NDC ndc("getResponse");
532 #endif
533     populate();
534     return CachedResponse(m_response_pre,m_response_post);
535 }
536
537 bool InternalCCacheEntry::responseValid()
538 {
539 #ifdef _DEBUG
540     saml::NDC ndc("responseValid");
541 #endif
542     m_cache->log->debug("checking validity of attribute assertions");
543     time_t now=time(NULL) - SAMLConfig::getConfig().clock_skew_secs;
544
545     int count = 0;
546     Iterator<SAMLAssertion*> assertions = m_response_post->getAssertions();
547     while (assertions.hasNext()) {
548         SAMLAssertion* assertion = assertions.next();
549         
550         // Only examine this assertion if it contains an attribute statement.
551         Iterator<SAMLStatement*> statements = assertion->getStatements();
552         while (statements.hasNext()) {
553             if (dynamic_cast<SAMLAttributeStatement*>(statements.next())) {
554                 const SAMLDateTime* thistime = assertion->getNotOnOrAfter();
555         
556                 // If there is no time, then just continue and ignore this assertion.
557                 if (!thistime)
558                     break;
559         
560                 count++;
561                 if (now >= thistime->getEpoch()) {
562                     m_cache->log->debug("found an expired attribute assertion, response is invalid");
563                     return false;
564                 }
565             }
566         }
567     }
568
569     // If we didn't find any assertions with times, then see if we're
570     // older than the default response lifetime.
571     if (!count) {
572         if (abs((int)(now - m_responseCreated)) > m_cache->m_defaultLifetime) {
573             m_cache->log->debug("response is beyond default life, so it's invalid");
574             return false;
575         }
576     }
577   
578     m_cache->log->debug("response still valid");
579     return true;
580 }
581
582 void InternalCCacheEntry::populate()
583 {
584 #ifdef _DEBUG
585   saml::NDC ndc("populate");
586 #endif
587   m_cache->log->debug("populating attributes for session (ID: %s)", m_id.c_str());
588
589   // Do we have any data cached?
590   if (m_response_pre) {
591       // Can we use what we have?
592       if (responseValid())
593         return;
594       
595       // If we're being strict, dump what we have and reset timestamps.
596       if (m_cache->m_strictValidity) {
597         m_cache->log->info("strictly enforcing attribute validity, dumping expired data");
598         delete m_response_pre;
599         delete m_response_post;
600         m_response_pre=m_response_post=NULL;
601         m_responseCreated=0;
602         m_lastRetry=0; 
603       }
604   }
605
606   try {
607     pair<SAMLResponse*,SAMLResponse*> new_responses=getNewResponse();
608     if (new_responses.first) {
609         delete m_response_pre;
610         delete m_response_post;
611         m_response_pre=new_responses.first;
612         m_response_post=new_responses.second;
613         m_responseCreated=time(NULL);
614         m_lastRetry=0;
615         m_cache->log->debug("fetched and stored new response");
616         STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
617         stc.getTransactionLog().infoStream() <<  "Successful attribute query for session (ID: " << m_id << ")";
618         stc.releaseTransactionLog();
619     }
620   }
621   catch (SAMLException&) {
622     if (m_cache->m_propagateErrors)
623         throw;
624     m_cache->log->warn("suppressed SAML exception caught while trying to fetch attributes");
625   }
626 #ifndef _DEBUG
627   catch (...) {
628     if (m_cache->m_propagateErrors)
629         throw;
630     m_cache->log->warn("suppressed unknown exception caught while trying to fetch attributes");
631   }
632 #endif
633 }
634
635 pair<SAMLResponse*,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 pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
645     if (m_lastRetry)
646         m_cache->log->debug("retry interval exceeded, so trying again");
647     m_lastRetry=now;
648
649     m_cache->log->info("trying to get new attributes for session (ID=%s)", m_id.c_str());
650     
651     // Transaction Logging
652     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
653     stc.getTransactionLog().infoStream() <<
654         "Making attribute query for session (ID: " <<
655             m_id <<
656         ") on (applicationId: " <<
657             m_application_id <<
658         ") for principal from (IdP: " <<
659             m_provider_id <<
660         ")";
661     stc.releaseTransactionLog();
662
663
664     // Caller must be holding the config lock.
665     // Lookup application for session to get providerId and attributes to request.
666     IConfig* conf=ShibTargetConfig::getConfig().getINI();
667     const IApplication* application=conf->getApplication(m_application_id.c_str());
668     if (!application) {
669         m_cache->log->crit("unable to locate application for session, deleted?");
670         throw SAMLException("Unable to locate application for session, deleted?");
671     }
672     pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
673     if (!providerID.first) {
674         m_cache->log->crit("unable to determine ProviderID for application, not set?");
675         throw SAMLException("Unable to determine ProviderID for application, not set?");
676     }
677
678     // Try this request.
679     Metadata m(application->getMetadataProviders());
680     const IEntityDescriptor* site=m.lookup(m_provider_id.c_str());
681     if (!site) {
682         m_cache->log->error("unable to locate identity provider's metadata during attribute query");
683         return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
684     }
685
686     // Try to locate an AA role.
687     int minorVersion=1;
688     const IAttributeAuthorityDescriptor* AA=site->getAttributeAuthorityDescriptor(saml::XML::SAML11_PROTOCOL_ENUM);
689     if (!AA) {
690         AA=site->getAttributeAuthorityDescriptor(saml::XML::SAML10_PROTOCOL_ENUM);
691         if (!AA) {
692             m_cache->log->warn("unable to locate metadata for identity provider's Attribute Authority");
693             return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
694         }
695         minorVersion=0;
696     }
697
698     // Get protocol signing policy.
699     const IPropertySet* credUse=application->getCredentialUse(site);
700     pair<bool,bool> signRequest=credUse ? credUse->getBool("signRequest") : make_pair(false,false);
701     pair<bool,const char*> signatureAlg=credUse ? credUse->getString("signatureAlg") : pair<bool,const char*>(false,NULL);
702     if (!signatureAlg.first)
703         signatureAlg.second=URI_ID_RSA_SHA1;
704     pair<bool,const char*> digestAlg=credUse ? credUse->getString("digestAlg") : pair<bool,const char*>(false,NULL);
705     if (!digestAlg.first)
706         digestAlg.second=URI_ID_SHA1;
707     pair<bool,bool> signedResponse=credUse ? credUse->getBool("signedResponse") : make_pair(false,false);
708     pair<bool,const char*> signingCred=credUse ? credUse->getString("Signing") : pair<bool,const char*>(false,NULL);
709     
710     SAMLResponse* response = NULL;
711     try {
712         // Build a SAML Request....
713         SAMLAttributeQuery* q=new SAMLAttributeQuery(
714             new SAMLSubject(static_cast<SAMLNameIdentifier*>(m_auth_statement->getSubject()->getNameIdentifier()->clone())),
715             providerID.second,
716             application->getAttributeDesignators().clone()
717             );
718         auto_ptr<SAMLRequest> req(new SAMLRequest(q));
719         req->setMinorVersion(minorVersion);
720         
721         // Sign it? Highly doubtful we'll ever use this, but just for fun...
722         if (signRequest.first && signRequest.second && signingCred.first) {
723             if (req->getMinorVersion()==1) {
724                 Credentials creds(conf->getCredentialsProviders());
725                 const ICredResolver* cr=creds.lookup(signingCred.second);
726                 if (cr)
727                     req->sign(cr->getKey(),cr->getCertificates(),signatureAlg.second,digestAlg.second);
728                 else
729                     m_cache->log->error("unable to sign attribute query, specified credential (%s) was not found",signingCred.second);
730             }
731             else
732                 m_cache->log->error("unable to sign SAML 1.0 attribute query, only SAML 1.1 defines signing adequately");
733         }
734             
735         m_cache->log->debug("trying to query an AA...");
736
737         // Call context object
738         Trust t(application->getTrustProviders());
739         ShibHTTPHook::ShibHTTPHookCallContext ctx(credUse,AA);
740         
741         // Use metadata to locate endpoints.
742         Iterator<const IEndpoint*> endpoints=AA->getAttributeServiceManager()->getEndpoints();
743         while (!response && endpoints.hasNext()) {
744             const IEndpoint* ep=endpoints.next();
745             try {
746                 // Get a binding object for this protocol.
747                 const SAMLBinding* binding = application->getBinding(ep->getBinding());
748                 if (!binding) {
749                     auto_ptr_char prot(ep->getBinding());
750                     m_cache->log->warn("skipping binding on unsupported protocol (%s)", prot.get());
751                     continue;
752                 }
753                 static const XMLCh https[] = {chLatin_h, chLatin_t, chLatin_t, chLatin_p, chLatin_s, chColon, chNull};
754                 auto_ptr<SAMLResponse> r(binding->send(ep->getLocation(), *(req.get()), &ctx));
755                 if (r->isSigned()) {
756                         if (!t.validate(*r,AA))
757                             throw TrustException("Unable to verify signed response message.");
758                 }
759                 else if (!ctx.isAuthenticated() || XMLString::compareNString(ep->getLocation(),https,6))
760                         throw TrustException("Response message was unauthenticated.");
761                 response = r.release();
762             }
763             catch (SAMLException& e) {
764                 m_cache->log->error("caught SAML exception during SAML attribute query: %s", e.what());
765                 // Check for shib:InvalidHandle error and propagate it out.
766                 Iterator<saml::QName> codes=e.getCodes();
767                 if (codes.size()>1) {
768                     const saml::QName& code=codes[1];
769                     if (!XMLString::compareString(code.getNamespaceURI(),shibboleth::Constants::SHIB_NS) &&
770                         !XMLString::compareString(code.getLocalName(), shibboleth::Constants::InvalidHandle)) {
771                         codes.reset();
772                         throw InvalidHandleException(e.what(),params(),codes);
773                     }
774                 }
775             }
776         }
777
778         if (response) {
779             if (signedResponse.first && signedResponse.second && !response->isSigned()) {
780                 delete response;
781                 m_cache->log->error("unsigned response obtained, but we were told it must be signed.");
782                 throw TrustException("Unable to obtain a signed response message.");
783             }
784             
785             // Run it through the filter.
786             return make_pair(response,filter(response,application,AA));
787         }
788     }
789     catch (SAMLException& e) {
790         m_cache->log->error("caught SAML exception during query to AA: %s", e.what());
791         annotateException(&e,AA);
792     }
793     
794     m_cache->log->error("no response obtained");
795     return pair<SAMLResponse*,SAMLResponse*>(NULL,NULL);
796 }
797
798 SAMLResponse* InternalCCacheEntry::filter(SAMLResponse* r, const IApplication* application, const IRoleDescriptor* source)
799 {
800     const IPropertySet* credUse=application->getCredentialUse(source->getEntityDescriptor());
801     pair<bool,bool> signedAssertions=credUse ? credUse->getBool("signedAssertions") : make_pair(false,false);
802     Trust t(application->getTrustProviders());
803
804     // Examine each original assertion...
805     Iterator<SAMLAssertion*> assertions=r->getAssertions();
806     for (unsigned long i=0; i < assertions.size();) {
807         // Check signing policy.
808         if (signedAssertions.first && signedAssertions.second && !(assertions[i]->isSigned())) {
809             m_cache->log->warn("removing unsigned assertion from response, in accordance with signedAssertions policy");
810             r->removeAssertion(i);
811             continue;
812         }
813
814         // Check any conditions.
815         bool pruned=false;
816         Iterator<SAMLCondition*> conds=assertions[i]->getConditions();
817         while (conds.hasNext()) {
818             SAMLAudienceRestrictionCondition* cond=dynamic_cast<SAMLAudienceRestrictionCondition*>(conds.next());
819             if (!cond || !cond->eval(application->getAudiences())) {
820                 m_cache->log->warn("assertion condition invalid, removing it");
821                 r->removeAssertion(i);
822                 pruned=true;
823                 break;
824             }
825         }
826         if (pruned)
827             continue;
828         
829         // Check token signature.
830         if (assertions[i]->isSigned() && !t.validate(*(assertions[i]),source)) {
831             m_cache->log->warn("signed assertion failed to validate, removing it");
832             r->removeAssertion(i);
833             continue;
834         }
835         i++;
836     }
837
838     // Make a copy of whatever's left and process that against the AAP.
839     auto_ptr<SAMLResponse> copy(static_cast<SAMLResponse*>(r->clone()));
840     copy->toDOM();
841
842     Iterator<SAMLAssertion*> copies=copy->getAssertions();
843     for (unsigned long j=0; j < copies.size();) {
844         try {
845             // Finally, filter the content.
846             AAP::apply(application->getAAPProviders(),*(copies[j]),source);
847             j++;
848
849         }
850         catch (SAMLException&) {
851             m_cache->log->info("no statements remain after AAP, removing assertion");
852             copy->removeAssertion(j);
853         }
854     }
855
856     // Audit the results.    
857     STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
858     Category& tran=stc.getTransactionLog();
859     if (tran.isInfoEnabled()) {
860         tran.infoStream() <<
861             "Caching the following attributes after AAP applied for session (ID: " <<
862                 m_id <<
863             ") on (applicationId: " <<
864                 m_application_id <<
865             ") for principal from (IdP: " <<
866                 m_provider_id <<
867             ") {";
868
869         Iterator<SAMLAssertion*> loggies=copy->getAssertions();
870         while (loggies.hasNext()) {
871             SAMLAssertion* logit=loggies.next();
872             Iterator<SAMLStatement*> states=logit->getStatements();
873             while (states.hasNext()) {
874                 SAMLAttributeStatement* state=dynamic_cast<SAMLAttributeStatement*>(states.next());
875                 Iterator<SAMLAttribute*> attrs=state ? state->getAttributes() : EMPTY(SAMLAttribute*);
876                 while (attrs.hasNext()) {
877                     SAMLAttribute* attr=attrs.next();
878                     auto_ptr_char attrname(attr->getName());
879                     tran.infoStream() << "\t" << attrname.get() << " (" << attr->getValues().size() << " values)";
880                 }
881             }
882         }
883         tran.info("}");
884     }
885     stc.releaseTransactionLog();
886     
887     return copy.release();
888 }