Fix JSON array punctuation.
[shibboleth/cpp-opensaml.git] / saml / saml2 / metadata / impl / ChainingMetadataProvider.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  * ChainingMetadataProvider.cpp
19  * 
20  * MetadataProvider that uses multiple providers in sequence.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "saml/binding/SAMLArtifact.h"
26 #include "saml2/metadata/Metadata.h"
27 #include "saml2/metadata/DiscoverableMetadataProvider.h"
28 #include "saml2/metadata/ObservableMetadataProvider.h"
29 #include "saml2/metadata/MetadataCredentialCriteria.h"
30
31 #include <memory>
32 #include <xercesc/util/XMLUniDefs.hpp>
33 #include <xmltooling/logging.h>
34 #include <xmltooling/util/Threads.h>
35 #include <xmltooling/util/XMLHelper.h>
36
37
38 using namespace opensaml::saml2md;
39 using namespace opensaml;
40 using namespace xmlsignature;
41 using namespace xmltooling::logging;
42 using namespace xmltooling;
43 using namespace std;
44
45 namespace opensaml {
46     namespace saml2md {
47
48         // per-thread structure allocated to track locks and role->provider mappings
49         struct SAML_DLLLOCAL tracker_t;
50         
51         class SAML_DLLLOCAL ChainingMetadataProvider
52             : public DiscoverableMetadataProvider, public ObservableMetadataProvider, public ObservableMetadataProvider::Observer {
53         public:
54             ChainingMetadataProvider(const xercesc::DOMElement* e=nullptr);
55             virtual ~ChainingMetadataProvider();
56     
57             using MetadataProvider::getEntityDescriptor;
58             using MetadataProvider::getEntitiesDescriptor;
59
60             Lockable* lock();
61             void unlock();
62             void init();
63             const XMLObject* getMetadata() const;
64             const EntitiesDescriptor* getEntitiesDescriptor(const char* name, bool requireValidMetadata=true) const;
65             pair<const EntityDescriptor*,const RoleDescriptor*> getEntityDescriptor(const Criteria& criteria) const;
66     
67             const Credential* resolve(const CredentialCriteria* criteria=nullptr) const;
68             vector<const Credential*>::size_type resolve(vector<const Credential*>& results, const CredentialCriteria* criteria=nullptr) const;
69
70             string getCacheTag() const {
71                 Lock lock(m_trackerLock);
72                 return m_feedTag;
73             }
74
75             ostream& outputFeed(ostream& os) const {
76                 os << "[\n";
77                 // Lock each provider in turn and suck in its feed.
78                 for (vector<MetadataProvider*>::const_iterator m = m_providers.begin(); m != m_providers.end(); ++m) {
79                     if (m != m_providers.begin())
80                         os << ",\n";
81                     DiscoverableMetadataProvider* d = dynamic_cast<DiscoverableMetadataProvider*>(*m);
82                     if (d) {
83                         Locker locker(d);
84                         d->outputFeed(os);
85                     }
86                 }
87                 os << ']';
88                 return os;
89             }
90
91             void onEvent(const ObservableMetadataProvider& provider) const {
92                 // Reset the cache tag for the feed.
93                 Lock lock(m_trackerLock);
94                 SAMLConfig::getConfig().generateRandomBytes(m_feedTag, 4);
95                 m_feedTag = SAMLArtifact::toHex(m_feedTag);
96                 emitChangeEvent();
97             }
98
99         protected:
100             void generateFeed() {
101                 // No-op.
102             }
103
104         private:
105             bool m_firstMatch;
106             mutable Mutex* m_trackerLock;
107             ThreadKey* m_tlsKey;
108             vector<MetadataProvider*> m_providers;
109             mutable set<tracker_t*> m_trackers;
110             static void tracker_cleanup(void*);
111             Category& m_log;
112             friend struct tracker_t;
113         };
114
115         struct SAML_DLLLOCAL tracker_t {
116             tracker_t(const ChainingMetadataProvider* m) : m_metadata(m) {
117                 Lock lock(m_metadata->m_trackerLock);
118                 m_metadata->m_trackers.insert(this);
119             }
120
121             void lock_if(MetadataProvider* m) {
122                 if (m_locked.count(m) == 0)
123                     m->lock();
124             }
125
126             void unlock_if(MetadataProvider* m) {
127                 if (m_locked.count(m) == 0)
128                     m->unlock();
129             }
130
131             void remember(MetadataProvider* m, const EntityDescriptor* entity=nullptr) {
132                 m_locked.insert(m);
133                 if (entity)
134                     m_objectMap.insert(pair<const XMLObject*,const MetadataProvider*>(entity,m));
135             }
136
137             const MetadataProvider* getProvider(const RoleDescriptor& role) {
138                 map<const XMLObject*,const MetadataProvider*>::const_iterator i = m_objectMap.find(role.getParent());
139                 return (i != m_objectMap.end()) ? i->second : nullptr;
140             }
141
142             const ChainingMetadataProvider* m_metadata;
143             set<MetadataProvider*> m_locked;
144             map<const XMLObject*,const MetadataProvider*> m_objectMap;
145         };
146
147         MetadataProvider* SAML_DLLLOCAL ChainingMetadataProviderFactory(const DOMElement* const & e)
148         {
149             return new ChainingMetadataProvider(e);
150         }
151
152         static const XMLCh _MetadataProvider[] =    UNICODE_LITERAL_16(M,e,t,a,d,a,t,a,P,r,o,v,i,d,e,r);
153         static const XMLCh precedence[] =           UNICODE_LITERAL_10(p,r,e,c,e,d,e,n,c,e);
154         static const XMLCh last[] =                 UNICODE_LITERAL_4(l,a,s,t);
155         static const XMLCh _type[] =                 UNICODE_LITERAL_4(t,y,p,e);
156     };
157 };
158
159 void ChainingMetadataProvider::tracker_cleanup(void* ptr)
160 {
161     if (ptr) {
162         // free the tracker after removing it from the parent plugin's tracker set
163         tracker_t* t = reinterpret_cast<tracker_t*>(ptr);
164         Lock lock(t->m_metadata->m_trackerLock);
165         t->m_metadata->m_trackers.erase(t);
166         delete t;
167     }
168 }
169
170 ChainingMetadataProvider::ChainingMetadataProvider(const DOMElement* e)
171     : ObservableMetadataProvider(e), m_firstMatch(true), m_trackerLock(nullptr), m_tlsKey(nullptr),
172         m_log(Category::getInstance(SAML_LOGCAT".Metadata.Chaining"))
173 {
174     if (XMLString::equals(e ? e->getAttributeNS(nullptr, precedence) : nullptr, last))
175         m_firstMatch = false;
176
177     e = XMLHelper::getFirstChildElement(e, _MetadataProvider);
178     while (e) {
179         string t = XMLHelper::getAttrString(e, nullptr, _type);
180         if (!t.empty()) {
181             try {
182                 m_log.info("building MetadataProvider of type %s", t.c_str());
183                 auto_ptr<MetadataProvider> provider(SAMLConfig::getConfig().MetadataProviderManager.newPlugin(t.c_str(), e));
184                 ObservableMetadataProvider* obs = dynamic_cast<ObservableMetadataProvider*>(provider.get());
185                 if (obs)
186                     obs->addObserver(this);
187                 m_providers.push_back(provider.get());
188                 provider.release();
189             }
190             catch (exception& ex) {
191                 m_log.error("error building MetadataProvider: %s", ex.what());
192             }
193         }
194         e = XMLHelper::getNextSiblingElement(e, _MetadataProvider);
195     }
196     m_trackerLock = Mutex::create();
197     m_tlsKey = ThreadKey::create(tracker_cleanup);
198 }
199
200 ChainingMetadataProvider::~ChainingMetadataProvider()
201 {
202     delete m_tlsKey;
203     delete m_trackerLock;
204     for_each(m_trackers.begin(), m_trackers.end(), xmltooling::cleanup<tracker_t>());
205     for_each(m_providers.begin(), m_providers.end(), xmltooling::cleanup<MetadataProvider>());
206 }
207
208 void ChainingMetadataProvider::init()
209 {
210     for (vector<MetadataProvider*>::const_iterator i=m_providers.begin(); i!=m_providers.end(); ++i) {
211         try {
212             (*i)->init();
213         }
214         catch (exception& ex) {
215             m_log.crit("failure initializing MetadataProvider: %s", ex.what());
216         }
217     }
218
219     // Set an initial cache tag for the state of the plugins.
220     SAMLConfig::getConfig().generateRandomBytes(m_feedTag, 4);
221     m_feedTag = SAMLArtifact::toHex(m_feedTag);
222 }
223
224 Lockable* ChainingMetadataProvider::lock()
225 {
226     return this;   // we're not lockable ourselves...
227 }
228
229 void ChainingMetadataProvider::unlock()
230 {
231     // Check for locked providers and remove role mappings.
232     void* ptr=m_tlsKey->getData();
233     if (ptr) {
234         tracker_t* t = reinterpret_cast<tracker_t*>(ptr);
235         for_each(t->m_locked.begin(), t->m_locked.end(), mem_fun<void,Lockable>(&Lockable::unlock));
236         t->m_locked.clear();
237         t->m_objectMap.clear();
238     }
239 }
240
241 const XMLObject* ChainingMetadataProvider::getMetadata() const
242 {
243     throw MetadataException("getMetadata operation not implemented on this provider.");
244 }
245
246 const EntitiesDescriptor* ChainingMetadataProvider::getEntitiesDescriptor(const char* name, bool requireValidMetadata) const
247 {
248     // Ensure we have a tracker to use.
249     tracker_t* tracker = nullptr;
250     void* ptr=m_tlsKey->getData();
251     if (ptr) {
252         tracker = reinterpret_cast<tracker_t*>(ptr);
253     }
254     else {
255         tracker = new tracker_t(this);
256         m_tlsKey->setData(tracker);
257     }
258
259     MetadataProvider* held = nullptr;
260     const EntitiesDescriptor* ret = nullptr;
261     const EntitiesDescriptor* cur = nullptr;
262     for (vector<MetadataProvider*>::const_iterator i=m_providers.begin(); i!=m_providers.end(); ++i) {
263         tracker->lock_if(*i);
264         if (cur=(*i)->getEntitiesDescriptor(name,requireValidMetadata)) {
265             // Are we using a first match policy?
266             if (m_firstMatch) {
267                 // Save locked provider.
268                 tracker->remember(*i);
269                 return cur;
270             }
271
272             // Using last match wins. Did we already have one?
273             if (held) {
274                 m_log.warn("found duplicate EntitiesDescriptor (%s), using last matching copy", name);
275                 tracker->unlock_if(held);
276             }
277
278             // Save off the latest match.
279             held = *i;
280             ret = cur;
281         }
282         else {
283             // No match, so just unlock this one and move on.
284             tracker->unlock_if(*i);
285         }
286     }
287
288     // Preserve any lock we're holding.
289     if (held)
290         tracker->remember(held);
291     return ret;
292 }
293
294 pair<const EntityDescriptor*,const RoleDescriptor*> ChainingMetadataProvider::getEntityDescriptor(const Criteria& criteria) const
295 {
296     // Ensure we have a tracker to use.
297     tracker_t* tracker = nullptr;
298     void* ptr=m_tlsKey->getData();
299     if (ptr) {
300         tracker = reinterpret_cast<tracker_t*>(ptr);
301     }
302     else {
303         tracker = new tracker_t(this);
304         m_tlsKey->setData(tracker);
305     }
306
307     // Do a search.
308     MetadataProvider* held = nullptr;
309     pair<const EntityDescriptor*,const RoleDescriptor*> ret = pair<const EntityDescriptor*,const RoleDescriptor*>(nullptr,nullptr);
310     pair<const EntityDescriptor*,const RoleDescriptor*> cur = ret;
311     for (vector<MetadataProvider*>::const_iterator i=m_providers.begin(); i!=m_providers.end(); ++i) {
312         tracker->lock_if(*i);
313         cur = (*i)->getEntityDescriptor(criteria);
314         if (cur.first) {
315             if (criteria.role) {
316                 // We want a role also. Did we find one?
317                 if (cur.second) {
318                     // Are we using a first match policy?
319                     if (m_firstMatch) {
320                         // We could have an entity-only match from earlier, so unlock it.
321                         if (held)
322                             tracker->unlock_if(held);
323                         // Save locked provider and role mapping.
324                         tracker->remember(*i, cur.first);
325                         return cur;
326                     }
327
328                     // Using last match wins. Did we already have one?
329                     if (held) {
330                         if (ret.second) {
331                             // We had a "complete" match, so log it.
332                             if (criteria.entityID_ascii) {
333                                 m_log.warn("found duplicate EntityDescriptor (%s) with role (%s), using last matching copy",
334                                     criteria.entityID_ascii, criteria.role->toString().c_str());
335                             }
336                             else if (criteria.entityID_unicode) {
337                                 auto_ptr_char temp(criteria.entityID_unicode);
338                                 m_log.warn("found duplicate EntityDescriptor (%s) with role (%s), using last matching copy",
339                                     temp.get(), criteria.role->toString().c_str());
340                             }
341                             else if (criteria.artifact) {
342                                 m_log.warn("found duplicate EntityDescriptor for artifact source (%s) with role (%s), using last matching copy",
343                                     criteria.artifact->getSource().c_str(), criteria.role->toString().c_str());
344                             }
345                         }
346                         tracker->unlock_if(held);
347                     }
348
349                     // Save off the latest match.
350                     held = *i;
351                     ret = cur;
352                 }
353                 else {
354                     // We didn't find the role, so we're going to keep looking,
355                     // but save this one if we didn't have the role yet.
356                     if (ret.second) {
357                         // We already had a role, so let's stick with that.
358                         tracker->unlock_if(*i);
359                     }
360                     else {
361                         // This is at least as good, so toss anything we had and keep it.
362                         if (held)
363                             tracker->unlock_if(held);
364                         held = *i;
365                         ret = cur;
366                     }
367                 }
368             }
369             else {
370                 // Are we using a first match policy?
371                 if (m_firstMatch) {
372                     // I don't think this can happen, but who cares, check anyway.
373                     if (held)
374                         tracker->unlock_if(held);
375                     
376                     // Save locked provider.
377                     tracker->remember(*i, cur.first);
378                     return cur;
379                 }
380
381                 // Using last match wins. Did we already have one?
382                 if (held) {
383                     if (criteria.entityID_ascii) {
384                         m_log.warn("found duplicate EntityDescriptor (%s), using last matching copy", criteria.entityID_ascii);
385                     }
386                     else if (criteria.entityID_unicode) {
387                         auto_ptr_char temp(criteria.entityID_unicode);
388                         m_log.warn("found duplicate EntityDescriptor (%s), using last matching copy", temp.get());
389                     }
390                     else if (criteria.artifact) {
391                         m_log.warn("found duplicate EntityDescriptor for artifact source (%s), using last matching copy",
392                             criteria.artifact->getSource().c_str());
393                     }
394                     tracker->unlock_if(held);
395                 }
396
397                 // Save off the latest match.
398                 held = *i;
399                 ret = cur;
400             }
401         }
402         else {
403             // No match, so just unlock this one and move on.
404             tracker->unlock_if(*i);
405         }
406     }
407
408     // Preserve any lock we're holding.
409     if (held)
410         tracker->remember(held, ret.first);
411     return ret;
412 }
413
414 const Credential* ChainingMetadataProvider::resolve(const CredentialCriteria* criteria) const
415 {
416     void* ptr=m_tlsKey->getData();
417     if (!ptr)
418         throw MetadataException("No locked MetadataProvider, where did the role object come from?");
419     tracker_t* tracker=reinterpret_cast<tracker_t*>(ptr);
420
421     const MetadataCredentialCriteria* mcc = dynamic_cast<const MetadataCredentialCriteria*>(criteria);
422     if (!mcc)
423         throw MetadataException("Cannot resolve credentials without a MetadataCredentialCriteria object.");
424     const MetadataProvider* m = tracker->getProvider(mcc->getRole());
425     if (!m)
426         throw MetadataException("No record of corresponding MetadataProvider, where did the role object come from?");
427     return m->resolve(mcc);
428 }
429
430 vector<const Credential*>::size_type ChainingMetadataProvider::resolve(
431     vector<const Credential*>& results, const CredentialCriteria* criteria
432     ) const
433 {
434     void* ptr=m_tlsKey->getData();
435     if (!ptr)
436         throw MetadataException("No locked MetadataProvider, where did the role object come from?");
437     tracker_t* tracker=reinterpret_cast<tracker_t*>(ptr);
438
439     const MetadataCredentialCriteria* mcc = dynamic_cast<const MetadataCredentialCriteria*>(criteria);
440     if (!mcc)
441         throw MetadataException("Cannot resolve credentials without a MetadataCredentialCriteria object.");
442     const MetadataProvider* m = tracker->getProvider(mcc->getRole());
443     if (!m)
444         throw MetadataException("No record of corresponding MetadataProvider, where did the role object come from?");
445     return m->resolve(results, mcc);
446 }