c3353f37bdc78c8570983bf4e8cb54c4033f3130
[shibboleth/cpp-sp.git] / shibsp / impl / RemotedSessionCache.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  * RemotedSessionCache.cpp\r
19  * \r
20  * SessionCache implementation that delegates to a remoted version.\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "Application.h"\r
25 #include "exceptions.h"\r
26 #include "ServiceProvider.h"\r
27 #include "SessionCache.h"\r
28 #include "TransactionLog.h"\r
29 #include "attribute/Attribute.h"\r
30 #include "remoting/ListenerService.h"\r
31 #include "util/SPConstants.h"\r
32 \r
33 #include <sstream>\r
34 #include <log4cpp/Category.hh>\r
35 #include <xmltooling/XMLToolingConfig.h>\r
36 #include <xmltooling/util/NDC.h>\r
37 #include <xmltooling/util/XMLHelper.h>\r
38 \r
39 using namespace shibsp;\r
40 using namespace opensaml::saml2md;\r
41 using namespace opensaml;\r
42 using namespace xmltooling;\r
43 using namespace log4cpp;\r
44 using namespace std;\r
45 \r
46 namespace shibsp {\r
47 \r
48     class RemotedCache;\r
49     class RemotedSession : public virtual Session\r
50     {\r
51     public:\r
52         RemotedSession(RemotedCache* cache, const Application& app, DDF& obj)\r
53                 : m_appId(app.getId()), m_version(obj["version"].integer()), m_obj(obj),\r
54                     m_nameid(NULL), m_expires(0), m_lastAccess(time(NULL)), m_cache(cache), m_lock(NULL) {\r
55             const char* nameid = obj["nameid"].string();\r
56             if (!nameid)\r
57                 throw FatalProfileException("NameID missing from remotely cached session.");\r
58             \r
59             // Parse and bind the document into an XMLObject.\r
60             istringstream instr(nameid);\r
61             DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
62             XercesJanitor<DOMDocument> janitor(doc);\r
63             auto_ptr<saml2::NameID> n(saml2::NameIDBuilder::buildNameID());\r
64             n->unmarshall(doc->getDocumentElement(), true);\r
65             janitor.release();\r
66             \r
67             // TODO: Process attributes...\r
68 \r
69             auto_ptr_XMLCh exp(m_obj["expires"].string());\r
70             if (exp.get()) {\r
71                 DateTime iso(exp.get());\r
72                 iso.parseDateTime();\r
73                 m_expires = iso.getEpoch();\r
74             }\r
75 \r
76             m_nameid = n.release();\r
77             m_lock = Mutex::create();\r
78         }\r
79         \r
80         ~RemotedSession() {\r
81             delete m_lock;\r
82             m_obj.destroy();\r
83             delete m_nameid;\r
84             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
85             for_each(m_tokens.begin(), m_tokens.end(), xmltooling::cleanup_pair<string,RootObject>());\r
86         }\r
87         \r
88         Lockable* lock() {\r
89             m_lock->lock();\r
90             return this;\r
91         }\r
92         void unlock() {\r
93             m_lock->unlock();\r
94         }\r
95         \r
96         const char* getClientAddress() const {\r
97             return m_obj["client_addr"].string();\r
98         }\r
99         const char* getEntityID() const {\r
100             return m_obj["entity_id"].string();\r
101         }\r
102         const char* getAuthnInstant() const {\r
103             return m_obj["authn_instant"].string();\r
104         }\r
105         const opensaml::saml2::NameID& getNameID() const {\r
106             return *m_nameid;\r
107         }\r
108         const char* getSessionIndex() const {\r
109             return m_obj["session_index"].string();\r
110         }\r
111         const char* getAuthnContextClassRef() const {\r
112             return m_obj["authncontext_class"].string();\r
113         }\r
114         const char* getAuthnContextDeclRef() const {\r
115             return m_obj["authncontext_decl"].string();\r
116         }\r
117         const vector<const Attribute*>& getAttributes() const {\r
118             return m_attributes;\r
119         }\r
120         const vector<const char*>& getAssertionIDs() const {\r
121             if (m_ids.empty()) {\r
122                 DDF id = m_obj["assertions"].first();\r
123                 while (id.isstring()) {\r
124                     m_ids.push_back(id.name());\r
125                     id = id.next();\r
126                 }\r
127             }\r
128             return m_ids;\r
129         }\r
130         \r
131         const RootObject* getAssertion(const char* id) const;\r
132 \r
133         void addAttributes(const vector<Attribute*>& attributes) {\r
134             throw ConfigurationException("addAttributes method not implemented by this session cache plugin.");\r
135         }\r
136         void addAssertion(RootObject* assertion) {\r
137             throw ConfigurationException("addAttributes method not implemented by this session cache plugin.");\r
138         }\r
139 \r
140         time_t expires() const { return m_expires; }\r
141         time_t lastAccess() const { return m_lastAccess; }\r
142         void validate(const Application& application, const char* client_addr, time_t timeout, bool local=true);\r
143 \r
144     private:\r
145         string m_appId;\r
146         int m_version;\r
147         mutable DDF m_obj;\r
148         saml2::NameID* m_nameid;\r
149         vector<const Attribute*> m_attributes;\r
150         mutable vector<const char*> m_ids;\r
151         mutable map<string,RootObject*> m_tokens;\r
152         time_t m_expires,m_lastAccess;\r
153         RemotedCache* m_cache;\r
154         Mutex* m_lock;\r
155     };\r
156     \r
157     class RemotedCache : public SessionCache\r
158     {\r
159     public:\r
160         RemotedCache(const DOMElement* e);\r
161         ~RemotedCache();\r
162     \r
163         string insert(\r
164             time_t expires,\r
165             const Application& application,\r
166             const char* client_addr,\r
167             const saml2md::EntityDescriptor* issuer,\r
168             const saml2::NameID& nameid,\r
169             const char* authn_instant=NULL,\r
170             const char* session_index=NULL,\r
171             const char* authncontext_class=NULL,\r
172             const char* authncontext_decl=NULL,\r
173             const RootObject* ssoToken=NULL,\r
174             const vector<Attribute*>* attributes=NULL\r
175             );\r
176         Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t timeout=0);\r
177         void remove(const char* key, const Application& application, const char* client_addr);\r
178         \r
179         void cleanup();\r
180     \r
181         Category& m_log;\r
182     private:\r
183         const DOMElement* m_root;         // Only valid during initialization\r
184         RWLock* m_lock;\r
185         map<string,RemotedSession*> m_hashtable;\r
186     \r
187         void dormant(const char* key);\r
188         static void* cleanup_fn(void*);\r
189         bool shutdown;\r
190         CondWait* shutdown_wait;\r
191         Thread* cleanup_thread;\r
192     };\r
193 \r
194     SessionCache* SHIBSP_DLLLOCAL RemotedCacheFactory(const DOMElement* const & e)\r
195     {\r
196         return new RemotedCache(e);\r
197     }\r
198 }\r
199 \r
200 const RootObject* RemotedSession::getAssertion(const char* id) const\r
201 {\r
202     map<string,RootObject*>::const_iterator i = m_tokens.find(id);\r
203     if (i!=m_tokens.end())\r
204         return i->second;\r
205 \r
206     const char* tokenstr = m_obj["assertions"][id].string();\r
207     if (!tokenstr)\r
208         return NULL;\r
209     \r
210     // Parse and bind the document into an XMLObject.\r
211     istringstream instr(tokenstr);\r
212     DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
213     XercesJanitor<DOMDocument> janitor(doc);\r
214     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));\r
215     janitor.release();\r
216     \r
217     RootObject* token = dynamic_cast<RootObject*>(xmlObject.get());\r
218     if (!token || !token->isAssertion())\r
219         throw FatalProfileException("Cached assertion was of an unknown object type.");\r
220 \r
221     // Transfer ownership to us.\r
222     xmlObject.release();\r
223     m_tokens[id]=token;\r
224     return token;\r
225 }\r
226 \r
227 void RemotedSession::validate(const Application& application, const char* client_addr, time_t timeout, bool local)\r
228 {\r
229     // Basic expiration?\r
230     time_t now = time(NULL);\r
231     if (now > m_expires) {\r
232         m_cache->m_log.info("session expired (ID: %s)", m_obj.name());\r
233         RetryableProfileException ex("Your session has expired, and you must re-authenticate.");\r
234         if (!getEntityID())\r
235             throw ex;\r
236         MetadataProvider* m=application.getMetadataProvider();\r
237         Locker locker(m);\r
238         annotateException(&ex,m->getEntityDescriptor(getEntityID(),false)); // throws it\r
239     }\r
240 \r
241     // Address check?\r
242     if (client_addr) {\r
243         if (m_cache->m_log.isDebugEnabled())\r
244             m_cache->m_log.debug("comparing client address %s against %s", client_addr, getClientAddress());\r
245         if (strcmp(getClientAddress(),client_addr)) {\r
246             m_cache->m_log.warn("client address mismatch");\r
247             RetryableProfileException ex(\r
248                 "Your IP address ($1) does not match the address recorded at the time the session was established.",\r
249                 params(1,client_addr)\r
250                 );\r
251             if (!getEntityID())\r
252                 throw ex;\r
253             MetadataProvider* m=application.getMetadataProvider();\r
254             Locker locker(m);\r
255             annotateException(&ex,m->getEntityDescriptor(getEntityID(),false)); // throws it\r
256         }\r
257     }\r
258 \r
259     if (local)\r
260         return;\r
261     \r
262     DDF in("touch::"REMOTED_SESSION_CACHE), out;\r
263     DDFJanitor jin(in);\r
264     in.structure();\r
265     in.addmember("key").string(m_obj.name());\r
266     in.addmember("application_id").string(m_appId.c_str());\r
267     in.addmember("version").integer(m_obj["version"].integer());\r
268     if (timeout) {\r
269         // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.  \r
270 #ifndef HAVE_GMTIME_R\r
271         struct tm* ptime=gmtime(&timeout);\r
272 #else\r
273         struct tm res;\r
274         struct tm* ptime=gmtime_r(&timeout,&res);\r
275 #endif\r
276         char timebuf[32];\r
277         strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
278         in.addmember("timeout").string(timebuf);\r
279     }\r
280 \r
281     try {\r
282         out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
283         if (out.isstruct()) {\r
284             // We got an updated record back.\r
285             m_ids.clear();\r
286             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
287             m_attributes.clear();\r
288             m_obj.destroy();\r
289             m_obj = out;\r
290             \r
291             // TODO: handle attributes\r
292         }\r
293     }\r
294     catch (...) {\r
295         out.destroy();\r
296         throw;\r
297     }\r
298     \r
299     m_lastAccess = now;\r
300 }\r
301 \r
302 RemotedCache::RemotedCache(const DOMElement* e)\r
303     : SessionCache(e), m_log(Category::getInstance(SHIBSP_LOGCAT".SessionCache")), m_root(e), m_lock(NULL), shutdown(false)\r
304 {\r
305     if (!SPConfig::getConfig().getServiceProvider()->getListenerService())\r
306         throw ConfigurationException("RemotedCacheService requires a ListenerService, but none available.");\r
307         \r
308     m_lock = RWLock::create();\r
309     shutdown_wait = CondWait::create();\r
310     cleanup_thread = Thread::create(&cleanup_fn, (void*)this);\r
311 }\r
312 \r
313 RemotedCache::~RemotedCache()\r
314 {\r
315     // Shut down the cleanup thread and let it know...\r
316     shutdown = true;\r
317     shutdown_wait->signal();\r
318     cleanup_thread->join(NULL);\r
319 \r
320     for_each(m_hashtable.begin(),m_hashtable.end(),xmltooling::cleanup_pair<string,RemotedSession>());\r
321     delete m_lock;\r
322     delete shutdown_wait;\r
323 }\r
324 \r
325 string RemotedCache::insert(\r
326     time_t expires,\r
327     const Application& application,\r
328     const char* client_addr,\r
329     const saml2md::EntityDescriptor* issuer,\r
330     const saml2::NameID& nameid,\r
331     const char* authn_instant,\r
332     const char* session_index,\r
333     const char* authncontext_class,\r
334     const char* authncontext_decl,\r
335     const RootObject* ssoToken,\r
336     const vector<Attribute*>* attributes\r
337     )\r
338 {\r
339     DDF in("insert::"REMOTED_SESSION_CACHE);\r
340     DDFJanitor jin(in);\r
341     in.structure();\r
342     if (expires) {\r
343 #ifndef HAVE_GMTIME_R\r
344         struct tm* ptime=gmtime(&expires);\r
345 #else\r
346         struct tm res;\r
347         struct tm* ptime=gmtime_r(&expires,&res);\r
348 #endif\r
349         char timebuf[32];\r
350         strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
351         in.addmember("expires").string(timebuf);\r
352     }\r
353     in.addmember("application_id").string(application.getId());\r
354     in.addmember("client_addr").string(client_addr);\r
355     if (issuer) {\r
356         auto_ptr_char provid(issuer->getEntityID());\r
357         in.addmember("entity_id").string(provid.get());\r
358     }\r
359     if (authn_instant)\r
360         in.addmember("authn_instant").string(authn_instant);\r
361     if (session_index)\r
362         in.addmember("session_index").string(session_index);\r
363     if (authncontext_class)\r
364         in.addmember("authncontext_class").string(authncontext_class);\r
365     if (authncontext_decl)\r
366         in.addmember("authncontext_decl").string(authncontext_decl);\r
367     \r
368     ostringstream namestr;\r
369     namestr << nameid;\r
370     in.addmember("nameid").string(namestr.str().c_str());\r
371 \r
372     if (ssoToken) {\r
373         ostringstream tokenstr;\r
374         tokenstr << *ssoToken;\r
375         auto_ptr_char tokenid(ssoToken->getID());\r
376         in.addmember("assertions").list().add(DDF(tokenid.get()).string(tokenstr.str().c_str()));\r
377     }\r
378     \r
379     if (attributes) {\r
380         DDF attr;\r
381         DDF attrs = in.addmember("attributes").list();\r
382         for (vector<Attribute*>::const_iterator a=attributes->begin(); a!=attributes->end(); ++a) {\r
383             attr = (*a)->marshall();\r
384             attrs.add(attr);\r
385         }\r
386     }\r
387 \r
388     DDF out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
389     DDFJanitor jout(out);\r
390     if (out["key"].isstring()) {\r
391         for_each(attributes->begin(), attributes->end(), xmltooling::cleanup<Attribute>());\r
392 \r
393         // Transaction Logging\r
394         auto_ptr_char name(nameid.getName());\r
395         const char* pid = in["entity_id"].string();\r
396         TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
397         Locker locker(xlog);\r
398         xlog->log.infoStream() <<\r
399             "New session (ID: " <<\r
400                 out["key"].string() <<\r
401             ") with (applicationId: " <<\r
402                 application.getId() <<\r
403             ") for principal from (IdP: " <<\r
404                 (pid ? pid : "none") <<\r
405             ") at (ClientAddress: " <<\r
406                 client_addr <<\r
407             ") with (NameIdentifier: " <<\r
408                 name.get() <<\r
409             ")";\r
410 \r
411         return out["key"].string();\r
412     }\r
413     throw RetryableProfileException("A remoted cache insertion operation did not return a usable session key.");\r
414 }\r
415 \r
416 Session* RemotedCache::find(const char* key, const Application& application, const char* client_addr, time_t timeout)\r
417 {\r
418 #ifdef _DEBUG\r
419     xmltooling::NDC ndc("find");\r
420 #endif\r
421 \r
422     bool localValidation = false;\r
423     RemotedSession* session=NULL;\r
424     m_log.debug("searching local cache for session (%s)", key);\r
425     m_lock->rdlock();\r
426     map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);\r
427     if (i==m_hashtable.end()) {\r
428         m_lock->unlock();\r
429         m_log.debug("session not found locally, searching remote cache");\r
430 \r
431         DDF in("find::"REMOTED_SESSION_CACHE), out;\r
432         DDFJanitor jin(in);\r
433         in.structure();\r
434         in.addmember("key").string(key);\r
435         in.addmember("application_id").string(application.getId());\r
436         if (timeout) {\r
437             // On 64-bit Windows, time_t doesn't fit in a long, so I'm using ISO timestamps.  \r
438 #ifndef HAVE_GMTIME_R\r
439             struct tm* ptime=gmtime(&timeout);\r
440 #else\r
441             struct tm res;\r
442             struct tm* ptime=gmtime_r(&timeout,&res);\r
443 #endif\r
444             char timebuf[32];\r
445             strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
446             in.addmember("timeout").string(timebuf);\r
447         }\r
448         \r
449         try {\r
450             out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
451             if (!out.isstruct()) {\r
452                 out.destroy();\r
453                 m_log.debug("session not found in remote cache");\r
454                 return NULL;\r
455             }\r
456             \r
457             // Wrap the results in a local entry and save it.\r
458             session = new RemotedSession(this, application, out);\r
459             // The remote end has handled timeout issues, we handle address and expiration checks.\r
460             localValidation = true;\r
461         }\r
462         catch (...) {\r
463             out.destroy();\r
464             throw;\r
465         }\r
466 \r
467         // Lock for writing and repeat the search to avoid duplication.\r
468         m_lock->wrlock();\r
469         SharedLock shared(m_lock, false);\r
470         if (m_hashtable.count(key)) {\r
471             delete session;\r
472             // We're using an existing session entry, so we have to switch back to full validation.\r
473             localValidation = false;\r
474             session = m_hashtable[key];\r
475             session->lock();\r
476         }\r
477         else {\r
478             m_hashtable[key]=session;\r
479             session->lock();\r
480         }\r
481     }\r
482     else {\r
483         // Save off and lock the session.\r
484         session = i->second;\r
485         session->lock();\r
486         m_lock->unlock();\r
487         \r
488         m_log.debug("session found locally, validating it for use");\r
489     }\r
490 \r
491     // Verify currency and update the timestamp. If the local switch is false, we also update the access time.\r
492     try {\r
493         session->validate(application, client_addr, timeout, localValidation);\r
494     }\r
495     catch (...) {\r
496         session->unlock();\r
497         throw;\r
498     }\r
499     \r
500     return session;\r
501 }\r
502 \r
503 void RemotedCache::remove(const char* key, const Application& application, const char* client_addr)\r
504 {\r
505     // Take care of local copy.\r
506     dormant(key);\r
507     \r
508     // Now remote...\r
509     DDF in("remove::"REMOTED_SESSION_CACHE);\r
510     DDFJanitor jin(in);\r
511     in.structure();\r
512     in.addmember("key").string(key);\r
513     in.addmember("application_id").string(application.getId());\r
514     in.addmember("client_addr").string(client_addr);\r
515     \r
516     DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
517     out.destroy();\r
518 }\r
519 \r
520 void RemotedCache::dormant(const char* key)\r
521 {\r
522 #ifdef _DEBUG\r
523     xmltooling::NDC ndc("dormant");\r
524 #endif\r
525 \r
526     m_log.debug("deleting local copy of session (%s)", key);\r
527 \r
528     // lock the cache for writing, which means we know nobody is sitting in find()\r
529     m_lock->wrlock();\r
530 \r
531     // grab the entry from the table\r
532     map<string,RemotedSession*>::const_iterator i=m_hashtable.find(key);\r
533     if (i==m_hashtable.end()) {\r
534         m_lock->unlock();\r
535         return;\r
536     }\r
537 \r
538     // ok, remove the entry and lock it\r
539     RemotedSession* entry=i->second;\r
540     m_hashtable.erase(key);\r
541     entry->lock();\r
542     \r
543     // unlock the cache\r
544     m_lock->unlock();\r
545 \r
546     // we can release the cache entry lock because we know we're not in the cache anymore\r
547     entry->unlock();\r
548 \r
549     delete entry;\r
550 }\r
551 \r
552 void RemotedCache::cleanup()\r
553 {\r
554 #ifdef _DEBUG\r
555     xmltooling::NDC ndc("cleanup");\r
556 #endif\r
557 \r
558     int rerun_timer = 0;\r
559     Mutex* mutex = Mutex::create();\r
560   \r
561     // Load our configuration details...\r
562     static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);\r
563     const XMLCh* tag=m_root->getAttributeNS(NULL,cleanupInterval);\r
564     if (tag && *tag)\r
565         rerun_timer = XMLString::parseInt(tag);\r
566 \r
567     if (rerun_timer <= 0)\r
568         rerun_timer = 300;        // rerun every 5 minutes\r
569 \r
570     mutex->lock();\r
571 \r
572     m_log.info("cleanup thread started...run every %d secs; timeout after %d secs", rerun_timer, m_cacheTimeout);\r
573 \r
574     while (!shutdown) {\r
575         shutdown_wait->timedwait(mutex,rerun_timer);\r
576         if (shutdown)\r
577             break;\r
578 \r
579         // Ok, let's run through the cleanup process and clean out\r
580         // really old sessions.  This is a two-pass process.  The\r
581         // first pass is done holding a read-lock while we iterate over\r
582         // the cache.  The second pass doesn't need a lock because\r
583         // the 'deletes' will lock the cache.\r
584     \r
585         // Pass 1: iterate over the map and find all entries that have not been\r
586         // used in X hours\r
587         vector<string> stale_keys;\r
588         time_t stale = time(NULL) - m_cacheTimeout;\r
589     \r
590         m_lock->rdlock();\r
591         for (map<string,RemotedSession*>::const_iterator i=m_hashtable.begin(); i!=m_hashtable.end(); ++i) {\r
592             // If the last access was BEFORE the stale timeout...\r
593             i->second->lock();\r
594             time_t last=i->second->lastAccess();\r
595             i->second->unlock();\r
596             if (last < stale)\r
597                 stale_keys.push_back(i->first);\r
598         }\r
599         m_lock->unlock();\r
600     \r
601         if (!stale_keys.empty()) {\r
602             m_log.info("purging %d old sessions", stale_keys.size());\r
603     \r
604             // Pass 2: walk through the list of stale entries and remove them from the cache\r
605             for (vector<string>::const_iterator j = stale_keys.begin(); j != stale_keys.end(); ++j)\r
606                 dormant(j->c_str());\r
607         }\r
608     }\r
609 \r
610     m_log.info("cleanup thread exiting");\r
611 \r
612     mutex->unlock();\r
613     delete mutex;\r
614     Thread::exit(NULL);\r
615 }\r
616 \r
617 void* RemotedCache::cleanup_fn(void* cache_p)\r
618 {\r
619     RemotedCache* cache = reinterpret_cast<RemotedCache*>(cache_p);\r
620 \r
621 #ifndef WIN32\r
622     // First, let's block all signals \r
623     Thread::mask_all_signals();\r
624 #endif\r
625 \r
626     // Now run the cleanup process.\r
627     cache->cleanup();\r
628     return NULL;\r
629 }\r
630 \r
631 /* These are currently unimplemented.\r
632 \r
633 void RemotedSession::addAttributes(const vector<Attribute*>& attributes)\r
634 {\r
635     DDF in("addAttributes::"REMOTED_SESSION_CACHE);\r
636     DDFJanitor jin(in);\r
637     in.structure();\r
638     in.addmember("key").string(m_key.c_str());\r
639     in.addmember("application_id").string(m_appId.c_str());\r
640 \r
641     DDF attr;\r
642     DDF attrs = in.addmember("attributes").list();\r
643     for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {\r
644         attr = (*a)->marshall();\r
645         attrs.add(attr);\r
646     }\r
647 \r
648     attr=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
649     DDFJanitor jout(attr);\r
650     \r
651     // Transfer ownership to us.\r
652     m_attributes.insert(m_attributes.end(), attributes.begin(), attributes.end());\r
653 }\r
654 \r
655 void RemotedSession::addAssertion(RootObject* assertion)\r
656 {\r
657     if (!assertion || !assertion->isAssertion())\r
658         throw FatalProfileException("Unknown object type passed to session cache for storage.");\r
659 \r
660     DDF in("addAssertion::"REMOTED_SESSION_CACHE);\r
661     DDFJanitor jin(in);\r
662     in.structure();\r
663     in.addmember("key").string(m_key.c_str());\r
664     in.addmember("application_id").string(m_appId.c_str());\r
665     \r
666     ostringstream os;\r
667     os << *assertion;\r
668     string token(os.str());\r
669     auto_ptr_char tokenid(assertion->getID());\r
670     in.addmember("assertion_id").string(tokenid.get());\r
671     in.addmember("assertion").string(token.c_str());\r
672 \r
673     DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
674     out.destroy();\r
675     \r
676     // Add to local record and token map.\r
677     // Next attempt to find and lock session will refresh from remote store anyway.\r
678     m_obj["assertions"].addmember(tokenid.get()).string(token.c_str());\r
679     m_ids.clear();\r
680     m_tokens[tokenid.get()] = assertion;\r
681 }\r
682 \r
683 */