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