Shift some SAML intelligence out of cache API, start on SS-based cache.
[shibboleth/sp.git] / shibsp / impl / StorageServiceSessionCache.cpp
1 /*\r
2  *  Copyright 2001-2005 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 /** StorageServiceSessionCache.cpp\r
18  * \r
19  * StorageService-based SessionCache implementation\r
20  */\r
21 \r
22 #include "internal.h"\r
23 #include "Application.h"\r
24 #include "exceptions.h"\r
25 #include "ServiceProvider.h"\r
26 #include "SessionCache.h"\r
27 #include "TransactionLog.h"\r
28 #include "attribute/Attribute.h"\r
29 #include "remoting/ListenerService.h"\r
30 #include "util/SPConstants.h"\r
31 \r
32 #include <log4cpp/Category.hh>\r
33 #include <saml/SAMLConfig.h>\r
34 #include <xmltooling/util/NDC.h>\r
35 #include <xmltooling/util/StorageService.h>\r
36 #include <xmltooling/util/XMLHelper.h>\r
37 #include <xercesc/util/XMLUniDefs.hpp>\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 SSCache;\r
49     class StoredSession : public virtual Session\r
50     {\r
51     public:\r
52         StoredSession(SSCache* cache, DDF& obj) : m_cache(cache), m_obj(obj) {}\r
53         \r
54         ~StoredSession();\r
55         \r
56         Lockable* lock() {\r
57             return this;\r
58         }\r
59         void unlock() {\r
60             delete this;\r
61         }\r
62         \r
63         const char* getClientAddress() const {\r
64             return m_obj["client_address"].string();\r
65         }\r
66         const char* getEntityID() const {\r
67             return m_obj["entity_id"].string();\r
68         }\r
69         const char* getAuthnInstant() const {\r
70             return m_obj["authn_instant"].string();\r
71         }\r
72         const opensaml::saml2::NameID& getNameID() const {\r
73             return *m_nameid;\r
74         }\r
75         const char* getSessionIndex() const {\r
76             return m_obj["session_index"].string();\r
77         }\r
78         const char* getAuthnContextClassRef() const {\r
79             return m_obj["authncontext_class"].string();\r
80         }\r
81         const char* getAuthnContextDeclRef() const {\r
82             return m_obj["authncontext_decl"].string();\r
83         }\r
84         const vector<const Attribute*>& getAttributes() const {\r
85             return m_attributes;\r
86         }\r
87         const vector<const char*>& getAssertionIDs() const {\r
88             if (m_ids.empty()) {\r
89                 DDF id = m_obj["assertion_ids"].first();\r
90                 while (id.isstring()) {\r
91                     m_ids.push_back(id.string());\r
92                     id = id.next();\r
93                 }\r
94             }\r
95             return m_ids;\r
96         }\r
97         \r
98         void addAttributes(const vector<Attribute*>& attributes);\r
99         const RootObject* getAssertion(const char* id) const;\r
100         void addAssertion(RootObject* assertion);\r
101 \r
102     private:\r
103         DDF m_obj;\r
104         saml2::NameID* m_nameid;\r
105         vector<const Attribute*> m_attributes;\r
106         mutable vector<const char*> m_ids;\r
107         mutable map<string,RootObject*> m_tokens;\r
108         time_t m_sessionCreated,m_lastAccess;\r
109         SSCache* m_cache;\r
110     };\r
111     \r
112     class SSCache : public SessionCache\r
113     {\r
114     public:\r
115         SSCache(const DOMElement* e);\r
116         ~SSCache() {}\r
117     \r
118         DDF receive(const DDF& in);\r
119         \r
120         string insert(\r
121             const Application& application,\r
122             const char* client_addr,\r
123             const saml2md::EntityDescriptor* issuer,\r
124             const saml2::NameID& nameid,\r
125             const char* authn_instant=NULL,\r
126             const char* session_index=NULL,\r
127             const char* authncontext_class=NULL,\r
128             const char* authncontext_decl=NULL,\r
129             const RootObject* ssoToken=NULL,\r
130             const vector<Attribute*>* attributes=NULL\r
131             );\r
132         Session* find(const char* key, const Application& application, const char* client_addr);\r
133         void remove(const char* key, const Application& application, const char* client_addr);\r
134 \r
135         Category& m_log;\r
136         StorageService* m_storage;\r
137     };\r
138 \r
139     SessionCache* SHIBSP_DLLLOCAL StorageServiceCacheFactory(const DOMElement* const & e)\r
140     {\r
141         return new SSCache(e);\r
142     }\r
143 \r
144     static const XMLCh storageService[] =   UNICODE_LITERAL_14(s,t,o,r,a,g,e,S,e,r,v,i,c,e);\r
145 }\r
146 \r
147 StoredSession::~StoredSession()\r
148 {\r
149     m_obj.destroy();\r
150     delete m_nameid;\r
151     for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
152     for_each(m_tokens.begin(), m_tokens.end(), xmltooling::cleanup_pair<string,RootObject>());\r
153 }\r
154 \r
155 void StoredSession::addAttributes(const vector<Attribute*>& attributes)\r
156 {\r
157 #ifdef _DEBUG\r
158     xmltooling::NDC ndc("addAttributes");\r
159 #endif\r
160 \r
161     m_cache->m_log.debug("adding attributes to session (%s)", m_obj.name());\r
162 \r
163     DDF attr;\r
164     DDF attrs = m_obj["attributes"];\r
165     if (!attrs.islist())\r
166         attrs = m_obj.addmember("attributes").list();\r
167     for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {\r
168         attr = (*a)->marshall();\r
169         attrs.add(attr);\r
170     }\r
171     \r
172     ostringstream record;\r
173     record << m_obj;\r
174     \r
175     if (!m_cache->m_storage->updateText(m_obj["application_id"].string(), m_obj.name(), record.str().c_str())) {\r
176         // Roll back modification to record.\r
177         vector<Attribute*>::size_type count = attributes.size();\r
178         while (count--)\r
179             attrs.last().destroy();            \r
180         m_cache->m_log.error("updateText failed on StorageService for session (%s)", m_obj.name());\r
181         throw IOException("Unable to update stored session.");\r
182     }\r
183 \r
184     // Transfer ownership to us.\r
185     m_attributes.insert(m_attributes.end(), attributes.begin(), attributes.end());\r
186 \r
187     TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
188     Locker locker(xlog);\r
189     xlog->log.infoStream() <<\r
190         "Added the following attributes to session (ID: " <<\r
191             m_obj.name() <<\r
192         ") for (applicationId: " <<\r
193             m_obj["application_id"].string() <<\r
194         ") {";\r
195     for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a)\r
196         xlog->log.infoStream() << "\t" << (*a)->getId() << " (" << (*a)->valueCount() << " values)";\r
197     xlog->log.info("}");\r
198 }\r
199 \r
200 const RootObject* StoredSession::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     // Parse and bind the document into an XMLObject.\r
207     const char* tokenstr = m_obj["assertions"][id].string();\r
208     if (!tokenstr)\r
209         throw FatalProfileException("Assertion not found in cache.");\r
210     istringstream instr(tokenstr);\r
211     DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
212     XercesJanitor<DOMDocument> janitor(doc);\r
213     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));\r
214     janitor.release();\r
215     \r
216     RootObject* token = dynamic_cast<RootObject*>(xmlObject.get());\r
217     if (!token || !token->isAssertion())\r
218         throw FatalProfileException("Request for cached assertion returned an unknown object type.");\r
219 \r
220     // Transfer ownership to us.\r
221     xmlObject.release();\r
222     m_tokens[id]=token;\r
223     return token;\r
224 }\r
225 \r
226 void StoredSession::addAssertion(RootObject* assertion)\r
227 {\r
228 #ifdef _DEBUG\r
229     xmltooling::NDC ndc("addAssertion");\r
230 #endif\r
231     \r
232     if (!assertion || !assertion->isAssertion())\r
233         throw FatalProfileException("Unknown object type passed to session for storage.");\r
234 \r
235     auto_ptr_char id(assertion->getID());\r
236 \r
237     m_cache->m_log.debug("adding assertion (%s) to session (%s)", id.get(), m_obj.name());\r
238 \r
239     ostringstream os;\r
240     os << *assertion;\r
241     \r
242     DDF tokens = m_obj["assertions"];\r
243     if (!tokens.isstruct())\r
244         tokens = m_obj.addmember("assertions").structure();\r
245     tokens = tokens.addmember(id.get()).string(os.str().c_str());\r
246 \r
247     ostringstream record;\r
248     record << m_obj;\r
249     \r
250     if (!m_cache->m_storage->updateText(m_obj["application_id"].string(), m_obj.name(), record.str().c_str())) {\r
251         // Roll back modification to record.\r
252         tokens.destroy();\r
253         m_cache->m_log.error("updateText failed on StorageService for session (%s)", m_obj.name());\r
254         throw IOException("Unable to update stored session.");\r
255     }\r
256 \r
257     delete assertion;\r
258 \r
259     TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
260     Locker locker(xlog);\r
261     xlog->log.info(\r
262         "Added assertion (ID: %s) to session for (applicationId: %s) with (ID: %s)",\r
263         id.get(), m_obj["application_id"].string(), m_obj.name()\r
264         );\r
265 }\r
266 \r
267 SSCache::SSCache(const DOMElement* e)\r
268     : SessionCache(e), m_log(Category::getInstance(SHIBSP_LOGCAT".SessionCache")), m_storage(NULL)\r
269 {\r
270     // TODO: assign storage service\r
271 }\r
272 \r
273 string SSCache::insert(\r
274     const Application& application,\r
275     const char* client_addr,\r
276     const saml2md::EntityDescriptor* issuer,\r
277     const saml2::NameID& nameid,\r
278     const char* authn_instant,\r
279     const char* session_index,\r
280     const char* authncontext_class,\r
281     const char* authncontext_decl,\r
282     const RootObject* ssoToken,\r
283     const vector<Attribute*>* attributes\r
284     )\r
285 {\r
286 #ifdef _DEBUG\r
287     xmltooling::NDC ndc("insert");\r
288 #endif\r
289 \r
290     m_log.debug("creating new session");\r
291 \r
292     auto_ptr_char key(SAMLConfig::getConfig().generateIdentifier());\r
293 \r
294     time_t created = time(NULL);\r
295 #ifndef HAVE_GMTIME_R\r
296     struct tm* ptime=gmtime(&created);\r
297 #else\r
298     struct tm res;\r
299     struct tm* ptime=gmtime_r(&created,&res);\r
300 #endif\r
301     char timebuf[32];\r
302     strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
303 \r
304     // Store session properties in DDF.\r
305     DDF obj = DDF(key.get()).structure();\r
306     obj.addmember("created").string(timebuf);\r
307     obj.addmember("client_address").string(client_addr);\r
308     obj.addmember("application_id").string(application.getId());\r
309     if (issuer) {\r
310         auto_ptr_char entity_id(issuer->getEntityID());\r
311         obj.addmember("entity_id").string(entity_id.get());\r
312     }\r
313     if (authn_instant)\r
314         obj.addmember("authn_instant").string(authn_instant);\r
315     if (session_index)\r
316         obj.addmember("session_index").string(session_index);\r
317     if (authncontext_class)\r
318         obj.addmember("authncontext_class").string(authncontext_class);\r
319     if (authncontext_decl)\r
320         obj.addmember("authncontext_decl").string(authncontext_decl);\r
321 \r
322     ostringstream namestr;\r
323     namestr << nameid;\r
324     obj.addmember("nameid").string(namestr.str().c_str());\r
325     \r
326     if (ssoToken) {\r
327         ostringstream tokenstr;\r
328         tokenstr << *ssoToken;\r
329         auto_ptr_char tokenid(ssoToken->getID());\r
330         obj.addmember("assertions").structure().addmember(tokenid.get()).string(tokenstr.str().c_str());\r
331     }\r
332     \r
333     if (attributes) {\r
334         DDF attr;\r
335         DDF attrlist = obj.addmember("attributes").list();\r
336         for (vector<Attribute*>::const_iterator a=attributes->begin(); a!=attributes->end(); ++a) {\r
337             attr = (*a)->marshall();\r
338             attrlist.add(attr);\r
339         }\r
340     }\r
341     \r
342     ostringstream record;\r
343     record << obj;\r
344     \r
345     m_log.debug("storing new session...");\r
346     m_storage->createText(application.getId(), key.get(), record.str().c_str(), created + m_cacheTimeout);\r
347     const char* pid = obj["entity_id"].string();\r
348     m_log.debug("new session created: SessionID (%s) IdP (%s) Address (%s)", key.get(), pid ? pid : "none", client_addr);\r
349 \r
350     // Transaction Logging\r
351     auto_ptr_char name(nameid.getName());\r
352     TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
353     Locker locker(xlog);\r
354     xlog->log.infoStream() <<\r
355         "New session (ID: " <<\r
356             key.get() <<\r
357         ") with (applicationId: " <<\r
358             application.getId() <<\r
359         ") for principal from (IdP: " <<\r
360             (pid ? pid : "none") <<\r
361         ") at (ClientAddress: " <<\r
362             client_addr <<\r
363         ") with (NameIdentifier: " <<\r
364             name.get() <<\r
365         ")";\r
366     \r
367     return key.get();\r
368 }\r
369 \r
370 Session* SSCache::find(const char* key, const Application& application, const char* client_addr)\r
371 {\r
372     return NULL;\r
373 }\r
374 \r
375 void SSCache::remove(const char* key, const Application& application, const char* client_addr)\r
376 {\r
377 #ifdef _DEBUG\r
378     xmltooling::NDC ndc("remove");\r
379 #endif\r
380 \r
381     m_log.debug("removing session (%s)", key);\r
382 \r
383     m_storage->deleteText(application.getId(), key);\r
384 \r
385     TransactionLog* xlog = SPConfig::getConfig().getServiceProvider()->getTransactionLog();\r
386     Locker locker(xlog);\r
387     xlog->log.info("Destroyed session (applicationId: %s) (ID: %s)", application.getId(), key);\r
388 }\r