Code drop of new target..
[shibboleth/sp.git] / shib-target / shib-ccache.cpp
1 /*
2  * The Shibboleth License, Version 1.
3  * Copyright (c) 2002
4  * University Corporation for Advanced Internet Development, Inc.
5  * All rights reserved
6  *
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  * Redistributions of source code must retain the above copyright notice, this
12  * list of conditions and the following disclaimer.
13  *
14  * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution, if any, must include
17  * the following acknowledgment: "This product includes software developed by
18  * the University Corporation for Advanced Internet Development
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20  * may appear in the software itself, if and wherever such third-party
21  * acknowledgments normally appear.
22  *
23  * Neither the name of Shibboleth nor the names of its contributors, nor
24  * Internet2, nor the University Corporation for Advanced Internet Development,
25  * Inc., nor UCAID may be used to endorse or promote products derived from this
26  * software without specific prior written permission. For written permission,
27  * please contact shibboleth@shibboleth.org
28  *
29  * Products derived from this software may not be called Shibboleth, Internet2,
30  * UCAID, or the University Corporation for Advanced Internet Development, nor
31  * may Shibboleth appear in their name, without prior written permission of the
32  * University Corporation for Advanced Internet Development.
33  *
34  *
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50
51 /*
52  * shib-ccache.cpp -- SHAR Credential Cache
53  *
54  * Originally from mod_shib
55  * Modified by: Derek Atkins <derek@ihtfp.com>
56  *
57  * $Id$
58  */
59
60 #include <unistd.h>
61
62 #include "shib-target.h"
63
64 #include <xercesc/util/Base64.hpp>
65 #include <log4cpp/Category.hh>
66
67 #include <strstream>
68 #include <stdexcept>
69
70 using namespace std;
71 using namespace saml;
72 using namespace shibboleth;
73 using namespace shibtarget;
74
75 class ResourceEntry
76 {
77 public:
78   ResourceEntry(SAMLResponse* response);
79   ~ResourceEntry();
80
81   bool isAssertionValid();
82   Iterator<SAMLAttribute*> getAttributes();
83   const char* getSerializedAssertion();
84
85   static vector<SAMLAttribute*> g_emptyVector;
86
87 private:
88   SAMLResponse* m_response;
89   SAMLAssertion* m_assertion;
90   char* m_serialized;
91
92   log4cpp::Category* log;
93 };
94
95 class InternalCCache;
96 class InternalCCacheEntry : public CCacheEntry
97 {
98 public:
99   InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
100   virtual ~InternalCCacheEntry();
101
102   virtual Iterator<SAMLAttribute*> getAttributes(const char* resource_url);
103   virtual const char* getSerializedAssertion(const char* resource_url);
104   virtual bool isSessionValid(time_t lifetime, time_t timeout);
105   virtual const char* getClientAddress() { return m_clientAddress.c_str(); }
106
107   virtual void setCache(CCache *cache);
108
109 private:
110   ResourceEntry* populate(const char* resource_url);
111   ResourceEntry* find(const char* resource_url);
112   void insert(const char* resource_url, ResourceEntry* entry);
113   void remove(const char* resource_url);
114
115   string m_originSite;
116   string m_handle;
117   string m_clientAddress;
118   time_t m_sessionCreated;
119   time_t m_lastAccess;
120   bool m_hasbinding;
121
122   const SAMLSubject* m_subject;
123   SAMLAuthenticationStatement* p_auth;
124   CCache *m_cache;
125
126   map<string,ResourceEntry*> m_resources;
127
128   static saml::QName g_authorityKind;
129   static saml::QName g_respondWith;
130
131   log4cpp::Category* log;
132 };
133
134 class InternalCCache : public CCache
135 {
136 public:
137   InternalCCache();
138   virtual ~InternalCCache();
139
140   virtual SAMLBinding* getBinding(const XMLCh* bindingProt);
141   virtual CCacheEntry* find(const char* key);
142   virtual void insert(const char* key, CCacheEntry* entry);
143   virtual void remove(const char* key);
144
145 private:
146   SAMLBinding* m_SAMLBinding;
147   map<string,CCacheEntry*> m_hashtable;
148
149   log4cpp::Category* log;
150 };
151
152 // Global Constructors & Destructors
153 CCache::~CCache() {}
154 CCacheEntry::~CCacheEntry() {}
155
156 CCache* CCache::getInstance()
157 {
158   return (CCache*) new InternalCCache();
159 }
160
161 CCacheEntry* CCacheEntry::getInstance(saml::SAMLAuthenticationStatement *s,
162                                       const char *client_addr)
163 {
164   return (CCacheEntry*) new InternalCCacheEntry(s, client_addr);
165 }
166
167 void CCache::setCache(CCacheEntry* entry)
168 {
169   entry->setCache(this);
170 }
171
172 // static members
173 saml::QName InternalCCacheEntry::g_authorityKind(saml::XML::SAMLP_NS,L(AttributeQuery));
174 saml::QName InternalCCacheEntry::g_respondWith(saml::XML::SAML_NS,L(AttributeStatement));
175 vector<SAMLAttribute*> ResourceEntry::g_emptyVector;
176
177
178 /******************************************************************************/
179 /* InternalCCache:  A Credential Cache                                        */
180 /******************************************************************************/
181
182 InternalCCache::InternalCCache()
183 {
184   m_SAMLBinding=SAMLBindingFactory::getInstance();
185   string ctx="shibtarget.InternalCCache";
186   log = &(log4cpp::Category::getInstance(ctx));
187 }
188
189 InternalCCache::~InternalCCache()
190 {
191   delete m_SAMLBinding;
192   for (map<string,CCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
193     delete i->second;
194 }
195
196 SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
197 {
198   log->debug("looking for binding...");
199   if (!XMLString::compareString(bindingProt,SAMLBinding::SAML_SOAP_HTTPS)) {
200     log->debug("https binding found");
201     return m_SAMLBinding;
202   }
203   return NULL;
204 }
205
206 CCacheEntry* InternalCCache::find(const char* key)
207 {
208   log->debug("Find: \"%s\"", key);
209   map<string,CCacheEntry*>::const_iterator i=m_hashtable.find(key);
210   if (i==m_hashtable.end()) {
211     log->debug("No Match found");
212     return NULL;
213   }
214   log->debug("Match Found.");
215   return i->second;
216 }
217
218 void InternalCCache::insert(const char* key, CCacheEntry* entry)
219 {
220   log->debug("caching new entry for \"%s\"", key);
221   m_hashtable[key]=entry;
222   setCache(entry);
223 }
224
225 void InternalCCache::remove(const char* key)
226 {
227   log->debug("removing cache entry \"key\"", key);
228   m_hashtable.erase(key);
229 }
230
231 /******************************************************************************/
232 /* InternalCCacheEntry:  A Credential Cache Entry                             */
233 /******************************************************************************/
234
235 InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
236   : m_hasbinding(false)
237 {
238   string ctx = "shibtarget::InternalCCacheEntry";
239   log = &(log4cpp::Category::getInstance(ctx));
240
241   if (s == NULL) {
242     log->error("NULL auth statement");
243     throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
244   }
245
246   m_subject = s->getSubject();
247
248   xstring name = m_subject->getName();
249   xstring qual = m_subject->getNameQualifier();
250
251   auto_ptr<char> h(XMLString::transcode(name.c_str()));
252   auto_ptr<char> d(XMLString::transcode(qual.c_str()));
253
254   m_handle = h.get();
255   m_originSite = d.get();
256
257   Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
258   if (bindings.hasNext())
259     m_hasbinding = true;
260
261   m_clientAddress = client_addr;
262   m_sessionCreated = m_lastAccess = time(NULL);
263
264   // Save for later.
265   p_auth = s;
266
267   log->info("New Session Created...");
268   log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
269              client_addr);
270 }
271
272 InternalCCacheEntry::~InternalCCacheEntry()
273 {
274   log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
275   delete p_auth;
276   for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
277        i!=m_resources.end(); i++)
278     delete i->second;
279 }
280
281 bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
282 {
283   saml::NDC("isSessionValid");
284   log->debug("test session %s@%s, (lifetime=%ld, timeout=%ld)",
285              m_handle.c_str(), m_originSite.c_str(), lifetime, timeout);
286   time_t now=time(NULL);
287   if (lifetime > 0 && now > m_sessionCreated+lifetime) {
288     log->debug("session beyond lifetime");
289     return false;
290   }
291   if (timeout > 0 && now-m_lastAccess >= timeout) {
292     log->debug("session timed out");
293     return false;
294   }
295   m_lastAccess=now;
296   return true;
297 }
298
299 void InternalCCacheEntry::setCache(CCache *cache)
300 {
301   m_cache = cache;
302 }
303
304 Iterator<SAMLAttribute*> InternalCCacheEntry::getAttributes(const char* resource_url)
305 {
306   saml::NDC("getAttributes");
307   ResourceEntry* entry = populate(resource_url);
308   if (entry)
309     return entry->getAttributes();
310   return Iterator<SAMLAttribute*>(ResourceEntry::g_emptyVector);
311 }
312
313 const char* InternalCCacheEntry::getSerializedAssertion(const char* resource_url)
314 {
315   saml::NDC("getSerializedAssertion");
316   ResourceEntry* entry = populate(resource_url);
317   if (entry)
318     return entry->getSerializedAssertion();
319   return NULL;
320 }
321
322 ResourceEntry* InternalCCacheEntry::populate(const char* resource_url)
323 {
324   saml::NDC("populate");
325   log->debug("populating entry for %s", resource_url);
326
327   // Can we use what we have?
328   ResourceEntry *entry = find(resource_url);
329   if (entry) {
330     log->debug("found resource");
331     if (entry->isAssertionValid())
332       return entry;
333
334     // entry is invalid (expired) -- go fetch a new one.
335     log->debug("removing resource cache; assertion is invalid");
336     remove (resource_url);
337     delete entry;
338   }
339
340   // Nope entry.. Create a new resource entry
341
342   if (!m_hasbinding) {
343     log->error("No binding!");
344     return NULL;
345   }
346
347   log->info("trying to request attributes for %s@%s -> %s",
348             m_handle.c_str(), m_originSite.c_str(), resource_url);
349
350   auto_ptr<XMLCh> resource(XMLString::transcode(resource_url));
351   Iterator<saml::QName> respond_withs = ArrayIterator<saml::QName>(&g_respondWith);
352
353   // Clone the subject...
354   // 1) I know the static_cast is safe from clone()
355   // 2) the AttributeQuery will destroy this new subject.
356   SAMLSubject* subject=static_cast<SAMLSubject*>(m_subject->clone());
357
358   // Build a SAML Request....
359   SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resource.get());
360   SAMLRequest* req=new SAMLRequest(q,respond_withs);
361
362   // Try this request against all the bindings in the AuthenticationStatement
363   // (i.e. send it to each AA in the list of bindings)
364   Iterator<SAMLAuthorityBinding*> bindings = p_auth->getBindings();
365   SAMLResponse* response = NULL;
366
367   while (!response && bindings.hasNext()) {
368     SAMLAuthorityBinding* binding = bindings.next();
369
370     log->debug("Trying binding...");
371     SAMLBinding* pBinding=m_cache->getBinding(binding->getBinding());
372     log->debug("Sending request");
373     response=pBinding->send(*binding,*req);
374   }
375
376   // ok, we can delete the request now.
377   delete req;
378
379   // Make sure we got a response
380   if (!response) {
381     log->info ("No Response");
382     return NULL;
383   }
384
385   entry = new ResourceEntry(response);
386   insert (resource_url, entry);
387
388   log->info("fetched and stored SAML response");
389   return entry;
390 }
391
392 ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
393 {
394   log->debug("find: %s", resource_url);
395   map<string,ResourceEntry*>::const_iterator i=m_resources.find(resource_url);
396   if (i==m_resources.end()) {
397     log->debug("no match found");
398     return NULL;
399   }
400   log->debug("match found");
401   return i->second;
402 }
403
404 void InternalCCacheEntry::insert(const char* resource_url, ResourceEntry* entry)
405 {
406   log->debug("inserting %s", resource_url);
407   m_resources[resource_url]=entry;
408 }
409
410 void InternalCCacheEntry::remove(const char* resource_url)
411 {
412   log->debug("removing %s", resource_url);
413   m_resources.erase(resource_url);
414 }
415
416 /******************************************************************************/
417 /* ResourceEntry:  A Credential Cache Entry for a particular Resource URL     */
418 /******************************************************************************/
419
420 ResourceEntry::ResourceEntry(SAMLResponse* response)
421   : m_assertion(NULL), m_serialized(NULL)
422 {
423   string ctx = "shibtarget::ResourceEntry";
424   log = &(log4cpp::Category::getInstance(ctx));
425
426   log->info("caching resource entry");
427
428   m_response = response;
429
430   // Store off the assertion for quick access.
431   // Memory mgmt is based on the response pointer.
432   Iterator<SAMLAssertion*> i=m_response->getAssertions();
433   if (i.hasNext())
434     m_assertion=i.next();
435 }
436
437 ResourceEntry::~ResourceEntry()
438 {
439   delete m_response;
440   delete[] m_serialized;
441 }
442
443 Iterator<SAMLAttribute*> ResourceEntry::getAttributes()
444 {
445   saml::NDC("getAttributes");
446   if (m_assertion)
447     {
448       Iterator<SAMLStatement*> i=m_assertion->getStatements();
449       if (i.hasNext())
450         {
451           SAMLAttributeStatement* s=static_cast<SAMLAttributeStatement*>(i.next());
452           if (s)
453             return s->getAttributes();
454         }
455     }
456   return Iterator<SAMLAttribute*>(g_emptyVector);
457 }
458
459 const char* ResourceEntry::getSerializedAssertion()
460 {
461   saml::NDC("getSerializedAssertion");
462   if (m_serialized)
463     return m_serialized;
464   if (!m_assertion)
465     return NULL;
466   ostrstream os;
467   os << *m_assertion;
468   unsigned int outlen;
469   XMLByte* serialized=Base64::encode(reinterpret_cast<XMLByte*>(os.str()),os.pcount(),&outlen);
470   return m_serialized=(char*)serialized;
471 }
472
473 bool ResourceEntry::isAssertionValid()
474 {
475   saml::NDC("isAssertionValid");
476
477   log->info("checking validity");
478   if (m_assertion && m_assertion->getNotOnOrAfter())
479   {
480     // This is awful, but the XMLDateTime class is truly horrible.
481     time_t now=time(NULL);
482 #ifdef WIN32
483     struct tm* ptime=gmtime(&now);
484 #else
485     struct tm res;
486     struct tm* ptime=gmtime_r(&now,&res);
487 #endif
488     char timebuf[32];
489     strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);
490     auto_ptr<XMLCh> timeptr(XMLString::transcode(timebuf));
491     XMLDateTime curDateTime(timeptr.get());
492     int result=XMLDateTime::compareOrder(&curDateTime,
493                                          m_assertion->getNotOnOrAfter());
494     if (result == XMLDateTime::LESS_THAN) {
495       log->debug("yes, still valid");
496       return true;
497     }
498   }
499
500   log->debug("not valid");
501   return false;
502 }