3026a3219934a67317b0b8bcc1f48e275eddf402
[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     : 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         if (m_filter) {
203             log.info("applying metadata filter (%s)", m_filter->getId());
204             m_filter->doFilter(*xmlObject);
205         }
206         
207         xmlObjectPtr->releaseThisAndChildrenDOM();
208         xmlObjectPtr->setDocument(NULL);
209         return xmlObjectPtr.release();
210     }
211     catch (XMLException& e) {
212         auto_ptr_char msg(e.getMessage());
213         log.errorStream() << "Xerces parser error while loading metadata from ("
214             << (m_source.empty() ? "inline" : m_source) << "): " << msg.get() << CategoryStream::ENDLINE;
215         throw XMLParserException(msg.get());
216     }
217     catch (XMLToolingException& e) {
218         log.errorStream() << "error while loading metadata from ("
219             << (m_source.empty() ? "inline" : m_source) << "): " << e.what() << CategoryStream::ENDLINE;
220         throw;
221     }
222 }
223
224 Lockable* FilesystemMetadataProvider::lock()
225 {
226     if (!m_lock)
227         return this;
228         
229     m_lock->rdlock();
230
231     // Check if we need to refresh.
232 #ifdef WIN32
233     struct _stat stat_buf;
234     if (_stat(m_source.c_str(), &stat_buf) == 0)
235 #else
236     struct stat stat_buf;
237     if (stat(m_source.c_str(), &stat_buf) == 0)
238 #endif
239     {
240         if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime) {
241             // Elevate lock and recheck.
242             m_lock->unlock();
243             m_lock->wrlock();
244             if (m_filestamp>0 && m_filestamp<stat_buf.st_mtime) {
245                 SharedLock lockwrap(m_lock,false);  // pops write lock
246                 try {
247                     // Update the timestamp regardless. No point in repeatedly trying.
248                     m_filestamp=stat_buf.st_mtime;
249                     XMLObject* newstuff = load();
250                     delete m_object;
251                     m_object = newstuff;
252                     index();
253                 }
254                 catch(XMLToolingException& e) {
255                     Category::getInstance(SAML_LOGCAT".Metadata").error("failed to reload metadata from file, sticking with what we have: %s", e.what());
256                 }
257             }
258             else {
259                 m_lock->unlock();
260             }
261             m_lock->rdlock();
262         }
263     }
264     return this;
265 }
266
267 void FilesystemMetadataProvider::index()
268 {
269     m_sources.clear();
270     m_sites.clear();
271     m_groups.clear();
272     
273     EntitiesDescriptor* group=dynamic_cast<EntitiesDescriptor*>(m_object);
274     if (group) {
275         index(group);
276         return;
277     }
278     EntityDescriptor* site=dynamic_cast<EntityDescriptor*>(m_object);
279     index(site);
280 }
281
282 void FilesystemMetadataProvider::index(EntityDescriptor* site, time_t validUntil)
283 {
284     if (validUntil < site->getValidUntilEpoch())
285         site->setValidUntil(validUntil);
286
287     auto_ptr_char id(site->getEntityID());
288     if (id.get()) {
289         m_sites.insert(make_pair(id.get(),site));
290     }
291 }
292
293 void FilesystemMetadataProvider::index(EntitiesDescriptor* group, time_t validUntil)
294 {
295     if (validUntil < group->getValidUntilEpoch())
296         group->setValidUntil(validUntil);
297
298     auto_ptr_char name(group->getName());
299     if (name.get()) {
300         m_groups.insert(make_pair(name.get(),group));
301     }
302     
303     const vector<EntitiesDescriptor*>& groups=const_cast<const EntitiesDescriptor*>(group)->getEntitiesDescriptors();
304     for (vector<EntitiesDescriptor*>::const_iterator i=groups.begin(); i!=groups.end(); i++)
305         index(*i,group->getValidUntilEpoch());
306
307     const vector<EntityDescriptor*>& sites=const_cast<const EntitiesDescriptor*>(group)->getEntityDescriptors();
308     for (vector<EntityDescriptor*>::const_iterator j=sites.begin(); j!=sites.end(); j++)
309         index(*j,group->getValidUntilEpoch());
310 }
311
312 const EntitiesDescriptor* FilesystemMetadataProvider::getEntitiesDescriptor(const char* name, bool strict) const
313 {
314     pair<groupmap_t::const_iterator,groupmap_t::const_iterator> range=m_groups.equal_range(name);
315
316     time_t now=time(NULL);
317     for (groupmap_t::const_iterator i=range.first; i!=range.second; i++)
318         if (now < i->second->getValidUntilEpoch())
319             return i->second;
320     
321     if (!strict && range.first!=range.second)
322         return range.first->second;
323         
324     return NULL;
325 }
326
327 const EntitiesDescriptor* FilesystemMetadataProvider::getEntitiesDescriptor(const XMLCh* name, bool strict) const
328 {
329     auto_ptr_char temp(name);
330     return getEntitiesDescriptor(temp.get(),strict);
331 }
332
333 const EntityDescriptor* FilesystemMetadataProvider::getEntityDescriptor(const char* name, bool strict) const
334 {
335     pair<sitemap_t::const_iterator,sitemap_t::const_iterator> range=m_sites.equal_range(name);
336
337     time_t now=time(NULL);
338     for (sitemap_t::const_iterator i=range.first; i!=range.second; i++)
339         if (now < i->second->getValidUntilEpoch())
340             return i->second;
341     
342     if (!strict && range.first!=range.second)
343         return range.first->second;
344         
345     return NULL;
346 }
347
348 const EntityDescriptor* FilesystemMetadataProvider::getEntityDescriptor(const XMLCh* name, bool strict) const
349 {
350     auto_ptr_char temp(name);
351     return getEntityDescriptor(temp.get(),strict);
352 }