X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=saml%2Fsaml2%2Fmetadata%2Fimpl%2FXMLMetadataProvider.cpp;h=2f410bd862defb0f5052989d69b91a8670ab3454;hb=0f3c25302b5938c77530b5779732e94d535e5ef9;hp=03deb22fd1e5af0384584037047a0cbb5e0ab705;hpb=0d5e6d568cd0945e18a8b54b7d75e9f5a352c41b;p=shibboleth%2Fcpp-opensaml.git diff --git a/saml/saml2/metadata/impl/XMLMetadataProvider.cpp b/saml/saml2/metadata/impl/XMLMetadataProvider.cpp index 03deb22..2f410bd 100644 --- a/saml/saml2/metadata/impl/XMLMetadataProvider.cpp +++ b/saml/saml2/metadata/impl/XMLMetadataProvider.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -21,12 +21,20 @@ */ #include "internal.h" +#include "binding/SAMLArtifact.h" #include "saml2/metadata/Metadata.h" #include "saml2/metadata/MetadataFilter.h" #include "saml2/metadata/AbstractMetadataProvider.h" +#include "saml2/metadata/DiscoverableMetadataProvider.h" +#include +#include +#include #include +#include #include +#include +#include using namespace opensaml::saml2md; using namespace xmltooling::logging; @@ -41,19 +49,26 @@ using namespace std; namespace opensaml { namespace saml2md { - class SAML_DLLLOCAL XMLMetadataProvider : public AbstractMetadataProvider, public ReloadableXMLFile + class SAML_DLLLOCAL XMLMetadataProvider + : public AbstractMetadataProvider, public DiscoverableMetadataProvider, public ReloadableXMLFile { public: - XMLMetadataProvider(const DOMElement* e) - : AbstractMetadataProvider(e), ReloadableXMLFile(e, Category::getInstance(SAML_LOGCAT".MetadataProvider.XML")), - m_object(NULL), m_maxCacheDuration(m_reloadInterval) { - } + XMLMetadataProvider(const DOMElement* e); + virtual ~XMLMetadataProvider() { + shutdown(); delete m_object; } void init() { - load(); // guarantees an exception or the metadata is loaded + try { + background_load(); + startup(); + } + catch (...) { + startup(); + throw; + } } const XMLObject* getMetadata() const { @@ -61,14 +76,19 @@ namespace opensaml { } protected: - pair load(); + pair load(bool backup); + pair background_load(); private: using AbstractMetadataProvider::index; - void index(); + void index(time_t& validUntil); + time_t computeNextRefresh(); XMLObject* m_object; - time_t m_maxCacheDuration; + bool m_discoveryFeed; + double m_refreshDelayFactor; + unsigned int m_backoffFactor; + time_t m_minRefreshDelay,m_maxRefreshDelay,m_lastValidUntil; }; MetadataProvider* SAML_DLLLOCAL XMLMetadataProviderFactory(const DOMElement* const & e) @@ -76,6 +96,9 @@ namespace opensaml { return new XMLMetadataProvider(e); } + static const XMLCh discoveryFeed[] = UNICODE_LITERAL_13(d,i,s,c,o,v,e,r,y,F,e,e,d); + static const XMLCh minRefreshDelay[] = UNICODE_LITERAL_15(m,i,n,R,e,f,r,e,s,h,D,e,l,a,y); + 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); }; }; @@ -83,13 +106,41 @@ namespace opensaml { #pragma warning( pop ) #endif -pair XMLMetadataProvider::load() +XMLMetadataProvider::XMLMetadataProvider(const DOMElement* e) + : AbstractMetadataProvider(e), DiscoverableMetadataProvider(e), ReloadableXMLFile(e, Category::getInstance(SAML_LOGCAT".MetadataProvider.XML"), false), + m_object(nullptr), m_discoveryFeed(XMLHelper::getAttrBool(e, true, discoveryFeed)), + m_refreshDelayFactor(0.75), m_backoffFactor(1), + m_minRefreshDelay(XMLHelper::getAttrInt(e, 600, minRefreshDelay)), + m_maxRefreshDelay(m_reloadInterval), m_lastValidUntil(SAMLTIME_MAX) { - // Load from source using base class. - pair raw = ReloadableXMLFile::load(); + if (!m_local && m_maxRefreshDelay) { + const XMLCh* setting = e->getAttributeNS(nullptr, refreshDelayFactor); + if (setting && *setting) { + auto_ptr_char delay(setting); + m_refreshDelayFactor = atof(delay.get()); + if (m_refreshDelayFactor <= 0.0 || m_refreshDelayFactor >= 1.0) { + m_log.error("invalid refreshDelayFactor setting, using default"); + m_refreshDelayFactor = 0.75; + } + } + + if (m_minRefreshDelay > m_maxRefreshDelay) { + m_log.error("minRefreshDelay setting exceeds maxRefreshDelay/refreshInterval setting, lowering to match it"); + m_minRefreshDelay = m_maxRefreshDelay; + } + } +} + +pair XMLMetadataProvider::load(bool backup) +{ + // Lower the refresh rate in case of an error. + m_reloadInterval = m_minRefreshDelay; + + // Call the base class to load/parse the appropriate XML resource. + pair raw = ReloadableXMLFile::load(backup); // If we own it, wrap it for now. - XercesJanitor docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL); + XercesJanitor docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr); // Unmarshall objects, binding the document. auto_ptr xmlObject(XMLObjectBuilder::buildOneFromElement(raw.second, true)); @@ -100,39 +151,151 @@ pair XMLMetadataProvider::load() "Root of metadata instance not recognized: $1", params(1,xmlObject->getElementQName().toString().c_str()) ); - // Preprocess the metadata. - doFilters(*xmlObject.get()); + // Preprocess the metadata (even if we schema-validated). + try { + SchemaValidators.validate(xmlObject.get()); + } + catch (exception& ex) { + m_log.error("metadata intance failed manual validation checking: %s", ex.what()); + throw MetadataException("Metadata instance failed manual validation checking."); + } + + // This is the best place to take a backup, since it's superficially "correct" metadata. + string backupKey; + if (!backup && !m_backing.empty()) { + // We compute a random filename extension to the "real" location. + SAMLConfig::getConfig().generateRandomBytes(backupKey, 2); + backupKey = m_backing + '.' + SAMLArtifact::toHex(backupKey); + m_log.debug("backing up remote metadata resource to (%s)", backupKey.c_str()); + try { + ofstream backer(backupKey.c_str()); + backer << *(raw.second->getOwnerDocument()); + } + catch (exception& ex) { + m_log.crit("exception while backing up metadata: %s", ex.what()); + backupKey.erase(); + } + } + + try { + doFilters(*xmlObject.get()); + } + catch (exception&) { + if (!backupKey.empty()) + remove(backupKey.c_str()); + throw; + } + + if (!backupKey.empty()) { + m_log.debug("committing backup file to permanent location (%s)", m_backing.c_str()); + Locker locker(getBackupLock()); + remove(m_backing.c_str()); + if (rename(backupKey.c_str(), m_backing.c_str()) != 0) + m_log.crit("unable to rename metadata backup file"); + preserveCacheTag(); + } + xmlObject->releaseThisAndChildrenDOM(); - xmlObject->setDocument(NULL); + xmlObject->setDocument(nullptr); - // Swap it in. - bool changed = m_object!=NULL; + // Swap it in after acquiring write lock if necessary. + if (m_lock) + m_lock->wrlock(); + SharedLock locker(m_lock, false); + bool changed = m_object!=nullptr; delete m_object; m_object = xmlObject.release(); - index(); + m_lastValidUntil = SAMLTIME_MAX; + index(m_lastValidUntil); + if (m_discoveryFeed) + generateFeed(); if (changed) emitChangeEvent(); - // If a remote resource, reduce the reload interval if cacheDuration is set. - if (!m_local) { - const CacheableSAMLObject* cacheable = dynamic_cast(m_object); - if (cacheable && cacheable->getCacheDuration() && cacheable->getCacheDurationEpoch() < m_maxCacheDuration) - m_reloadInterval = cacheable->getCacheDurationEpoch(); - else - m_reloadInterval = m_maxCacheDuration; + // Tracking cacheUntil through the tree is TBD, but + // validUntil is the tightest interval amongst the children. + + // If a remote resource, adjust the reload interval. + if (!backup && !m_local) { + m_backoffFactor = 1; + m_reloadInterval = computeNextRefresh(); + m_log.info("adjusted reload interval to %d seconds", m_reloadInterval); } - return make_pair(false,(DOMElement*)NULL); + m_loaded = true; + return make_pair(false,(DOMElement*)nullptr); +} + +pair XMLMetadataProvider::background_load() +{ + try { + return load(false); + } + catch (long& ex) { + if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED) { + // Unchanged document, so re-establish previous refresh interval. + m_reloadInterval = computeNextRefresh(); + m_log.info("remote resource (%s) unchanged, adjusted reload interval to %u seconds", m_source.c_str(), m_reloadInterval); + } + else { + // Any other status code, just treat as an error. + m_reloadInterval = m_minRefreshDelay * m_backoffFactor++; + if (m_reloadInterval > m_maxRefreshDelay) + m_reloadInterval = m_maxRefreshDelay; + m_log.warn("adjusted reload interval to %u seconds", m_reloadInterval); + } + if (!m_loaded && !m_backing.empty()) + return load(true); + throw; + } + catch (exception&) { + if (!m_local) { + m_reloadInterval = m_minRefreshDelay * m_backoffFactor++; + if (m_reloadInterval > m_maxRefreshDelay) + m_reloadInterval = m_maxRefreshDelay; + m_log.warn("adjusted reload interval to %u seconds", m_reloadInterval); + if (!m_loaded && !m_backing.empty()) + return load(true); + } + throw; + } +} + +time_t XMLMetadataProvider::computeNextRefresh() +{ + time_t now = time(nullptr); + + // If some or all of the metadata is already expired, reload after the minimum. + if (m_lastValidUntil < now) { + return m_minRefreshDelay; + } + else { + // Compute the smaller of the validUntil / cacheDuration constraints. + time_t ret = m_lastValidUntil - now; + const CacheableSAMLObject* cacheable = dynamic_cast(m_object); + if (cacheable && cacheable->getCacheDuration()) + ret = min(ret, cacheable->getCacheDurationEpoch()); + + // Adjust for the delay factor. + ret *= m_refreshDelayFactor; + + // Bound by max and min. + if (ret > m_maxRefreshDelay) + return m_maxRefreshDelay; + else if (ret < m_minRefreshDelay) + return m_minRefreshDelay; + + return ret; + } } -void XMLMetadataProvider::index() +void XMLMetadataProvider::index(time_t& validUntil) { clearDescriptorIndex(); EntitiesDescriptor* group=dynamic_cast(m_object); if (group) { - AbstractMetadataProvider::index(group, SAMLTIME_MAX); + indexGroup(group, validUntil); return; } - EntityDescriptor* site=dynamic_cast(m_object); - AbstractMetadataProvider::index(site, SAMLTIME_MAX); + indexEntity(dynamic_cast(m_object), validUntil); }