SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-opensaml.git] / saml / saml2 / metadata / impl / AbstractMetadataProvider.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * AbstractMetadataProvider.cpp
23  * 
24  * Base class for caching metadata providers.
25  */
26
27 #include "internal.h"
28 #include "binding/SAMLArtifact.h"
29 #include "saml2/metadata/Metadata.h"
30 #include "saml2/metadata/AbstractMetadataProvider.h"
31 #include "saml2/metadata/MetadataCredentialContext.h"
32 #include "saml2/metadata/MetadataCredentialCriteria.h"
33
34 #include <boost/iterator/indirect_iterator.hpp>
35 #include <boost/lambda/bind.hpp>
36 #include <boost/lambda/if.hpp>
37 #include <boost/lambda/lambda.hpp>
38 #include <xercesc/util/XMLUniDefs.hpp>
39 #include <xmltooling/logging.h>
40 #include <xmltooling/XMLToolingConfig.h>
41 #include <xmltooling/security/Credential.h>
42 #include <xmltooling/security/KeyInfoResolver.h>
43 #include <xmltooling/security/SecurityHelper.h>
44 #include <xmltooling/util/DateTime.h>
45 #include <xmltooling/util/Threads.h>
46 #include <xmltooling/util/XMLHelper.h>
47
48 using namespace opensaml::saml2md;
49 using namespace xmltooling::logging;
50 using namespace xmltooling;
51 using namespace boost::lambda;
52 using namespace boost;
53 using namespace std;
54 using opensaml::SAMLArtifact;
55
56 static const XMLCh _KeyInfoResolver[] = UNICODE_LITERAL_15(K,e,y,I,n,f,o,R,e,s,o,l,v,e,r);
57 static const XMLCh _type[] =            UNICODE_LITERAL_4(t,y,p,e);
58
59 AbstractMetadataProvider::AbstractMetadataProvider(const DOMElement* e)
60     : ObservableMetadataProvider(e), m_lastUpdate(0),  m_resolver(nullptr), m_credentialLock(Mutex::create())
61 {
62     e = XMLHelper::getFirstChildElement(e, _KeyInfoResolver);
63     if (e) {
64         string t = XMLHelper::getAttrString(e, nullptr, _type);
65         if (!t.empty()) {
66             m_resolverWrapper.reset(XMLToolingConfig::getConfig().KeyInfoResolverManager.newPlugin(t.c_str(), e));
67             m_resolver = m_resolverWrapper.get();
68         }
69         else {
70             throw UnknownExtensionException("<KeyInfoResolver> element found with no type attribute");
71         }
72     }
73 }
74
75 AbstractMetadataProvider::~AbstractMetadataProvider()
76 {
77     for (credmap_t::iterator c = m_credentialMap.begin(); c!=m_credentialMap.end(); ++c)
78         for_each(c->second.begin(), c->second.end(), xmltooling::cleanup<Credential>());
79 }
80
81 void AbstractMetadataProvider::outputStatus(ostream& os) const
82 {
83     os << "<MetadataProvider";
84
85     if (getId() && *getId()) {
86         os << " id='" << getId() << "'";
87     }
88
89     if (m_lastUpdate > 0) {
90         DateTime ts(m_lastUpdate);
91         ts.parseDateTime();
92         auto_ptr_char timestamp(ts.getFormattedString());
93         os << " lastUpdate='" << timestamp.get() << "'";
94     }
95
96     os << "/>";
97 }
98
99 void AbstractMetadataProvider::emitChangeEvent() const
100 {
101     for (credmap_t::iterator c = m_credentialMap.begin(); c!=m_credentialMap.end(); ++c)
102         for_each(c->second.begin(), c->second.end(), xmltooling::cleanup<Credential>());
103     m_credentialMap.clear();
104     ObservableMetadataProvider::emitChangeEvent();
105 }
106
107 void AbstractMetadataProvider::emitChangeEvent(const EntityDescriptor& entity) const
108 {
109     for (credmap_t::iterator c = m_credentialMap.begin(); c!=m_credentialMap.end(); ++c)
110         for_each(c->second.begin(), c->second.end(), xmltooling::cleanup<Credential>());
111     m_credentialMap.clear();
112     ObservableMetadataProvider::emitChangeEvent(entity);
113 }
114
115 void AbstractMetadataProvider::indexEntity(EntityDescriptor* site, time_t& validUntil, bool replace) const
116 {
117     // If child expires later than input, reset child, otherwise lower input to match.
118     if (validUntil < site->getValidUntilEpoch())
119         site->setValidUntil(validUntil);
120     else
121         validUntil = site->getValidUntilEpoch();
122
123     auto_ptr_char id(site->getEntityID());
124     if (id.get()) {
125         if (replace) {
126             // The data structure here needs work.
127             // We have to find all the sites stored against the replaced ID. Then we have to
128             // search for those sites in the entire set of sites tracked by the sources map and
129             // remove them from both places.
130             set<const EntityDescriptor*> existingSites;
131             pair<sitemap_t::iterator,sitemap_t::iterator> existingRange = m_sites.equal_range(id.get());
132             static pair<set<const EntityDescriptor*>::iterator,bool> (set<const EntityDescriptor*>::* ins)(const EntityDescriptor* const &) =
133                 &set<const EntityDescriptor*>::insert;
134             for_each(
135                 existingRange.first, existingRange.second,
136                 lambda::bind(ins, boost::ref(existingSites), lambda::bind(&sitemap_t::value_type::second, _1))
137                 );
138             m_sites.erase(existingRange.first, existingRange.second);
139             for (sitemap_t::iterator s = m_sources.begin(); s != m_sources.end();) {
140                 if (existingSites.count(s->second) > 0) {
141                     sitemap_t::iterator temp = s;
142                     ++s;
143                     m_sources.erase(temp);
144                 }
145                 else {
146                     ++s;
147                 }
148             }
149         }
150         m_sites.insert(sitemap_t::value_type(id.get(),site));
151     }
152     
153     // Process each IdP role.
154     const vector<IDPSSODescriptor*>& roles = const_cast<const EntityDescriptor*>(site)->getIDPSSODescriptors();
155     for (vector<IDPSSODescriptor*>::const_iterator i = roles.begin(); i != roles.end(); i++) {
156         // SAML 1.x?
157         if ((*i)->hasSupport(samlconstants::SAML10_PROTOCOL_ENUM) || (*i)->hasSupport(samlconstants::SAML11_PROTOCOL_ENUM)) {
158             // Check for SourceID extension element.
159             const Extensions* exts = (*i)->getExtensions();
160             if (exts && exts->hasChildren()) {
161                 const vector<XMLObject*>& children = exts->getUnknownXMLObjects();
162                 for (vector<XMLObject*>::const_iterator ext = children.begin(); ext != children.end(); ++ext) {
163                     SourceID* sid = dynamic_cast<SourceID*>(*ext);
164                     if (sid) {
165                         auto_ptr_char sourceid(sid->getID());
166                         if (sourceid.get()) {
167                             m_sources.insert(sitemap_t::value_type(sourceid.get(),site));
168                             break;
169                         }
170                     }
171                 }
172             }
173             
174             // Hash the ID.
175             m_sources.insert(sitemap_t::value_type(SecurityHelper::doHash("SHA1", id.get(), strlen(id.get())),site));
176                 
177             // Load endpoints for type 0x0002 artifacts.
178             const vector<ArtifactResolutionService*>& locs = const_cast<const IDPSSODescriptor*>(*i)->getArtifactResolutionServices();
179             for (vector<ArtifactResolutionService*>::const_iterator loc = locs.begin(); loc != locs.end(); loc++) {
180                 auto_ptr_char location((*loc)->getLocation());
181                 if (location.get())
182                     m_sources.insert(sitemap_t::value_type(location.get(),site));
183             }
184         }
185         
186         // SAML 2.0?
187         if ((*i)->hasSupport(samlconstants::SAML20P_NS)) {
188             // Hash the ID.
189             m_sources.insert(sitemap_t::value_type(SecurityHelper::doHash("SHA1", id.get(), strlen(id.get())),site));
190         }
191     }
192 }
193
194 void AbstractMetadataProvider::indexGroup(EntitiesDescriptor* group, time_t& validUntil) const
195 {
196     // If child expires later than input, reset child, otherwise lower input to match.
197     if (validUntil < group->getValidUntilEpoch())
198         group->setValidUntil(validUntil);
199     else
200         validUntil = group->getValidUntilEpoch();
201
202     auto_ptr_char name(group->getName());
203     if (name.get()) {
204         m_groups.insert(groupmap_t::value_type(name.get(),group));
205     }
206     
207     // Track the smallest validUntil amongst the children.
208     time_t minValidUntil = validUntil;
209
210     const vector<EntitiesDescriptor*>& groups = const_cast<const EntitiesDescriptor*>(group)->getEntitiesDescriptors();
211     for (vector<EntitiesDescriptor*>::const_iterator i = groups.begin(); i != groups.end(); i++) {
212         // Use the current validUntil fence for each child, but track the smallest we find.
213         time_t subValidUntil = validUntil;
214         indexGroup(*i, subValidUntil);
215         if (subValidUntil < minValidUntil)
216             minValidUntil = subValidUntil;
217     }
218
219     const vector<EntityDescriptor*>& sites = const_cast<const EntitiesDescriptor*>(group)->getEntityDescriptors();
220     for (vector<EntityDescriptor*>::const_iterator j = sites.begin(); j != sites.end(); j++) {
221         // Use the current validUntil fence for each child, but track the smallest we find.
222         time_t subValidUntil = validUntil;
223         indexEntity(*j, subValidUntil);
224         if (subValidUntil < minValidUntil)
225             minValidUntil = subValidUntil;
226     }
227
228     // Pass back up the smallest child we found.
229     if (minValidUntil < validUntil)
230         validUntil = minValidUntil;
231 }
232
233 void AbstractMetadataProvider::index(EntityDescriptor* site, time_t validUntil, bool replace) const
234 {
235     indexEntity(site, validUntil, replace);
236 }
237
238 void AbstractMetadataProvider::index(EntitiesDescriptor* group, time_t validUntil) const
239 {
240     indexGroup(group, validUntil);
241 }
242
243 void AbstractMetadataProvider::clearDescriptorIndex(bool freeSites)
244 {
245     if (freeSites)
246         for_each(m_sites.begin(), m_sites.end(), cleanup_const_pair<string,EntityDescriptor>());
247     m_sites.clear();
248     m_groups.clear();
249     m_sources.clear();
250 }
251
252 const EntitiesDescriptor* AbstractMetadataProvider::getEntitiesDescriptor(const char* name, bool strict) const
253 {
254     pair<groupmap_t::const_iterator,groupmap_t::const_iterator> range=const_cast<const groupmap_t&>(m_groups).equal_range(name);
255
256     time_t now=time(nullptr);
257     for (groupmap_t::const_iterator i=range.first; i!=range.second; i++)
258         if (now < i->second->getValidUntilEpoch())
259             return i->second;
260     
261     if (range.first != range.second) {
262         Category& log = Category::getInstance(SAML_LOGCAT ".MetadataProvider");
263         if (strict) {
264             log.warn("ignored expired metadata group (%s)", range.first->first.c_str());
265         }
266         else {
267             log.info("no valid metadata found, returning expired metadata group (%s)", range.first->first.c_str());
268             return range.first->second;
269         }
270     }
271
272     return nullptr;
273 }
274
275 pair<const EntityDescriptor*,const RoleDescriptor*> AbstractMetadataProvider::getEntityDescriptor(const Criteria& criteria) const
276 {
277     pair<sitemap_t::const_iterator,sitemap_t::const_iterator> range;
278     if (criteria.entityID_ascii)
279         range = const_cast<const sitemap_t&>(m_sites).equal_range(criteria.entityID_ascii);
280     else if (criteria.entityID_unicode) {
281         auto_ptr_char id(criteria.entityID_unicode);
282         range = const_cast<const sitemap_t&>(m_sites).equal_range(id.get());
283     }
284     else if (criteria.artifact)
285         range = const_cast<const sitemap_t&>(m_sources).equal_range(criteria.artifact->getSource());
286     else
287         return pair<const EntityDescriptor*,const RoleDescriptor*>(nullptr,nullptr);
288     
289     pair<const EntityDescriptor*,const RoleDescriptor*> result;
290     result.first = nullptr;
291     result.second = nullptr;
292     
293     time_t now=time(nullptr);
294     for (sitemap_t::const_iterator i=range.first; i!=range.second; i++) {
295         if (now < i->second->getValidUntilEpoch()) {
296             result.first = i->second;
297             break;
298         }
299     }
300     
301     if (!result.first && range.first!=range.second) {
302         Category& log = Category::getInstance(SAML_LOGCAT ".MetadataProvider");
303         if (criteria.validOnly) {
304             log.warn("ignored expired metadata instance for (%s)", range.first->first.c_str());
305         }
306         else {
307             log.info("no valid metadata found, returning expired instance for (%s)", range.first->first.c_str());
308             result.first = range.first->second;
309         }
310     }
311
312     if (result.first && criteria.role) {
313         result.second = result.first->getRoleDescriptor(*criteria.role, criteria.protocol);
314         if (!result.second && criteria.protocol2)
315             result.second = result.first->getRoleDescriptor(*criteria.role, criteria.protocol2);
316     }
317     
318     return result;
319 }
320
321 const Credential* AbstractMetadataProvider::resolve(const CredentialCriteria* criteria) const
322 {
323     const MetadataCredentialCriteria* metacrit = dynamic_cast<const MetadataCredentialCriteria*>(criteria);
324     if (!metacrit)
325         throw MetadataException("Cannot resolve credentials without a MetadataCredentialCriteria object.");
326
327     Lock lock(m_credentialLock);
328     const credmap_t::mapped_type& creds = resolveCredentials(metacrit->getRole());
329
330     for (credmap_t::mapped_type::const_iterator c = creds.begin(); c!=creds.end(); ++c)
331         if (metacrit->matches(*(*c)))
332         return *c;
333 return nullptr;
334 }
335
336 vector<const Credential*>::size_type AbstractMetadataProvider::resolve(
337     vector<const Credential*>& results, const CredentialCriteria* criteria
338     ) const
339 {
340     const MetadataCredentialCriteria* metacrit = dynamic_cast<const MetadataCredentialCriteria*>(criteria);
341     if (!metacrit)
342         throw MetadataException("Cannot resolve credentials without a MetadataCredentialCriteria object.");
343
344     Lock lock(m_credentialLock);
345     const credmap_t::mapped_type& creds = resolveCredentials(metacrit->getRole());
346
347    for (credmap_t::mapped_type::const_iterator c = creds.begin(); c!=creds.end(); ++c)
348         if (metacrit->matches(*(*c)))
349             results.push_back(*c); 
350     return results.size();
351 }
352
353 const AbstractMetadataProvider::credmap_t::mapped_type& AbstractMetadataProvider::resolveCredentials(const RoleDescriptor& role) const
354 {
355     credmap_t::const_iterator i = m_credentialMap.find(&role);
356     if (i != m_credentialMap.end())
357         return i->second;
358
359     const KeyInfoResolver* resolver = m_resolver ? m_resolver : XMLToolingConfig::getConfig().getKeyInfoResolver();
360     const vector<KeyDescriptor*>& keys = role.getKeyDescriptors();
361     AbstractMetadataProvider::credmap_t::mapped_type& resolved = m_credentialMap[&role];
362     for (indirect_iterator<vector<KeyDescriptor*>::const_iterator> k = make_indirect_iterator(keys.begin());
363             k != make_indirect_iterator(keys.end()); ++k) {
364         if (k->getKeyInfo()) {
365             auto_ptr<MetadataCredentialContext> mcc(new MetadataCredentialContext(*k));
366             auto_ptr<Credential> c(resolver->resolve(mcc.get()));
367             if (c.get()) {
368                 mcc.release();  // this API sucks, the object is now owned by the Credential
369                 resolved.push_back(c.get());
370                 c.release();
371             }
372         }
373     }
374     return resolved;
375 }