#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;
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()
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()) {
return i->second;
}
-CCacheEntry* InternalCCache::find(const char* key)
+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 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();
{
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()
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();
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);
}
// 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.");
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();
/* 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);
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;
}