2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
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.
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
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.
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.
52 * shib-ccache.cpp -- SHAR Credential Cache
54 * Originally from mod_shib
55 * Modified by: Derek Atkins <derek@ihtfp.com>
62 #include "shib-target.h"
64 #include <xercesc/util/Base64.hpp>
65 #include <log4cpp/Category.hh>
72 using namespace shibboleth;
73 using namespace shibtarget;
78 ResourceEntry(SAMLResponse* response);
81 bool isAssertionValid();
82 Iterator<SAMLAttribute*> getAttributes();
83 const char* getSerializedAssertion();
85 static vector<SAMLAttribute*> g_emptyVector;
88 SAMLResponse* m_response;
89 SAMLAssertion* m_assertion;
92 log4cpp::Category* log;
96 class InternalCCacheEntry : public CCacheEntry
99 InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr);
100 virtual ~InternalCCacheEntry();
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(); }
107 virtual void setCache(CCache *cache);
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);
117 string m_clientAddress;
118 time_t m_sessionCreated;
122 const SAMLSubject* m_subject;
123 SAMLAuthenticationStatement* p_auth;
126 map<string,ResourceEntry*> m_resources;
128 static saml::QName g_authorityKind;
129 static saml::QName g_respondWith;
131 log4cpp::Category* log;
134 class InternalCCache : public CCache
138 virtual ~InternalCCache();
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);
146 SAMLBinding* m_SAMLBinding;
147 map<string,CCacheEntry*> m_hashtable;
149 log4cpp::Category* log;
152 // Global Constructors & Destructors
154 CCacheEntry::~CCacheEntry() {}
156 CCache* CCache::getInstance()
158 return (CCache*) new InternalCCache();
161 CCacheEntry* CCacheEntry::getInstance(saml::SAMLAuthenticationStatement *s,
162 const char *client_addr)
164 return (CCacheEntry*) new InternalCCacheEntry(s, client_addr);
167 void CCache::setCache(CCacheEntry* entry)
169 entry->setCache(this);
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;
178 /******************************************************************************/
179 /* InternalCCache: A Credential Cache */
180 /******************************************************************************/
182 InternalCCache::InternalCCache()
184 m_SAMLBinding=SAMLBindingFactory::getInstance();
185 string ctx="shibtarget.InternalCCache";
186 log = &(log4cpp::Category::getInstance(ctx));
189 InternalCCache::~InternalCCache()
191 delete m_SAMLBinding;
192 for (map<string,CCacheEntry*>::iterator i=m_hashtable.begin(); i!=m_hashtable.end(); i++)
196 SAMLBinding* InternalCCache::getBinding(const XMLCh* bindingProt)
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;
206 CCacheEntry* InternalCCache::find(const char* key)
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");
214 log->debug("Match Found.");
218 void InternalCCache::insert(const char* key, CCacheEntry* entry)
220 log->debug("caching new entry for \"%s\"", key);
221 m_hashtable[key]=entry;
225 void InternalCCache::remove(const char* key)
227 log->debug("removing cache entry \"key\"", key);
228 m_hashtable.erase(key);
231 /******************************************************************************/
232 /* InternalCCacheEntry: A Credential Cache Entry */
233 /******************************************************************************/
235 InternalCCacheEntry::InternalCCacheEntry(SAMLAuthenticationStatement *s, const char *client_addr)
236 : m_hasbinding(false)
238 string ctx = "shibtarget::InternalCCacheEntry";
239 log = &(log4cpp::Category::getInstance(ctx));
242 log->error("NULL auth statement");
243 throw runtime_error("InternalCCacheEntry() was passed an empty SAML Statement");
246 m_subject = s->getSubject();
248 xstring name = m_subject->getName();
249 xstring qual = m_subject->getNameQualifier();
251 auto_ptr<char> h(XMLString::transcode(name.c_str()));
252 auto_ptr<char> d(XMLString::transcode(qual.c_str()));
255 m_originSite = d.get();
257 Iterator<SAMLAuthorityBinding*> bindings = s->getBindings();
258 if (bindings.hasNext())
261 m_clientAddress = client_addr;
262 m_sessionCreated = m_lastAccess = time(NULL);
267 log->info("New Session Created...");
268 log->debug("Handle: \"%s\", Site: \"%s\", Address: %s", h.get(), d.get(),
272 InternalCCacheEntry::~InternalCCacheEntry()
274 log->debug("deleting entry for %s@%s", m_handle.c_str(), m_originSite.c_str());
276 for (map<string,ResourceEntry*>::iterator i=m_resources.begin();
277 i!=m_resources.end(); i++)
281 bool InternalCCacheEntry::isSessionValid(time_t lifetime, time_t timeout)
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");
291 if (timeout > 0 && now-m_lastAccess >= timeout) {
292 log->debug("session timed out");
299 void InternalCCacheEntry::setCache(CCache *cache)
304 Iterator<SAMLAttribute*> InternalCCacheEntry::getAttributes(const char* resource_url)
306 saml::NDC("getAttributes");
307 ResourceEntry* entry = populate(resource_url);
309 return entry->getAttributes();
310 return Iterator<SAMLAttribute*>(ResourceEntry::g_emptyVector);
313 const char* InternalCCacheEntry::getSerializedAssertion(const char* resource_url)
315 saml::NDC("getSerializedAssertion");
316 ResourceEntry* entry = populate(resource_url);
318 return entry->getSerializedAssertion();
322 ResourceEntry* InternalCCacheEntry::populate(const char* resource_url)
324 saml::NDC("populate");
325 log->debug("populating entry for %s", resource_url);
327 // Can we use what we have?
328 ResourceEntry *entry = find(resource_url);
330 log->debug("found resource");
331 if (entry->isAssertionValid())
334 // entry is invalid (expired) -- go fetch a new one.
335 log->debug("removing resource cache; assertion is invalid");
336 remove (resource_url);
340 // Nope entry.. Create a new resource entry
343 log->error("No binding!");
347 log->info("trying to request attributes for %s@%s -> %s",
348 m_handle.c_str(), m_originSite.c_str(), resource_url);
350 auto_ptr<XMLCh> resource(XMLString::transcode(resource_url));
351 Iterator<saml::QName> respond_withs = ArrayIterator<saml::QName>(&g_respondWith);
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());
358 // Build a SAML Request....
359 SAMLAttributeQuery* q=new SAMLAttributeQuery(subject,resource.get());
360 SAMLRequest* req=new SAMLRequest(q,respond_withs);
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;
367 while (!response && bindings.hasNext()) {
368 SAMLAuthorityBinding* binding = bindings.next();
370 log->debug("Trying binding...");
371 SAMLBinding* pBinding=m_cache->getBinding(binding->getBinding());
372 log->debug("Sending request");
373 response=pBinding->send(*binding,*req);
376 // ok, we can delete the request now.
379 // Make sure we got a response
381 log->info ("No Response");
385 entry = new ResourceEntry(response);
386 insert (resource_url, entry);
388 log->info("fetched and stored SAML response");
392 ResourceEntry* InternalCCacheEntry::find(const char* resource_url)
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");
400 log->debug("match found");
404 void InternalCCacheEntry::insert(const char* resource_url, ResourceEntry* entry)
406 log->debug("inserting %s", resource_url);
407 m_resources[resource_url]=entry;
410 void InternalCCacheEntry::remove(const char* resource_url)
412 log->debug("removing %s", resource_url);
413 m_resources.erase(resource_url);
416 /******************************************************************************/
417 /* ResourceEntry: A Credential Cache Entry for a particular Resource URL */
418 /******************************************************************************/
420 ResourceEntry::ResourceEntry(SAMLResponse* response)
421 : m_assertion(NULL), m_serialized(NULL)
423 string ctx = "shibtarget::ResourceEntry";
424 log = &(log4cpp::Category::getInstance(ctx));
426 log->info("caching resource entry");
428 m_response = response;
430 // Store off the assertion for quick access.
431 // Memory mgmt is based on the response pointer.
432 Iterator<SAMLAssertion*> i=m_response->getAssertions();
434 m_assertion=i.next();
437 ResourceEntry::~ResourceEntry()
440 delete[] m_serialized;
443 Iterator<SAMLAttribute*> ResourceEntry::getAttributes()
445 saml::NDC("getAttributes");
448 Iterator<SAMLStatement*> i=m_assertion->getStatements();
451 SAMLAttributeStatement* s=static_cast<SAMLAttributeStatement*>(i.next());
453 return s->getAttributes();
456 return Iterator<SAMLAttribute*>(g_emptyVector);
459 const char* ResourceEntry::getSerializedAssertion()
461 saml::NDC("getSerializedAssertion");
469 XMLByte* serialized=Base64::encode(reinterpret_cast<XMLByte*>(os.str()),os.pcount(),&outlen);
470 return m_serialized=(char*)serialized;
473 bool ResourceEntry::isAssertionValid()
475 saml::NDC("isAssertionValid");
477 log->info("checking validity");
478 if (m_assertion && m_assertion->getNotOnOrAfter())
480 // This is awful, but the XMLDateTime class is truly horrible.
481 time_t now=time(NULL);
483 struct tm* ptime=gmtime(&now);
486 struct tm* ptime=gmtime_r(&now,&res);
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");
500 log->debug("not valid");