X-Git-Url: http://www.project-moonshot.org/gitweb/?p=shibboleth%2Fcpp-opensaml.git;a=blobdiff_plain;f=saml%2Fsaml2%2Fmetadata%2Fimpl%2FDynamicMetadataProvider.cpp;h=2768440a13d5a2cecf4cf330d50ce03ab461c221;hp=47106ecdc1938bf350565917c730467d70285223;hb=1462057b3b9ae7e165d34d988e30b14c213672ca;hpb=9247fd98448d17c495f1d811ac1ba82571f7ef98 diff --git a/saml/saml2/metadata/impl/DynamicMetadataProvider.cpp b/saml/saml2/metadata/impl/DynamicMetadataProvider.cpp index 47106ec..2768440 100644 --- a/saml/saml2/metadata/impl/DynamicMetadataProvider.cpp +++ b/saml/saml2/metadata/impl/DynamicMetadataProvider.cpp @@ -1,17 +1,21 @@ -/* - * Copyright 2001-2009 Internet2 +/** + * Licensed to the University Corporation for Advanced Internet + * Development, Inc. (UCAID) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. * - * 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 + * UCAID licenses this file to you 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 * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ /** @@ -28,6 +32,9 @@ #include #include #include +#include +#include +#include #include #include @@ -40,8 +47,11 @@ using namespace std; # 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 validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e); +static const XMLCh id[] = UNICODE_LITERAL_2(i,d); +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 { @@ -53,15 +63,31 @@ namespace opensaml { }; DynamicMetadataProvider::DynamicMetadataProvider(const DOMElement* e) - : AbstractMetadataProvider(e), m_maxCacheDuration(28800), m_lock(RWLock::create()) + : AbstractMetadataProvider(e), + m_validate(XMLHelper::getAttrBool(e, false, validate)), + m_id(XMLHelper::getAttrString(e, "Dynamic", id)), + m_lock(RWLock::create()), + m_refreshDelayFactor(0.75), + m_minCacheDuration(XMLHelper::getAttrInt(e, 600, minCacheDuration)), + m_maxCacheDuration(XMLHelper::getAttrInt(e, 28800, maxCacheDuration)) { - const XMLCh* flag=e ? e->getAttributeNS(NULL,validate) : NULL; - m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE)); - flag = e ? e->getAttributeNS(NULL,maxCacheDuration) : NULL; - if (flag && *flag) { - m_maxCacheDuration = XMLString::parseInt(flag); - if (m_maxCacheDuration == 0) - m_maxCacheDuration = 28800; + 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; + } } } @@ -69,19 +95,71 @@ DynamicMetadataProvider::~DynamicMetadataProvider() { // Each entity in the map is unique (no multimap semantics), so this is safe. clearDescriptorIndex(true); - delete m_lock; +} + +const XMLObject* DynamicMetadataProvider::getMetadata() const +{ + 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() +{ +} + +const char* DynamicMetadataProvider::getId() const +{ + return m_id.c_str(); } pair DynamicMetadataProvider::getEntityDescriptor(const Criteria& criteria) const { - // Check cache while holding the read lock. + Category& log = Category::getInstance(SAML_LOGCAT ".MetadataProvider.Dynamic"); + + // First we check the underlying cache. pair entity = AbstractMetadataProvider::getEntityDescriptor(criteria); - if (entity.first) // even if the role isn't found, we're done - return entity; + + // 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); + } string name; - if (criteria.entityID_ascii) + if (criteria.entityID_ascii) { name = criteria.entityID_ascii; + } else if (criteria.entityID_unicode) { auto_ptr_char temp(criteria.entityID_unicode); name = temp.get(); @@ -89,11 +167,14 @@ pair DynamicMetadataProvider::get else if (criteria.artifact) { name = criteria.artifact->getSource(); } - else + else { return entity; + } - Category& log = Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic"); - log.info("resolving metadata for (%s)", name.c_str()); + 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()); try { // Try resolving it. @@ -112,39 +193,56 @@ pair DynamicMetadataProvider::get } } - if (!m_validate) { - try { - SchemaValidators.validate(entity2.get()); - } - catch (exception& ex) { - log.error("metadata intance failed manual schema validation checking: %s", ex.what()); - throw MetadataException("Metadata instance failed manual schema validation checking."); - } + // 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."); } // Filter it, which may throw. - doFilters(*entity2.get()); - - time_t now = time(NULL); + doFilters(*entity2); + 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(); + emitChangeEvent(*entity2); + + // 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. - time_t exp = m_maxCacheDuration; - if (entity2->getCacheDuration()) - exp = min(m_maxCacheDuration, entity2->getCacheDurationEpoch()); - exp += now; - index(entity2.release(), exp, true); + cacheExp = SAMLTIME_MAX; + indexEntity(entity2.get(), cacheExp, true); + entity2.release(); + + m_lastUpdate = now; // Downgrade back to a read lock. m_lock->unlock(); @@ -152,6 +250,18 @@ pair DynamicMetadataProvider::get } 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; } @@ -174,7 +284,7 @@ EntityDescriptor* DynamicMetadataProvider::resolve(const Criteria& criteria) con } try { - DOMDocument* doc=NULL; + DOMDocument* doc=nullptr; auto_ptr_XMLCh widenit(name.c_str()); URLInputSource src(widenit.get()); Wrapper4InputSource dsrc(&src,false); @@ -202,7 +312,7 @@ EntityDescriptor* DynamicMetadataProvider::resolve(const Criteria& criteria) con } catch (XMLException& e) { auto_ptr_char msg(e.getMessage()); - Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic").error( + Category::getInstance(SAML_LOGCAT ".MetadataProvider.Dynamic").error( "Xerces error while resolving entityID (%s): %s", name.c_str(), msg.get() ); throw MetadataException(msg.get());