Convert from NULL macro to nullptr, remove unused zlib code.
[shibboleth/cpp-opensaml.git] / saml / saml2 / metadata / impl / DynamicMetadataProvider.cpp
1 /*
2  *  Copyright 2001-2010 Internet2
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * DynamicMetadataProvider.cpp
19  *
20  * Simple implementation of a dynamic caching MetadataProvider.
21  */
22
23 #include "internal.h"
24 #include "binding/SAMLArtifact.h"
25 #include "saml2/metadata/Metadata.h"
26 #include "saml2/metadata/DynamicMetadataProvider.h"
27
28 #include <xercesc/framework/Wrapper4InputSource.hpp>
29 #include <xercesc/util/XMLUniDefs.hpp>
30 #include <xmltooling/logging.h>
31 #include <xmltooling/XMLToolingConfig.h>
32 #include <xmltooling/util/ParserPool.h>
33 #include <xmltooling/util/Threads.h>
34 #include <xmltooling/util/XMLHelper.h>
35 #include <xmltooling/validation/ValidatorSuite.h>
36
37 using namespace opensaml::saml2md;
38 using namespace xmltooling::logging;
39 using namespace xmltooling;
40 using namespace std;
41
42 # ifndef min
43 #  define min(a,b)            (((a) < (b)) ? (a) : (b))
44 # endif
45
46 static const XMLCh maxCacheDuration[] = UNICODE_LITERAL_16(m,a,x,C,a,c,h,e,D,u,r,a,t,i,o,n);
47 static const XMLCh validate[] =         UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
48
49 namespace opensaml {
50     namespace saml2md {
51         MetadataProvider* SAML_DLLLOCAL DynamicMetadataProviderFactory(const DOMElement* const & e)
52         {
53             return new DynamicMetadataProvider(e);
54         }
55     };
56 };
57
58 DynamicMetadataProvider::DynamicMetadataProvider(const DOMElement* e)
59     : AbstractMetadataProvider(e), m_maxCacheDuration(28800), m_lock(RWLock::create())
60 {
61     const XMLCh* flag=e ? e->getAttributeNS(nullptr,validate) : nullptr;
62     m_validate=(XMLString::equals(flag,xmlconstants::XML_TRUE) || XMLString::equals(flag,xmlconstants::XML_ONE));
63     flag = e ? e->getAttributeNS(nullptr,maxCacheDuration) : nullptr;
64     if (flag && *flag) {
65         m_maxCacheDuration = XMLString::parseInt(flag);
66         if (m_maxCacheDuration == 0)
67             m_maxCacheDuration = 28800;
68     }
69 }
70
71 DynamicMetadataProvider::~DynamicMetadataProvider()
72 {
73     // Each entity in the map is unique (no multimap semantics), so this is safe.
74     clearDescriptorIndex(true);
75     delete m_lock;
76 }
77
78 const XMLObject* DynamicMetadataProvider::getMetadata() const
79 {
80     throw MetadataException("getMetadata operation not implemented on this provider.");
81 }
82
83 Lockable* DynamicMetadataProvider::lock()
84 {
85     m_lock->rdlock();
86     return this;
87 }
88
89 void DynamicMetadataProvider::unlock()
90 {
91     m_lock->unlock();
92 }
93
94 void DynamicMetadataProvider::init()
95 {
96 }
97
98 pair<const EntityDescriptor*,const RoleDescriptor*> DynamicMetadataProvider::getEntityDescriptor(const Criteria& criteria) const
99 {
100     pair<const EntityDescriptor*,const RoleDescriptor*> entity = AbstractMetadataProvider::getEntityDescriptor(criteria);
101     if (entity.first) {
102         // Check to see if we're within the caching interval.
103         cachemap_t::iterator cit = m_cacheMap.find(entity.first->getEntityID());
104         if (cit != m_cacheMap.end()) {
105             if (time(nullptr) <= cit->second)
106                 return entity;
107             m_cacheMap.erase(cit);
108         }
109     }
110
111     string name;
112     if (criteria.entityID_ascii)
113         name = criteria.entityID_ascii;
114     else if (criteria.entityID_unicode) {
115         auto_ptr_char temp(criteria.entityID_unicode);
116         name = temp.get();
117     }
118     else if (criteria.artifact) {
119         name = criteria.artifact->getSource();
120     }
121     else
122         return entity;
123
124     Category& log = Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic");
125     if (entity.first)
126         log.info("metadata for (%s) is beyond caching interval, attempting to refresh", name.c_str());
127     else
128         log.info("resolving metadata for (%s)", name.c_str());
129
130     try {
131         // Try resolving it.
132         auto_ptr<EntityDescriptor> entity2(resolve(criteria));
133
134         // Verify the entityID.
135         if (criteria.entityID_unicode && !XMLString::equals(criteria.entityID_unicode, entity2->getEntityID())) {
136             log.error("metadata instance did not match expected entityID");
137             return entity;
138         }
139         else {
140             auto_ptr_XMLCh temp2(name.c_str());
141             if (!XMLString::equals(temp2.get(), entity2->getEntityID())) {
142                 log.error("metadata instance did not match expected entityID");
143                 return entity;
144             }
145         }
146
147         // Preprocess the metadata (even if we schema-validated).
148         try {
149             SchemaValidators.validate(entity2.get());
150         }
151         catch (exception& ex) {
152             log.error("metadata intance failed manual validation checking: %s", ex.what());
153             throw MetadataException("Metadata instance failed manual validation checking.");
154         }
155
156         // Filter it, which may throw.
157         doFilters(*entity2.get());
158
159         time_t now = time(nullptr);
160         if (entity2->getValidUntil() && entity2->getValidUntilEpoch() < now + 60)
161             throw MetadataException("Metadata was already invalid at the time of retrieval.");
162
163         log.info("caching resolved metadata for (%s)", name.c_str());
164
165         // Upgrade our lock so we can cache the new metadata.
166         m_lock->unlock();
167         m_lock->wrlock();
168
169         // Notify observers.
170         emitChangeEvent();
171
172         // Note the cache duration.
173         time_t cacheExp = m_maxCacheDuration;
174         if (entity2->getCacheDuration())
175             cacheExp = min(m_maxCacheDuration, entity2->getCacheDurationEpoch());
176         cacheExp = max(cacheExp, 60);
177         m_cacheMap[entity2->getEntityID()] = time(nullptr) + cacheExp;
178
179         // Make sure we clear out any existing copies, including stale metadata or if somebody snuck in.
180         index(entity2.release(), SAMLTIME_MAX, true);
181
182         // Downgrade back to a read lock.
183         m_lock->unlock();
184         m_lock->rdlock();
185     }
186     catch (exception& e) {
187         log.error("error while resolving entityID (%s): %s", name.c_str(), e.what());
188         // This will return entries that are beyond their cache period,
189         // but not beyond their validity unless that criteria option was set.
190         // If it is a cache-expired entry, bump the cache period to prevent retries.
191         if (entity.first) {
192             time_t cacheExp = 600;
193             if (entity.first->getCacheDuration())
194                 cacheExp = min(m_maxCacheDuration, entity.first->getCacheDurationEpoch());
195             cacheExp = max(cacheExp, 60);
196             m_cacheMap[entity.first->getEntityID()] = time(nullptr) + cacheExp;
197         }
198         return entity;
199     }
200
201     // Rinse and repeat.
202     return getEntityDescriptor(criteria);
203 }
204
205 EntityDescriptor* DynamicMetadataProvider::resolve(const Criteria& criteria) const
206 {
207     string name;
208     if (criteria.entityID_ascii) {
209         name = criteria.entityID_ascii;
210     }
211     else if (criteria.entityID_unicode) {
212         auto_ptr_char temp(criteria.entityID_unicode);
213         name = temp.get();
214     }
215     else if (criteria.artifact) {
216         throw MetadataException("Unable to resolve metadata dynamically from an artifact.");
217     }
218
219     try {
220         DOMDocument* doc=nullptr;
221         auto_ptr_XMLCh widenit(name.c_str());
222         URLInputSource src(widenit.get());
223         Wrapper4InputSource dsrc(&src,false);
224         if (m_validate)
225             doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
226         else
227             doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
228
229         // Wrap the document for now.
230         XercesJanitor<DOMDocument> docjanitor(doc);
231
232         // Unmarshall objects, binding the document.
233         auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
234         docjanitor.release();
235
236         // Make sure it's metadata.
237         EntityDescriptor* entity = dynamic_cast<EntityDescriptor*>(xmlObject.get());
238         if (!entity) {
239             throw MetadataException(
240                 "Root of metadata instance not recognized: $1", params(1,xmlObject->getElementQName().toString().c_str())
241                 );
242         }
243         xmlObject.release();
244         return entity;
245     }
246     catch (XMLException& e) {
247         auto_ptr_char msg(e.getMessage());
248         Category::getInstance(SAML_LOGCAT".MetadataProvider.Dynamic").error(
249             "Xerces error while resolving entityID (%s): %s", name.c_str(), msg.get()
250             );
251         throw MetadataException(msg.get());
252     }
253 }