/// <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(); } }
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(); } }