Removed Policy element from configuration
[shibboleth/sp.git] / shib-target / shib-ccache.cpp
index 1903782..39077f7 100644 (file)
 #include <dmalloc.h>
 #endif
 
+using namespace std;
+using namespace log4cpp;
+using namespace saml;
+using namespace shibboleth;
+using namespace shibtarget;
+
+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 };
+
+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(const char* resource);
-  virtual void preFetch(const char* resource, int prefetch_window);
-  virtual bool isSessionValid(time_t lifetime, time_t timeout);
-  virtual const char* getClientAddress() { return m_clientAddress.c_str(); }
-  virtual const char* getSerializedStatement() { return m_statement.c_str(); }
-  virtual const SAMLAuthenticationStatement* getStatement() { return p_auth; }
-  virtual void release() { cacheitem_lock->unlock(); }
+  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() { Lock lock(access_lock); return m_lastAccess; }
-  void rdlock() { cacheitem_lock->rdlock(); }
-  void wrlock() { cacheitem_lock->wrlock(); }
+  time_t lastAccess() const { return m_lastAccess; }
 
 private:
-  ResourceEntry* populate(const char* resource, int slop);
-  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;
-
   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;
-  RWLock*      cacheitem_lock;
-
-  class ResourceLock
-  {
-  public:
-    ResourceLock(InternalCCacheEntry* entry, const char* resource);
-    ~ResourceLock();
-
-  private:
-    Mutex*                     find(const char* resource);
-    InternalCCacheEntry*       entry;
-    string                     m_resource;
-  };
-
-  friend class ResourceLock;
+  Mutex* m_lock;
 };
 
-class InternalCCache : public CCache
+class InternalCCache : public ISessionCache
 {
 public:
-  InternalCCache();
+  InternalCCache(const DOMElement* e);
   virtual ~InternalCCache();
 
-  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:
+  const DOMElement* m_root;         // Only valid during initialization
   RWLock *lock;
-
   map<string,InternalCCacheEntry*> m_hashtable;
 
   log4cpp::Category* log;
@@ -165,54 +193,67 @@ private:
   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;
 };
 
-namespace {
-  map<string,CCache::CCacheFactory> g_ccacheFactoryDB;
-};
-
-// Global Constructors & Destructors
-CCache::~CCache() { }
-
-void CCache::registerFactory(const char* name, CCache::CCacheFactory factory)
+IPlugIn* MemoryCacheFactory(const DOMElement* e)
 {
-  string ctx = "shibtarget.CCache";
-  log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
-  saml::NDC ndc("registerFactory");
-
-  log.info ("Registered factory %p for CCache %s", factory, name);
-  g_ccacheFactoryDB[name] = factory;
-}
-
-CCache* CCache::getInstance(const char* type)
-{
-  string ctx = "shibtarget.CCache";
-  log4cpp::Category& log = log4cpp::Category::getInstance(ctx);
-  saml::NDC ndc("getInstance");
-
-  map<string,CCache::CCacheFactory>::const_iterator i=g_ccacheFactoryDB.find(type);
-  if (i!=g_ccacheFactoryDB.end()) {
-    log.info ("Loading CCache: %s at %p", type, i->second);
-    return ((i->second)());
-  }
-
-  log.info ("Loading default memory CCache");
-  return (CCache*) new InternalCCache();
+    return new InternalCCache(e);
 }
 
-
 /******************************************************************************/
-/* 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"))
 {
-  log = &(log4cpp::Category::getInstance("shibtarget.InternalCCache"));
-  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;
+    }
 
-  shutdown_wait = CondWait::create();
-  shutdown = false;
-  cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
+    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()
@@ -228,10 +269,17 @@ InternalCCache::~InternalCCache()
   delete shutdown_wait;
 }
 
-// assumed a lock is held..
+string InternalCCache::generateKey() const
+{
+    SAMLIdentifier id;
+    auto_ptr_char c(id);
+    return c.get();
+}
+
+// assumes a lock is held..
 InternalCCacheEntry* InternalCCache::findi(const char* key)
 {
-  log->debug("FindI: \"%s\"", key);
+  log->debug("findI: \"%s\"", key);
 
   map<string,InternalCCacheEntry*>::const_iterator i=m_hashtable.find(key);
   if (i==m_hashtable.end()) {
@@ -243,7 +291,7 @@ InternalCCacheEntry* InternalCCache::findi(const char* key)
   return i->second;
 }
 
-CCacheEntry* InternalCCache::find(const char* key)
+ISessionCacheEntry* InternalCCache::find(const char* key)
 {
   log->debug("Find: \"%s\"", key);
   ReadLock rwlock(lock);
@@ -251,16 +299,18 @@ CCacheEntry* InternalCCache::find(const char* key)
   InternalCCacheEntry* entry = findi(key);
   if (!entry) return NULL;
 
-  // Lock the database for the caller -- they have to release the item.
-  entry->rdlock();
-  return dynamic_cast<CCacheEntry*>(entry);
+  // Lock the "database record" for the caller -- they have to unlock the item.
+  entry->lock();
+  return entry;
 }
 
-void InternalCCache::insert(const char* key, SAMLAuthenticationStatement *s, const char *client_addr)
+void InternalCCache::insert(
+    const char* key, const IApplication* application, SAMLAuthenticationStatement* s, const char* client_addr, SAMLResponse* r
+    )
 {
-  log->debug("caching new entry for \"%s\"", key);
+  log->debug("caching new entry for application %s: \"%s\"", application->getId(), key);
 
-  InternalCCacheEntry* entry = new InternalCCacheEntry (s, client_addr);
+  InternalCCacheEntry* entry = new InternalCCacheEntry(key, application, s, client_addr, r);
   entry->setCache(this);
 
   lock->wrlock();
@@ -273,36 +323,27 @@ void InternalCCache::remove(const char* key)
 {
   log->debug("removing cache entry \"key\"", key);
 
-  // grab the entry from the database.  We'll have a readlock on it.
-  CCacheEntry* entry = findi(key);
-
-  if (!entry)
-    return;
-
-  // grab the cache write lock
+  // lock the cache for writing, which means we know nobody is sitting in find()
   lock->wrlock();
 
-  // verify we've still got the same entry.
-  if (entry != findi(key)) {
-    // Nope -- must've already been removed.
+  // grab the entry from the database.
+  ISessionCacheEntry* entry = findi(key);
+
+  if (!entry) {
     lock->unlock();
     return;
   }
 
-  // ok, remove the entry.
+  // ok, remove the entry and lock it
   m_hashtable.erase(key);
+  dynamic_cast<InternalCCacheEntry*>(entry)->lock();
   lock->unlock();
 
-  // now grab the write lock on the cacheitem.
-  // This will make sure all other threads have released this item.
-  InternalCCacheEntry* ientry = dynamic_cast<InternalCCacheEntry*>(entry);
-  ientry->wrlock();
-
-  // we can release immediately because we know we're not in the database!
-  ientry->release();
+  // we can release the entry lock because we know we're not in the cache anymore
+  entry->unlock();
 
   // Now delete the entry
-  delete ientry;
+  delete entry;
 }
 
 void InternalCCache::cleanup()
@@ -310,23 +351,23 @@ void InternalCCache::cleanup()
   Mutex* mutex = Mutex::create();
   saml::NDC ndc("InternalCCache::cleanup()");
 
-  ShibTargetConfig& config = ShibTargetConfig::getConfig();
-  ShibINI& ini = config.getINI();
-
   int rerun_timer = 0;
   int timeout_life = 0;
 
-  string tag;
-  if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHECLEAN, true, &tag))
-    rerun_timer = atoi(tag.c_str());
-  if (ini.get_tag (SHIBTARGET_SHAR, SHIBTARGET_TAG_CACHETIMEOUT, true, &tag))
-    timeout_life = atoi(tag.c_str());
+  // 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
+    rerun_timer = 300;        // rerun every 5 minutes
 
   if (timeout_life <= 0)
-    timeout_life = 28800;      // timeout after 8 hours
+    timeout_life = 28800; // timeout after 8 hours
 
   mutex->lock();
 
@@ -357,7 +398,9 @@ void InternalCCache::cleanup()
         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);
     }
@@ -367,12 +410,13 @@ void InternalCCache::cleanup()
 
     // 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++)
-    {
+    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.");
@@ -384,7 +428,7 @@ void InternalCCache::cleanup()
 
 void* InternalCCache::cleanup_fcn(void* cache_p)
 {
-  InternalCCache* cache = (InternalCCache*)cache_p;
+  InternalCCache* cache = reinterpret_cast<InternalCCache*>(cache_p);
 
   // First, let's block all signals 
   Thread::mask_all_signals();
@@ -398,35 +442,23 @@ void* InternalCCache::cleanup_fcn(void* cache_p)
 /* 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"))
 {
-  log = &(log4cpp::Category::getInstance("shibtarget::InternalCCacheEntry"));
-  pop_locks_lock = Mutex::create();
-  access_lock = Mutex::create();
-  resource_lock = RWLock::create();
-  cacheitem_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();
-
-  const XMLCh* name = m_subject->getName();
-  const XMLCh* qual = m_subject->getNameQualifier();
-
-  auto_ptr_char h(name);
-  auto_ptr_char d(qual);
+  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);
 
@@ -438,168 +470,311 @@ InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const c
   os << *s;
   m_statement = os.str();
 
-  log->info("New Session Created...");
-  log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(), client_addr);
+  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 (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 j=populate_locks.begin();
-       j!=populate_locks.end(); j++)
-    delete j->second;
-
-  delete pop_locks_lock;
-  delete cacheitem_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(const char* resource)
+Iterator<SAMLAssertion*> InternalCCacheEntry::getAssertions()
 {
   saml::NDC ndc("getAssertions");
-  ResourceEntry* entry = populate(resource, 0);
-  if (entry)
-    return entry->getAssertions();
-  return EMPTY(SAMLAssertion*);
+  populate(0);
+  return (m_response) ? m_response->getAssertions() : EMPTY(SAMLAssertion*);
 }
 
-void InternalCCacheEntry::preFetch(const char* resource, int prefetch_window)
+void InternalCCacheEntry::preFetch(int prefetch_window)
 {
   saml::NDC ndc("preFetch");
-  populate(resource, prefetch_window);
+  populate(prefetch_window);
 }
 
-ResourceEntry* InternalCCacheEntry::populate(const char* resource, int slop)
+bool InternalCCacheEntry::responseValid(int slop)
 {
-  saml::NDC ndc("populate");
-  log->debug("populating entry for %s", resource);
-
-  // Lock the resource within this entry...
-  InternalCCacheEntry::ResourceLock lock(this, resource);
-
-  // Can we use what we have?
-  ResourceEntry *entry = find(resource);
-  if (entry) {
-    log->debug("found resource");
-    if (entry->isValid(slop))
-      return entry;
-
-    // entry is invalid (expired) -- go fetch a new one.
-    log->debug("removing resource cache; assertion is invalid");
-    remove(resource);
-    delete entry;
-  }
+  saml::NDC ndc("responseValid");
 
-  // Nope, no entry.. Create a new resource entry
+  log->debug("checking AA response validity");
 
-  if (!m_hasbinding) {
-    log->error("No binding!");
-    return NULL;
-  }
+  // 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();
 
-  log->info("trying to request attributes for %s@%s -> %s",
-           m_handle.c_str(), m_originSite.c_str(), resource);
+  int count = 0;
+  Iterator<SAMLAssertion*> iter = m_response->getAssertions();
+  while (iter.hasNext()) {
+    SAMLAssertion* assertion = iter.next();
 
-  try {
-    entry = new ResourceEntry(resource, *m_subject, m_cache, p_auth->getBindings());
-  } catch (ShibTargetException&) {
-    return NULL;
-  }
-  insert(resource, entry);
+    log->debug("testing assertion...");
 
-  log->info("fetched and stored SAML response");
-  return entry;
-}
+    const XMLDateTime* thistime = assertion->getNotOnOrAfter();
 
-ResourceEntry* InternalCCacheEntry::find(const char* resource)
-{
-  ReadLock rwlock(resource_lock);
+    // If there is no time, then just continue and ignore this assertion.
+    if (!thistime)
+      continue;
 
-  log->debug("find: %s", resource);
-  map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource);
-  if (i==m_resources.end()) {
-    log->debug("no match found");
-    return NULL;
+    count++;
+    auto_ptr_char nowptr(curDateTime.getRawData());
+    auto_ptr_char assnptr(thistime->getRawData());
+
+    log->debug("comparing now (%s) to %s", nowptr.get(), assnptr.get());
+    int result=XMLDateTime::compareOrder(&curDateTime, thistime);
+
+    if (result != XMLDateTime::LESS_THAN) {
+      log->debug("nope, not still valid");
+      return false;
+    }
   }
-  log->debug("match found");
-  return i->second;
+
+  // 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;
 }
 
-void InternalCCacheEntry::insert(const char* resource, ResourceEntry* entry)
+void InternalCCacheEntry::populate(int slop)
 {
-  log->debug("inserting %s", resource);
+  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; 
+      }
+  }
+
+  // Need to try and get a new response.
 
-  resource_lock->wrlock();
-  m_resources[resource]=entry;
-  resource_lock->unlock();
+  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");
+  }
 }
 
-// caller will delete the entry.. don't worry about that here.
-void InternalCCacheEntry::remove(const char* resource)
+SAMLResponse* InternalCCacheEntry::getNewResponse()
 {
-  log->debug("removing %s", resource);
+    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?");
+    }
 
-  resource_lock->wrlock();
-  m_resources.erase(resource);
-  resource_lock->unlock();
-}
+    // 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.");
+    }
 
+    // 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);
+    }
 
-// 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.
 
-InternalCCacheEntry::ResourceLock::ResourceLock(InternalCCacheEntry* entry, const char* resource) :
-  entry(entry), m_resource(resource)
-{
-  Mutex *mutex = find(resource);
-  mutex->lock();
-}
+    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);
+    }
 
-InternalCCacheEntry::ResourceLock::~ResourceLock()
-{
-  Mutex *mutex = find(m_resource.c_str());
-  mutex->unlock();
-}
+    // 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);
+        }
+    }
 
-Mutex* InternalCCacheEntry::ResourceLock::find(const char* 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;
-  }
-  return i->second;
+    return response;
 }