34454b952eacba5edf08778cc6e82be3ffb306b3
[shibboleth/cpp-sp.git] / shibsp / handler / impl / SessionHandler.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * SessionHandler.cpp
23  *
24  * Handler for dumping information about an active session.
25  */
26
27 #include "internal.h"
28 #include "Application.h"
29 #include "exceptions.h"
30 #include "ServiceProvider.h"
31 #include "SessionCache.h"
32 #include "SPRequest.h"
33 #include "attribute/Attribute.h"
34 #include "handler/SecuredHandler.h"
35
36 #include <ctime>
37
38 using namespace shibsp;
39 using namespace xmltooling;
40 using namespace std;
41
42 namespace shibsp {
43
44 #if defined (_MSC_VER)
45     #pragma warning( push )
46     #pragma warning( disable : 4250 )
47 #endif
48
49     class SHIBSP_API SessionHandler : public SecuredHandler
50     {
51     public:
52         SessionHandler(const DOMElement* e, const char* appId);
53         virtual ~SessionHandler() {}
54
55         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
56
57     private:
58         pair<bool,long> doHTML(SPRequest& request) const;
59         pair<bool,long> doJSON(SPRequest& request) const;
60
61         bool m_values;
62         string m_contentType;
63     };
64
65 #if defined (_MSC_VER)
66     #pragma warning( pop )
67 #endif
68
69     Handler* SHIBSP_DLLLOCAL SessionHandlerFactory(const pair<const DOMElement*,const char*>& p)
70     {
71         return new SessionHandler(p.first, p.second);
72     }
73
74 };
75
76 SessionHandler::SessionHandler(const DOMElement* e, const char* appId)
77     : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionHandler")), m_values(false)
78 {
79     pair<bool,const char*> prop = getString("contentType");
80     if (prop.first)
81         m_contentType = prop.second;
82     if (!m_contentType.empty() && m_contentType != "application/json" && m_contentType != "text/html")
83         throw ConfigurationException("Unsupported contentType property in Session Handler configuration.");
84
85     pair<bool,bool> flag = getBool("showAttributeValues");
86     if (flag.first)
87         m_values = flag.second;
88 }
89
90 namespace {
91     static ostream& json_safe(ostream& os, const char* buf)
92     {
93         os << '"';
94         for (; *buf; ++buf) {
95             switch (*buf) {
96                 case '\\':
97                 case '"':
98                     os << '\\';
99                     os << *buf;
100                     break;
101                 case '\b':
102                     os << "\\b";
103                     break;
104                 case '\t':
105                     os << "\\t";
106                     break;
107                 case '\n':
108                     os << "\\n";
109                     break;
110                 case '\f':
111                     os << "\\f";
112                     break;
113                 case '\r':
114                     os << "\\r";
115                     break;
116                 default:
117                     os << *buf;
118             }
119         }
120         os << '"';
121         return os;
122     }
123 };
124
125 pair<bool,long> SessionHandler::run(SPRequest& request, bool isHandler) const
126 {
127     // Check ACL in base class.
128     pair<bool,long> ret = SecuredHandler::run(request, isHandler);
129     if (ret.first)
130         return ret;
131     request.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT");
132     request.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0");
133     if (m_contentType == "application/json") {
134         request.setContentType(m_contentType.c_str());
135         return doJSON(request);
136     }
137     request.setContentType("text/html; charset=UTF-8");
138     return doHTML(request);
139 }
140
141 pair<bool,long> SessionHandler::doJSON(SPRequest& request) const
142 {
143     stringstream s;
144
145     Session* session = nullptr;
146     try {
147         session = request.getSession(); // caches the locked session in the request so it's unlocked automatically
148         if (!session) {
149             s << "{}" << endl;
150             return make_pair(true, request.sendResponse(s));
151         }
152     }
153     catch (std::exception& ex) {
154         m_log.info("exception accessing user session: %s", ex.what());
155         s << "{}" << endl;
156         return make_pair(true, request.sendError(s));
157     }
158
159     s << "{ ";
160     s << "\"expiration\": ";
161     if (session->getExpiration())
162         s << ((session->getExpiration() - time(nullptr)) / 60);
163     else
164         s << 0;
165
166     if (session->getClientAddress()) {
167         s << ", \"client_address\": ";
168         json_safe(s, session->getClientAddress());
169     }
170
171     if (session->getProtocol()) {
172         s << ", \"protocol\": ";
173         json_safe(s, session->getProtocol());
174     }
175
176     pair<bool,bool> stdvars = request.getRequestSettings().first->getBool("exportStdVars");
177     if (!stdvars.first || stdvars.second) {
178         if (session->getEntityID()) {
179             s << ", \"identity_provider\": ";
180             json_safe(s, session->getEntityID());
181         }
182
183         if (session->getAuthnInstant()) {
184             s << ", \"authn_instant\": ";
185             json_safe(s, session->getAuthnInstant());
186         }
187
188         if (session->getAuthnContextClassRef()) {
189             s << ", \"authncontext_class\": ";
190             json_safe(s, session->getAuthnContextClassRef());
191         }
192
193         if (session->getAuthnContextDeclRef()) {
194             s << ", \"authncontext_decl\": ";
195             json_safe(s, session->getAuthnContextDeclRef());
196         }
197
198     }
199
200     /*
201         attributes: [ { "name": "foo", "values" : count } ]
202
203         attributes: [
204             { "name": "foo", "values": [ "val", "val" ] }
205         ]
206     */
207
208     const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
209     if (!attributes.empty()) {
210         s << ", \"attributes\": [ ";
211         string key;
212         vector<string>::size_type count=0;
213         for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
214             if (a->first != key) {
215                 // We're starting a new attribute.
216                 if (a != attributes.begin()) {
217                     // Need to close out the previous.
218                     if (m_values) {
219                         s << " ] }, ";
220                     }
221                     else {
222                         s << ", \"values\": " << count << " }, ";
223                         count = 0;
224                     }
225                 }
226                 s << "{ \"name\": ";
227                 json_safe(s, a->first.c_str());
228             }
229
230             if (m_values) {
231                 const vector<string>& vals = a->second->getSerializedValues();
232                 for (vector<string>::const_iterator v = vals.begin(); v!=vals.end(); ++v) {
233                     if (v != vals.begin() || a->first == key) {
234                         s << ", ";
235                     }
236                     else {
237                         s << ", \"values\": [ ";
238                     }
239                     json_safe(s, v->c_str());
240                 }
241             }
242             else {
243                 count += a->second->getSerializedValues().size();
244             }
245             key = a->first;
246         }
247
248         if (m_values)
249             s << " ] } ";
250         else
251             s << ", \"values\": " << count << " }";
252         s << " ]";
253     }
254
255     s << " }" << endl;
256     return make_pair(true, request.sendResponse(s));
257 }
258
259 pair<bool,long> SessionHandler::doHTML(SPRequest& request) const
260 {
261     stringstream s;
262     s << "<html><head><title>Session Summary</title></head><body><pre>" << endl;
263
264     Session* session = nullptr;
265     try {
266         session = request.getSession(); // caches the locked session in the request so it's unlocked automatically
267         if (!session) {
268             s << "A valid session was not found.</pre></body></html>" << endl;
269             return make_pair(true, request.sendResponse(s));
270         }
271     }
272     catch (std::exception& ex) {
273         s << "Exception while retrieving active session:" << endl
274             << '\t' << ex.what() << "</pre></body></html>" << endl;
275         return make_pair(true, request.sendResponse(s));
276     }
277
278     s << "<u>Miscellaneous</u>" << endl;
279
280     s << "<strong>Session Expiration (barring inactivity):</strong> ";
281     if (session->getExpiration())
282         s << ((session->getExpiration() - time(nullptr)) / 60) << " minute(s)" << endl;
283     else
284         s << "Infinite" << endl;
285
286     s << "<strong>Client Address:</strong> " << (session->getClientAddress() ? session->getClientAddress() : "(none)") << endl;
287     s << "<strong>SSO Protocol:</strong> " << (session->getProtocol() ? session->getProtocol() : "(none)") << endl;
288
289     pair<bool,bool> stdvars = request.getRequestSettings().first->getBool("exportStdVars");
290     if (!stdvars.first || stdvars.second) {
291         s << "<strong>Identity Provider:</strong> " << (session->getEntityID() ? session->getEntityID() : "(none)") << endl;
292         s << "<strong>Authentication Time:</strong> " << (session->getAuthnInstant() ? session->getAuthnInstant() : "(none)") << endl;
293         s << "<strong>Authentication Context Class:</strong> " << (session->getAuthnContextClassRef() ? session->getAuthnContextClassRef() : "(none)") << endl;
294         s << "<strong>Authentication Context Decl:</strong> " << (session->getAuthnContextDeclRef() ? session->getAuthnContextDeclRef() : "(none)") << endl;
295     }
296
297     s << endl << "<u>Attributes</u>" << endl;
298
299     string key;
300     vector<string>::size_type count=0;
301     const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
302     for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
303         if (a->first != key) {
304             if (a != attributes.begin()) {
305                 if (m_values)
306                     s << endl;
307                 else {
308                     s << count << " value(s)" << endl;
309                     count = 0;
310                 }
311             }
312             s << "<strong>" << a->first << "</strong>: ";
313         }
314
315         if (m_values) {
316             const vector<string>& vals = a->second->getSerializedValues();
317             for (vector<string>::const_iterator v = vals.begin(); v!=vals.end(); ++v) {
318                 if (v != vals.begin() || a->first == key)
319                     s << ';';
320                 string::size_type pos = v->find_first_of(';',string::size_type(0));
321                 if (pos!=string::npos) {
322                     string value(*v);
323                     for (; pos != string::npos; pos = value.find_first_of(';',pos)) {
324                         value.insert(pos, "\\");
325                         pos += 2;
326                     }
327                     s << value;
328                 }
329                 else {
330                     s << *v;
331                 }
332             }
333         }
334         else {
335             count += a->second->getSerializedValues().size();
336         }
337         key = a->first;
338     }
339
340     if (!m_values && !attributes.empty())
341         s << count << " value(s)" << endl;
342
343     s << "</pre></body></html>";
344     return make_pair(true, request.sendResponse(s));
345 }