Example #1
0
        /// <summary>
        /// Handles a request from a Web Client who is requesting a proxied web socket connection to a specific Host Service.
        /// </summary>
        /// <param name="p">The HttpProcessor handling the request.</param>
        private static void HandleWebSocketProxyRequest(HttpProcessor p)
        {
            string[] parts = p.request_url.Segments;
            #region Validate Input
            if (parts.Length != 3)
            {
                p.writeFailure("400 Bad Request");
                return;
            }
            if (!int.TryParse(parts[1], out int computerId))
            {
                p.writeFailure("400 Bad Request");
                return;
            }
            #endregion
            #region Verify Permission
            string        sid     = parts[2];
            ServerSession session = SessionManager.GetSession(sid);
            if (session == null || session.Expired)
            {
                p.writeFailure("403 Forbidden");
                return;
            }
            Computer computer = ServiceWrapper.db.GetComputer(computerId);
            if (computer == null)
            {
                p.writeFailure("404 Not Found");
                return;
            }

            User user = session.GetUser();
            if (user == null)
            {
                p.writeFailure("403 Forbidden");
                return;
            }

            // If we get here, we have an active authenticated session.
            if (!user.IsAdmin)
            {
                // Admin users can access all computers.
                // This user is not an adminn, so we must check group membership.
                ComputerGroupMembership[] cgm = computer.GetGroupMemberships();
                if (cgm.Length == 0)
                {
                    Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") but computer has no group memberships.");
                    p.writeFailure("403 Forbidden");
                    return;
                }
                UserGroupMembership[] ugm = user.GetGroupMemberships();
                if (ugm.Length == 0)
                {
                    Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") but user has no group memberships.");
                    p.writeFailure("403 Forbidden");
                    return;
                }

                // The computer is accessible to this user if the computer and the user share at least one group membership.
                bool accessible = 0 < cgm.Select(m => m.GroupID).Intersect(ugm.Select(m => m.GroupID)).Count();
                if (!accessible)
                {
                    Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") without permission.");
                    p.writeFailure("403 Forbidden");
                    return;
                }
            }
            #endregion

            // Now that permission has been verified, find out of the specified computer is online.
            HostConnectHandle host = HostConnect.GetOnlineComputer(computer.ID);
            if (host == null)
            {
                p.writeFailure("504 Gateway Timeout", "Computer " + computer.ID + " is not online.");
                return;
            }

            // The computer is online.  Send a request to have the Host Service connect to this Master Server's web socket proxy service.
            string        proxyKey      = Util.GetRandomAlphaNumericString(64);
            WaitingClient waitingClient = null;
            try
            {
                waitingClient = new WaitingClient(p);
                pendingProxyConnections[proxyKey] = waitingClient;
                host.RequestWebSocketProxy(p.RemoteIPAddress, proxyKey);

                // Wait for the connection from the Host Service.
                if (!waitingClient.clientWaitHandle.WaitOne(10000))
                {
                    p.writeFailure("504 Gateway Timeout", "Computer " + computer.ID + " did not respond in a timely manner.");
                    return;
                }
                Stream hostStream = waitingClient.hostProcessor?.tcpStream;
                if (hostStream == null)
                {
                    p.writeFailure("500 Internal Server Error");
                    Logger.Debug("hostStream was null in WebSocketProxy handler");
                    return;
                }

                // The Host Service has connected.  Remove the pending connection and clean up before starting to proxy data between the sockets.
                pendingProxyConnections.TryRemove(proxyKey, out WaitingClient ignored);
                proxyKey = null;
                waitingClient.Dispose();

                // Copy data from Host Service to Web Client
                p.responseWritten = true;
                CopyStreamUntilClosed(hostStream, p.tcpStream);
            }
            finally
            {
                if (proxyKey != null)
                {
                    pendingProxyConnections.TryRemove(proxyKey, out WaitingClient ignored);
                }

                waitingClient?.Dispose();
            }
        }
Example #2
0
        public static void HandleRequest(HttpProcessor p, string jsonStr)
        {
            object response = null;

            try
            {
                dynamic       requestObj = JsonConvert.DeserializeObject(jsonStr);
                ServerSession session    = GetSession(requestObj);
                string        cmd        = Try.Get(() => (string)requestObj.cmd);
                if (cmd == "login")
                {
                    if (session == null)
                    {
                        session = ServerSession.CreateUnauthenticated();
                        SessionManager.AddSession(session);
                    }
                    // Get current challenge and generate a new one so the previous one can not be used again, preventing replay attacks.
                    byte[] currentChallenge = session.authChallenge;
                    session.authChallenge = ByteUtil.GenerateRandomBytes(32);
                    // Get user
                    string userName = Try.Get(() => (string)requestObj.user);
                    if (string.IsNullOrEmpty(userName))
                    {
                        response = new ResultFailWithReason(session, "missing parameter: \"user\"");
                        return;
                    }
                    User user = ServiceWrapper.db.GetUser(userName);

                    string responseToken = Try.Get(() => (string)requestObj.response);
                    if (string.IsNullOrEmpty(responseToken))
                    {
                        // No response token was provided. This is step 1 of authentication, where the client requests information necessary to build the response token.
                        string salt;
                        if (user != null)
                        {
                            salt = user.Salt;
                        }
                        else
                        {
                            salt = Util.GenerateFakeUserSalt(userName);
                            Logger.Info("Fake salt \"" + salt + "\" created for user name \"" + userName + "\". Remote IP: " + p.RemoteIPAddressStr);
                        }

                        response = new ResultLoginChallengeAndSalt(session, salt);
                    }
                    else
                    {
                        bool authenticated = false;
                        // If the user hasn't gotten a challenge token yet, currentChallenge will be blank.
                        if (currentChallenge != null && currentChallenge.Length > 0)
                        {
                            if (user != null)
                            {
                                authenticated = user.AuthenticateUser(responseToken, currentChallenge);
                            }
                        }
                        if (authenticated)
                        {
                            session.userId = user.ID;
                            response       = new ResultLoginSuccess(session, user.SettingsKey);
                        }
                        else
                        {
                            response = new ResultFailWithReason(session, "authentication rejected");
                        }
                    }
                    return;
                }
                else
                {
                    if (session == null || !session.IsAuthValid)
                    {
                        response = new ResultFailNoSession(session);
                        return;
                    }
                    ///////////////////////////////////////////////////////////////////////////////////////////////////
                    ///////////////////////////////////////////////////////////////////////////////////////////////////
                    // Commands after this point require an authenticated session, but not administrator permission. //
                    ///////////////////////////////////////////////////////////////////////////////////////////////////
                    ///////////////////////////////////////////////////////////////////////////////////////////////////
                    User user = session.GetUser();
                    if (user == null)
                    {
                        Logger.Debug("Session has user ID " + session.userId + " but GetUser returned null");
                        response = new ResultFailWithReason(session, "session corrupted");
                        return;
                    }
                    bool handled = true;
                    switch (cmd)
                    {
                    case "getComputers":
                        UserGroup[]             userGroups = user.GetGroups();
                        GetComputerGroupsResult result     = new GetComputerGroupsResult();
                        result.Groups = userGroups.Select(g => new GroupOfComputers(g, ServiceWrapper.db.GetComputersInGroup(g.ID))).ToArray();
                        response      = result;
                        break;

                    case "logout":
                        SessionManager.RemoveSession(session.sid);
                        response = new ResultSuccess();
                        break;

                    default:
                        handled = false;
                        break;
                    }
                    if (handled)
                    {
                        return;
                    }

                    if (!user.IsAdmin)
                    {
                        response = new ResultFailInsufficientPrivileges();
                        return;
                    }
                    /////////////////////////////////////////////////////////////////
                    /////////////////////////////////////////////////////////////////
                    // Commands after this point require administrator permission. //
                    /////////////////////////////////////////////////////////////////
                    /////////////////////////////////////////////////////////////////
                    switch (cmd)
                    {
                        #region admin/getComputers
                    case "admin/getComputers":
                    {
                        ResultComputersAndTheirGroups result = new ResultComputersAndTheirGroups();

                        ComputerAndItsGroups[] allComputers = ServiceWrapper.db.GetAllComputersAndTheirGroups();
                        result.Computers = allComputers.Select(c => new AdminResultComputer(c)).ToArray();

                        response = result;
                        break;
                    }

                        #endregion
                        #region admin/getUsers
                    case "admin/getUsers":
                    {
                        ResultAllUsers result = new ResultAllUsers();

                        UserAndItsGroups[] allUsers = ServiceWrapper.db.GetAllUsersAndTheirGroups();
                        result.Users = allUsers.Select(u => new AdminResultUser(u)).ToArray();

                        response = result;
                        break;
                    }

                        #endregion
                    default:
                    {
                        response = new ResultFailWithReason(session, "unrecognized command");
                        break;
                    }
                    }
                }
            }
            catch (Exception ex)
            {
                Logger.Debug(ex);
                response = new ResultFail()
                {
                    error = "An unexpected error occurred."
                };
            }
            finally
            {
                if (response == null)
                {
                    response = new ResultFail()
                    {
                        error = "Application Error: A response was not generated, so this response was generated as a fallback."
                    }
                }
                ;
                p.writeSuccess(jsonType);
                p.outputStream.Write(JsonConvert.SerializeObject(response));
            }
        }
        /// <summary>
        /// Handles a request from a Web Client who is requesting a proxied web socket connection to a specific Host Service.
        /// </summary>
        /// <param name="p">The HttpProcessor handling the request.</param>
        public static void HandleWebSocketClientProxyRequest(HttpProcessor p)
        {
            #region Perform Validation
            // Path format: "/WebSocketClientProxy/1/SESSION". More path segments, if given, are ignored (the last path segment is typically used as a display name in browser developer tools).
            string[] parts = p.request_url.LocalPath.Split('/').Skip(1).ToArray();
            int      computerId;        // The computer ID the client wishes to connect to.
            if (parts.Length < 3 || parts[0] != "WebSocketClientProxy" || !int.TryParse(parts[1], out computerId))
            {
                p.writeFailure("400 Bad Request");
                return;
            }
            string sid = parts[2];             // The session ID of the client's session.
            #endregion
            #region Verify Permission
            ServerSession session = SessionManager.GetSession(sid);
            if (session == null || session.Expired)
            {
                p.writeFailure("403 Forbidden");
                return;
            }
            Computer computer = ServiceWrapper.db.GetComputer(computerId);
            if (computer == null)
            {
                p.writeFailure("404 Not Found");
                return;
            }

            User user = session.GetUser();
            if (user == null)
            {
                p.writeFailure("403 Forbidden");
                return;
            }

            // If we get here, we have an active authenticated session.
            if (!user.IsAdmin)
            {
                // Admin users can access all computers.
                // This user is not an adminn, so we must check group membership.
                ComputerGroupMembership[] cgm = computer.GetGroupMemberships();
                if (cgm.Length == 0)
                {
                    Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") but computer has no group memberships.");
                    p.writeFailure("403 Forbidden");
                    return;
                }
                UserGroupMembership[] ugm = user.GetGroupMemberships();
                if (ugm.Length == 0)
                {
                    Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") but user has no group memberships.");
                    p.writeFailure("403 Forbidden");
                    return;
                }

                // The computer is accessible to this user if the computer and the user share at least one group membership.
                bool accessible = 0 < cgm.Select(m => m.GroupID).Intersect(ugm.Select(m => m.GroupID)).Count();
                if (!accessible)
                {
                    Logger.Info("Non-admin user " + user.ID + " (" + user.Name + ") attempted to access computer " + computer.ID + " (" + computer.Name + ") without permission.");
                    p.writeFailure("403 Forbidden");
                    return;
                }
            }
            #endregion

            // Now that permission has been verified, find out of the specified computer is online.
            HostConnectHandle host = HostConnect.GetOnlineComputer(computer.ID);
            if (host == null)
            {
                p.writeFailure("504 Gateway Timeout", "Computer " + computer.ID + " is not online.");
                return;
            }

            // The computer is online.  Send a request to have the Host Service connect to this Master Server's web socket proxy service.
            string        proxyKey      = StringUtil.GetRandomAlphaNumericString(64);
            WaitingClient waitingClient = null;
            try
            {
                waitingClient = new WaitingClient(p);
                pendingProxyConnections[proxyKey] = waitingClient;
                host.RequestWebSocketProxy(p.RemoteIPAddress, proxyKey);

                // Wait for the connection from the Host Service.
                if (!waitingClient.clientWaitHandle.WaitOne(10000))
                {
                    p.writeFailure("504 Gateway Timeout", "Computer " + computer.ID + " did not respond in a timely manner.");
                    return;
                }
                Stream hostStream = waitingClient.hostProcessor?.tcpStream;
                if (hostStream == null)
                {
                    p.writeFailure("500 Internal Server Error");
                    Logger.Debug("hostStream was null in WebSocketProxy handler");
                    return;
                }

                // The Host Service has connected.  Remove the pending connection and clean up before starting to proxy data between the sockets.
                pendingProxyConnections.TryRemove(proxyKey, out WaitingClient ignored);
                proxyKey = null;
                waitingClient.Dispose();

                // Copy data from Host Service to Web Client
                p.responseWritten   = true;
                p.tcpClient.NoDelay = true;
                Console.WriteLine("Client Proxy Initialized");
                CopyStreamUntilClosed(hostStream, p.tcpStream);
            }
            finally
            {
                // If anything went wrong initializing the proxy connection, we might not have cleaned up yet.
                if (proxyKey != null)
                {
                    pendingProxyConnections.TryRemove(proxyKey, out WaitingClient ignored);
                }

                waitingClient?.Dispose();
            }
        }