X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=saml%2Fsaml2%2Fmetadata%2Fimpl%2FDynamicMetadataProvider.cpp;h=4bf36565df636be6eb42b76d592b1dcfd7c9527f;hb=16d5976c9821b70d95675983702e0032d8769467;hp=a23308fe4c9b5915ec4667ecc7fe9fa32edb1086;hpb=a0f7ddfb1954304a01b6a49580ce8d2603a60446;p=shibboleth%2Fcpp-opensaml.git diff --git a/saml/saml2/metadata/impl/DynamicMetadataProvider.cpp b/saml/saml2/metadata/impl/DynamicMetadataProvider.cpp index a23308f..4bf3656 100644 --- a/saml/saml2/metadata/impl/DynamicMetadataProvider.cpp +++ b/saml/saml2/metadata/impl/DynamicMetadataProvider.cpp @@ -1,6 +1,6 @@ /* - * Copyright 2001-2007 Internet2 - * + * Copyright 2001-2010 Internet2 + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -16,26 +16,37 @@ /** * DynamicMetadataProvider.cpp - * - * Base class for caching metadata providers. + * + * Simple implementation of a dynamic caching MetadataProvider. */ #include "internal.h" +#include "binding/SAMLArtifact.h" #include "saml2/metadata/Metadata.h" #include "saml2/metadata/DynamicMetadataProvider.h" -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include +#include using namespace opensaml::saml2md; using namespace xmltooling::logging; using namespace xmltooling; using namespace std; -static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e); +# ifndef min +# define min(a,b) (((a) < (b)) ? (a) : (b)) +# endif + +static const XMLCh maxCacheDuration[] = UNICODE_LITERAL_16(m,a,x,C,a,c,h,e,D,u,r,a,t,i,o,n); +static const XMLCh minCacheDuration[] = UNICODE_LITERAL_16(m,i,n,C,a,c,h,e,D,u,r,a,t,i,o,n); +static const XMLCh refreshDelayFactor[] = UNICODE_LITERAL_18(r,e,f,r,e,s,h,D,e,l,a,y,F,a,c,t,o,r); +static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e); namespace opensaml { namespace saml2md { @@ -47,10 +58,31 @@ namespace opensaml { }; DynamicMetadataProvider::DynamicMetadataProvider(const DOMElement* e) - : AbstractMetadataProvider(e), m_lock(RWLock::create()) + : AbstractMetadataProvider(e), + m_maxCacheDuration(XMLHelper::getAttrInt(e, 28800, maxCacheDuration)), + m_lock(RWLock::create()), + m_refreshDelayFactor(0.75), + m_minCacheDuration(XMLHelper::getAttrInt(e, 600, minCacheDuration)), + m_validate(XMLHelper::getAttrBool(e, false, validate)) { - const XMLCh* flag=e ? e->getAttributeNS(NULL,validate) : NULL; - m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE)); + if (m_minCacheDuration > m_maxCacheDuration) { + Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic").error( + "minCacheDuration setting exceeds maxCacheDuration setting, lowering to match it" + ); + m_minCacheDuration = m_maxCacheDuration; + } + + const XMLCh* delay = e ? e->getAttributeNS(nullptr, refreshDelayFactor) : nullptr; + if (delay && *delay) { + auto_ptr_char temp(delay); + m_refreshDelayFactor = atof(temp.get()); + if (m_refreshDelayFactor <= 0.0 || m_refreshDelayFactor >= 1.0) { + Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic").error( + "invalid refreshDelayFactor setting, using default" + ); + m_refreshDelayFactor = 0.75; + } + } } DynamicMetadataProvider::~DynamicMetadataProvider() @@ -60,61 +92,197 @@ DynamicMetadataProvider::~DynamicMetadataProvider() delete m_lock; } -const EntityDescriptor* DynamicMetadataProvider::getEntityDescriptor(const char* name, bool strict) const +const XMLObject* DynamicMetadataProvider::getMetadata() const { - // Check cache while holding the read lock. - const EntityDescriptor* entity = AbstractMetadataProvider::getEntityDescriptor(name, strict); - if (entity) - return entity; + throw MetadataException("getMetadata operation not implemented on this provider."); +} + +Lockable* DynamicMetadataProvider::lock() +{ + m_lock->rdlock(); + return this; +} + +void DynamicMetadataProvider::unlock() +{ + m_lock->unlock(); +} +void DynamicMetadataProvider::init() +{ +} + +pair DynamicMetadataProvider::getEntityDescriptor(const Criteria& criteria) const +{ Category& log = Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic"); - log.info("resolving metadata for (%s)", name); - // Try resolving it. - auto_ptr entity2(resolve(name)); + // First we check the underlying cache. + pair entity = AbstractMetadataProvider::getEntityDescriptor(criteria); - // Filter it, which may throw. - doFilters(*entity2.get()); + // Check to see if we're within the caching interval for a lookup of this entity. + // This applies *even if we didn't get a hit* because the cache map tracks failed + // lookups also, to prevent constant reload attempts. + cachemap_t::iterator cit; + if (entity.first) { + cit = m_cacheMap.find(entity.first->getEntityID()); + } + else if (criteria.entityID_ascii) { + auto_ptr_XMLCh widetemp(criteria.entityID_ascii); + cit = m_cacheMap.find(widetemp.get()); + } + else if (criteria.entityID_unicode) { + cit = m_cacheMap.find(criteria.entityID_unicode); + } + else if (criteria.artifact) { + auto_ptr_XMLCh widetemp(criteria.artifact->getSource().c_str()); + cit = m_cacheMap.find(widetemp.get()); + } + else { + cit = m_cacheMap.end(); + } + if (cit != m_cacheMap.end()) { + if (time(nullptr) <= cit->second) + return entity; + m_cacheMap.erase(cit); + } - log.info("caching resolved metadata for (%s)", name); + string name; + if (criteria.entityID_ascii) { + name = criteria.entityID_ascii; + } + else if (criteria.entityID_unicode) { + auto_ptr_char temp(criteria.entityID_unicode); + name = temp.get(); + } + else if (criteria.artifact) { + name = criteria.artifact->getSource(); + } + else { + return entity; + } - // Translate cacheDuration into validUntil. - if (entity2->getCacheDuration()) - entity2->setValidUntil(time(NULL) + entity2->getCacheDurationEpoch()); + if (entity.first) + log.info("metadata for (%s) is beyond caching interval, attempting to refresh", name.c_str()); + else + log.info("resolving metadata for (%s)", name.c_str()); - // Upgrade our lock so we can cache the new metadata. - m_lock->unlock(); - m_lock->wrlock(); + try { + // Try resolving it. + auto_ptr entity2(resolve(criteria)); - // Notify observers. - emitChangeEvent(); + // Verify the entityID. + if (criteria.entityID_unicode && !XMLString::equals(criteria.entityID_unicode, entity2->getEntityID())) { + log.error("metadata instance did not match expected entityID"); + return entity; + } + else { + auto_ptr_XMLCh temp2(name.c_str()); + if (!XMLString::equals(temp2.get(), entity2->getEntityID())) { + log.error("metadata instance did not match expected entityID"); + return entity; + } + } - // Make sure we clear out any existing copies, including stale metadata or if somebody snuck in. - index(entity2.release(), SAMLTIME_MAX, true); + // Preprocess the metadata (even if we schema-validated). + try { + SchemaValidators.validate(entity2.get()); + } + catch (exception& ex) { + log.error("metadata intance failed manual validation checking: %s", ex.what()); + throw MetadataException("Metadata instance failed manual validation checking."); + } - // Downgrade back to a read lock. - m_lock->unlock(); - m_lock->rdlock(); + // Filter it, which may throw. + doFilters(*entity2.get()); + + time_t now = time(nullptr); + if (entity2->getValidUntil() && entity2->getValidUntilEpoch() < now + 60) + throw MetadataException("Metadata was already invalid at the time of retrieval."); + + log.info("caching resolved metadata for (%s)", name.c_str()); + + // Compute the smaller of the validUntil / cacheDuration constraints. + time_t cacheExp = (entity2->getValidUntil() ? entity2->getValidUntilEpoch() : SAMLTIME_MAX) - now; + if (entity2->getCacheDuration()) + cacheExp = min(cacheExp, entity2->getCacheDurationEpoch()); + + // Adjust for the delay factor. + cacheExp *= m_refreshDelayFactor; + + // Bound by max and min. + if (cacheExp > m_maxCacheDuration) + cacheExp = m_maxCacheDuration; + else if (cacheExp < m_minCacheDuration) + cacheExp = m_minCacheDuration; + + log.info("next refresh of metadata for (%s) no sooner than %u seconds", name.c_str(), cacheExp); + + // Upgrade our lock so we can cache the new metadata. + m_lock->unlock(); + m_lock->wrlock(); + + // Notify observers. + emitChangeEvent(); + + // Record the proper refresh time. + m_cacheMap[entity2->getEntityID()] = now + cacheExp; + + // Make sure we clear out any existing copies, including stale metadata or if somebody snuck in. + cacheExp = SAMLTIME_MAX; + indexEntity(entity2.release(), cacheExp, true); + + // Downgrade back to a read lock. + m_lock->unlock(); + m_lock->rdlock(); + } + catch (exception& e) { + log.error("error while resolving entityID (%s): %s", name.c_str(), e.what()); + // This will return entries that are beyond their cache period, + // but not beyond their validity unless that criteria option was set. + // If it is a cache-expired entry, bump the cache period to prevent retries. + if (entity.first) + m_cacheMap[entity.first->getEntityID()] = time(nullptr) + m_minCacheDuration; + else if (criteria.entityID_unicode) + m_cacheMap[criteria.entityID_unicode] = time(nullptr) + m_minCacheDuration; + else { + auto_ptr_XMLCh widetemp(name.c_str()); + m_cacheMap[widetemp.get()] = time(nullptr) + m_minCacheDuration; + } + log.warn("next refresh of metadata for (%s) no sooner than %u seconds", name.c_str(), m_minCacheDuration); + return entity; + } // Rinse and repeat. - return getEntityDescriptor(name, strict); + return getEntityDescriptor(criteria); } -EntityDescriptor* DynamicMetadataProvider::resolve(const char* entityID) const +EntityDescriptor* DynamicMetadataProvider::resolve(const Criteria& criteria) const { - try { - DOMDocument* doc=NULL; - auto_ptr_XMLCh widenit(entityID); - URLInputSource src(widenit.get()); - Wrapper4InputSource dsrc(&src,false); - if (m_validate) - doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc); - else - doc=XMLToolingConfig::getConfig().getParser().parse(dsrc); - + string name; + if (criteria.entityID_ascii) { + name = criteria.entityID_ascii; + } + else if (criteria.entityID_unicode) { + auto_ptr_char temp(criteria.entityID_unicode); + name = temp.get(); + } + else if (criteria.artifact) { + throw MetadataException("Unable to resolve metadata dynamically from an artifact."); + } + + try { + DOMDocument* doc=nullptr; + auto_ptr_XMLCh widenit(name.c_str()); + URLInputSource src(widenit.get()); + Wrapper4InputSource dsrc(&src,false); + if (m_validate) + doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc); + else + doc=XMLToolingConfig::getConfig().getParser().parse(dsrc); + // Wrap the document for now. XercesJanitor docjanitor(doc); - + // Unmarshall objects, binding the document. auto_ptr xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true)); docjanitor.release(); @@ -126,20 +294,14 @@ EntityDescriptor* DynamicMetadataProvider::resolve(const char* entityID) const "Root of metadata instance not recognized: $1", params(1,xmlObject->getElementQName().toString().c_str()) ); } - xmlObject.release(); - return entity; - } - catch (XMLException& e) { - auto_ptr_char msg(e.getMessage()); - Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic").error( - "Xerces error while resolving entityID (%s): %s", entityID, msg.get() - ); - throw MetadataException(msg.get()); - } - catch (exception& e) { - Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic").error( - "error while resolving entityID (%s): %s", entityID, e.what() - ); - throw; - } + xmlObject.release(); + return entity; + } + catch (XMLException& e) { + auto_ptr_char msg(e.getMessage()); + Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic").error( + "Xerces error while resolving entityID (%s): %s", name.c_str(), msg.get() + ); + throw MetadataException(msg.get()); + } }