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