SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-sp.git] / shibsp / handler / impl / SessionHandler.cpp
index 72951bf..ba8f71a 100644 (file)
@@ -1,22 +1,26 @@
-/*
- *  Copyright 2001-2007 Internet2
- * 
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the
+ * License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ * either express or implied. See the License for the specific
+ * language governing permissions and limitations under the License.
  */
 
 /**
  * SessionHandler.cpp
- * 
+ *
  * Handler for dumping information about an active session.
  */
 
@@ -27,7 +31,7 @@
 #include "SessionCache.h"
 #include "SPRequest.h"
 #include "attribute/Attribute.h"
-#include "handler/AbstractHandler.h"
+#include "handler/SecuredHandler.h"
 
 #include <ctime>
 
@@ -42,17 +46,7 @@ namespace shibsp {
     #pragma warning( disable : 4250 )
 #endif
 
-    class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
-    {
-    public:
-        short acceptNode(const DOMNode* node) const {
-            return FILTER_REJECT;
-        }
-    };
-
-    static SHIBSP_DLLLOCAL Blocker g_Blocker;
-
-    class SHIBSP_API SessionHandler : public AbstractHandler
+    class SHIBSP_API SessionHandler : public SecuredHandler
     {
     public:
         SessionHandler(const DOMElement* e, const char* appId);
@@ -61,8 +55,11 @@ namespace shibsp {
         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
 
     private:
+        pair<bool,long> doHTML(SPRequest& request) const;
+        pair<bool,long> doJSON(SPRequest& request) const;
+
         bool m_values;
-        set<string> m_acl;
+        string m_contentType;
     };
 
 #if defined (_MSC_VER)
@@ -77,60 +74,226 @@ namespace shibsp {
 };
 
 SessionHandler::SessionHandler(const DOMElement* e, const char* appId)
-    : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionHandler"), &g_Blocker), m_values(false)
+    : SecuredHandler(e, Category::getInstance(SHIBSP_LOGCAT ".SessionHandler")), m_values(false)
 {
-    pair<bool,const char*> acl = getString("acl");
-    if (acl.first) {
-        string aclbuf=acl.second;
-        int j = 0;
-        for (unsigned int i=0;  i < aclbuf.length();  i++) {
-            if (aclbuf.at(i)==' ') {
-                m_acl.insert(aclbuf.substr(j, i-j));
-                j = i+1;
-            }
-        }
-        m_acl.insert(aclbuf.substr(j, aclbuf.length()-j));
-    }
+    pair<bool,const char*> prop = getString("contentType");
+    if (prop.first)
+        m_contentType = prop.second;
+    if (!m_contentType.empty() && m_contentType != "application/json" && m_contentType != "text/html")
+        throw ConfigurationException("Unsupported contentType property in Session Handler configuration.");
 
     pair<bool,bool> flag = getBool("showAttributeValues");
     if (flag.first)
         m_values = flag.second;
 }
 
+namespace {
+    static ostream& json_safe(ostream& os, const char* buf)
+    {
+        os << '"';
+        for (; *buf; ++buf) {
+            switch (*buf) {
+                case '\\':
+                case '"':
+                    os << '\\';
+                    os << *buf;
+                    break;
+                case '\b':
+                    os << "\\b";
+                    break;
+                case '\t':
+                    os << "\\t";
+                    break;
+                case '\n':
+                    os << "\\n";
+                    break;
+                case '\f':
+                    os << "\\f";
+                    break;
+                case '\r':
+                    os << "\\r";
+                    break;
+                default:
+                    os << *buf;
+            }
+        }
+        os << '"';
+        return os;
+    }
+};
+
 pair<bool,long> SessionHandler::run(SPRequest& request, bool isHandler) const
 {
-    if (!m_acl.empty() && m_acl.count(request.getRemoteAddr()) == 0) {
-        m_log.error("session handler request blocked from invalid address (%s)", request.getRemoteAddr().c_str());
-        istringstream msg("Session Handler Blocked");
-        return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_UNAUTHORIZED));
+    // Check ACL in base class.
+    pair<bool,long> ret = SecuredHandler::run(request, isHandler);
+    if (ret.first)
+        return ret;
+    request.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT");
+    request.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0");
+    if (m_contentType == "application/json") {
+        request.setContentType(m_contentType.c_str());
+        return doJSON(request);
+    }
+    request.setContentType("text/html; charset=UTF-8");
+    return doHTML(request);
+}
+
+pair<bool,long> SessionHandler::doJSON(SPRequest& request) const
+{
+    stringstream s;
+
+    Session* session = nullptr;
+    try {
+        session = request.getSession(); // caches the locked session in the request so it's unlocked automatically
+        if (!session) {
+            s << "{}" << endl;
+            return make_pair(true, request.sendResponse(s));
+        }
+    }
+    catch (std::exception& ex) {
+        m_log.info("exception accessing user session: %s", ex.what());
+        s << "{}" << endl;
+        return make_pair(true, request.sendError(s));
+    }
+
+    s << "{ ";
+    s << "\"expiration\": ";
+    if (session->getExpiration())
+        s << ((session->getExpiration() - time(nullptr)) / 60);
+    else
+        s << 0;
+
+    if (session->getClientAddress()) {
+        s << ", \"client_address\": ";
+        json_safe(s, session->getClientAddress());
+    }
+
+    if (session->getProtocol()) {
+        s << ", \"protocol\": ";
+        json_safe(s, session->getProtocol());
+    }
+
+    pair<bool,bool> stdvars = request.getRequestSettings().first->getBool("exportStdVars");
+    if (!stdvars.first || stdvars.second) {
+        if (session->getEntityID()) {
+            s << ", \"identity_provider\": ";
+            json_safe(s, session->getEntityID());
+        }
+
+        if (session->getAuthnInstant()) {
+            s << ", \"authn_instant\": ";
+            json_safe(s, session->getAuthnInstant());
+        }
+
+        if (session->getAuthnContextClassRef()) {
+            s << ", \"authncontext_class\": ";
+            json_safe(s, session->getAuthnContextClassRef());
+        }
+
+        if (session->getAuthnContextDeclRef()) {
+            s << ", \"authncontext_decl\": ";
+            json_safe(s, session->getAuthnContextDeclRef());
+        }
+
     }
 
+    /*
+        attributes: [ { "name": "foo", "values" : count } ]
+
+        attributes: [
+            { "name": "foo", "values": [ "val", "val" ] }
+        ]
+    */
+
+    const multimap<string,const Attribute*>& attributes = session->getIndexedAttributes();
+    if (!attributes.empty()) {
+        s << ", \"attributes\": [ ";
+        string key;
+        vector<string>::size_type count=0;
+        for (multimap<string,const Attribute*>::const_iterator a = attributes.begin(); a != attributes.end(); ++a) {
+            if (a->first != key) {
+                // We're starting a new attribute.
+                if (a != attributes.begin()) {
+                    // Need to close out the previous.
+                    if (m_values) {
+                        s << " ] }, ";
+                    }
+                    else {
+                        s << ", \"values\": " << count << " }, ";
+                        count = 0;
+                    }
+                }
+                s << "{ \"name\": ";
+                json_safe(s, a->first.c_str());
+            }
+
+            if (m_values) {
+                const vector<string>& vals = a->second->getSerializedValues();
+                for (vector<string>::const_iterator v = vals.begin(); v!=vals.end(); ++v) {
+                    if (v != vals.begin() || a->first == key) {
+                        s << ", ";
+                    }
+                    else {
+                        s << ", \"values\": [ ";
+                    }
+                    json_safe(s, v->c_str());
+                }
+            }
+            else {
+                count += a->second->getSerializedValues().size();
+            }
+            key = a->first;
+        }
+
+        if (m_values)
+            s << " ] } ";
+        else
+            s << ", \"values\": " << count << " }";
+        s << " ]";
+    }
+
+    s << " }" << endl;
+    return make_pair(true, request.sendResponse(s));
+}
+
+pair<bool,long> SessionHandler::doHTML(SPRequest& request) const
+{
     stringstream s;
     s << "<html><head><title>Session Summary</title></head><body><pre>" << endl;
 
-    Session* session = request.getSession();
-    if (!session) {
-        s << "A valid session was not found.</pre></body></html>" << endl;
-        request.setContentType("text/html");
-        request.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
-        request.setResponseHeader("Cache-Control","private,no-store,no-cache");
+    Session* session = nullptr;
+    try {
+        session = request.getSession(); // caches the locked session in the request so it's unlocked automatically
+        if (!session) {
+            s << "A valid session was not found.</pre></body></html>" << endl;
+            return make_pair(true, request.sendResponse(s));
+        }
+    }
+    catch (std::exception& ex) {
+        s << "Exception while retrieving active session:" << endl
+            << '\t' << ex.what() << "</pre></body></html>" << endl;
         return make_pair(true, request.sendResponse(s));
     }
 
     s << "<u>Miscellaneous</u>" << endl;
 
-    s << "<strong>Client Address</strong>: " << (session->getClientAddress() ? session->getClientAddress() : "(none)") << endl;
-    s << "<strong>Identity Provider</strong>: " << (session->getEntityID() ? session->getEntityID() : "(none)") << endl;
-    s << "<strong>SSO Protocol</strong>: " << (session->getProtocol() ? session->getProtocol() : "(none)") << endl;
-    s << "<strong>Authentication Time</strong>: " << (session->getAuthnInstant() ? session->getAuthnInstant() : "(none)") << endl;
-    s << "<strong>Authentication Context Class</strong>: " << (session->getAuthnContextClassRef() ? session->getAuthnContextClassRef() : "(none)") << endl;
-    s << "<strong>Authentication Context Decl</strong>: " << (session->getAuthnContextDeclRef() ? session->getAuthnContextDeclRef() : "(none)") << endl;
-    s << "<strong>Session Expiration (barring inactivity)</strong>: ";
+    s << "<strong>Session Expiration (barring inactivity):</strong> ";
     if (session->getExpiration())
-        s << ((session->getExpiration() - time(NULL)) / 60) << " minute(s)" << endl;
+        s << ((session->getExpiration() - time(nullptr)) / 60) << " minute(s)" << endl;
     else
         s << "Infinite" << endl;
-    
+
+    s << "<strong>Client Address:</strong> " << (session->getClientAddress() ? session->getClientAddress() : "(none)") << endl;
+    s << "<strong>SSO Protocol:</strong> " << (session->getProtocol() ? session->getProtocol() : "(none)") << endl;
+
+    pair<bool,bool> stdvars = request.getRequestSettings().first->getBool("exportStdVars");
+    if (!stdvars.first || stdvars.second) {
+        s << "<strong>Identity Provider:</strong> " << (session->getEntityID() ? session->getEntityID() : "(none)") << endl;
+        s << "<strong>Authentication Time:</strong> " << (session->getAuthnInstant() ? session->getAuthnInstant() : "(none)") << endl;
+        s << "<strong>Authentication Context Class:</strong> " << (session->getAuthnContextClassRef() ? session->getAuthnContextClassRef() : "(none)") << endl;
+        s << "<strong>Authentication Context Decl:</strong> " << (session->getAuthnContextDeclRef() ? session->getAuthnContextDeclRef() : "(none)") << endl;
+    }
+
     s << endl << "<u>Attributes</u>" << endl;
 
     string key;
@@ -146,7 +309,7 @@ pair<bool,long> SessionHandler::run(SPRequest& request, bool isHandler) const
                     count = 0;
                 }
             }
-            s << "<strong>" << a->second->getId() << "</strong>: ";
+            s << "<strong>" << a->first << "</strong>: ";
         }
 
         if (m_values) {
@@ -171,14 +334,12 @@ pair<bool,long> SessionHandler::run(SPRequest& request, bool isHandler) const
         else {
             count += a->second->getSerializedValues().size();
         }
+        key = a->first;
     }
 
     if (!m_values && !attributes.empty())
         s << count << " value(s)" << endl;
-    
+
     s << "</pre></body></html>";
-    request.setContentType("text/html; charset=UTF-8");
-    request.setResponseHeader("Expires","01-Jan-1997 12:00:00 GMT");
-    request.setResponseHeader("Cache-Control","private,no-store,no-cache");
     return make_pair(true, request.sendResponse(s));
 }