Removed Policy element from configuration
[shibboleth/sp.git] / shib-target / shib-ccache.cpp
index 556266e..39077f7 100644 (file)
  * $Id$
  */
 
-#ifndef WIN32
+#include "internal.h"
+
+#if HAVE_UNISTD_H
 # include <unistd.h>
 #endif
 
-#include "shib-target.h"
-#include "shib-threads.h"
+#include <shib/shib-threads.h>
 
 #include <log4cpp/Category.hh>
 
 #endif
 
 using namespace std;
+using namespace log4cpp;
 using namespace saml;
 using namespace shibboleth;
 using namespace shibtarget;
 
-class ResourceEntry
-{
-public:
-  ResourceEntry(SAMLResponse* response);
-  ~ResourceEntry();
-
-  bool isValid();
-  Iterator<SAMLAssertion*> getAssertions();
-
-  static vector<SAMLAssertion*> g_emptyVector;
-
-private:
-  SAMLResponse* m_response;
+static const XMLCh cleanupInterval[] =
+{ chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
+  chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
+};
+static const XMLCh cacheTimeout[] =
+{ chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e,
+  chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
+};
+static const XMLCh AAConnectTimeout[] =
+{ chLatin_A, chLatin_A, chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t,
+  chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull
+};
+static const XMLCh AATimeout[] =
+{ chLatin_A, chLatin_A, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
 
-  log4cpp::Category* log;
+static const XMLCh defaultLifetime[] =
+{ chLatin_d, chLatin_e, chLatin_f, chLatin_a, chLatin_u, chLatin_l, chLatin_t,
+  chLatin_L, chLatin_i, chLatin_f, chLatin_e, chLatin_t, chLatin_i, chLatin_m, chLatin_e, chNull
+};
+static const XMLCh retryInterval[] =
+{ chLatin_r, chLatin_e, chLatin_t, chLatin_r, chLatin_y,
+  chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
+};
+static const XMLCh strictValidity[] =
+{ chLatin_s, chLatin_t, chLatin_r, chLatin_i, chLatin_c, chLatin_t,
+  chLatin_V, chLatin_a, chLatin_l, chLatin_i, chLatin_d, chLatin_i, chLatin_t, chLatin_y, chNull
+};
+static const XMLCh propagateErrors[] =
+{ chLatin_p, chLatin_r, chLatin_o, chLatin_p, chLatin_a, chLatin_g, chLatin_a, chLatin_t, chLatin_e,
+  chLatin_E, chLatin_r, chLatin_r, chLatin_o, chLatin_r, chLatin_s, chNull
 };
 
 class InternalCCache;
-class InternalCCacheEntry : public CCacheEntry
+class InternalCCacheEntry : public ISessionCacheEntry
 {
 public:
-  InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
+  InternalCCacheEntry(
+    const char* id,
+    const IApplication* application,
+    SAMLAuthenticationStatement* s,
+    const char *client_addr,
+    SAMLResponse* r=NULL
+    );
   ~InternalCCacheEntry();
 
-  virtual Iterator<SAMLAssertion*> getAssertions(Resource& resource);
-  virtual bool isSessionValid(time_t lifetime, time_t timeout);
-  virtual const char* getClientAddress() { return m_clientAddress.c_str(); }
+  void lock() { m_lock->lock(); }
+  void unlock() { m_lock->unlock(); }
+
+  bool isValid(time_t lifetime, time_t timeout) const;
+  const char* getClientAddress() const { return m_clientAddress.c_str(); }
+  const char* getSerializedStatement() const { return m_statement.c_str(); }
+  const SAMLAuthenticationStatement* getStatement() const { return p_auth; }
+
+  Iterator<SAMLAssertion*> getAssertions();
+  void preFetch(int prefetch_window);
 
   void setCache(InternalCCache *cache) { m_cache = cache; }
+  time_t lastAccess() const { return m_lastAccess; }
 
 private:
-  ResourceEntry* populate(Resource& resource);
-  ResourceEntry* find(const char* resource);
-  void insert(const char* resource, ResourceEntry* entry);
-  void remove(const char* resource);
-
+  bool responseValid(int slop);
+  void populate(int slop);
+  SAMLResponse* getNewResponse();
+  
+  string m_id;
+  string m_application_id;
+  string m_statement;
   string m_originSite;
-  string m_handle;
   string m_clientAddress;
   time_t m_sessionCreated;
-  time_t m_lastAccess;
-  bool m_hasbinding;
+  time_t m_responseCreated;
+  mutable time_t m_lastAccess;
+  time_t m_lastRetry;
 
-  const SAMLSubject* m_subject;
+  const SAMLNameIdentifier* m_nameid;
   SAMLAuthenticationStatement* p_auth;
+  SAMLResponse* m_response;
   InternalCCache *m_cache;
 
-  map<string,ResourceEntry*> m_resources;
-
-  static saml::QName g_authorityKind;
-  static saml::QName g_respondWith;
-
   log4cpp::Category* log;
-
-  // This is used to keep track of in-process "populate()" calls,
-  // to make sure that we don't try to populate the same resource
-  // in multiple threads.
-  map<string,Mutex*>   populate_locks;
-  Mutex*       pop_locks_lock;
-
-  Mutex*       access_lock;
-  RWLock*      resource_lock;
-
-  class ResourceLock
-  {
-  public:
-    ResourceLock(InternalCCacheEntry* entry, string resource);
-    ~ResourceLock();
-
-  private:
-    Mutex*                     find(string& resource);
-    InternalCCacheEntry*       entry;
-    string                     resource;
-  };
+  Mutex* m_lock;
 };
 
-class InternalCCache : public CCache
+class InternalCCache : public ISessionCache
 {
 public:
-  InternalCCache();
+  InternalCCache(const DOMElement* e);
   virtual ~InternalCCache();
 
-  virtual SAMLBinding* getBinding(const XMLCh* bindingProt);
-  virtual CCacheEntry* find(const char* key);
-  virtual void insert(const char* key, SAMLAuthenticationStatement *s,
-                     const char *client_addr);
-  virtual void remove(const char* key);
+  void thread_init() {};
+  void thread_end() {};
+
+  string generateKey() const;
+  ISessionCacheEntry* find(const char* key);
+  void insert(
+    const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char *client_addr, SAMLResponse* r=NULL
+    );
+  void remove(const char* key);
+
+  InternalCCacheEntry* findi(const char* key);
+  void cleanup();
 
 private:
-  SAMLBinding* m_SAMLBinding;
+  const DOMElement* m_root;         // Only valid during initialization
+  RWLock *lock;
   map<string,InternalCCacheEntry*> m_hashtable;
 
   log4cpp::Category* log;
-  RWLock *lock;
+
+  static void* cleanup_fcn(void*); // XXX Assumed an InternalCCache
+  bool         shutdown;
+  CondWait*    shutdown_wait;
+  Thread*      cleanup_thread;
+  
+  // extracted config settings
+  unsigned int m_AATimeout,m_AAConnectTimeout;
+  unsigned int m_defaultLifetime,m_retryInterval;
+  bool m_strictValidity,m_propagateErrors;
+  friend class InternalCCacheEntry;
 };
 
-// Global Constructors & Destructors
-CCache* CCache::getInstance(const char* type)
+IPlugIn* MemoryCacheFactory(const DOMElement* e)
 {
-  return (CCache*) new InternalCCache();
+    return new InternalCCache(e);
 }
 
-// static members
-saml::QName InternalCCacheEntry::g_authorityKind(saml::XML::SAMLP_NS,L(AttributeQuery));
-saml::QName InternalCCacheEntry::g_respondWith(saml::XML::SAML_NS,L(AttributeStatement));
-vector<SAMLAssertion*> ResourceEntry::g_emptyVector;
-
-
 /******************************************************************************/
-/* InternalCCache:  A Credential Cache                                        */
+/* InternalCCache:  in memory session cache                                   */
 /******************************************************************************/
 
-InternalCCache::InternalCCache()
+InternalCCache::InternalCCache(const DOMElement* e)
+    : m_root(e), m_AATimeout(30), m_AAConnectTimeout(15), m_defaultLifetime(1800), m_retryInterval(300),
+        m_strictValidity(true), m_propagateErrors(false), lock(RWLock::create()),
+        log (&Category::getInstance("shibtarget.InternalCCache"))
 {
-  m_SAMLBinding=SAMLBindingFactory::getInstance();
-  string ctx="shibtarget.InternalCCache";
-  log = &(log4cpp::Category::getInstance(ctx));
-  lock = RWLock::create();
+    const XMLCh* tag=m_root->getAttributeNS(NULL,AATimeout);
+    if (tag && *tag) {
+        m_AATimeout = XMLString::parseInt(tag);
+        if (!m_AATimeout)
+            m_AATimeout=30;
+    }
+
+    tag=m_root->getAttributeNS(NULL,AAConnectTimeout);
+    if (tag && *tag) {
+        m_AAConnectTimeout = XMLString::parseInt(tag);
+        if (!m_AAConnectTimeout)
+            m_AAConnectTimeout=15;
+    }
+    
+    tag=m_root->getAttributeNS(NULL,defaultLifetime);
+    if (tag && *tag) {
+        m_defaultLifetime = XMLString::parseInt(tag);
+        if (!m_defaultLifetime)
+            m_defaultLifetime=1800;
+    }
+
+    tag=m_root->getAttributeNS(NULL,retryInterval);
+    if (tag && *tag) {
+        m_retryInterval = XMLString::parseInt(tag);
+        if (!m_retryInterval)
+            m_retryInterval=300;
+    }
+    
+    tag=m_root->getAttributeNS(NULL,strictValidity);
+    if (tag && (*tag==chDigit_0 || *tag==chLatin_f))
+        m_strictValidity=false;
+        
+    tag=m_root->getAttributeNS(NULL,propagateErrors);
+    if (tag && (*tag==chDigit_1 || *tag==chLatin_t))
+        m_propagateErrors=true;
+
+    shutdown_wait = CondWait::create();
+    shutdown = false;
+    cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
 }
 
 InternalCCache::~InternalCCache()
 {
-  delete m_SAMLBinding;
+  // Shut down the cleanup thread and let it know...
+  shutdown = true;
+  shutdown_wait->signal();
+  cleanup_thread->join(NULL);
+
   for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
     delete i->second;
   delete lock;
+  delete shutdown_wait;
 }
 
-SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
+string InternalCCache::generateKey() const
 {
-  log->debug("looking for binding...");
-  if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) {
-    log->debug("https binding found");
-    return m_SAMLBinding;
-  }
-  return NULL;
+    SAMLIdentifier id;
+    auto_ptr_char c(id);
+    return c.get();
 }
 
-CCacheEntry* InternalCCache::find(const char* key)
+// assumes a lock is held..
+InternalCCacheEntry* InternalCCache::findi(const char* key)
 {
-  log->debug("Find: \"%s\"", key);
-  ReadLock rwlock(lock);
+  log->debug("findI: \"%s\"", key);
 
   map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
   if (i==m_hashtable.end()) {
@@ -227,15 +287,30 @@ CCacheEntry* InternalCCache::find(const char* key)
     return NULL;
   }
   log->debug("Match Found.");
-  return dynamic_cast<CCacheEntry*>(i->second);
+
+  return i->second;
 }
 
-void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
-                           const char *client_addr)
+ISessionCacheEntry* InternalCCache::find(const char* key)
 {
-  log->debug("caching new entry for \"%s\"", key);
+  log->debug("Find: \"%s\"", key);
+  ReadLock rwlock(lock);
 
-  InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
+  InternalCCacheEntry* entry = findi(key);
+  if (!entry) return NULL;
+
+  // Lock the "database record" for the caller -- they have to unlock the item.
+  entry->lock();
+  return entry;
+}
+
+void InternalCCache::insert(
+    const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char* client_addr, SAMLResponse* r
+    )
+{
+  log->debug("caching new entry for application %s: \"%s\"", application->getId(), key);
+
+  InternalCCacheEntry* entry = new InternalCCacheEntry(key, application, s, client_addr, r);
   entry->setCache(this);
 
   lock->wrlock();
@@ -243,312 +318,463 @@ void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s,
   lock->unlock();
 }
 
+// remove the entry from the database and then destroy the cacheentry
 void InternalCCache::remove(const char* key)
 {
   log->debug("removing cache entry \"key\"", key);
 
-  // XXX: FIXME? do we need to delete the CacheEntry?
-
+  // lock the cache for writing, which means we know nobody is sitting in find()
   lock->wrlock();
+
+  // grab the entry from the database.
+  ISessionCacheEntry* entry = findi(key);
+
+  if (!entry) {
+    lock->unlock();
+    return;
+  }
+
+  // ok, remove the entry and lock it
   m_hashtable.erase(key);
+  dynamic_cast<InternalCCacheEntry*>(entry)->lock();
   lock->unlock();
+
+  // we can release the entry lock because we know we're not in the cache anymore
+  entry->unlock();
+
+  // Now delete the entry
+  delete entry;
+}
+
+void InternalCCache::cleanup()
+{
+  Mutex* mutex = Mutex::create();
+  saml::NDC ndc("InternalCCache::cleanup()");
+
+  int rerun_timer = 0;
+  int timeout_life = 0;
+
+  // Load our configuration details...
+  const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);
+  if (tag && *tag)
+    rerun_timer = XMLString::parseInt(tag);
+
+  tag=m_root->getAttributeNS(NULL,cacheTimeout);
+  if (tag && *tag)
+    timeout_life = XMLString::parseInt(tag);
+  
+  if (rerun_timer <= 0)
+    rerun_timer = 300;        // rerun every 5 minutes
+
+  if (timeout_life <= 0)
+    timeout_life = 28800; // timeout after 8 hours
+
+  mutex->lock();
+
+  log->debug("Cleanup thread started...  Run every %d secs; timeout after %d secs",
+            rerun_timer, timeout_life);
+
+  while (shutdown == false) {
+    shutdown_wait->timedwait(mutex,rerun_timer);
+
+    if (shutdown == true)
+      break;
+
+    log->info("Cleanup thread running...");
+
+    // Ok, let's run through the cleanup process and clean out
+    // really old sessions.  This is a two-pass process.  The
+    // first pass is done holding a read-lock while we iterate over
+    // the database.  The second pass doesn't need a lock because
+    // the 'deletes' will lock the database.
+
+    // Pass 1: iterate over the map and find all entries that have not been
+    // used in X hours
+    vector<string> stale_keys;
+    time_t stale = time(NULL) - timeout_life;
+
+    lock->rdlock();
+    for (map<string,InternalCCacheEntry*>::iterator i=m_hashtable.begin();
+        i != m_hashtable.end(); i++)
+    {
+      // If the last access was BEFORE the stale timeout...
+      i->second->lock();
+      time_t last=i->second->lastAccess();
+      i->second->unlock();
+      if (last < stale)
+        stale_keys.push_back(i->first);
+    }
+    lock->unlock();
+
+    log->info("deleting %d old items.", stale_keys.size());
+
+    // Pass 2: walk through the list of stale entries and remove them from
+    // the database
+    for (vector<string>::iterator j = stale_keys.begin(); j != stale_keys.end(); j++) {
+      remove (j->c_str());
+      // Transaction Logging
+      STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
+      stc.getTransactionLog().infoStream() << "Purged expired session from memory (ID: " << j->c_str() << ")";
+      stc.releaseTransactionLog();
+    }
+  }
+
+  log->debug("Cleanup thread finished.");
+
+  mutex->unlock();
+  delete mutex;
+  Thread::exit(NULL);
+}
+
+void* InternalCCache::cleanup_fcn(void* cache_p)
+{
+  InternalCCache* cache = reinterpret_cast<InternalCCache*>(cache_p);
+
+  // First, let's block all signals 
+  Thread::mask_all_signals();
+
+  // Now run the cleanup process.
+  cache->cleanup();
+  return NULL;
 }
 
 /******************************************************************************/
 /* InternalCCacheEntry:  A Credential Cache Entry                             */
 /******************************************************************************/
 
-InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
-  : m_hasbinding(false)
+InternalCCacheEntry::InternalCCacheEntry(
+    const char* id, const IApplication* application, SAMLAuthenticationStatement *s, const char* client_addr, SAMLResponse* r
+    ) : m_response(r), m_responseCreated(r ? time(NULL) : 0), m_lastRetry(0),
+        log(&Category::getInstance("shibtarget::InternalCCacheEntry"))
 {
-  string ctx = "shibtarget::InternalCCacheEntry";
-  log = &(log4cpp::Category::getInstance(ctx));
-  pop_locks_lock = Mutex::create();
-  access_lock = Mutex::create();
-  resource_lock = RWLock::create();
-
-  if (s == NULL) {
-    log->error("NULL auth statement");
-    throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
+  if (!id || !s) {
+    log->error("NULL session ID or auth statement");
+    throw SAMLException("InternalCCacheEntry() passed an empty session ID or SAML Statement");
   }
 
-  m_subject = s->getSubject();
-
-  xstring name = m_subject->getName();
-  xstring qual = m_subject->getNameQualifier();
-
-  auto_ptr<char> h(XMLString::transcode(name.c_str()));
-  auto_ptr<char> d(XMLString::transcode(qual.c_str()));
+  m_id=id;
+  m_application_id=application->getId();
 
-  m_handle = h.get();
+  m_nameid = s->getSubject()->getNameIdentifier();
+  auto_ptr_char d(m_nameid->getNameQualifier());
   m_originSite = d.get();
 
-  Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
-  if (bindings.hasNext())
-    m_hasbinding = true;
-
   m_clientAddress = client_addr;
   m_sessionCreated = m_lastAccess = time(NULL);
 
   // Save for later.
   p_auth = s;
+  
+  // Save the serialized version of the auth statement
+  ostringstream os;
+  os << *s;
+  m_statement = os.str();
+
+  if (r) {
+    // Run pushed data through the AAP. Note that we could end up with an empty response!
+    Metadata m(application->getMetadataProviders());
+    const IProvider* site=m.lookup(m_nameid->getNameQualifier());
+    if (!site)
+        throw MetadataException("unable to locate origin site's metadata during attribute acceptance processing");
+    Iterator<SAMLAssertion*> assertions=r->getAssertions();
+    for (unsigned long i=0; i < assertions.size();) {
+        try {
+            AAP::apply(application->getAAPProviders(),site,*(assertions[i]));
+            i++;
+        }
+        catch (SAMLException&) {
+            log->info("no statements remain, removing assertion");
+            r->removeAssertion(i);
+        }
+    }
+  }
+
+  m_lock = Mutex::create();
 
-  log->info("New Session Created...");
-  log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
-            client_addr);
+  log->info("new session created (ID: %s)", id);
+  if (log->isDebugEnabled()) {
+      auto_ptr_char h(m_nameid->getName());
+      log->debug("Handle: \"%s\", Origin: \"%s\", Address: %s", h.get(), d.get(), client_addr);
+  }
 }
 
 InternalCCacheEntry::~InternalCCacheEntry()
 {
-  log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
+  log->debug("deleting session (ID: %s)", m_id.c_str());
+  delete m_response;
   delete p_auth;
-  for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
-       i!=m_resources.end(); i++)
-    delete i->second;
-
-  for (map<string,Mutex*>::iterator i=populate_locks.begin();
-       i!=populate_locks.end(); i++)
-    delete i->second;
-
-  delete pop_locks_lock;
-  delete resource_lock;
-  delete access_lock;
+  delete m_lock;
 }
 
-bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
+bool InternalCCacheEntry::isValid(time_t lifetime, time_t timeout) const
 {
-  saml::NDC ndc("isSessionValid");
-  log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
-            m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
+  saml::NDC ndc("isValid");
+  log->debug("testing session (ID: %s) (lifetime=%ld, timeout=%ld)", m_id.c_str(), lifetime, timeout);
   time_t now=time(NULL);
   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
-    log->debug("session beyond lifetime");
+    log->debug("session beyond lifetime (ID: %s)", m_id.c_str());
     return false;
   }
 
-  // Lock the access-time from here until we return
-  Lock lock(access_lock);
   if (timeout > 0 && now-m_lastAccess >= timeout) {
-    log->debug("session timed out");
+    log->debug("session timed out (ID: %s)", m_id.c_str());
     return false;
   }
   m_lastAccess=now;
   return true;
 }
 
-Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions(Resource& resource)
+Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions()
 {
   saml::NDC ndc("getAssertions");
-  ResourceEntry* entry = populate(resource);
-  if (entry)
-    return entry->getAssertions();
-  return Iterator<SAMLAssertion*>(ResourceEntry::g_emptyVector);
+  populate(0);
+  return (m_response) ? m_response->getAssertions() : EMPTY(SAMLAssertion*);
 }
 
-ResourceEntry* InternalCCacheEntry::populate(Resource& resource)
+void InternalCCacheEntry::preFetch(int prefetch_window)
 {
-  saml::NDC ndc("populate");
-  log->debug("populating entry for %s (%s)",
-            resource.getResource(), resource.getURL());
-
-  // Lock the resource within this entry...
-  InternalCCacheEntry::ResourceLock lock(this, resource.getResource());
-
-  // Can we use what we have?
-  ResourceEntry *entry = find(resource.getResource());
-  if (entry) {
-    log->debug("found resource");
-    if (entry->isValid())
-      return entry;
-
-    // entry is invalid (expired) -- go fetch a new one.
-    log->debug("removing resource cache; assertion is invalid");
-    remove (resource.getResource());
-    delete entry;
-  }
-
-  // Nope, no entry.. Create a new resource entry
-
-  if (!m_hasbinding) {
-    log->error("No binding!");
-    return NULL;
-  }
-
-  log->info("trying to request attributes for %s@%s -> %s",
-           m_handle.c_str(), m_originSite.c_str(), resource.getURL());
-
-  auto_ptr<XMLCh> resourceURL(XMLString::transcode(resource.getURL()));
-  Iterator<saml::QName> respond_withs = ArrayIterator<saml::QName>(&g_respondWith);
-
-  // Clone the subject...
-  // 1) I know the static_cast is safe from clone()
-  // 2) the AttributeQuery will destroy this new subject.
-  SAMLSubject* subject=static_cast<SAMLSubject*>(m_subject->clone());
-
-  // Build a SAML Request....
-  SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resourceURL.get());
-  SAMLRequest* req=new SAMLRequest(respond_withs,q);
-
-  // Try this request against all the bindings in the AuthenticationStatement
-  // (i.e. send it to each AA in the list of bindings)
-  Iterator<SAMLAuthorityBinding*> bindings = p_auth->getBindings();
-  SAMLResponse* response = NULL;
-
-  while (!response && bindings.hasNext()) {
-    SAMLAuthorityBinding* binding = bindings.next();
-
-    log->debug("Trying binding...");
-    SAMLBinding* pBinding=m_cache->getBinding(binding->getBinding());
-    log->debug("Sending request");
-    response=pBinding->send(*binding,*req);
-  }
-
-  // ok, we can delete the request now.
-  delete req;
-
-  // Make sure we got a response
-  if (!response) {
-    log->info ("No Response");
-    return NULL;
-  }
-
-  entry = new ResourceEntry(response);
-  insert (resource.getResource(), entry);
-
-  log->info("fetched and stored SAML response");
-  return entry;
+  saml::NDC ndc("preFetch");
+  populate(prefetch_window);
 }
 
-ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
+bool InternalCCacheEntry::responseValid(int slop)
 {
-  ReadLock rwlock(resource_lock);
+  saml::NDC ndc("responseValid");
 
-  log->debug("find: %s", resource_url);
-  map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
-  if (i==m_resources.end()) {
-    log->debug("no match found");
-    return NULL;
-  }
-  log->debug("match found");
-  return i->second;
-}
-
-void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
-{
-  log->debug("inserting %s", resource);
+  log->debug("checking AA response validity");
 
-  resource_lock->wrlock();
-  m_resources[resource]=entry;
-  resource_lock->unlock();
-}
+  // This is awful, but the XMLDateTime class is truly horrible.
+  time_t now=time(NULL)+slop;
+#ifdef WIN32
+  struct tm* ptime=gmtime(&now);
+#else
+  struct tm res;
+  struct tm* ptime=gmtime_r(&now,&res);
+#endif
+  char timebuf[32];
+  strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
+  auto_ptr_XMLCh timeptr(timebuf);
+  XMLDateTime curDateTime(timeptr.get());
+  curDateTime.parseDateTime();
 
-void InternalCCacheEntry::remove(const char* resource)
-{
-  log->debug("removing %s", resource);
+  int count = 0;
+  Iterator<SAMLAssertion*> iter = m_response->getAssertions();
+  while (iter.hasNext()) {
+    SAMLAssertion* assertion = iter.next();
 
-  resource_lock->wrlock();
-  m_resources.erase(resource);
-  resource_lock->unlock();
-}
+    log->debug("testing assertion...");
 
+    const XMLDateTime* thistime = assertion->getNotOnOrAfter();
 
-// a lock on a resource.  This is a specific "table of locks" that
-// will provide a mutex on a particular resource within a Cache Entry.
-// Just instantiate a ResourceLock within scope of the function and it
-// will obtain and hold the proper lock until it goes out of scope and
-// deconstructs.
+    // If there is no time, then just continue and ignore this assertion.
+    if (!thistime)
+      continue;
 
-InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry,
-                                               string resource) :
-  entry(entry), resource(resource)
-{
-  Mutex *mutex = find(resource);
-  mutex->lock();
-}
+    count++;
+    auto_ptr_char nowptr(curDateTime.getRawData());
+    auto_ptr_char assnptr(thistime->getRawData());
 
-InternalCCacheEntry::ResourceLock::~ResourceLock()
-{
-  Mutex *mutex = find(resource);
-  mutex->unlock();
-}
+    log->debug("comparing now (%s) to %s", nowptr.get(), assnptr.get());
+    int result=XMLDateTime::compareOrder(&curDateTime, thistime);
 
-Mutex* InternalCCacheEntry::ResourceLock::find(string& resource)
-{
-  Lock(entry->pop_locks_lock);
-  
-  map<string,Mutex*>::const_iterator i=entry->populate_locks.find(resource);
-  if (i==entry->populate_locks.end()) {
-    Mutex* mutex = Mutex::create();
-    entry->populate_locks[resource] = mutex;
-    return mutex;
+    if (result != XMLDateTime::LESS_THAN) {
+      log->debug("nope, not still valid");
+      return false;
+    }
   }
-  return i->second;
-}
-
-/******************************************************************************/
-/* ResourceEntry:  A Credential Cache Entry for a particular Resource URL     */
-/******************************************************************************/
-
-ResourceEntry::ResourceEntry(SAMLResponse* response)
-{
-  string ctx = "shibtarget::ResourceEntry";
-  log = &(log4cpp::Category::getInstance(ctx));
 
-  log->info("caching resource entry");
-
-  m_response = response;
+  // If we didn't find any assertions with times, then see if we're
+  // older than the default response lifetime.
+  if (!count) {
+      if ((now - m_responseCreated) > m_cache->m_defaultLifetime) {
+        log->debug("response is beyond default life, so it's invalid");
+        return false;
+      }
+  }
+  
+  log->debug("yep, response still valid");
+  return true;
 }
 
-ResourceEntry::~ResourceEntry()
+void InternalCCacheEntry::populate(int slop)
 {
-  delete m_response;
-}
+  saml::NDC ndc("populate");
+  log->debug("populating attributes for session (ID: %s)", m_id.c_str());
+
+  // Do we have any data cached?
+  if (m_response) {
+      // Can we use what we have?
+      if (responseValid(slop))
+        return;
+      
+      // If we're being strict, dump what we have and reset timestamps.
+      if (m_cache->m_strictValidity) {
+        log->info("strictly enforcing attribute validity, dumping expired data");
+        delete m_response;
+        m_response=NULL;
+        m_responseCreated=0;
+        m_lastRetry=0; 
+      }
+  }
 
-Iterator<SAMLAssertion*> ResourceEntry::getAssertions()
-{
-  saml::NDC ndc("getAssertions");
-  return m_response->getAssertions();
+  // Need to try and get a new response.
+
+  try {
+
+    // Transaction Logging
+    STConfig& stc=static_cast<STConfig&>(ShibTargetConfig::getConfig());
+    stc.getTransactionLog().infoStream() <<
+        "Making attribute query for session (ID: " <<
+            m_id <<
+        ") on (applicationId: " <<
+            m_application_id <<
+        ") for principal from (IdP: " <<
+            m_originSite <<
+        ")";
+    stc.releaseTransactionLog();
+
+    SAMLResponse* new_response=getNewResponse();
+    if (new_response) {
+        delete m_response;
+        m_response=new_response;
+        m_responseCreated=time(NULL);
+        m_lastRetry=0;
+        log->debug("fetched and stored new response");
+        stc.getTransactionLog().infoStream() <<  "Successful attribute query for session (ID: " << m_id << ")";
+        stc.releaseTransactionLog();
+    }
+  }
+  catch (SAMLException& e) {
+    if (typeid(e)==typeid(InvalidHandleException) || m_cache->m_propagateErrors)
+        throw;
+    log->warn("suppressed SAML exception caught while trying to fetch attributes");
+  }
+  catch (...) {
+    if (m_cache->m_propagateErrors)
+        throw;
+    log->warn("suppressed exception caught while trying to fetch attributes");
+  }
 }
 
-bool ResourceEntry::isValid()
+SAMLResponse* InternalCCacheEntry::getNewResponse()
 {
-  saml::NDC ndc("isValid");
-
-  log->info("checking validity");
-
-  // This is awful, but the XMLDateTime class is truly horrible.
-  time_t now=time(NULL);
-#ifdef WIN32
-  struct tm* ptime=gmtime(&now);
-#else
-  struct tm res;
-  struct tm* ptime=gmtime_r(&now,&res);
-#endif
-  char timebuf[32];
-  strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
-  auto_ptr<XMLCh> timeptr(XMLString::transcode(timebuf));
-  XMLDateTime curDateTime(timeptr.get());
+    saml::NDC ndc("getNewResponse");
+
+    // The retryInterval determines how often to poll an AA that might be down.
+    if ((time(NULL) - m_lastRetry) < m_cache->m_retryInterval)
+        return NULL;
+    if (m_lastRetry)
+        log->debug("retry interval exceeded, so trying again");
+    m_lastRetry=time(NULL);
+    
+    log->info("trying to get new attributes for session (ID=%s)", m_id.c_str());
+
+    // Lookup application for session to get providerId and attributes to request.
+    IConfig* conf=ShibTargetConfig::getConfig().getINI();
+    Locker locker(conf);
+    const IApplication* application=conf->getApplication(m_application_id.c_str());
+    if (!application) {
+        log->crit("unable to locate application for session, deleted?");
+        throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate application for session, deleted?");
+    }
+    pair<bool,const XMLCh*> providerID=application->getXMLString("providerId");
+    if (!providerID.first) {
+        log->crit("unable to determine ProviderID for application, not set?");
+        throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to determine ProviderID for application, not set?");
+    }
 
-  Iterator<SAMLAssertion*> iter = getAssertions();
+    // Get signing policies.
+    pair<bool,bool> signRequest=application->getBool("signRequest");
+    pair<bool,bool> signedResponse=application->getBool("signedResponse");
+    pair<bool,bool> signedAssertions=application->getBool("signedAssertions");
+    
+    // Try this request. The binding wrapper class handles most of the details.
+    Metadata m(application->getMetadataProviders());
+    const IProvider* site=m.lookup(m_nameid->getNameQualifier());
+    if (!site) {
+        log->error("unable to locate origin site's metadata during attribute query");
+        throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate origin site's metadata during attribute query.");
+    }
 
-  while (iter.hasNext()) {
-    SAMLAssertion* assertion = iter.next();
+    // Try to locate an AA role.
+    const IAttributeAuthorityRole* AA=NULL;
+    Iterator<const IProviderRole*> roles=site->getRoles();
+    while (!AA && roles.hasNext()) {
+        const IProviderRole* role=roles.next();
+        if (dynamic_cast<const IAttributeAuthorityRole*>(role)) {
+            // Check for SAML 1.x protocol support.
+            if (role->hasSupport(saml::XML::SAMLP_NS))
+                AA=dynamic_cast<const IAttributeAuthorityRole*>(role);
+        }
+    }
+    if (!AA) {
+        log->error("unable to locate metadata for origin site's Attribute Authority");
+        throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to locate metadata for origin site's Attribute Authority.",site);
+    }
 
-    log->debug ("testing assertion...");
 
-    if (! assertion->getNotOnOrAfter()) {
-      log->debug ("getNotOnOrAfter failed.");
-      return false;
+    SAMLResponse* response = NULL;
+    try {
+        // Build a SAML Request....
+        SAMLAttributeQuery* q=new SAMLAttributeQuery(
+            new SAMLSubject(static_cast<SAMLNameIdentifier*>(m_nameid->clone())),
+            providerID.second,
+            application->getAttributeDesignators().clone()
+            );
+        auto_ptr<SAMLRequest> req(new SAMLRequest(EMPTY(QName),q));
+        
+        // Sign it? Highly doubtful we'll ever use this, but just for fun...
+        if (signRequest.first && signRequest.second) {
+            Credentials creds(conf->getCredentialsProviders());
+            const ICredResolver* signingCred=creds.lookup(application->getSigningCred(site));
+            req->sign(SIGNATURE_RSA,signingCred->getKey(),signingCred->getCertificates());
+        }
+            
+        log->debug("trying to query an AA...");
+
+        SAMLConfig::SAMLBindingConfig bindconf;
+        bindconf.timeout=m_cache->m_AATimeout;
+        bindconf.conn_timeout=m_cache->m_AAConnectTimeout;
+        ShibBinding binding(application->getRevocationProviders(),application->getTrustProviders(),conf->getCredentialsProviders());
+        response=binding.send(*req,AA,application->getTLSCred(site),application->getAudiences(),p_auth->getBindings(),bindconf);
+    }
+    catch (SAMLException& e) {
+        log->error("caught SAML exception during query to AA: %s", e.what());
+        if (typeid(e)==typeid(InvalidHandleException))
+            throw;
+        ostringstream os;
+        os << e;
+        throw ShibTargetException(SHIBRPC_SAML_EXCEPTION, os.str().c_str(), AA);
+    }
+    
+    // See if we got a response.
+    if (!response) {
+        log->error("no response obtained");
+        throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to obtain attributes from user's origin site.",AA);
+    }
+    else if (signedResponse.first && signedResponse.second && !response->isSigned()) {
+        delete response;
+        log->error("unsigned response obtained, but we were told it must be signed.");
+        throw ShibTargetException(SHIBRPC_INTERNAL_ERROR,"Unable to obtain attributes from user's origin site.",AA);
     }
 
-    int result=XMLDateTime::compareOrder(&curDateTime,
-                                        assertion->getNotOnOrAfter());
-    if (result != XMLDateTime::LESS_THAN) {
-      log->debug("nope, not still valid");
-      return false;
+    // Run it through the AAP. Note that we could end up with an empty response!
+    Iterator<SAMLAssertion*> a=response->getAssertions();
+    for (unsigned long i=0; i < a.size();) {
+        try {
+            if (signedAssertions.first && signedAssertions.second && !(a[i]->isSigned())) {
+                log->warn("removing unsigned assertion from response, in accordance with signedAssertions policy");
+                response->removeAssertion(i);
+                continue;
+            }
+            AAP::apply(application->getAAPProviders(),site,*(a[i]));
+            i++;
+        }
+        catch (SAMLException&) {
+            log->info("no statements remain, removing assertion");
+            response->removeAssertion(i);
+        }
     }
-  } // while
 
-  log->debug("yep, all still valid");
-  return true;
+    return response;
 }