diff --git a/Server/Data/DataService.cs b/Server/Data/DataService.cs index 7cc90fc3..8cfc1306 100644 --- a/Server/Data/DataService.cs +++ b/Server/Data/DataService.cs @@ -39,16 +39,30 @@ namespace Remotely.Server.Data RemotelyContext.SaveChanges(); } - public bool AddOrUpdateDevice(Device device) + public bool AddOrUpdateDevice(Device device, out Device updatedDevice) { var existingDevice = RemotelyContext.Devices.Find(device.ID); if (existingDevice != null) { - device.ServerVerificationToken = existingDevice.ServerVerificationToken; - RemotelyContext.Entry(existingDevice).CurrentValues.SetValues(device); + existingDevice.CurrentUser = device.CurrentUser; + existingDevice.DeviceName = device.DeviceName; + existingDevice.Drives = device.Drives; + existingDevice.FreeMemory = device.FreeMemory; + existingDevice.FreeStorage = device.FreeStorage; + existingDevice.Is64Bit = device.Is64Bit; + existingDevice.IsOnline = true; + existingDevice.LastOnline = DateTime.Now; + existingDevice.OSArchitecture = device.OSArchitecture; + existingDevice.OSDescription = device.OSDescription; + existingDevice.Platform = device.Platform; + existingDevice.ProcessorCount = device.ProcessorCount; + existingDevice.TotalMemory = device.TotalMemory; + existingDevice.TotalStorage = device.TotalStorage; + updatedDevice = existingDevice; } else { + updatedDevice = device; if (!RemotelyContext.Organizations.Any(x => x.ID == device.OrganizationID)) { WriteEvent(new EventLog() @@ -95,6 +109,8 @@ namespace Remotely.Server.Data { var targetDevice = RemotelyContext.Devices .Include(x=>x.DevicePermissionLinks) + .ThenInclude(x=>x.PermissionGroup) + .ThenInclude(x=>x.UserPermissionLinks) .FirstOrDefault(x => x.ID == deviceID && x.OrganizationID == remotelyUser.OrganizationID); return remotelyUser.IsAdministrator || @@ -102,12 +118,34 @@ namespace Remotely.Server.Data targetDevice.DevicePermissionLinks.Any(x => x.PermissionGroup.UserPermissionLinks.Any(y => y.RemotelyUserID == remotelyUser.Id)); } + public List GetUsersWithAccessToDevice(IEnumerable userIDs, Device device) + { + var targetDevice = RemotelyContext.Devices + .Include(x => x.DevicePermissionLinks) + .ThenInclude(x => x.PermissionGroup) + .ThenInclude(x => x.UserPermissionLinks) + .FirstOrDefault(x => x.ID == device.ID && x.OrganizationID == device.OrganizationID); + + var authorizedUsers = RemotelyContext.Users.Where(x => + x.OrganizationID == device.OrganizationID && + userIDs.Contains(x.Id) && + ( + targetDevice.DevicePermissionLinks.Count == 0 || + x.IsAdministrator || + targetDevice.DevicePermissionLinks.Any(y => y.PermissionGroup.UserPermissionLinks.Any(z=>z.RemotelyUserID == x.Id)) + )); + + return authorizedUsers.ToList(); + } + public bool DoesUserHaveAccessToDevice(string deviceID, string remotelyUserID) { var remotelyUser = RemotelyContext.Users.Find(remotelyUserID); var targetDevice = RemotelyContext.Devices .Include(x => x.DevicePermissionLinks) + .ThenInclude(x => x.PermissionGroup) + .ThenInclude(x => x.UserPermissionLinks) .FirstOrDefault(x => x.ID == deviceID && x.OrganizationID == remotelyUser.OrganizationID); return remotelyUser.IsAdministrator || diff --git a/Server/Services/BrowserSocketHub.cs b/Server/Services/BrowserSocketHub.cs index b4cc9c17..6eeec01a 100644 --- a/Server/Services/BrowserSocketHub.cs +++ b/Server/Services/BrowserSocketHub.cs @@ -32,8 +32,9 @@ namespace Remotely.Server.Services AppConfig = appConfig; } - private ApplicationConfig AppConfig { get; } - private DataService DataService { get; } + public static ConcurrentDictionary ConnectionIdToUserLookup { get; } = new ConcurrentDictionary(); + private ApplicationConfig AppConfig { get; } + private DataService DataService { get; } private IHubContext DeviceHub { get; } private RemotelyUser RemotelyUser { @@ -59,71 +60,79 @@ namespace Remotely.Server.Services DataService.AddPermissionToDevices(RemotelyUser.Id, deviceIDs, groupName); await Clients.Caller.SendAsync("DisplayConsoleMessage", "Group added."); } + public async Task DeployScript(string fileID, string mode, string[] deviceIDs) + { + deviceIDs = DataService.FilterDeviceIDsByUserPermission(deviceIDs, RemotelyUser); + var connections = GetActiveClientConnections(deviceIDs); + var commandContext = new CommandContext() + { + CommandMode = mode, + CommandText = Encoding.UTF8.GetString(DataService.GetSharedFiled(fileID).FileContents), + SenderConnectionID = Context.ConnectionId, + SenderUserID = Context.UserIdentifier, + TargetDeviceIDs = connections.Select(x => x.Value.ID).ToArray(), + OrganizationID = RemotelyUser.OrganizationID + }; + DataService.AddOrUpdateCommandContext(commandContext); + await Clients.Caller.SendAsync("CommandContextCreated", commandContext); + foreach (var connection in connections) + { + await DeviceHub.Clients.Client(connection.Key).SendAsync("DeployScript", mode, fileID, commandContext.ID, Context.ConnectionId); + } + } + + public async Task ExecuteCommandOnClient(string mode, string command, string[] deviceIDs) + { + deviceIDs = DataService.FilterDeviceIDsByUserPermission(deviceIDs, RemotelyUser); + var connections = GetActiveClientConnections(deviceIDs); + + var commandContext = new CommandContext() + { + CommandMode = mode, + CommandText = command, + SenderConnectionID = Context.ConnectionId, + SenderUserID = Context.UserIdentifier, + TargetDeviceIDs = connections.Select(x => x.Value.ID).ToArray(), + OrganizationID = RemotelyUser.Organization.ID + }; + DataService.AddOrUpdateCommandContext(commandContext); + await Clients.Caller.SendAsync("CommandContextCreated", commandContext); + foreach (var connection in connections) + { + await DeviceHub.Clients.Client(connection.Key).SendAsync("ExecuteCommand", mode, command, commandContext.ID, Context.ConnectionId); + } + } + public async Task GetGroups(string[] deviceIDs) { deviceIDs = DataService.FilterDeviceIDsByUserPermission(deviceIDs, RemotelyUser); var result = DataService.GetDevicesAndPermissions(RemotelyUser.Id, deviceIDs); await Clients.Caller.SendAsync("GetGroupsResult", result); } - public async Task DeployScript(string fileID, string mode, string[] deviceIDs) - { - deviceIDs = DataService.FilterDeviceIDsByUserPermission(deviceIDs, RemotelyUser); - var connections = GetActiveClientConnections(deviceIDs); - var commandContext = new CommandContext() - { - CommandMode = mode, - CommandText = Encoding.UTF8.GetString(DataService.GetSharedFiled(fileID).FileContents), - SenderConnectionID = Context.ConnectionId, - SenderUserID = Context.UserIdentifier, - TargetDeviceIDs = connections.Select(x => x.Value.ID).ToArray(), - OrganizationID = RemotelyUser.OrganizationID - }; - DataService.AddOrUpdateCommandContext(commandContext); - await Clients.Caller.SendAsync("CommandContextCreated", commandContext); - foreach (var connection in connections) - { - await DeviceHub.Clients.Client(connection.Key).SendAsync("DeployScript", mode, fileID, commandContext.ID, Context.ConnectionId); - } - } - - public async Task ExecuteCommandOnClient(string mode, string command, string[] deviceIDs) - { - deviceIDs = DataService.FilterDeviceIDsByUserPermission(deviceIDs, RemotelyUser); - var connections = GetActiveClientConnections(deviceIDs); - - var commandContext = new CommandContext() - { - CommandMode = mode, - CommandText = command, - SenderConnectionID = Context.ConnectionId, - SenderUserID = Context.UserIdentifier, - TargetDeviceIDs = connections.Select(x => x.Value.ID).ToArray(), - OrganizationID = RemotelyUser.Organization.ID - }; - DataService.AddOrUpdateCommandContext(commandContext); - await Clients.Caller.SendAsync("CommandContextCreated", commandContext); - foreach (var connection in connections) - { - await DeviceHub.Clients.Client(connection.Key).SendAsync("ExecuteCommand", mode, command, commandContext.ID, Context.ConnectionId); - } - } - public override async Task OnConnectedAsync() { - RemotelyUser = DataService.GetUserAndPermissionsByID(Context?.UserIdentifier); - if (IsConnectionValid()?.Result == false) + RemotelyUser = DataService.GetUserAndPermissionsByID(Context.UserIdentifier); + if (await IsConnectionValid() == false) { return; } - await Groups.AddToGroupAsync(Context.ConnectionId, RemotelyUser.Organization.ID); + while (!ConnectionIdToUserLookup.TryAdd(Context.ConnectionId, RemotelyUser)) + { + DataService.WriteEvent("Retrying ConnectionIdToUserLookup.TryAdd in BrowserSocketHub."); + await Task.Delay(100); + } await Clients.Caller.SendAsync("UserOptions", RemotelyUser.UserOptions); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception exception) { - await Groups.RemoveFromGroupAsync(Context.ConnectionId, RemotelyUser.Organization.ID); - await base.OnDisconnectedAsync(exception); + while (!ConnectionIdToUserLookup.TryRemove(Context.ConnectionId, out _)) + { + DataService.WriteEvent("Retrying ConnectionIdToUserLookup.TryRemove in BrowserSocketHub."); + await Task.Delay(100); + } + await base.OnDisconnectedAsync(exception); } public async Task RemoteControl(string deviceID) @@ -142,8 +151,15 @@ namespace Remotely.Server.Services } } - public async Task RemoveGroup(string[] deviceIDs, string groupName) - { + public async Task RemoveDevices(string[] deviceIDs) + { + var filterDevices = DataService.FilterDeviceIDsByUserPermission(deviceIDs, RemotelyUser); + DataService.RemoveDevices(filterDevices); + await Clients.Caller.SendAsync("RefreshDeviceList"); + } + + public async Task RemoveGroup(string[] deviceIDs, string groupName) + { groupName = groupName.Trim(); deviceIDs = DataService.FilterDeviceIDsByUserPermission(deviceIDs, RemotelyUser); if (!DataService.DoesGroupExist(RemotelyUser.Id, groupName)) @@ -154,12 +170,6 @@ namespace Remotely.Server.Services DataService.RemovePermissionFromDevices(RemotelyUser.Id, deviceIDs, groupName); await Clients.Caller.SendAsync("DisplayConsoleMessage", "Group removed."); } - public async Task RemoveDevices(string[] deviceIDs) - { - var filterDevices = DataService.FilterDeviceIDsByUserPermission(deviceIDs, RemotelyUser); - DataService.RemoveDevices(filterDevices); - await Clients.Caller.SendAsync("RefreshDeviceList"); - } public async Task TransferFiles(List fileIDs, string transferID, string[] deviceIDs) { DataService.WriteEvent(new EventLog() diff --git a/Server/Services/DeviceSocketHub.cs b/Server/Services/DeviceSocketHub.cs index 6d8fbf87..6040c3be 100644 --- a/Server/Services/DeviceSocketHub.cs +++ b/Server/Services/DeviceSocketHub.cs @@ -73,24 +73,26 @@ namespace Remotely.Server.Services Context.Abort(); return; } - device.IsOnline = true; - device.LastOnline = DateTime.Now; - Device = device; - if (DataService.AddOrUpdateDevice(device)) + + if (DataService.AddOrUpdateDevice(device, out var updatedDevice)) { - var failCount = 0; - while (!ServiceConnections.TryAdd(Context.ConnectionId, device)) + Device = updatedDevice; + while (!ServiceConnections.TryAdd(Context.ConnectionId, Device)) { - if (failCount > 3) - { - Context.Abort(); - return; - } - failCount++; - await Task.Delay(1000); + DataService.WriteEvent("Retrying ServiceConnections.TryAdd in DeviceSocketHub."); + await Task.Delay(100); } - await this.Groups.AddToGroupAsync(this.Context.ConnectionId, device.OrganizationID); - await BrowserHub.Clients.Group(Device.OrganizationID).SendAsync("DeviceCameOnline", Device); + + var onlineOrganizationUsers = BrowserSocketHub.ConnectionIdToUserLookup + .Where(x => x.Value.OrganizationID == Device.OrganizationID); + + var authorizedUsers = DataService.GetUsersWithAccessToDevice(onlineOrganizationUsers.Select(x => x.Value.Id), Device); + var connectionIds = onlineOrganizationUsers + .Where(onlineUser => authorizedUsers.Any(authorizedUser => authorizedUser.Id == onlineUser.Value.Id)) + .Select(x => x.Key) + .ToList(); + + await BrowserHub.Clients.Clients(connectionIds).SendAsync("DeviceCameOnline", Device); } else { @@ -107,11 +109,18 @@ namespace Remotely.Server.Services public async Task DeviceHeartbeat(Device device) { - device.IsOnline = true; - device.LastOnline = DateTime.Now; - Device = device; - DataService.AddOrUpdateDevice(device); - await BrowserHub.Clients.Group(Device.OrganizationID).SendAsync("DeviceHeartbeat", Device); + DataService.AddOrUpdateDevice(device, out var updatedDevice); + Device = updatedDevice; + var onlineOrganizationUsers = BrowserSocketHub.ConnectionIdToUserLookup + .Where(x => x.Value.OrganizationID == Device.OrganizationID); + + var authorizedUsers = DataService.GetUsersWithAccessToDevice(onlineOrganizationUsers.Select(x=>x.Value.Id), Device); + var connectionIds = onlineOrganizationUsers + .Where(onlineUser => authorizedUsers.Any(authorizedUser => authorizedUser.Id == onlineUser.Value.Id)) + .Select(x => x.Key) + .ToList(); + + await BrowserHub.Clients.Clients(connectionIds).SendAsync("DeviceHeartbeat", Device); } public async Task DisplayConsoleMessage(string message, string requesterID) @@ -127,12 +136,24 @@ namespace Remotely.Server.Services if (Device != null) { DataService.DeviceDisconnected(Device.ID); - await this.Groups.RemoveFromGroupAsync(this.Context.ConnectionId, Device.OrganizationID); + Device.IsOnline = false; - await BrowserHub.Clients.Group(Device.OrganizationID).SendAsync("DeviceWentOffline", Device); - while (!ServiceConnections.TryRemove(Context.ConnectionId, out var device)) + + var onlineOrganizationUsers = BrowserSocketHub.ConnectionIdToUserLookup + .Where(x => x.Value.OrganizationID == Device.OrganizationID); + + var authorizedUsers = DataService.GetUsersWithAccessToDevice(onlineOrganizationUsers.Select(x => x.Value.Id), Device); + var connectionIds = onlineOrganizationUsers + .Where(onlineUser => authorizedUsers.Any(authorizedUser => authorizedUser.Id == onlineUser.Value.Id)) + .Select(x => x.Key) + .ToList(); + + await BrowserHub.Clients.Clients(connectionIds).SendAsync("DeviceWentOffline", Device); + + while (!ServiceConnections.TryRemove(Context.ConnectionId, out _)) { - await Task.Delay(1000); + DataService.WriteEvent("Retrying ServiceConnections.TryRemove in DeviceSocketHub."); + await Task.Delay(100); } } diff --git a/Server/Services/RCBrowserSocketHub.cs b/Server/Services/RCBrowserSocketHub.cs index 84734bcc..2c36bf74 100644 --- a/Server/Services/RCBrowserSocketHub.cs +++ b/Server/Services/RCBrowserSocketHub.cs @@ -116,23 +116,28 @@ namespace Remotely.Server.Services await RCDeviceHub.Clients.Client(ScreenCasterID).SendAsync("MouseWheel", deltaX, deltaY, Context.ConnectionId); } - public override Task OnConnectedAsync() + public override async Task OnConnectedAsync() { if (Context.User.Identity.IsAuthenticated) { var user = DataService.GetUserByName(Context.User.Identity.Name); - OrganizationConnectionList.TryAdd(Context.ConnectionId, user); + while (!OrganizationConnectionList.TryAdd(Context.ConnectionId, user)) + { + DataService.WriteEvent("Retrying OrganizationConnectionList.TryAdd in RCBrowserSocketHub."); + await Task.Delay(100); + } } - return base.OnConnectedAsync(); + await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception exception) { if (Context.User.Identity.IsAuthenticated) { - while (!OrganizationConnectionList.TryRemove(Context.ConnectionId, out var user)) + while (!OrganizationConnectionList.TryRemove(Context.ConnectionId, out _)) { - await Task.Delay(1000); + DataService.WriteEvent("Retrying OrganizationConnectionList.TryRemove in RCBrowserSocketHub."); + await Task.Delay(100); } } await RCDeviceHub.Clients.Client(ScreenCasterID).SendAsync("ViewerDisconnected", Context.ConnectionId); diff --git a/Server/Services/RCDeviceSocketHub.cs b/Server/Services/RCDeviceSocketHub.cs index 8310b7c1..bcd71b53 100644 --- a/Server/Services/RCDeviceSocketHub.cs +++ b/Server/Services/RCDeviceSocketHub.cs @@ -127,6 +127,7 @@ namespace Remotely.Server.Services }; while (!SessionInfoList.TryAdd(Context.ConnectionId, SessionInfo)) { + DataService.WriteEvent("Retrying SessionInfoList.TryAdd in RCDeviceSocketHub."); await Task.Delay(100); } @@ -136,6 +137,7 @@ namespace Remotely.Server.Services { while (!SessionInfoList.TryRemove(Context.ConnectionId, out _)) { + DataService.WriteEvent("Retrying SessionInfoList.TryRemove in RCDeviceSocketHub."); await Task.Delay(100); } diff --git a/Server/Services/RemoteControlSessionRecorder.cs b/Server/Services/RemoteControlSessionRecorder.cs index 1f6ffd94..8482f549 100644 --- a/Server/Services/RemoteControlSessionRecorder.cs +++ b/Server/Services/RemoteControlSessionRecorder.cs @@ -89,6 +89,10 @@ namespace Remotely.Server.Services } } } + catch (Exception ex) + { + DataService.WriteEvent(ex); + } finally { IsProcessing = false;