35319090a896abe51516b01d96b6aa7e2e0bdc0c
[shibboleth/cpp-sp.git] / shibsp / impl / StorageServiceSessionCache.cpp
1 /*\r
2  *  Copyright 2001-2007 Internet2\r
3  * \r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /**\r
18  * StorageServiceSessionCache.cpp\r
19  * \r
20  * StorageService-based SessionCache implementation.\r
21  * \r
22  * Instead of optimizing this plugin with a buffering scheme that keeps objects around\r
23  * and avoids extra parsing steps, I'm assuming that systems that require such can\r
24  * layer their own cache plugin on top of this version either by delegating to it\r
25  * or using the remoting support. So this version will load sessions directly\r
26  * from the StorageService, instantiate enough to expose the Session API,\r
27  * and then delete everything when they're unlocked. All data in memory is always\r
28  * kept in sync with the StorageService (no lazy updates).\r
29  */\r
30 \r
31 #include "internal.h"\r
32 #include "Application.h"\r
33 #include "exceptions.h"\r
34 #include "ServiceProvider.h"\r
35 #include "SessionCache.h"\r
36 #include "TransactionLog.h"\r
37 #include "attribute/Attribute.h"\r
38 #include "remoting/ListenerService.h"\r
39 #include "util/SPConstants.h"\r
40 \r
41 #include <log4cpp/Category.hh>\r
42 #include <saml/SAMLConfig.h>\r
43 #include <xmltooling/util/NDC.h>\r
44 #include <xmltooling/util/StorageService.h>\r
45 #include <xmltooling/util/XMLHelper.h>\r
46 #include <xercesc/util/XMLUniDefs.hpp>\r
47 \r
48 using namespace shibsp;\r
49 using namespace opensaml::saml2md;\r
50 using namespace opensaml;\r
51 using namespace xmltooling;\r
52 using namespace log4cpp;\r
53 using namespace std;\r
54 \r
55 namespace shibsp {\r
56 \r
57     class SSCache;\r
58     class StoredSession : public virtual Session\r
59     {\r
60     public:\r
61         StoredSession(SSCache* cache, const Application& app, DDF& obj)\r
62                 : m_appId(app.getId()), m_obj(obj), m_cache(cache) {\r
63             const char* nameid = obj["nameid"].string();\r
64             if (!nameid)\r
65                 throw FatalProfileException("NameID missing from cached session.");\r
66             \r
67             // Parse and bind the document into an XMLObject.\r
68             istringstream instr(nameid);\r
69             DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
70             XercesJanitor<DOMDocument> janitor(doc);\r
71             auto_ptr<saml2::NameID> n(saml2::NameIDBuilder::buildNameID());\r
72             n->unmarshall(doc->getDocumentElement(), true);\r
73             janitor.release();\r
74             \r
75             // TODO: Process attributes...\r
76 \r
77             m_nameid = n.release();\r
78         }\r
79         \r
80         ~StoredSession();\r
81         \r
82         Lockable* lock() {\r
83             return this;\r
84         }\r
85         void unlock() {\r
86             delete this;\r
87         }\r
88         \r
89         const char* getClientAddress() const {\r
90             return m_obj["client_addr"].string();\r
91         }\r
92         const char* getEntityID() const {\r
93             return m_obj["entity_id"].string();\r
94         }\r
95         const char* getAuthnInstant() const {\r
96             return m_obj["authn_instant"].string();\r
97         }\r
98         const opensaml::saml2::NameID& getNameID() const {\r
99             return *m_nameid;\r
100         }\r
101         const char* getSessionIndex() const {\r
102             return m_obj["session_index"].string();\r
103         }\r
104         const char* getAuthnContextClassRef() const {\r
105             return m_obj["authncontext_class"].string();\r
106         }\r
107         const char* getAuthnContextDeclRef() const {\r
108             return m_obj["authncontext_decl"].string();\r
109         }\r
110         const vector<const Attribute*>& getAttributes() const {\r
111             return m_attributes;\r
112         }\r
113         const vector<const char*>& getAssertionIDs() const {\r
114             if (m_ids.empty()) {\r
115                 DDF id = m_obj["assertions"].first();\r
116                 while (id.isstring()) {\r
117                     m_ids.push_back(id.name());\r
118                     id = id.next();\r
119                 }\r
120             }\r
121             return m_ids;\r
122         }\r
123         \r
124         void addAttributes(const vector<Attribute*>& attributes);\r
125         const RootObject* getAssertion(const char* id) const;\r
126         void addAssertion(RootObject* assertion);\r
127 \r
128     private:\r
129         string m_appId;\r
130         DDF m_obj;\r
131         saml2::NameID* m_nameid;\r
132         vector<const Attribute*> m_attributes;\r
133         mutable vector<const char*> m_ids;\r
134         mutable map<string,RootObject*> m_tokens;\r
135         SSCache* m_cache;\r
136     };\r
137     \r
138     class SSCache : public SessionCache, public virtual Remoted\r
139     {\r
140     public:\r
141         SSCache(const DOMElement* e);\r
142         ~SSCache() {}\r
143     \r
144         void receive(DDF& in, ostream& out);\r
145         \r
146         string insert(\r
147             time_t expires,\r
148             const Application& application,\r
149             const char* client_addr,\r
150             const saml2md::EntityDescriptor* issuer,\r
151             const saml2::NameID& nameid,\r
152             const char* authn_instant=NULL,\r
153             const char* session_index=NULL,\r
154             const char* authncontext_class=NULL,\r
155             const char* authncontext_decl=NULL,\r
156             const RootObject* ssoToken=NULL,\r
157             const vector<Attribute*>* attributes=NULL\r
158             );\r
159         Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t timeout=0);\r
160         void remove(const char* key, const Application& application, const char* client_addr);\r
161 \r
162         Category& m_log;\r
163         StorageService* m_storage;\r
164     };\r
165 \r
166     SessionCache* SHIBSP_DLLLOCAL StorageServiceCacheFactory(const DOMElement* const & e)\r
167     {\r
168         return new SSCache(e);\r
169     }\r
170 \r
171     static const XMLCh _StorageService[] =   UNICODE_LITERAL_14(S,t,o,r,a,g,e,S,e,r,v,i,c,e);\r
172 }\r
173 \r
174 StoredSession::~StoredSession()\r
175 {\r
176     m_obj.destroy();\r
177     delete m_nameid;\r
178     for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
179     for_each(m_tokens.begin(), m_tokens.end(), xmltooling::cleanup_pair<string,RootObject>());\r
180 }\r
181 \r
182 void StoredSession::addAttributes(const vector<Attribute*>& attributes)\r
183 {\r
184 #ifdef _DEBUG\r
185     xmltooling::NDC ndc("addAttributes");\r
186 #endif\r
187 \r
188     m_cache->m_log.debug("adding attributes to session (%s)", m_obj.name());\r
189     \r
190     int ver;\r
191     do {\r
192         DDF attr;\r
193         DDF attrs = m_obj["attributes"];\r
194         if (!attrs.islist())\r
195             attrs = m_obj.addmember("attributes").list();\r
196         for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {\r
197             attr = (*a)->marshall();\r
198             attrs.add(attr);\r
199         }\r
200         \r
201         // Tentatively increment the version.\r
202         m_obj["version"].integer(m_obj["version"].integer()+1);\r
203         \r
204         ostringstream str;\r
205         str << m_obj;\r
206         string record(str.str()); \r
207 \r
208         ver = m_cache->m_storage->updateText(m_appId.c_str(), m_obj.name(), record.c_str(), 0, m_obj["version"].integer()-1);\r
209         if (ver <= 0) {\r
210             // Roll back modification to record.\r
211             m_obj["version"].integer(m_obj["version"].integer()-1);\r
212             vector<Attribute*>::size_type count = attributes.size();\r
213             while (count--)\r
214                 attrs.last().destroy();            \r
215         }\r
216         if (!ver) {\r
217             // Fatal problem with update.\r
218             m_cache->m_log.error("updateText failed on StorageService for session (%s)", m_obj.name());\r
219             throw IOException("Unable to update stored session.");\r
220         }\r
221         else if (ver < 0) {\r
222             // Out of sync.\r
223             m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");\r
224             ver = m_cache->m_storage->readText(m_appId.c_str(), m_obj.name(), &record, NULL);\r
225             if (!ver) {\r
226                 m_cache->m_log.error("updateText failed on StorageService for session (%s)", m_obj.name());\r
227                 throw IOException("Unable to update stored session.");\r
228             }\r
229             \r
230             // Reset object.\r
231             DDF newobj;\r
232             istringstream in(record);\r
233             in >> newobj;\r
234 \r
235             m_obj.destroy();\r
236             m_ids.clear();\r
237             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
238             m_attributes.clear();\r
239             newobj["version"].integer(ver);\r
240             m_obj = newobj;\r
241             // TODO: handle attributes\r
242             \r
243             ver = -1;\r
244         }\r
245     } while (ver < 0);  // negative indicates a sync issue so we retry\r
246 \r
247     // Transfer ownership to us.\r
248     m_attributes.insert(m_attributes.end(), attributes.begin(), attributes.end());\r
249 \r
250     TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
251     Locker locker(xlog);\r
252     xlog->log.infoStream() <<\r
253         "Added the following attributes to session (ID: " <<\r
254             m_obj.name() <<\r
255         ") for (applicationId: " <<\r
256             m_appId.c_str() <<\r
257         ") {";\r
258     for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a)\r
259         xlog->log.infoStream() << "\t" << (*a)->getId() << " (" << (*a)->valueCount() << " values)";\r
260     xlog->log.info("}");\r
261 }\r
262 \r
263 const RootObject* StoredSession::getAssertion(const char* id) const\r
264 {\r
265     map<string,RootObject*>::const_iterator i = m_tokens.find(id);\r
266     if (i!=m_tokens.end())\r
267         return i->second;\r
268     \r
269     // Parse and bind the document into an XMLObject.\r
270     const char* tokenstr = m_obj["assertions"][id].string();\r
271     if (!tokenstr)\r
272         throw FatalProfileException("Assertion not found in cache.");\r
273     istringstream instr(tokenstr);\r
274     DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
275     XercesJanitor<DOMDocument> janitor(doc);\r
276     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));\r
277     janitor.release();\r
278     \r
279     RootObject* token = dynamic_cast<RootObject*>(xmlObject.get());\r
280     if (!token || !token->isAssertion())\r
281         throw FatalProfileException("Request for cached assertion returned an unknown object type.");\r
282 \r
283     // Transfer ownership to us.\r
284     xmlObject.release();\r
285     m_tokens[id]=token;\r
286     return token;\r
287 }\r
288 \r
289 void StoredSession::addAssertion(RootObject* assertion)\r
290 {\r
291 #ifdef _DEBUG\r
292     xmltooling::NDC ndc("addAssertion");\r
293 #endif\r
294     \r
295     if (!assertion || !assertion->isAssertion())\r
296         throw FatalProfileException("Unknown object type passed to session for storage.");\r
297 \r
298     auto_ptr_char id(assertion->getID());\r
299 \r
300     m_cache->m_log.debug("adding assertion (%s) to session (%s)", id.get(), m_obj.name());\r
301 \r
302     ostringstream os;\r
303     os << *assertion;\r
304     \r
305     int ver;\r
306     do {\r
307         DDF token = m_obj["assertions"];\r
308         if (!token.isstruct())\r
309             token = m_obj.addmember("assertions").structure();\r
310         token = token.addmember(id.get()).string(os.str().c_str());\r
311 \r
312         // Tentatively increment the version.\r
313         m_obj["version"].integer(m_obj["version"].integer()+1);\r
314     \r
315         ostringstream str;\r
316         str << m_obj;\r
317         string record(str.str()); \r
318 \r
319         ver = m_cache->m_storage->updateText(m_appId.c_str(), m_obj.name(), record.c_str(), 0, m_obj["version"].integer()-1);\r
320         if (ver <= 0) {\r
321             token.destroy();\r
322             m_obj["version"].integer(m_obj["version"].integer()-1);\r
323         }            \r
324         if (!ver) {\r
325             // Fatal problem with update.\r
326             m_cache->m_log.error("updateText failed on StorageService for session (%s)", m_obj.name());\r
327             throw IOException("Unable to update stored session.");\r
328         }\r
329         else if (ver < 0) {\r
330             // Out of sync.\r
331             m_cache->m_log.warn("storage service indicates the record is out of sync, updating with a fresh copy...");\r
332             ver = m_cache->m_storage->readText(m_appId.c_str(), m_obj.name(), &record, NULL);\r
333             if (!ver) {\r
334                 m_cache->m_log.error("updateText failed on StorageService for session (%s)", m_obj.name());\r
335                 throw IOException("Unable to update stored session.");\r
336             }\r
337             \r
338             // Reset object.\r
339             DDF newobj;\r
340             istringstream in(record);\r
341             in >> newobj;\r
342 \r
343             m_obj.destroy();\r
344             m_ids.clear();\r
345             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
346             m_attributes.clear();\r
347             newobj["version"].integer(ver);\r
348             m_obj = newobj;\r
349             // TODO: handle attributes\r
350             \r
351             ver = -1;\r
352         }\r
353     } while (ver < 0); // negative indicates a sync issue so we retry\r
354 \r
355     delete assertion;\r
356 \r
357     TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
358     Locker locker(xlog);\r
359     xlog->log.info(\r
360         "Added assertion (ID: %s) to session for (applicationId: %s) with (ID: %s)",\r
361         id.get(), m_appId.c_str(), m_obj.name()\r
362         );\r
363 }\r
364 \r
365 SSCache::SSCache(const DOMElement* e)\r
366     : SessionCache(e), m_log(Category::getInstance(SHIBSP_LOGCAT".SessionCache")), m_storage(NULL)\r
367 {\r
368     SPConfig& conf = SPConfig::getConfig();\r
369     const XMLCh* tag = e ? e->getAttributeNS(NULL,_StorageService) : NULL;\r
370     if (tag && *tag) {\r
371         auto_ptr_char ssid(tag);\r
372         m_storage = conf.getServiceProvider()->getStorageService(ssid.get());\r
373         if (m_storage)\r
374             m_log.info("bound to StorageService (%s)", ssid.get());\r
375         else\r
376             throw ConfigurationException("SessionCache unable to locate StorageService, check configuration.");\r
377     }\r
378 \r
379     ListenerService* listener=conf.getServiceProvider()->getListenerService(false);\r
380     if (listener && conf.isEnabled(SPConfig::OutOfProcess)) {\r
381         listener->regListener("insert::"REMOTED_SESSION_CACHE,this);\r
382         listener->regListener("find::"REMOTED_SESSION_CACHE,this);\r
383         listener->regListener("remove::"REMOTED_SESSION_CACHE,this);\r
384     }\r
385     else {\r
386         m_log.info("no ListenerService available, cache remoting is disabled");\r
387     }\r
388 }\r
389 \r
390 string SSCache::insert(\r
391     time_t expires,\r
392     const Application& application,\r
393     const char* client_addr,\r
394     const saml2md::EntityDescriptor* issuer,\r
395     const saml2::NameID& nameid,\r
396     const char* authn_instant,\r
397     const char* session_index,\r
398     const char* authncontext_class,\r
399     const char* authncontext_decl,\r
400     const RootObject* ssoToken,\r
401     const vector<Attribute*>* attributes\r
402     )\r
403 {\r
404 #ifdef _DEBUG\r
405     xmltooling::NDC ndc("insert");\r
406 #endif\r
407 \r
408     m_log.debug("creating new session");\r
409 \r
410     auto_ptr_char key(SAMLConfig::getConfig().generateIdentifier());\r
411 \r
412     // Store session properties in DDF.\r
413     DDF obj = DDF(key.get()).structure();\r
414     obj.addmember("version").integer(1);\r
415     if (expires > 0) {\r
416         // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.  \r
417 #ifndef HAVE_GMTIME_R\r
418         struct tm* ptime=gmtime(&expires);\r
419 #else\r
420         struct tm res;\r
421         struct tm* ptime=gmtime_r(&expires,&res);\r
422 #endif\r
423         char timebuf[32];\r
424         strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
425         obj.addmember("expires").string(timebuf);\r
426     }\r
427     obj.addmember("client_addr").string(client_addr);\r
428     if (issuer) {\r
429         auto_ptr_char entity_id(issuer->getEntityID());\r
430         obj.addmember("entity_id").string(entity_id.get());\r
431     }\r
432     if (authn_instant)\r
433         obj.addmember("authn_instant").string(authn_instant);\r
434     if (session_index)\r
435         obj.addmember("session_index").string(session_index);\r
436     if (authncontext_class)\r
437         obj.addmember("authncontext_class").string(authncontext_class);\r
438     if (authncontext_decl)\r
439         obj.addmember("authncontext_decl").string(authncontext_decl);\r
440 \r
441     ostringstream namestr;\r
442     namestr << nameid;\r
443     obj.addmember("nameid").string(namestr.str().c_str());\r
444     \r
445     if (ssoToken) {\r
446         ostringstream tokenstr;\r
447         tokenstr << *ssoToken;\r
448         auto_ptr_char tokenid(ssoToken->getID());\r
449         obj.addmember("assertions").structure().addmember(tokenid.get()).string(tokenstr.str().c_str());\r
450     }\r
451     \r
452     if (attributes) {\r
453         DDF attr;\r
454         DDF attrlist = obj.addmember("attributes").list();\r
455         for (vector<Attribute*>::const_iterator a=attributes->begin(); a!=attributes->end(); ++a) {\r
456             attr = (*a)->marshall();\r
457             attrlist.add(attr);\r
458         }\r
459     }\r
460     \r
461     ostringstream record;\r
462     record << obj;\r
463     \r
464     m_log.debug("storing new session...");\r
465     m_storage->createText(application.getId(), key.get(), record.str().c_str(), time(NULL) + m_cacheTimeout);\r
466     const char* pid = obj["entity_id"].string();\r
467     m_log.debug("new session created: SessionID (%s) IdP (%s) Address (%s)", key.get(), pid ? pid : "none", client_addr);\r
468 \r
469     // Transaction Logging\r
470     auto_ptr_char name(nameid.getName());\r
471     TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
472     Locker locker(xlog);\r
473     xlog->log.infoStream() <<\r
474         "New session (ID: " <<\r
475             key.get() <<\r
476         ") with (applicationId: " <<\r
477             application.getId() <<\r
478         ") for principal from (IdP: " <<\r
479             (pid ? pid : "none") <<\r
480         ") at (ClientAddress: " <<\r
481             client_addr <<\r
482         ") with (NameIdentifier: " <<\r
483             name.get() <<\r
484         ")";\r
485     \r
486     return key.get();\r
487 }\r
488 \r
489 Session* SSCache::find(const char* key, const Application& application, const char* client_addr, time_t timeout)\r
490 {\r
491 #ifdef _DEBUG\r
492     xmltooling::NDC ndc("find");\r
493 #endif\r
494 \r
495     m_log.debug("searching for session (%s)", key);\r
496     \r
497     time_t lastAccess;\r
498     string record;\r
499     int ver = m_storage->readText(application.getId(), key, &record, &lastAccess);\r
500     if (!ver)\r
501         return NULL;\r
502     \r
503     m_log.debug("reconstituting session and checking for validity");\r
504     \r
505     DDF obj;\r
506     istringstream in(record);\r
507     in >> obj;\r
508     \r
509     lastAccess -= m_cacheTimeout;   // adjusts it back to the last time the record's timestamp was touched\r
510  \r
511     if (client_addr) {\r
512         if (m_log.isDebugEnabled())\r
513             m_log.debug("comparing client address %s against %s", client_addr, obj["client_addr"].string());\r
514         if (strcmp(obj["client_addr"].string(),client_addr)) {\r
515             m_log.warn("client address mismatch");\r
516             RetryableProfileException ex(\r
517                 "Your IP address ($1) does not match the address recorded at the time the session was established.",\r
518                 params(1,client_addr)\r
519                 );\r
520             string eid(obj["entity_id"].string());\r
521             obj.destroy();\r
522             if (eid.empty())\r
523                 throw ex;\r
524             MetadataProvider* m=application.getMetadataProvider();\r
525             Locker locker(m);\r
526             annotateException(&ex,m->getEntityDescriptor(eid.c_str(),false)); // throws it\r
527         }\r
528     }\r
529 \r
530     time_t now=time(NULL);\r
531     \r
532     if (timeout > 0 && now - lastAccess >= timeout) {\r
533         m_log.info("session timed out (ID: %s)", key);\r
534         RetryableProfileException ex("Your session has expired, and you must re-authenticate.");\r
535         string eid(obj["entity_id"].string());\r
536         obj.destroy();\r
537         if (eid.empty())\r
538             throw ex;\r
539         MetadataProvider* m=application.getMetadataProvider();\r
540         Locker locker(m);\r
541         annotateException(&ex,m->getEntityDescriptor(eid.c_str(),false)); // throws it\r
542     }\r
543     \r
544     auto_ptr_XMLCh exp(obj["expires"].string());\r
545     if (exp.get()) {\r
546         DateTime iso(exp.get());\r
547         iso.parseDateTime();\r
548         if (now > iso.getEpoch()) {\r
549             m_log.info("session expired (ID: %s)", key);\r
550             RetryableProfileException ex("Your session has expired, and you must re-authenticate.");\r
551             string eid(obj["entity_id"].string());\r
552             obj.destroy();\r
553             if (eid.empty())\r
554                 throw ex;\r
555             MetadataProvider* m=application.getMetadataProvider();\r
556             Locker locker(m);\r
557             annotateException(&ex,m->getEntityDescriptor(eid.c_str(),false)); // throws it\r
558         }\r
559     }\r
560     \r
561     // Update storage expiration, if possible.\r
562     if (!m_storage->updateText(application.getId(), key, NULL, now + m_cacheTimeout))\r
563         m_log.error("failed to update record expiration");\r
564 \r
565     // Finally build the Session object.\r
566     try {\r
567         return new StoredSession(this, application, obj);\r
568     }\r
569     catch (exception&) {\r
570         obj.destroy();\r
571         throw;\r
572     }\r
573 }\r
574 \r
575 void SSCache::remove(const char* key, const Application& application, const char* client_addr)\r
576 {\r
577 #ifdef _DEBUG\r
578     xmltooling::NDC ndc("remove");\r
579 #endif\r
580 \r
581     m_log.debug("removing session (%s)", key);\r
582 \r
583     m_storage->deleteText(application.getId(), key);\r
584 \r
585     TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
586     Locker locker(xlog);\r
587     xlog->log.info("Destroyed session (applicationId: %s) (ID: %s)", application.getId(), key);\r
588 }\r
589 \r
590 void SSCache::receive(DDF& in, ostream& out)\r
591 {\r
592 #ifdef _DEBUG\r
593     xmltooling::NDC ndc("receive");\r
594 #endif\r
595 \r
596     // Find application.\r
597     ServiceProvider* sp = SPConfig::getConfig().getServiceProvider(); \r
598     Locker confLocker(sp);\r
599     const char* aid=in["application_id"].string();\r
600     const Application* app=aid ? sp->getApplication(aid) : NULL;\r
601     if (!app) {\r
602         // Something's horribly wrong.\r
603         m_log.error("couldn't find application (%s) for session", aid ? aid : "(missing)");\r
604         throw ConfigurationException("Unable to locate application for session, deleted?");\r
605     }\r
606 \r
607     if (!strcmp(in.name(),"insert::"REMOTED_SESSION_CACHE)) {\r
608         in["application_id"].destroy();\r
609         auto_ptr_char key(SAMLConfig::getConfig().generateIdentifier());\r
610         in.name(key.get());\r
611         \r
612         m_log.debug("storing new session...");\r
613         ostringstream record;\r
614         record << in;\r
615         m_storage->createText(app->getId(), key.get(), record.str().c_str(), time(NULL) + m_cacheTimeout);\r
616         const char* pid = in["entity_id"].string();\r
617         m_log.debug("new session created: SessionID (%s) IdP (%s) Address (%s)", key.get(), pid ? pid : "none", in["client_addr"].string());\r
618     \r
619         DDF ret = DDF(NULL).structure();\r
620         DDFJanitor jan(ret);\r
621         ret.addmember("key").string(key.get());\r
622         out << ret;\r
623     }\r
624     else if (!strcmp(in.name(),"find::"REMOTED_SESSION_CACHE)) {\r
625         const char* key=in["key"].string();\r
626         if (!key)\r
627             throw ListenerException("Required parameters missing for session removal.");\r
628 \r
629         // Do an unversioned read.\r
630         string record;\r
631         time_t lastAccess;\r
632         if (!m_storage->readText(aid, key, &record, &lastAccess)) {\r
633             DDF ret(NULL);\r
634             DDFJanitor jan(ret);\r
635             out << ret;\r
636             return;\r
637         }\r
638 \r
639         // Adjust for expiration to recover last access time and check timeout.\r
640         lastAccess -= m_cacheTimeout;\r
641         time_t now=time(NULL);\r
642 \r
643         // See if we need to check for a timeout.\r
644         time_t timeout = 0;\r
645         auto_ptr_XMLCh dt(in["timeout"].string());\r
646         if (dt.get()) {\r
647             DateTime dtobj(dt.get());\r
648             dtobj.parseDateTime();\r
649             timeout = dtobj.getEpoch();\r
650         }\r
651                 \r
652         if (timeout > 0 && now - lastAccess >= timeout) {\r
653             m_log.info("session timed out (ID: %s)", key);\r
654             throw RetryableProfileException("Your session has expired, and you must re-authenticate.");\r
655         } \r
656 \r
657         // Update storage expiration, if possible.\r
658         if (!m_storage->updateText(aid, key, NULL, now + m_cacheTimeout)) \r
659             m_log.error("failed to update record expiration");\r
660             \r
661         // Send the record back.\r
662         out << record;\r
663     }\r
664     else if (!strcmp(in.name(),"touch::"REMOTED_SESSION_CACHE)) {\r
665         const char* key=in["key"].string();\r
666         if (!key)\r
667             throw ListenerException("Required parameters missing for session check.");\r
668 \r
669         // Do a versioned read.\r
670         string record;\r
671         time_t lastAccess;\r
672         int curver = in["version"].integer();\r
673         int ver = m_storage->readText(aid, key, &record, &lastAccess, curver);\r
674         if (ver == 0) {\r
675             m_log.warn("unsuccessful versioned read of session (ID: %s), caches out of sync?", key);\r
676             throw RetryableProfileException("Your session has expired, and you must re-authenticate.");\r
677         }\r
678 \r
679         // Adjust for expiration to recover last access time and check timeout.\r
680         lastAccess -= m_cacheTimeout;\r
681         time_t now=time(NULL);\r
682 \r
683         // See if we need to check for a timeout.\r
684         time_t timeout = 0;\r
685         auto_ptr_XMLCh dt(in["timeout"].string());\r
686         if (dt.get()) {\r
687             DateTime dtobj(dt.get());\r
688             dtobj.parseDateTime();\r
689             timeout = dtobj.getEpoch();\r
690         }\r
691                 \r
692         if (timeout > 0 && now - lastAccess >= timeout) {\r
693             m_log.info("session timed out (ID: %s)", key);\r
694             throw RetryableProfileException("Your session has expired, and you must re-authenticate.");\r
695         } \r
696 \r
697         // Update storage expiration, if possible.\r
698         if (!m_storage->updateText(aid, key, NULL, now + m_cacheTimeout)) \r
699             m_log.error("failed to update record expiration");\r
700             \r
701         if (ver > curver) {\r
702             // Send the record back.\r
703             out << record;\r
704         }\r
705         else {\r
706             DDF ret(NULL);\r
707             DDFJanitor jan(ret);\r
708             out << ret;\r
709         }\r
710     }\r
711     else if (!strcmp(in.name(),"remove::"REMOTED_SESSION_CACHE)) {\r
712         const char* key=in["key"].string();\r
713         const char* client_addr=in["client_addr"].string();\r
714         if (!key || !client_addr)\r
715             throw ListenerException("Required parameters missing for session removal.");\r
716         \r
717         remove(key,*app,client_addr);\r
718         DDF ret(NULL);\r
719         DDFJanitor jan(ret);\r
720         out << ret;\r
721     }\r
722 }\r