113e1a3b39a5c5175a5b0572b07be7c50e8be048
[shibboleth/cpp-opensaml.git] / saml / saml2 / metadata / impl / FilesystemMetadataProvider.cpp
1 /*
2  *  Copyright 2001-2006 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  * FilesystemMetadataProvider.cpp
19  * 
20  * Supplies metadata from a local file, detecting and reloading changes.
21  */
22
23 #include "internal.h"
24 #include "saml2/metadata/MetadataProvider.h"
25
26 #include <ctime>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <log4cpp/Category.hh>
30 #include <xercesc/framework/LocalFileInputSource.hpp>
31 #include <xercesc/framework/Wrapper4InputSource.hpp>
32 #include <xmltooling/util/NDC.h>
33 #include <xmltooling/util/Threads.h>
34
35 using namespace opensaml::saml2md;
36 using namespace xmltooling;
37 using namespace log4cpp;
38 using namespace std;
39
40 namespace opensaml {
41     namespace saml2md {
42         
43         
44         class SAML_DLLLOCAL FilesystemMetadataProvider : public MetadataProvider
45         {
46         public:
47             FilesystemMetadataProvider(const DOMElement* e);
48             ~FilesystemMetadataProvider();
49
50             Lockable* lock();
51             void unlock() {
52                 if (m_lock)
53                     m_lock->unlock();
54             }
55
56             void init();
57
58             const EntityDescriptor* getEntityDescriptor(const XMLCh* id, bool requireValidMetadata=true) const;
59             const EntityDescriptor* getEntityDescriptor(const char* id, bool requireValidMetadata=true) const;
60             const EntitiesDescriptor* getEntitiesDescriptor(const XMLCh* name, bool requireValidMetadata=true) const;
61             const EntitiesDescriptor* getEntitiesDescriptor(const char* name, bool requireValidMetadata=true) const;
62             const XMLObject* getMetadata() const {
63                 return m_object;
64             }
65
66         private:
67             XMLObject* load() const;
68             void index();
69             void index(EntityDescriptor* site, time_t validUntil=LLONG_MAX);
70             void index(EntitiesDescriptor* group, time_t validUntil=LLONG_MAX);
71         
72             // index of loaded metadata
73             typedef multimap<string,const EntityDescriptor*> sitemap_t;
74             typedef multimap<string,const EntitiesDescriptor*> groupmap_t;
75             sitemap_t m_sites;
76             sitemap_t m_sources;
77             groupmap_t m_groups;
78
79             const DOMElement* m_root; // survives only until init() method is done
80             std::string m_source;
81             time_t m_filestamp;
82             bool m_validate;
83             RWLock* m_lock;
84             XMLObject* m_object;
85         }; 
86
87         MetadataProvider* SAML_DLLLOCAL FilesystemMetadataProviderFactory(const DOMElement* const & e)
88         {
89             return new FilesystemMetadataProvider(e);
90         }
91
92     };
93 };
94
95 static const XMLCh uri[] =      UNICODE_LITERAL_3(u,r,i);
96 static const XMLCh url[] =      UNICODE_LITERAL_3(u,r,l);
97 static const XMLCh path[] =     UNICODE_LITERAL_4(p,a,t,h);
98 static const XMLCh pathname[] = UNICODE_LITERAL_8(p,a,t,h,n,a,m,e);
99 static const XMLCh file[] =     UNICODE_LITERAL_4(f,i,l,e);
100 static const XMLCh filename[] = UNICODE_LITERAL_8(f,i,l,e,n,a,m,e);
101 static const XMLCh validate[] = UNICODE_LITERAL_8(v,a,l,i,d,a,t,e);
102
103 FilesystemMetadataProvider::FilesystemMetadataProvider(const DOMElement* e)
104     : MetadataProvider(e), m_root(e), m_filestamp(0), m_validate(false), m_lock(NULL), m_object(NULL)
105 {
106 #ifdef _DEBUG
107     NDC ndc("FilesystemMetadataProvider");
108 #endif
109     Category& log=Category::getInstance(SAML_LOGCAT".Metadata");
110
111     // Establish source of data...
112     const XMLCh* source=e->getAttributeNS(NULL,uri);
113     if (!source || !*source) {
114         source=e->getAttributeNS(NULL,url);
115         if (!source || !*source) {
116             source=e->getAttributeNS(NULL,path);
117             if (!source || !*source) {
118                 source=e->getAttributeNS(NULL,pathname);
119                 if (!source || !*source) {
120                     source=e->getAttributeNS(NULL,file);
121                     if (!source || !*source) {
122                         source=e->getAttributeNS(NULL,filename);
123                     }
124                 }
125             }
126         }
127     }
128     
129     if (source && *source) {
130         const XMLCh* valflag=e->getAttributeNS(NULL,validate);
131         m_validate=(XMLString::equals(valflag,XMLConstants::XML_TRUE) || XMLString::equals(valflag,XMLConstants::XML_ONE));
132         
133         auto_ptr_char temp(source);
134         m_source=temp.get();
135         log.debug("using external metadata file (%s)", temp.get());
136
137 #ifdef WIN32
138         struct _stat stat_buf;
139         if (_stat(m_source.c_str(), &stat_buf) == 0)
140 #else
141         struct stat stat_buf;
142         if (stat(m_source.c_str(), &stat_buf) == 0)
143 #endif
144             m_filestamp=stat_buf.st_mtime;
145         m_lock=RWLock::create();
146     }
147     else
148         log.debug("no file path/name supplied, will look for metadata inline");
149 }
150
151 FilesystemMetadataProvider::~FilesystemMetadataProvider()
152 {
153     delete m_lock;
154     delete m_object;
155 }
156
157 void FilesystemMetadataProvider::init()
158 {
159     m_object=load();
160     index();
161 }
162
163 XMLObject* FilesystemMetadataProvider::load() const
164 {
165 #ifdef _DEBUG
166     NDC ndc("load");
167 #endif
168     Category& log=Category::getInstance(SAML_LOGCAT".Metadata");
169     
170     try {
171         XMLObject* xmlObject=NULL;
172         
173         if (!m_source.empty()) {
174             // Data comes from a file we have to parse.
175             log.debug("loading metadata from file...");
176             auto_ptr_XMLCh widenit(m_source.c_str());
177             LocalFileInputSource src(widenit.get());
178             Wrapper4InputSource dsrc(&src,false);
179             DOMDocument* doc=NULL;
180             if (m_validate)
181                 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
182             else
183                 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
184             XercesJanitor<DOMDocument> docjanitor(doc);
185             log.infoStream() << "loaded and parsed XML file (" << m_source << ")" << CategoryStream::ENDLINE;
186             
187             // Unmarshall objects, binding the document.
188             xmlObject = XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true);
189             docjanitor.release();
190         }
191         else {
192             // Data comes from the DOM we were handed.
193             log.debug("loading inline metadata...");
194             DOMElement* child = XMLHelper::getFirstChildElement(m_root);
195             if (!child)
196                 throw XMLToolingException("No metadata was found inline.");
197             xmlObject = XMLObjectBuilder::buildOneFromElement(child);
198         }
199         
200         auto_ptr<XMLObject> xmlObjectPtr(xmlObject);
201         
202         doFilters(*xmlObject);
203         
204         xmlObjectPtr->releaseThisAndChildrenDOM();
205         xmlObjectPtr->setDocument(NULL);
206         return xmlObjectPtr.release();
207     }
208     catch (XMLException& e) {
209         auto_ptr_char msg(e.getMessage());
210         log.errorStream() << "Xerces parser error while loading metadata from ("
211             << (m_source.empty() ? "inline" : m_source) << "): " << msg.get() << CategoryStream::ENDLINE;
212         throw XMLParserException(msg.get());
213     }
214     catch (XMLToolingException& e) {
215         log.errorStream() << "error while loading metadata from ("
216             << (m_source.empty() ? "inline" : m_source) << "): " << e.what() << CategoryStream::ENDLINE;
217         throw;
218     }
219 }
220
221 Lockable* FilesystemMetadataProvider::lock()
222 {
223     if (!m_lock)
224         return this;
225         
226     m_lock->rdlock();
227
228     // Check if we need to refresh.
229 #ifdef WIN32
230     struct _stat stat_buf;
231     if (_stat(m_source.c_str(), &stat_buf) == 0)
232 #else
233     struct stat stat_buf;
234     if (stat(m_source.c_str(), &stat_buf) == 0)
235 #endif
236     {
237         if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime) {
238             // Elevate lock and recheck.
239             m_lock->unlock();
240             m_lock->wrlock();
241             if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime) {
242                 SharedLock lockwrap(m_lock,false);  // pops write lock
243                 try {
244                     // Update the timestamp regardless. No point in repeatedly trying.
245                     m_filestamp=stat_buf.st_mtime;
246                     XMLObject* newstuff = load();
247                     delete m_object;
248                     m_object = newstuff;
249                     index();
250                 }
251                 catch(XMLToolingException& e) {
252                     Category::getInstance(SAML_LOGCAT".Metadata").error("failed to reload metadata from file, sticking with what we have: %s", e.what());
253                 }
254             }
255             else {
256                 m_lock->unlock();
257             }
258             m_lock->rdlock();
259         }
260     }
261     return this;
262 }
263
264 void FilesystemMetadataProvider::index()
265 {
266     m_sources.clear();
267     m_sites.clear();
268     m_groups.clear();
269     
270     EntitiesDescriptor* group=dynamic_cast<EntitiesDescriptor*>(m_object);
271     if (group) {
272         index(group);
273         return;
274     }
275     EntityDescriptor* site=dynamic_cast<EntityDescriptor*>(m_object);
276     index(site);
277 }
278
279 void FilesystemMetadataProvider::index(EntityDescriptor* site, time_t validUntil)
280 {
281     if (validUntil < site->getValidUntilEpoch())
282         site->setValidUntil(validUntil);
283
284     auto_ptr_char id(site->getEntityID());
285     if (id.get()) {
286         m_sites.insert(make_pair(id.get(),site));
287     }
288 }
289
290 void FilesystemMetadataProvider::index(EntitiesDescriptor* group, time_t validUntil)
291 {
292     if (validUntil < group->getValidUntilEpoch())
293         group->setValidUntil(validUntil);
294
295     auto_ptr_char name(group->getName());
296     if (name.get()) {
297         m_groups.insert(make_pair(name.get(),group));
298     }
299     
300     const vector<EntitiesDescriptor*>& groups=const_cast<const EntitiesDescriptor*>(group)->getEntitiesDescriptors();
301     for (vector<EntitiesDescriptor*>::const_iterator i=groups.begin(); i!=groups.end(); i++)
302         index(*i,group->getValidUntilEpoch());
303
304     const vector<EntityDescriptor*>& sites=const_cast<const EntitiesDescriptor*>(group)->getEntityDescriptors();
305     for (vector<EntityDescriptor*>::const_iterator j=sites.begin(); j!=sites.end(); j++)
306         index(*j,group->getValidUntilEpoch());
307 }
308
309 const EntitiesDescriptor* FilesystemMetadataProvider::getEntitiesDescriptor(const char* name, bool strict) const
310 {
311     pair<groupmap_t::const_iterator,groupmap_t::const_iterator> range=m_groups.equal_range(name);
312
313     time_t now=time(NULL);
314     for (groupmap_t::const_iterator i=range.first; i!=range.second; i++)
315         if (now < i->second->getValidUntilEpoch())
316             return i->second;
317     
318     if (!strict && range.first!=range.second)
319         return range.first->second;
320         
321     return NULL;
322 }
323
324 const EntitiesDescriptor* FilesystemMetadataProvider::getEntitiesDescriptor(const XMLCh* name, bool strict) const
325 {
326     auto_ptr_char temp(name);
327     return getEntitiesDescriptor(temp.get(),strict);
328 }
329
330 const EntityDescriptor* FilesystemMetadataProvider::getEntityDescriptor(const char* name, bool strict) const
331 {
332     pair<sitemap_t::const_iterator,sitemap_t::const_iterator> range=m_sites.equal_range(name);
333
334     time_t now=time(NULL);
335     for (sitemap_t::const_iterator i=range.first; i!=range.second; i++)
336         if (now < i->second->getValidUntilEpoch())
337             return i->second;
338     
339     if (!strict && range.first!=range.second)
340         return range.first->second;
341         
342     return NULL;
343 }
344
345 const EntityDescriptor* FilesystemMetadataProvider::getEntityDescriptor(const XMLCh* name, bool strict) const
346 {
347     auto_ptr_char temp(name);
348     return getEntityDescriptor(temp.get(),strict);
349 }