* $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() { Lock lock(access_lock); return m_lastAccess; }
+ 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() { }
-
-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();
-
- shutdown_wait = CondWait::create();
- shutdown = false;
- cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
+ 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()
shutdown_wait->signal();
cleanup_thread->join(NULL);
- delete m_SAMLBinding;
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()) {
return NULL;
}
log->debug("Match Found.");
- return dynamic_cast<CCacheEntry*>(i->second);
+
+ return i->second;
+}
+
+ISessionCacheEntry* InternalCCache::find(const char* key)
+{
+ log->debug("Find: \"%s\"", key);
+ ReadLock rwlock(lock);
+
+ 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, 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();
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();
- while (shutdown == false) {
- struct timespec ts;
- memset (&ts, 0, sizeof(ts));
- ts.tv_sec = time(NULL) + 3600; // run every hour
+ log->debug("Cleanup thread started... Run every %d secs; timeout after %d secs",
+ rerun_timer, timeout_life);
- shutdown_wait->timedwait(mutex, &ts);
+ 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
// 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) - 8 * 3600; // XXX: 8 hour timeout.
+ 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...
- if (i->second->lastAccess() < stale)
- stale_keys.push_back(i->first);
+ 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 i = stale_keys.begin();
- i != stale_keys.end(); i++)
- {
- remove (i->c_str());
+ 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 = (InternalCCache*)cache_p;
+ InternalCCache* cache = reinterpret_cast<InternalCCache*>(cache_p);
- // First, let's block all signals
- sigset_t sigmask;
- sigfillset(&sigmask);
- Thread::mask_signals(SIG_BLOCK, &sigmask, NULL);
+ // 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();
+ m_id=id;
+ m_application_id=application->getId();
- auto_ptr<char> h(XMLString::transcode(name.c_str()));
- auto_ptr<char> d(XMLString::transcode(qual.c_str()));
-
- 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(),
- resource.getDesignators());
- 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);
-
- 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;
-}
+ saml::NDC ndc("responseValid");
-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;
}