[Libreoffice-commits] online.git: 2 commits - loolwsd/Admin.cpp loolwsd/FileServer.hpp

Pranav Kant pranavk at collabora.com
Mon Mar 21 14:33:13 UTC 2016


 loolwsd/Admin.cpp      |  113 +++++++++++++++++++++++++++++------
 loolwsd/FileServer.hpp |  154 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 248 insertions(+), 19 deletions(-)

New commits:
commit a0d3c5f3e4dc26790ec72ce434cb664ad302e7b7
Author: Pranav Kant <pranavk at collabora.com>
Date:   Mon Mar 21 14:36:54 2016 +0530

    loolwsd: Use JWT authentication to access admin console
    
    File server serves the admin html file after successfull
    authentication, and sets the cookie in client which would be sent
    for all subsequent connections by client to connect to admin websocket.
    
    Change-Id: I0ee3bbfca7eefc428020d29612374410556b1e27

diff --git a/loolwsd/Admin.cpp b/loolwsd/Admin.cpp
index a1c11cc..0bf1718 100644
--- a/loolwsd/Admin.cpp
+++ b/loolwsd/Admin.cpp
@@ -12,15 +12,21 @@
 #include <sys/poll.h>
 #include <sys/prctl.h>
 
+#include <Poco/Net/HTTPCookie.h>
+#include <Poco/Net/HTTPBasicCredentials.h>
+#include <Poco/Net/HTTPRequest.h>
 #include <Poco/Net/HTTPRequestHandler.h>
 #include <Poco/Net/HTTPServerParams.h>
 #include <Poco/Net/HTTPServerRequest.h>
 #include <Poco/Net/HTTPServerResponse.h>
 #include <Poco/Net/NetException.h>
+#include <Poco/Net/SecureServerSocket.h>
 #include <Poco/Net/WebSocket.h>
 #include <Poco/StringTokenizer.h>
+#include <Poco/Util/ServerApplication.h>
 #include <Poco/Util/Timer.h>
 
+#include "Auth.hpp"
 #include "Admin.hpp"
 #include "AdminModel.hpp"
 #include "Common.hpp"
@@ -30,41 +36,31 @@
 
 using namespace LOOLProtocol;
 
+using Poco::StringTokenizer;
+using Poco::Net::HTTPBasicCredentials;
+using Poco::Net::HTTPCookie;
+using Poco::Net::HTTPRequest;
 using Poco::Net::HTTPRequestHandler;
 using Poco::Net::HTTPRequestHandlerFactory;
 using Poco::Net::HTTPResponse;
 using Poco::Net::HTTPServerParams;
 using Poco::Net::HTTPServerRequest;
 using Poco::Net::HTTPServerResponse;
+using Poco::Net::SecureServerSocket;
 using Poco::Net::ServerSocket;
 using Poco::Net::Socket;
 using Poco::Net::WebSocket;
 using Poco::Net::WebSocketException;
-using Poco::StringTokenizer;
+using Poco::Util::Application;
 
 /// Handle admin requests.
 class AdminRequestHandler: public HTTPRequestHandler
 {
-public:
-
-    AdminRequestHandler(Admin* adminManager)
-        : _admin(adminManager)
-    {    }
-
-    void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
+private:
+    void handleWSRequests(HTTPServerRequest& request, HTTPServerResponse& response, int nSessionId)
     {
-        assert(request.serverAddress().port() == ADMIN_PORT_NUMBER);
-
-        // Different session id pool for admin sessions (?)
-        const auto nSessionId = Util::decodeId(LOOLWSD::GenSessionId());
-        const std::string thread_name = "admin_ws_" + std::to_string(nSessionId);
         try
         {
-            if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
-                Log::error("Cannot set thread name to " + thread_name + ".");
-
-            Log::debug("Thread [" + thread_name + "] started.");
-
             auto ws = std::make_shared<WebSocket>(request, response);
 
             {
@@ -313,11 +309,90 @@ public:
                 break;
             }
         }
+        catch (const Poco::Net::NotAuthenticatedException& exc)
+        {
+            Log::info("NotAuthenticatedException");
+            response.set("WWW-Authenticate", "Basic realm=\"ws-online\"");
+            response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
+            response.setContentLength(0);
+            response.send();
+        }
         catch (const std::exception& exc)
         {
             Log::error(std::string("AdminRequestHandler::handleRequest: Exception: ") + exc.what());
         }
+    }
+
+public:
+
+    AdminRequestHandler(Admin* adminManager)
+        : _admin(adminManager)
+    {    }
+
+    void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
+    {
+        assert(request.serverAddress().port() == ADMIN_PORT_NUMBER);
+
+        // Different session id pool for admin sessions (?)
+        const auto nSessionId = Util::decodeId(LOOLWSD::GenSessionId());
+        const std::string thread_name = "admin_ws_" + std::to_string(nSessionId);
+
+        if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
+                Log::error("Cannot set thread name to " + thread_name + ".");
 
+        Log::debug("Thread [" + thread_name + "] started.");
+
+        try
+        {
+            std::string requestURI = request.getURI();
+            StringTokenizer pathTokens(requestURI, "/", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+
+            if (request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0)
+            {
+                if (request.find("Cookie") != request.end())
+                {
+                    // FIXME: Handle other cookie params like '; httponly; secure'
+                    const std::size_t pos = request["Cookie"].find_first_of("=");
+                    if (pos == std::string::npos)
+                        throw Poco::Net::NotAuthenticatedException("Missing JWT");
+
+                    const std::string jwtToken = request["Cookie"].substr(pos + 1);
+                    Log::info("Verifying JWT token: " + jwtToken);
+                    const std::string keyPath = Poco::Path(Application::instance().commandPath()).parent().toString() + SSL_KEY_FILE;
+                    JWTAuth authAgent(keyPath, "admin", "admin", "admin");
+                    if (authAgent.verify(jwtToken))
+                    {
+                        Log::trace("JWT token is valid");
+                        handleWSRequests(request, response, nSessionId);
+                    }
+                    else
+                    {
+                        Log::info("Invalid JWT token");
+                        throw Poco::Net::NotAuthenticatedException("Invalid Token");
+                    }
+                }
+                else
+                {
+                    Log::info("Missing authentication cookie. Handshake declined.");
+                    throw Poco::Net::NotAuthenticatedException("Missing token");
+                }
+            }
+        }
+        catch(const Poco::Net::NotAuthenticatedException& exc)
+        {
+            Log::info("Admin::NotAuthneticated");
+            response.set("WWW-Authenticate", "Basic realm=\"online\"");
+            response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
+            response.setContentLength(0);
+            response.send();
+        }
+        catch (const std::exception& exc)
+        {
+            Log::info("Unknown Exception caught");
+            response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST);
+            response.setContentLength(0);
+            response.send();
+        }
         Log::debug("Thread [" + thread_name + "] finished.");
     }
 
@@ -355,7 +430,7 @@ private:
 
 /// An admin command processor.
 Admin::Admin(const Poco::Process::PID brokerPid, const int brokerPipe, const int notifyPipe) :
-    _srv(new AdminRequestHandlerFactory(this), ServerSocket(ADMIN_PORT_NUMBER), new HTTPServerParams),
+    _srv(new AdminRequestHandlerFactory(this), SecureServerSocket(ADMIN_PORT_NUMBER), new HTTPServerParams),
     _model(AdminModel())
 {
     Admin::BrokerPid = brokerPid;
commit 8f435d6e7eabbbfb72b338a7da13b0a63d4709be
Author: Pranav Kant <pranavk at collabora.com>
Date:   Mon Mar 21 14:10:10 2016 +0530

    loolwsd: FileServer class to serve static contents
    
    Only purpose, at the moment, is to create
    Poco::HTTPRequestHandler which would be passed on the serving
    handling the static file requests.
    
    Change-Id: I97c3fc0c73da077d3efee919416098b880c9c2ad

diff --git a/loolwsd/FileServer.hpp b/loolwsd/FileServer.hpp
new file mode 100644
index 0000000..9c62e41
--- /dev/null
+++ b/loolwsd/FileServer.hpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDED_FILE_SERVER_HPP
+#define INCLUDED_FILE_SERVER_HPP
+
+#include <string>
+#include <vector>
+
+#include <Poco/Net/NetException.h>
+
+#include <Poco/Net/HTTPCookie.h>
+#include <Poco/Net/HTTPBasicCredentials.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPRequestHandler.h>
+#include <Poco/Net/HTTPServer.h>
+#include <Poco/Net/HTTPServerParams.h>
+#include <Poco/Net/HTTPServerRequest.h>
+#include <Poco/Net/HTTPServerResponse.h>
+#include <Poco/Net/SecureServerSocket.h>
+#include <Poco/Net/WebSocket.h>
+#include <Poco/Runnable.h>
+#include <Poco/StringTokenizer.h>
+#include <Poco/URI.h>
+#include <Poco/Util/ServerApplication.h>
+#include <Poco/Util/Timer.h>
+
+#include "Common.hpp"
+
+using Poco::Net::HTTPRequest;
+using Poco::Net::HTTPRequestHandler;
+using Poco::Net::HTTPRequestHandlerFactory;
+using Poco::Net::HTTPResponse;
+using Poco::Net::HTTPServerParams;
+using Poco::Net::HTTPServerRequest;
+using Poco::Net::HTTPServerResponse;
+using Poco::Net::SecureServerSocket;
+using Poco::Net::HTTPBasicCredentials;
+using Poco::Util::Application;
+
+class FileServerRequestHandler: public HTTPRequestHandler
+{
+public:
+    void handleRequest(HTTPServerRequest& request, HTTPServerResponse& response) override
+    {
+        try
+        {
+            Poco::URI requestUri(request.getURI());
+            std::vector<std::string> requestSegments;
+            requestUri.getPathSegments(requestSegments);
+
+            // FIXME: We might want to package all dist files from leaflet to some other dir (?)
+            const std::string loleafletPath = Poco::Path(Application::instance().commandPath()).parent().parent().toString() + "loleaflet";
+            const std::string endPoint = requestSegments[requestSegments.size() - 1];
+
+            if (request.getMethod() == HTTPRequest::HTTP_GET)
+            {
+                // FIXME: Some nice way to ask for credentials for protected files
+                if (endPoint == "admin.html" ||
+                    endPoint == "adminSettings.html" ||
+                    endPoint == "adminAnalytics.html")
+                {
+                    HTTPBasicCredentials credentials(request);
+                    // TODO: Read username and password from config file
+                    if (credentials.getUsername() == "admin"
+                        && credentials.getPassword() == "admin")
+                    {
+                        const std::string htmlMimeType = "text/html";
+                        // generate and set the cookie
+                        const std::string keyPath = Poco::Path(Application::instance().commandPath()).parent().toString() + SSL_KEY_FILE;
+                        JWTAuth authAgent(keyPath, "admin", "admin", "admin");
+                        const std::string jwtToken = authAgent.getAccessToken();
+                        Poco::Net::HTTPCookie cookie("jwt", jwtToken);
+                        response.addCookie(cookie);
+                        response.setContentType(htmlMimeType);
+                        response.sendFile(loleafletPath + "/debug/document/" + endPoint, htmlMimeType);
+                    }
+                    else
+                    {
+                        Log::info("Wrong admin credentials.");
+                        throw Poco::Net::NotAuthenticatedException("Wrong credentials.");
+                    }
+                }
+                else if (requestSegments.size() > 1 && requestSegments[0] == "dist")
+                {
+                    const std::string filePath = requestUri.getPath();
+                    const std::size_t extPoint = endPoint.find_last_of(".");
+                    if (extPoint == std::string::npos)
+                        return;
+
+                    const std::string fileType = endPoint.substr(extPoint + 1);
+                    std::string mimeType;
+                    if (fileType == "js")
+                        mimeType = "application/javascript";
+                    else if (fileType == "css")
+                        mimeType = "text/css";
+                    else
+                        mimeType = "text/plain";
+
+                    response.setContentType(mimeType);
+                    response.sendFile(loleafletPath + request.getURI(), mimeType);
+                }
+                else
+                {
+                    throw Poco::FileNotFoundException("");
+                }
+            }
+        }
+        catch (Poco::Net::NotAuthenticatedException& exc)
+        {
+            Log::info ("FileServerRequestHandler::NotAuthenticated");
+            response.set("WWW-Authenticate", "Basic realm=\"online\"");
+            response.setStatus(HTTPResponse::HTTP_UNAUTHORIZED);
+            response.setContentLength(0);
+            response.send();
+        }
+        catch (Poco::FileNotFoundException& exc)
+        {
+            Log::info("FileServerRequestHandler:: File " + request.getURI() + " not found.");
+            response.setStatus(HTTPResponse::HTTP_NOT_FOUND);
+            response.setContentLength(0);
+            response.send();
+        }
+    }
+};
+
+class FileServer
+{
+public:
+    FileServer()
+    {
+        Log::info("File server ctor.");
+    }
+
+    ~FileServer()
+    {
+        Log::info("File Server dtor.");
+    }
+
+    FileServerRequestHandler* createRequestHandler()
+    {
+        return new FileServerRequestHandler();
+    }
+};
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */


More information about the Libreoffice-commits mailing list