Nearly testable draft of storage-based cache, minus remoting.
[shibboleth/cpp-sp.git] / shibsp / impl / RemotedSessionCache.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 /**\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 "attribute/Attribute.h"\r
29 #include "remoting/ListenerService.h"\r
30 #include "util/SPConstants.h"\r
31 \r
32 #include <sstream>\r
33 #include <xmltooling/XMLToolingConfig.h>\r
34 #include <xmltooling/util/XMLHelper.h>\r
35 \r
36 using namespace shibsp;\r
37 using namespace opensaml::saml2md;\r
38 using namespace opensaml;\r
39 using namespace xmltooling;\r
40 using namespace std;\r
41 \r
42 namespace shibsp {\r
43 \r
44     class RemotedSession : public virtual Session\r
45     {\r
46     public:\r
47         RemotedSession(const char* key, DDF& obj) : m_key(key), m_obj(obj), m_nameid(NULL) {\r
48             const char* nameid = obj["nameid"].string();\r
49             if (!nameid)\r
50                 throw FatalProfileException("NameID missing from remotely cached session.");\r
51             \r
52             // Parse and bind the document into an XMLObject.\r
53             istringstream instr(nameid);\r
54             DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
55             XercesJanitor<DOMDocument> janitor(doc);\r
56             auto_ptr<saml2::NameID> n(saml2::NameIDBuilder::buildNameID());\r
57             n->unmarshall(doc->getDocumentElement(), true);\r
58             janitor.release();\r
59             \r
60             // TODO: Process attributes...\r
61 \r
62             m_nameid = n.release();\r
63         }\r
64         \r
65         ~RemotedSession() {\r
66             m_obj.destroy();\r
67             delete m_nameid;\r
68             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());\r
69             for_each(m_tokens.begin(), m_tokens.end(), xmltooling::cleanup_pair<string,RootObject>());\r
70         }\r
71         \r
72         Lockable* lock() {\r
73             return this;\r
74         }\r
75         void unlock() {\r
76             delete this;\r
77         }\r
78         \r
79         const char* getClientAddress() const {\r
80             return m_obj["client_address"].string();\r
81         }\r
82         const char* getEntityID() const {\r
83             return m_obj["entity_id"].string();\r
84         }\r
85         const char* getAuthnInstant() const {\r
86             return m_obj["authn_instant"].string();\r
87         }\r
88         const opensaml::saml2::NameID& getNameID() const {\r
89             return *m_nameid;\r
90         }\r
91         const char* getSessionIndex() const {\r
92             return m_obj["session_index"].string();\r
93         }\r
94         const char* getAuthnContextClassRef() const {\r
95             return m_obj["authncontext_class"].string();\r
96         }\r
97         const char* getAuthnContextDeclRef() const {\r
98             return m_obj["authncontext_decl"].string();\r
99         }\r
100         const vector<const Attribute*>& getAttributes() const {\r
101             return m_attributes;\r
102         }\r
103         const vector<const char*>& getAssertionIDs() const {\r
104             if (m_ids.empty()) {\r
105                 DDF id = m_obj["assertion_ids"].first();\r
106                 while (id.isstring()) {\r
107                     m_ids.push_back(id.string());\r
108                     id = id.next();\r
109                 }\r
110             }\r
111             return m_ids;\r
112         }\r
113         \r
114         void addAttributes(const vector<Attribute*>& attributes);\r
115         const RootObject* getAssertion(const char* id) const;\r
116         void addAssertion(RootObject* assertion);\r
117 \r
118     private:\r
119         string m_key;\r
120         mutable DDF m_obj;\r
121         saml2::NameID* m_nameid;\r
122         vector<const Attribute*> m_attributes;\r
123         mutable vector<const char*> m_ids;\r
124         mutable map<string,RootObject*> m_tokens;\r
125     };\r
126     \r
127     class RemotedCache : public SessionCache\r
128     {\r
129     public:\r
130         RemotedCache(const DOMElement* e);\r
131         ~RemotedCache() {}\r
132     \r
133         string insert(\r
134             time_t expires,\r
135             const Application& application,\r
136             const char* client_addr,\r
137             const saml2md::EntityDescriptor* issuer,\r
138             const saml2::NameID& nameid,\r
139             const char* authn_instant=NULL,\r
140             const char* session_index=NULL,\r
141             const char* authncontext_class=NULL,\r
142             const char* authncontext_decl=NULL,\r
143             const RootObject* ssoToken=NULL,\r
144             const vector<Attribute*>* attributes=NULL\r
145             );\r
146         Session* find(const char* key, const Application& application, const char* client_addr=NULL, time_t timeout=0);\r
147         void remove(const char* key, const Application& application, const char* client_addr);\r
148     };\r
149 \r
150     SessionCache* SHIBSP_DLLLOCAL RemotedCacheFactory(const DOMElement* const & e)\r
151     {\r
152         return new RemotedCache(e);\r
153     }\r
154 }\r
155 \r
156 void RemotedSession::addAttributes(const vector<Attribute*>& attributes)\r
157 {\r
158     DDF in("addAttributes::"REMOTED_SESSION_CACHE);\r
159     DDFJanitor jin(in);\r
160     in.structure();\r
161     in.addmember("key").string(m_key.c_str());\r
162 \r
163     DDF attr;\r
164     DDF attrs = in.addmember("attributes").list();\r
165     for (vector<Attribute*>::const_iterator a=attributes.begin(); a!=attributes.end(); ++a) {\r
166         attr = (*a)->marshall();\r
167         attrs.add(attr);\r
168     }\r
169 \r
170     attr=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
171     DDFJanitor jout(attr);\r
172     \r
173     // Transfer ownership to us.\r
174     m_attributes.insert(m_attributes.end(), attributes.begin(), attributes.end());\r
175 }\r
176 \r
177 const RootObject* RemotedSession::getAssertion(const char* id) const\r
178 {\r
179     map<string,RootObject*>::const_iterator i = m_tokens.find(id);\r
180     if (i!=m_tokens.end())\r
181         return i->second;\r
182     \r
183     DDF in("getAssertion::"REMOTED_SESSION_CACHE);\r
184     DDFJanitor jin(in);\r
185     in.structure();\r
186     in.addmember("key").string(m_key.c_str());\r
187     in.addmember("assertion_id").string(id);\r
188 \r
189     DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
190     DDFJanitor jout(out);\r
191     \r
192     const char* tokenstr = out["assertion"].string();\r
193     if (!tokenstr)\r
194         return NULL;\r
195     \r
196     // Parse and bind the document into an XMLObject.\r
197     istringstream instr(tokenstr);\r
198     DOMDocument* doc = XMLToolingConfig::getConfig().getParser().parse(instr); \r
199     XercesJanitor<DOMDocument> janitor(doc);\r
200     auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));\r
201     janitor.release();\r
202     \r
203     RootObject* token = dynamic_cast<RootObject*>(xmlObject.get());\r
204     if (!token || !token->isAssertion())\r
205         throw FatalProfileException("Remoted call for cached assertion returned an unknown object type.");\r
206 \r
207     // Transfer ownership to us.\r
208     xmlObject.release();\r
209     m_tokens[id]=token;\r
210     return token;\r
211 }\r
212 \r
213 void RemotedSession::addAssertion(RootObject* assertion)\r
214 {\r
215     if (!assertion || !assertion->isAssertion())\r
216         throw FatalProfileException("Unknown object type passed to session cache for storage.");\r
217 \r
218     DDF in("addAssertion::"REMOTED_SESSION_CACHE);\r
219     DDFJanitor jin(in);\r
220     in.structure();\r
221     in.addmember("key").string(m_key.c_str());\r
222     \r
223     ostringstream os;\r
224     os << *assertion;\r
225     in.addmember("assertion").string(os.str().c_str());\r
226 \r
227     DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
228     out.destroy();\r
229     delete assertion;\r
230 }\r
231 \r
232 RemotedCache::RemotedCache(const DOMElement* e) : SessionCache(e)\r
233 {\r
234     if (!SPConfig::getConfig().getServiceProvider()->getListenerService())\r
235         throw ConfigurationException("RemotedCacheService requires a ListenerService, but none available.");\r
236 }\r
237 \r
238 string RemotedCache::insert(\r
239     time_t expires,\r
240     const Application& application,\r
241     const char* client_addr,\r
242     const saml2md::EntityDescriptor* issuer,\r
243     const saml2::NameID& nameid,\r
244     const char* authn_instant,\r
245     const char* session_index,\r
246     const char* authncontext_class,\r
247     const char* authncontext_decl,\r
248     const RootObject* ssoToken,\r
249     const vector<Attribute*>* attributes\r
250     )\r
251 {\r
252     DDF in("insert::"REMOTED_SESSION_CACHE);\r
253     DDFJanitor jin(in);\r
254     in.structure();\r
255     if (expires) {\r
256 #ifndef HAVE_GMTIME_R\r
257         struct tm* ptime=gmtime(&expires);\r
258 #else\r
259         struct tm res;\r
260         struct tm* ptime=gmtime_r(&expires,&res);\r
261 #endif\r
262         char timebuf[32];\r
263         strftime(timebuf,32,"%Y-%m-%dT%H:%M:%SZ",ptime);\r
264         in.addmember("expires").string(timebuf);\r
265     }\r
266     in.addmember("application_id").string(application.getId());\r
267     in.addmember("client_address").string(client_addr);\r
268     if (issuer) {\r
269         auto_ptr_char provid(issuer->getEntityID());\r
270         in.addmember("entity_id").string(provid.get());\r
271     }\r
272     if (authn_instant)\r
273         in.addmember("authn_instant").string(authn_instant);\r
274     if (session_index)\r
275         in.addmember("session_index").string(session_index);\r
276     if (authncontext_class)\r
277         in.addmember("authncontext_class").string(authncontext_class);\r
278     if (authncontext_decl)\r
279         in.addmember("authncontext_decl").string(authncontext_decl);\r
280     \r
281     ostringstream namestr;\r
282     namestr << nameid;\r
283     in.addmember("nameid").string(namestr.str().c_str());\r
284 \r
285     if (ssoToken) {\r
286         ostringstream tokenstr;\r
287         tokenstr << *ssoToken;\r
288         auto_ptr_char tokenid(ssoToken->getID());\r
289         in.addmember("assertion_ids").list().add(DDF(NULL).string(tokenid.get()));\r
290         in.addmember("assertions").list().add(DDF(NULL).string(tokenstr.str().c_str()));\r
291     }\r
292     \r
293     if (attributes) {\r
294         DDF attr;\r
295         DDF attrs = in.addmember("attributes").list();\r
296         for (vector<Attribute*>::const_iterator a=attributes->begin(); a!=attributes->end(); ++a) {\r
297             attr = (*a)->marshall();\r
298             attrs.add(attr);\r
299         }\r
300     }\r
301 \r
302     DDF out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
303     DDFJanitor jout(out);\r
304     if (out["key"].isstring()) {\r
305         for_each(attributes->begin(), attributes->end(), xmltooling::cleanup<Attribute>());\r
306         return out["key"].string();\r
307     }\r
308     throw RetryableProfileException("A remoted cache insertion operation did not return a usable session key.");\r
309 }\r
310 \r
311 Session* RemotedCache::find(const char* key, const Application& application, const char* client_addr, time_t timeout)\r
312 {\r
313     DDF in("find::"REMOTED_SESSION_CACHE), out;\r
314     DDFJanitor jin(in);\r
315     in.structure();\r
316     in.addmember("key").string(key);\r
317     in.addmember("application_id").string(application.getId());\r
318     in.addmember("client_address").string(client_addr);\r
319     \r
320     try {\r
321         out=SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
322         if (!out.isstruct()) {\r
323             out.destroy();\r
324             return NULL;\r
325         }\r
326         \r
327         // Wrap the results in a stub entry and return it to the caller.\r
328         return new RemotedSession(key, out);\r
329     }\r
330     catch (...) {\r
331         out.destroy();\r
332         throw;\r
333     }\r
334 }\r
335 \r
336 void RemotedCache::remove(const char* key, const Application& application, const char* client_addr)\r
337 {\r
338     DDF in("remove::"REMOTED_SESSION_CACHE);\r
339     DDFJanitor jin(in);\r
340     in.structure();\r
341     in.addmember("key").string(key);\r
342     in.addmember("application_id").string(application.getId());\r
343     in.addmember("client_address").string(client_addr);\r
344     \r
345     DDF out = SPConfig::getConfig().getServiceProvider()->getListenerService()->send(in);\r
346     out.destroy();\r
347 }\r