Moved NameIdentifier out of Subject.
[shibboleth/sp.git] / shib-target / shib-ccache.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50
51 /*
52  * shib-ccache.cpp -- SHAR Credential Cache
53  *
54  * Originally from mod_shib
55  * Modified by: Derek Atkins <derek@ihtfp.com>
56  *
57  * $Id$
58  */
59
60 #include "internal.h"
61
62 #if HAVE_UNISTD_H
63 # include <unistd.h>
64 #endif
65
66 #include <shib/shib-threads.h>
67
68 #include <log4cpp/Category.hh>
69
70 #include <sstream>
71 #include <stdexcept>
72
73 #ifdef HAVE_LIBDMALLOCXX
74 #include <dmalloc.h>
75 #endif
76
77 using namespace std;
78 using namespace log4cpp;
79 using namespace saml;
80 using namespace shibboleth;
81 using namespace shibtarget;
82
83 static const XMLCh cleanupInterval[] =
84 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
85   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
86 };
87 static const XMLCh cacheTimeout[] =
88 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
89   chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
90 };
91 static const XMLCh AAConnectTimeout[] =
92 { chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
93   chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
94 };
95 static const XMLCh AATimeout[] =
96 { chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
97
98 static const XMLCh defaultLifetime[] =
99 { chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
100   chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
101 };
102 static const XMLCh retryInterval[] =
103 { chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
104   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
105 };
106 static const XMLCh strictValidity[] =
107 { chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
108   chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
109 };
110 static const XMLCh propagateErrors[] =
111 { chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
112   chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
113 };
114
115 class InternalCCache;
116 class InternalCCacheEntry : public ISessionCacheEntry
117 {
118 public:
119   InternalCCacheEntry(const IApplication* application, SAMLAuthenticationStatement* s, const char *client_addr, SAMLResponse* r=NULL);
120   ~InternalCCacheEntry();
121
122   void lock() { m_lock->lock(); }
123   void unlock() { m_lock->unlock(); }
124
125   bool isValid(time_t lifetime, time_t timeout) const;
126   const char* getClientAddress() const { return m_clientAddress.c_str(); }
127   const char* getSerializedStatement() const { return m_statement.c_str(); }
128   const SAMLAuthenticationStatement* getStatement() const { return p_auth; }
129
130   Iterator<SAMLAssertion*> getAssertions();
131   void preFetch(int prefetch_window);
132
133   void setCache(InternalCCache *cache) { m_cache = cache; }
134   time_t lastAccess() const { return m_lastAccess; }
135
136 private:
137   bool responseValid(int slop);
138   void populate(int slop);
139   SAMLResponse* getNewResponse();
140               
141   string m_application_id;
142   string m_statement;
143   string m_originSite;
144   string m_handle;
145   string m_clientAddress;
146   time_t m_sessionCreated;
147   time_t m_responseCreated;
148   mutable time_t m_lastAccess;
149   time_t m_lastRetry;
150
151   const SAMLNameIdentifier* m_nameid;
152   SAMLAuthenticationStatement* p_auth;
153   SAMLResponse* m_response;
154   InternalCCache *m_cache;
155
156   log4cpp::Category* log;
157   
158   Mutex* m_lock;
159 };
160
161 class InternalCCache : public ISessionCache
162 {
163 public:
164   InternalCCache(const DOMElement* e);
165   virtual ~InternalCCache();
166
167   void thread_init() {};
168   void thread_end() {};
169
170   string generateKey() const;
171   ISessionCacheEntry* find(const char* key);
172   void insert(
173     const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char *client_addr, SAMLResponse* r=NULL
174     );
175   void remove(const char* key);
176
177   InternalCCacheEntry* findi(const char* key);
178   void  cleanup();
179
180 private:
181   const DOMElement* m_root;         // Only valid during initialization
182   RWLock *lock;
183   map<string,InternalCCacheEntry*> m_hashtable;
184
185   log4cpp::Category* log;
186
187   static void*  cleanup_fcn(void*); // XXX Assumed an InternalCCache
188   bool          shutdown;
189   CondWait*     shutdown_wait;
190   Thread*       cleanup_thread;
191   
192   // extracted config settings
193   unsigned int m_AATimeout,m_AAConnectTimeout;
194   unsigned int m_defaultLifetime,m_retryInterval;
195   bool m_strictValidity,m_propagateErrors;
196   friend class InternalCCacheEntry;
197 };
198
199 IPlugIn* MemoryCacheFactory(const DOMElement* e)
200 {
201     return new InternalCCache(e);
202 }
203
204 /******************************************************************************/
205 /* InternalCCache:  in memory session cache                                   */
206 /******************************************************************************/
207
208 InternalCCache::InternalCCache(const DOMElement* e)
209     : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
210         m_strictValidity(true), m_propagateErrors(false), lock(RWLock::create()),
211         log (&Category::getInstance("shibtarget.InternalCCache"))
212 {
213     const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
214     if (tag && *tag) {
215         m_AATimeout = XMLString::parseInt(tag);
216         if (!m_AATimeout)
217             m_AATimeout=30;
218     }
219
220     tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
221     if (tag && *tag) {
222         m_AAConnectTimeout = XMLString::parseInt(tag);
223         if (!m_AAConnectTimeout)
224             m_AAConnectTimeout=15;
225     }
226     
227     tag=m_root->getAttributeNS(NULL,defaultLifetime);
228     if (tag && *tag) {
229         m_defaultLifetime = XMLString::parseInt(tag);
230         if (!m_defaultLifetime)
231             m_defaultLifetime=1800;
232     }
233
234     tag=m_root->getAttributeNS(NULL,retryInterval);
235     if (tag && *tag) {
236         m_retryInterval = XMLString::parseInt(tag);
237         if (!m_retryInterval)
238             m_retryInterval=300;
239     }
240     
241     tag=m_root->getAttributeNS(NULL,strictValidity);
242     if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
243         m_strictValidity=false;
244         
245     tag=m_root->getAttributeNS(NULL,propagateErrors);
246     if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
247         m_propagateErrors=true;
248
249     shutdown_wait = CondWait::create();
250     shutdown = false;
251     cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
252 }
253
254 InternalCCache::~InternalCCache()
255 {
256   // Shut down the cleanup thread and let it know...
257   shutdown = true;
258   shutdown_wait->signal();
259   cleanup_thread->join(NULL);
260
261   for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
262     delete i->second;
263   delete lock;
264   delete shutdown_wait;
265 }
266
267 string InternalCCache::generateKey() const
268 {
269     SAMLIdentifier id;
270     auto_ptr_char c(id);
271     return c.get();
272 }
273
274 // assumes a lock is held..
275 InternalCCacheEntry* InternalCCache::findi(const char* key)
276 {
277   log->debug("findI: \"%s\"", key);
278
279   map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
280   if (i==m_hashtable.end()) {
281     log->debug("No Match found");
282     return NULL;
283   }
284   log->debug("Match Found.");
285
286   return i->second;
287 }
288
289 ISessionCacheEntry* InternalCCache::find(const char* key)
290 {
291   log->debug("Find: \"%s\"", key);
292   ReadLock rwlock(lock);
293
294   InternalCCacheEntry* entry = findi(key);
295   if (!entry) return NULL;
296
297   // Lock the "database record" for the caller -- they have to unlock the item.
298   entry->lock();
299   return entry;
300 }
301
302 void InternalCCache::insert(
303     const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char* client_addr, SAMLResponse* r
304     )
305 {
306   log->debug("caching new entry for application %s: \"%s\"", application->getId(), key);
307
308   InternalCCacheEntry* entry = new InternalCCacheEntry(application, s, client_addr, r);
309   entry->setCache(this);
310
311   lock->wrlock();
312   m_hashtable[key]=entry;
313   lock->unlock();
314 }
315
316 // remove the entry from the database and then destroy the cacheentry
317 void InternalCCache::remove(const char* key)
318 {
319   log->debug("removing cache entry \"key\"", key);
320
321   // lock the cache for writing, which means we know nobody is sitting in find()
322   lock->wrlock();
323
324   // grab the entry from the database.
325   ISessionCacheEntry* entry = findi(key);
326
327   if (!entry) {
328     lock->unlock();
329     return;
330   }
331
332   // ok, remove the entry and lock it
333   m_hashtable.erase(key);
334   dynamic_cast<InternalCCacheEntry*>(entry)->lock();
335   lock->unlock();
336
337   // we can release the entry lock because we know we're not in the cache anymore
338   entry->unlock();
339
340   // Now delete the entry
341   delete entry;
342 }
343
344 void InternalCCache::cleanup()
345 {
346   Mutex* mutex = Mutex::create();
347   saml::NDC ndc("InternalCCache::cleanup()");
348
349   int rerun_timer = 0;
350   int timeout_life = 0;
351
352   // Load our configuration details...
353   const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
354   if (tag && *tag)
355     rerun_timer = XMLString::parseInt(tag);
356
357   tag=m_root->getAttributeNS(NULL,cacheTimeout);
358   if (tag && *tag)
359     timeout_life = XMLString::parseInt(tag);
360   
361   if (rerun_timer <= 0)
362     rerun_timer = 300;        // rerun every 5 minutes
363
364   if (timeout_life <= 0)
365     timeout_life = 28800; // timeout after 8 hours
366
367   mutex->lock();
368
369   log->debug("Cleanup thread started...  Run every %d secs; timeout after %d secs",
370              rerun_timer, timeout_life);
371
372   while (shutdown == false) {
373     shutdown_wait->timedwait(mutex,rerun_timer);
374
375     if (shutdown == true)
376       break;
377
378     log->info("Cleanup thread running...");
379
380     // Ok, let's run through the cleanup process and clean out
381     // really old sessions.  This is a two-pass process.  The
382     // first pass is done holding a read-lock while we iterate over
383     // the database.  The second pass doesn't need a lock because
384     // the 'deletes' will lock the database.
385
386     // Pass 1: iterate over the map and find all entries that have not been
387     // used in X hours
388     vector<string> stale_keys;
389     time_t stale = time(NULL) - timeout_life;
390
391     lock->rdlock();
392     for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
393          i != m_hashtable.end(); i++)
394     {
395       // If the last access was BEFORE the stale timeout...
396       i->second->lock();
397       time_t last=i->second->lastAccess();
398       i->second->unlock();
399       if (last < stale)
400         stale_keys.push_back(i->first);
401     }
402     lock->unlock();
403
404     log->info("deleting %d old items.", stale_keys.size());
405
406     // Pass 2: walk through the list of stale entries and remove them from
407     // the database
408     for (vector<string>::iterator j = stale_keys.begin(); j != stale_keys.end(); j++)
409       remove (j->c_str());
410   }
411
412   log->debug("Cleanup thread finished.");
413
414   mutex->unlock();
415   delete mutex;
416   Thread::exit(NULL);
417 }
418
419 void* InternalCCache::cleanup_fcn(void* cache_p)
420 {
421   InternalCCache* cache = reinterpret_cast<InternalCCache*>(cache_p);
422
423   // First, let's block all signals 
424   Thread::mask_all_signals();
425
426   // Now run the cleanup process.
427   cache->cleanup();
428   return NULL;
429 }
430
431 /******************************************************************************/
432 /* InternalCCacheEntry:  A Credential Cache Entry                             */
433 /******************************************************************************/
434
435 InternalCCacheEntry::InternalCCacheEntry(
436     const IApplication* application, SAMLAuthenticationStatement *s, const char* client_addr, SAMLResponse* r
437     ) : m_response(r), m_responseCreated(r ? time(NULL) : 0), m_lastRetry(0),
438         log(&Category::getInstance("shibtarget::InternalCCacheEntry"))
439 {
440   if (s == NULL) {
441     log->error("NULL auth statement");
442     throw SAMLException("InternalCCacheEntry() passed an empty SAML Statement");
443   }
444
445   m_application_id=application->getId();
446
447   m_nameid = s->getSubject()->getNameIdentifier();
448   auto_ptr_char h(m_nameid->getName());
449   auto_ptr_char d(m_nameid->getNameQualifier());
450   m_handle = h.get();
451   m_originSite = d.get();
452
453   m_clientAddress = client_addr;
454   m_sessionCreated = m_lastAccess = time(NULL);
455
456   // Save for later.
457   p_auth = s;
458   
459   // Save the serialized version of the auth statement
460   ostringstream os;
461   os << *s;
462   m_statement = os.str();
463
464   if (r) {
465     // Run pushed data through the AAP. Note that we could end up with an empty response!
466     Metadata m(application->getMetadataProviders());
467     const IProvider* site=m.lookup(m_nameid->getNameQualifier());
468     if (!site)
469         throw MetadataException("unable to locate origin site's metadata during attribute acceptance processing");
470     Iterator<SAMLAssertion*> assertions=r->getAssertions();
471     for (unsigned long i=0; i < assertions.size();) {
472         try {
473             AAP::apply(application->getAAPProviders(),site,*(assertions[i]));
474             i++;
475         }
476         catch (SAMLException&) {
477             log->info("no statements remain, removing assertion");
478             r->removeAssertion(i);
479         }
480     }
481   }
482
483   m_lock = Mutex::create();
484
485   log->info("New Session Created...");
486   log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(), client_addr);
487 }
488
489 InternalCCacheEntry::~InternalCCacheEntry()
490 {
491   log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
492   delete m_response;
493   delete p_auth;
494   delete m_lock;
495 }
496
497 bool InternalCCacheEntry::isValid(time_t lifetime, time_t timeout) const
498 {
499   saml::NDC ndc("isValid");
500   log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
501              m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
502   time_t now=time(NULL);
503   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
504     log->debug("session beyond lifetime");
505     return false;
506   }
507
508   if (timeout > 0 && now-m_lastAccess >= timeout) {
509     log->debug("session timed out");
510     return false;
511   }
512   m_lastAccess=now;
513   return true;
514 }
515
516 Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions()
517 {
518   saml::NDC ndc("getAssertions");
519   populate(0);
520   
521   return (m_response) ? m_response->getAssertions() : EMPTY(SAMLAssertion*);
522 }
523
524 void InternalCCacheEntry::preFetch(int prefetch_window)
525 {
526   saml::NDC ndc("preFetch");
527   populate(prefetch_window);
528 }
529
530 bool InternalCCacheEntry::responseValid(int slop)
531 {
532   saml::NDC ndc("responseValid");
533
534   log->info("checking AA response validity");
535
536   // This is awful, but the XMLDateTime class is truly horrible.
537   time_t now=time(NULL)+slop;
538 #ifdef WIN32
539   struct tm* ptime=gmtime(&now);
540 #else
541   struct tm res;
542   struct tm* ptime=gmtime_r(&now,&res);
543 #endif
544   char timebuf[32];
545   strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
546   auto_ptr_XMLCh timeptr(timebuf);
547   XMLDateTime curDateTime(timeptr.get());
548   curDateTime.parseDateTime();
549
550   int count = 0;
551   Iterator<SAMLAssertion*> iter = m_response->getAssertions();
552   while (iter.hasNext()) {
553     SAMLAssertion* assertion = iter.next();
554
555     log->debug ("testing assertion...");
556
557     const XMLDateTime* thistime = assertion->getNotOnOrAfter();
558
559     // If there is no time, then just continue and ignore this assertion.
560     if (!thistime)
561       continue;
562
563     count++;
564     auto_ptr_char nowptr(curDateTime.getRawData());
565     auto_ptr_char assnptr(thistime->getRawData());
566
567     log->debug ("comparing now (%s) to %s", nowptr.get(), assnptr.get());
568     int result=XMLDateTime::compareOrder(&curDateTime, thistime);
569
570     if (result != XMLDateTime::LESS_THAN) {
571       log->debug("nope, not still valid");
572       return false;
573     }
574   }
575
576   // If we didn't find any assertions with times, then see if we're
577   // older than the default response lifetime.
578   if (!count) {
579       if ((now - m_responseCreated) > m_cache->m_defaultLifetime) {
580         log->debug("response is beyond default life, so it's invalid");
581         return false;
582       }
583   }
584   
585   log->debug("yep, response still valid");
586   return true;
587 }
588
589 void InternalCCacheEntry::populate(int slop)
590 {
591   saml::NDC ndc("populate");
592   log->debug("populating session cache for application %s", m_application_id.c_str());
593
594   // Do we have any data cached?
595   if (m_response) {
596       // Can we use what we have?
597       if (responseValid(slop))
598         return;
599       
600       // If we're being strict, dump what we have and reset timestamps.
601       if (m_cache->m_strictValidity) {
602         log->info("strictly enforcing attribute validity, dumping expired data");
603         delete m_response;
604         m_response=NULL;
605         m_responseCreated=0;
606         m_lastRetry=0; 
607       }
608   }
609
610   // Need to try and get a new response.
611
612   try {
613     SAMLResponse* new_response=getNewResponse();
614     if (new_response) {
615         delete m_response;
616         m_response=new_response;
617         m_responseCreated=time(NULL);
618         m_lastRetry=0;
619         log->debug("fetched and stored new response");
620     }
621   }
622   catch (SAMLException& e) {
623     if (typeid(e)==typeid(InvalidHandleException) || m_cache->m_propagateErrors)
624         throw;
625     log->warn("suppressed SAML exception caught while trying to fetch attributes");
626   }
627   catch (...) {
628     if (m_cache->m_propagateErrors)
629         throw;
630     log->warn("suppressed exception caught while trying to fetch attributes");
631   }
632 }
633
634 SAMLResponse* InternalCCacheEntry::getNewResponse()
635 {
636     saml::NDC ndc("getNewResponse");
637
638     // The retryInterval determines how often to poll an AA that might be down.
639     if ((time(NULL) - m_lastRetry) < m_cache->m_retryInterval)
640         return NULL;
641     if (m_lastRetry)
642         log->debug("retry interval exceeded, so trying again");
643     m_lastRetry=time(NULL);
644     
645     log->info("trying to request attributes for %s@%s -> %s", m_handle.c_str(), m_originSite.c_str(), m_application_id.c_str());
646
647     // Lookup application for session to get providerId and attributes to request.
648     IConfig* conf=ShibTargetConfig::getConfig().getINI();
649     Locker locker(conf);
650     const IApplication* application=conf->getApplication(m_application_id.c_str());
651     if (!application) {
652         log->crit("unable to locate application for session, deleted?");
653         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate application for session, deleted?");
654     }
655     pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
656     if (!providerID.first) {
657         log->crit("unable to determine ProviderID for application, not set?");
658         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to determine ProviderID for application, not set?");
659     }
660
661     // Get signing policies.
662     bool signRequest=false;
663     bool signedResponse=false;
664     bool signedAssertions=false;
665     const IPropertySet* props=application->getPropertySet("Policy");
666     if (props) {
667         pair<bool,bool> flag=props->getBool("signRequest");
668         if (flag.first)
669             signRequest=flag.second;
670         flag=props->getBool("signedResponse");
671         if (flag.first)
672             signedResponse=flag.second;
673         flag=props->getBool("signedAssertions");
674         if (flag.first)
675             signedAssertions=flag.second;
676     }
677     
678     // Try this request. The binding wrapper class handles most of the details.
679     Metadata m(application->getMetadataProviders());
680     const IProvider* site=m.lookup(m_nameid->getNameQualifier());
681     if (!site) {
682         log->error("unable to locate origin site's metadata during attribute query");
683         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate origin site's metadata during attribute query.");
684     }
685
686     // Try to locate an AA role.
687     const IAttributeAuthorityRole* AA=NULL;
688     Iterator<const IProviderRole*> roles=site->getRoles();
689     while (!AA && roles.hasNext()) {
690         const IProviderRole* role=roles.next();
691         if (dynamic_cast<const IAttributeAuthorityRole*>(role)) {
692             // Check for SAML 1.x protocol support.
693             if (role->hasSupport(saml::XML::SAMLP_NS))
694                 AA=dynamic_cast<const IAttributeAuthorityRole*>(role);
695         }
696     }
697     if (!AA) {
698         log->error("unable to locate metadata for origin site's Attribute Authority");
699         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate metadata for origin site's Attribute Authority.",site);
700     }
701
702
703     SAMLResponse* response = NULL;
704     try {
705         // Build a SAML Request....
706         SAMLAttributeQuery* q=new SAMLAttributeQuery(
707             new SAMLSubject(static_cast<SAMLNameIdentifier*>(m_nameid->clone())),
708             providerID.second,
709             application->getAttributeDesignators().clone()
710             );
711         auto_ptr<SAMLRequest> req(new SAMLRequest(EMPTY(QName),q));
712         
713         // Sign it? Highly doubtful we'll ever use this, but just for fun...
714         if (signRequest) {
715             Credentials creds(conf->getCredentialsProviders());
716             const ICredResolver* signingCred=creds.lookup(application->getSigningCred(site));
717             req->sign(SIGNATURE_RSA,signingCred->getKey(),signingCred->getCertificates());
718         }
719             
720         log->debug("Trying to query an AA...");
721
722         SAMLConfig::SAMLBindingConfig bindconf;
723         bindconf.timeout=m_cache->m_AATimeout;
724         bindconf.conn_timeout=m_cache->m_AAConnectTimeout;
725         ShibBinding binding(application->getRevocationProviders(),application->getTrustProviders(),conf->getCredentialsProviders());
726         response=binding.send(*req,AA,application->getTLSCred(site),application->getAudiences(),p_auth->getBindings(),bindconf);
727     }
728     catch (SAMLException& e) {
729         log->error("caught SAML exception during query to AA: %s", e.what());
730         if (typeid(e)==typeid(InvalidHandleException))
731             throw;
732         ostringstream os;
733         os << e;
734         throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), AA);
735     }
736     // See if we got a response.
737     if (!response) {
738         log->error("no response obtained");
739         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to obtain attributes from user's origin site.",AA);
740     }
741     else if (signedResponse && !response->isSigned()) {
742         delete response;
743         log->error("unsigned response obtained, but we were told it must be signed.");
744         throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to obtain attributes from user's origin site.",AA);
745     }
746
747     // Run it through the AAP. Note that we could end up with an empty response!
748     Iterator<SAMLAssertion*> a=response->getAssertions();
749     for (unsigned long i=0; i < a.size();) {
750         try {
751             if (signedAssertions && !(a[i]->isSigned())) {
752                 log->warn("removing unsigned assertion from response, in accordance with signedAssertions policy");
753                 response->removeAssertion(i);
754                 continue;
755             }
756             AAP::apply(application->getAAPProviders(),site,*(a[i]));
757             i++;
758         }
759         catch (SAMLException&) {
760             log->info("no statements remain, removing assertion");
761             response->removeAssertion(i);
762         }
763     }
764
765     return response;
766 }