Sync Remotely with latest integration changes.

This commit is contained in:
Jared Goodwin 2023-04-25 15:09:57 -07:00
parent 0fa41e879f
commit bb2126bbab
31 changed files with 281 additions and 384 deletions

View File

@ -380,7 +380,6 @@ namespace Remotely.Agent.Installer.Win.Services
Description = "Background service that maintains a connection to the Remotely server. The service is used for remote support and maintenance by this computer's administrators.",
ServiceName = "Remotely_Service",
StartType = ServiceStartMode.Automatic,
DelayedAutoStart = true,
Parent = new ServiceProcessInstaller()
};

View File

@ -15,6 +15,5 @@ namespace Remotely.Agent.Interfaces
(double usedGB, double totalGB) GetMemoryInGB();
string GetAgentVersion();
List<Drive> GetAllDrives();
Task<double> GetCpuUtilization();
}
}

View File

@ -9,7 +9,6 @@ using System;
using System.Diagnostics;
using System.IO;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.Versioning;
using Remotely.Agent.Services.Linux;
@ -55,6 +54,8 @@ namespace Remotely.Agent
// TODO: All these should be registered as interfaces.
serviceCollection.AddSingleton<IAgentHubConnection, AgentHubConnection>();
serviceCollection.AddSingleton<ICpuUtilizationSampler, CpuUtilizationSampler>();
serviceCollection.AddHostedService(services => services.GetRequiredService<ICpuUtilizationSampler>());
serviceCollection.AddScoped<ChatClientService>();
serviceCollection.AddTransient<PSCore>();
serviceCollection.AddTransient<ExternalScriptingShell>();

View File

@ -489,10 +489,7 @@ namespace Remotely.Agent.Services
}
});
_hubConnection.On("TriggerHeartbeat", async () =>
{
await SendHeartbeat().ConfigureAwait(false);
});
_hubConnection.On("TriggerHeartbeat", SendHeartbeat);
}
private async Task<bool> VerifyServer()

View File

@ -1,4 +1,5 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@ -9,24 +10,36 @@ using System.Threading.Tasks;
namespace Remotely.Agent.Services;
internal interface ICpuUtilizationSampler : IHostedService
public interface ICpuUtilizationSampler : IHostedService
{
double CurrentUtilization { get; }
}
internal class CpuUtilizationSampler : BackgroundService, ICpuUtilizationSampler
{
private readonly ILogger<CpuUtilizationSampler> _logger;
private double _currentUtilization;
public CpuUtilizationSampler(ILogger<CpuUtilizationSampler> logger)
{
_logger = logger;
}
public double CurrentUtilization => _currentUtilization;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var currentUtil = await GetCpuUtilization(stoppingToken);
Interlocked.Exchange(ref _currentUtilization, currentUtil);
try
{
var currentUtil = await GetCpuUtilization(stoppingToken);
Interlocked.Exchange(ref _currentUtilization, currentUtil);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while getting CPU utilization sample.");
}
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}

View File

@ -1,4 +1,5 @@
using Remotely.Shared.Models;
using Microsoft.Extensions.Logging;
using Remotely.Shared.Models;
using Remotely.Shared.Services;
using Remotely.Shared.Utilities;
using System;
@ -14,6 +15,13 @@ namespace Remotely.Agent.Services
{
public class DeviceInfoGeneratorBase
{
protected readonly ILogger<DeviceInfoGeneratorBase> _logger;
public DeviceInfoGeneratorBase(ILogger<DeviceInfoGeneratorBase> logger)
{
_logger = logger;
}
public Device GetDeviceBase(string deviceID, string orgID)
{
@ -63,7 +71,7 @@ namespace Remotely.Agent.Services
}
catch (Exception ex)
{
Logger.Write(ex, "Error getting system drive info.");
_logger.LogError(ex, "Error getting system drive info.");
}
return (0, 0);
@ -80,7 +88,7 @@ namespace Remotely.Agent.Services
}
catch (Exception ex)
{
Logger.Write(ex, "Error getting agent version.");
_logger.LogError(ex, "Error getting agent version.");
}
return "0.0.0.0";
@ -103,55 +111,9 @@ namespace Remotely.Agent.Services
}
catch (Exception ex)
{
Logger.Write(ex, "Error getting drive info.");
_logger.LogError(ex, "Error getting drive info.");
return null;
}
}
public async Task<double> GetCpuUtilization()
{
double totalUtilization = 0;
var utilizations = new Dictionary<int, Tuple<DateTimeOffset, TimeSpan>>();
var processes = Process.GetProcesses();
foreach (var proc in processes)
{
try
{
var startTime = DateTimeOffset.Now;
var startCpuUsage = proc.TotalProcessorTime;
utilizations.Add(proc.Id, new Tuple<DateTimeOffset, TimeSpan>(startTime, startCpuUsage));
}
catch
{
continue;
}
}
await Task.Delay(500);
foreach (var kvp in utilizations)
{
var endTime = DateTimeOffset.Now;
try
{
var proc = Process.GetProcessById(kvp.Key);
var startTime = kvp.Value.Item1;
var startCpuUsage = kvp.Value.Item2;
var endCpuUsage = proc.TotalProcessorTime;
var cpuUsedMs = (endCpuUsage - startCpuUsage).TotalMilliseconds;
var totalMsPassed = (endTime - startTime).TotalMilliseconds;
var cpuUsageTotal = cpuUsedMs / (Environment.ProcessorCount * totalMsPassed);
totalUtilization += cpuUsageTotal;
}
catch
{
continue;
}
}
return totalUtilization;
}
}
}

View File

@ -1,4 +1,5 @@
using Remotely.Agent.Interfaces;
using Microsoft.Extensions.Logging;
using Remotely.Agent.Interfaces;
using Remotely.Shared.Models;
using Remotely.Shared.Services;
using Remotely.Shared.Utilities;
@ -13,12 +14,19 @@ namespace Remotely.Agent.Services.Linux
public class DeviceInfoGeneratorLinux : DeviceInfoGeneratorBase, IDeviceInformationService
{
private readonly IProcessInvoker _processInvoker;
private readonly ICpuUtilizationSampler _cpuUtilSampler;
public DeviceInfoGeneratorLinux(IProcessInvoker processInvoker)
public DeviceInfoGeneratorLinux(
IProcessInvoker processInvoker,
ICpuUtilizationSampler cpuUtilSampler,
ILogger<DeviceInfoGeneratorLinux> logger)
: base(logger)
{
_processInvoker = processInvoker;
_cpuUtilSampler = cpuUtilSampler;
}
public async Task<Device> CreateDevice(string deviceId, string orgId)
public Task<Device> CreateDevice(string deviceId, string orgId)
{
var device = GetDeviceBase(deviceId, orgId);
@ -34,15 +42,15 @@ namespace Remotely.Agent.Services.Linux
device.TotalStorage = totalStorage;
device.UsedMemory = usedMemory;
device.TotalMemory = totalMemory;
device.CpuUtilization = await GetCpuUtilization();
device.CpuUtilization = _cpuUtilSampler.CurrentUtilization;
device.AgentVersion = GetAgentVersion();
}
catch (Exception ex)
{
Logger.Write(ex, "Error getting device info.");
_logger.LogError(ex, "Error getting device info.");
}
return device;
return Task.FromResult(device);
}
private string GetCurrentUser()

View File

@ -1,3 +1,4 @@
using Microsoft.Extensions.Logging;
using Remotely.Agent.Interfaces;
using Remotely.Shared.Models;
using Remotely.Shared.Services;
@ -15,7 +16,8 @@ namespace Remotely.Agent.Services.MacOS
{
private readonly IProcessInvoker _processInvoker;
public DeviceInfoGeneratorMac(IProcessInvoker processInvoker)
public DeviceInfoGeneratorMac(IProcessInvoker processInvoker, ILogger<DeviceInfoGeneratorMac> logger)
: base(logger)
{
_processInvoker = processInvoker;
}
@ -40,40 +42,12 @@ namespace Remotely.Agent.Services.MacOS
}
catch (Exception ex)
{
Logger.Write(ex, "Error getting device info.");
_logger.LogError(ex, "Error getting device info.");
}
return device;
}
public new Task<double> GetCpuUtilization()
{
try
{
var cpuPercentStrings = _processInvoker.InvokeProcessOutput("zsh", "-c \"ps -A -o %cpu\"");
double cpuPercent = 0;
cpuPercentStrings
.Split(Environment.NewLine)
.ToList()
.ForEach(x =>
{
if (double.TryParse(x, out var result))
{
cpuPercent += result;
}
});
return Task.FromResult(cpuPercent / Environment.ProcessorCount / 100);
}
catch (Exception ex)
{
Logger.Write(ex, "Error while getting CPU utilization.");
}
return Task.FromResult((double)0);
}
public (double usedGB, double totalGB) GetMemoryInGB()
{
try
@ -114,6 +88,33 @@ namespace Remotely.Agent.Services.MacOS
}
}
private Task<double> GetCpuUtilization()
{
try
{
var cpuPercentStrings = _processInvoker.InvokeProcessOutput("zsh", "-c \"ps -A -o %cpu\"");
double cpuPercent = 0;
cpuPercentStrings
.Split(Environment.NewLine)
.ToList()
.ForEach(x =>
{
if (double.TryParse(x, out var result))
{
cpuPercent += result;
}
});
return Task.FromResult(cpuPercent / Environment.ProcessorCount / 100);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while getting CPU utilization.");
}
return Task.FromResult((double)0);
}
private string GetCurrentUser()
{
var users = _processInvoker.InvokeProcessOutput("users", "");

View File

@ -1,4 +1,5 @@
using Remotely.Agent.Interfaces;
using Microsoft.Extensions.Logging;
using Remotely.Agent.Interfaces;
using Remotely.Shared.Models;
using Remotely.Shared.Utilities;
using Remotely.Shared.Win32;
@ -12,7 +13,17 @@ namespace Remotely.Agent.Services.Windows
{
public class DeviceInfoGeneratorWin : DeviceInfoGeneratorBase, IDeviceInformationService
{
public async Task<Device> CreateDevice(string deviceId, string orgId)
private readonly ICpuUtilizationSampler _cpuUtilSampler;
public DeviceInfoGeneratorWin(
ICpuUtilizationSampler cpuUtilSampler,
ILogger<DeviceInfoGeneratorWin> logger)
: base(logger)
{
_cpuUtilSampler = cpuUtilSampler;
}
public Task<Device> CreateDevice(string deviceId, string orgId)
{
var device = GetDeviceBase(deviceId, orgId);
@ -28,15 +39,15 @@ namespace Remotely.Agent.Services.Windows
device.TotalStorage = totalStorage;
device.UsedMemory = usedMemory;
device.TotalMemory = totalMemory;
device.CpuUtilization = await GetCpuUtilization();
device.CpuUtilization = _cpuUtilSampler.CurrentUtilization;
device.AgentVersion = GetAgentVersion();
}
catch (Exception ex)
{
Logger.Write(ex, "Error getting device info.");
_logger.LogError(ex, "Error getting device info.");
}
return device;
return Task.FromResult(device);
}
public (double usedGB, double totalGB) GetMemoryInGB()

View File

@ -66,11 +66,12 @@ if (appState.ArgDict.TryGetValue("org-id", out var orgId))
var result = await provider.UseRemoteControlClient(
args,
"The remote control client for Remotely.",
serverUrl);
serverUrl,
false);
if (!result.IsSuccess)
{
logger.LogError(result.Exception, "Failed to remote control client.");
logger.LogError(result.Exception, "Failed to start remote control client.");
Environment.Exit(1);
}

View File

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Configuration>Release</Configuration>
<Platform>x64</Platform>
<TargetFramework>.net7.0</TargetFramework>
<PublishDir>..\Agent\bin\Release\.net7.0\linux-x64\publish\Desktop</PublishDir>
<PublishDir>..\Agent\bin\Release\net7.0\linux-x64\publish\Desktop</PublishDir>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>

View File

@ -60,7 +60,7 @@
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="if $(SolutionDir) == *Undefined* (&#xD;&#xA; exit 0&#xD;&#xA;)&#xD;&#xA;if $(ConfigurationName) == Debug (&#xD;&#xA; if $(PlatformName) == AnyCPU (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\Debug\.net7.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\Debug\.net7.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x64 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x64\Debug\.net7.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x64\Debug\.net7.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x86 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x86\Debug\.net7.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x86\Debug\.net7.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA;)" />
<Exec Command="if $(SolutionDir) == *Undefined* (&#xD;&#xA; exit 0&#xD;&#xA;)&#xD;&#xA;if $(ConfigurationName) == Debug (&#xD;&#xA; if $(PlatformName) == AnyCPU (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\Debug\net7.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\Debug\net7.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x64 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x64\Debug\net7.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x64\Debug\net7.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA; if $(PlatformName) == x86 (&#xD;&#xA; md &quot;$(SolutionDir)Agent\bin\x86\Debug\net7.0\Desktop\&quot;&#xD;&#xA; xcopy &quot;$(TargetDir)*&quot; &quot;$(SolutionDir)Agent\bin\x86\Debug\net7.0\Desktop\&quot; /y /e /i&#xD;&#xA; )&#xD;&#xA;)" />
</Target>
</Project>

View File

@ -66,11 +66,12 @@ if (appState.ArgDict.TryGetValue("org-id", out var orgId))
var result = await provider.UseRemoteControlClient(
args,
"The remote control client for Remotely.",
serverUrl);
serverUrl,
false);
if (!result.IsSuccess)
{
logger.LogError(result.Exception, "Failed to remote control client.");
logger.LogError(result.Exception, "Failed to start remote control client.");
Environment.Exit(1);
}

View File

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
<TargetFramework>.net7.0-windows</TargetFramework>
<PublishDir>..\Agent\bin\Release\.net7.0\win10-x64\publish\Desktop</PublishDir>
<PublishDir>..\Agent\bin\Release\net7.0\win10-x64\publish\Desktop</PublishDir>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<PublishSingleFile>True</PublishSingleFile>

View File

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Configuration>Release</Configuration>
<Platform>x64</Platform>
<TargetFramework>.net7.0-windows</TargetFramework>
<PublishDir>..\Agent\bin\Release\.net7.0\win10-x64\publish\Desktop</PublishDir>
<PublishDir>..\Agent\bin\Release\net7.0\win10-x64\publish\Desktop</PublishDir>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<PublishSingleFile>False</PublishSingleFile>

View File

@ -8,7 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Configuration>Release</Configuration>
<Platform>x86</Platform>
<TargetFramework>.net7.0-windows</TargetFramework>
<PublishDir>..\Agent\bin\Release\.net7.0\win10-x86\publish\Desktop</PublishDir>
<PublishDir>..\Agent\bin\Release\net7.0\win10-x86\publish\Desktop</PublishDir>
<RuntimeIdentifier>win10-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>False</PublishSingleFile>

View File

@ -49,11 +49,14 @@ namespace Remotely.Server.API
if (HttpContext?.User?.Identity?.IsAuthenticated == true)
{
orgId = _dataService.GetUserByNameWithOrg(HttpContext.User.Identity.Name)?.OrganizationID;
var activeSessions = _desktopSessionCache.Sessions.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name);
foreach (var session in activeSessions.ToList())
var activeSessions = _desktopSessionCache
.Sessions
.Where(x => x.RequesterUserName == HttpContext.User.Identity.Name);
foreach (var session in activeSessions)
{
await _desktopHub.Clients.Client(session.Value.DesktopConnectionId).SendAsync("Disconnect", "User logged out.");
await _viewerHub.Clients.Clients(session.Value.ViewerList).SendAsync("ConnectionFailed");
await _desktopHub.Clients.Client(session.DesktopConnectionId).SendAsync("Disconnect", "User logged out.");
await _viewerHub.Clients.Clients(session.ViewerList).SendAsync("ConnectionFailed");
}
}
await _signInManager.SignOutAsync();

View File

@ -29,7 +29,7 @@ namespace Remotely.Server.API
private readonly IServiceHubSessionCache _serviceSessionCache;
private readonly IApplicationConfig _appConfig;
private readonly IOtpProvider _otpProvider;
private readonly IHubEventHandlerEx _hubEvents;
private readonly IHubEventHandler _hubEvents;
private readonly IDataService _dataService;
private readonly SignInManager<RemotelyUser> _signInManager;
@ -40,7 +40,7 @@ namespace Remotely.Server.API
IHubContext<AgentHub> serviceHub,
IServiceHubSessionCache serviceSessionCache,
IOtpProvider otpProvider,
IHubEventHandlerEx hubEvents,
IHubEventHandler hubEvents,
IApplicationConfig appConfig)
{
_dataService = dataService;
@ -112,7 +112,7 @@ namespace Remotely.Server.API
}
var sessionCount = _desktopSessionCache.Sessions.Values
var sessionCount = _desktopSessionCache.Sessions
.OfType<RemoteControlSessionEx>()
.Count(x => x.OrganizationId == orgID);
@ -121,7 +121,7 @@ namespace Remotely.Server.API
return BadRequest("There are already the maximum amount of active remote control sessions for your organization.");
}
var sessionId = Guid.NewGuid().ToString();
var sessionId = Guid.NewGuid();
var accessKey = RandomGenerator.GenerateAccessKey();
var session = new RemoteControlSessionEx()
@ -133,33 +133,32 @@ namespace Remotely.Server.API
OrganizationId = orgID
};
_desktopSessionCache.Sessions.AddOrUpdate(sessionId, session, (k, v) =>
_desktopSessionCache.AddOrUpdate($"{sessionId}", session, (k, v) =>
{
if (v is RemoteControlSessionEx ex)
{
ex.ServiceConnectionId = HttpContext.Connection.Id;
return ex;
}
v.Dispose();
return session;
});
var orgName = _dataService.GetOrganizationNameById(orgID);
Task CreateSessionFunc()
{
return _serviceHub.Clients.Client(serviceConnectionId).SendAsync("RemoteControl",
sessionId,
accessKey,
HttpContext.Connection.Id,
string.Empty,
orgName);
}
await _serviceHub.Clients.Client(serviceConnectionId).SendAsync("RemoteControl",
sessionId,
accessKey,
HttpContext.Connection.Id,
string.Empty,
orgName);
if (!await _hubEvents.TryWaitForSession(sessionId, CreateSessionFunc))
var waitResult = await session.WaitForSessionReady(TimeSpan.FromSeconds(30));
if (!waitResult)
{
return StatusCode(408, "The remote control process failed to start in time on the remote device.");
}
var otp = _otpProvider.GetOtp(targetDevice.ID);
return Ok($"{HttpContext.Request.Scheme}://{Request.Host}/RemoteControl/Viewer?mode=Unattended&sessionId={sessionId}&accessKey={accessKey}&otp={otp}");

View File

@ -16,11 +16,11 @@
{
if (SignInManager.IsSignedIn(User))
{
var activeSessions = DesktopSessionCache.Sessions.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name);
var activeSessions = DesktopSessionCache.Sessions.Where(x => x.RequesterUserName == HttpContext.User.Identity.Name);
foreach (var session in activeSessions)
{
await DesktopHubContext.Clients.Client(session.Value.DesktopConnectionId).SendAsync("Disconnect", "User logged out.");
await ViewerHubContext.Clients.Clients(session.Value.ViewerList).SendAsync("ConnectionFailed");
await DesktopHubContext.Clients.Client(session.DesktopConnectionId).SendAsync("Disconnect", "User logged out.");
await ViewerHubContext.Clients.Clients(session.ViewerList).SendAsync("ConnectionFailed");
}
await SignInManager.SignOutAsync();

View File

@ -268,15 +268,34 @@ namespace Remotely.Server.Components.Devices
AppState.InvokePropertyChanged(nameof(AppState.DevicesFrameChatSessions));
}
private void StartRemoteControl(bool viewOnly)
private async Task StartRemoteControl(bool viewOnly)
{
if (!ServiceSessionCache.TryGetConnectionId(Device.ID, out var connectionId))
if (!ServiceSessionCache.TryGetConnectionId(Device.ID, out _))
{
ToastService.ShowToast("Device connection not found", classString: "bg-danger");
return;
}
CircuitConnection.RemoteControl(Device.ID, viewOnly);
var result = await CircuitConnection.RemoteControl(Device.ID, viewOnly);
if (!result.IsSuccess)
{
return;
}
var session = result.Value;
if (!await session.WaitForSessionReady(TimeSpan.FromSeconds(20)))
{
ToastService.ShowToast("Session failed to start", classString: "bg-danger");
return;
}
JsInterop.OpenWindow(
$"/RemoteControl/Viewer" +
$"?mode=Unattended&sessionId={session.UnattendedSessionId}" +
$"&accessKey={session.AccessKey}" +
$"&viewonly={viewOnly}",
"_blank");
}
private void ToggleIsSelected(ChangeEventArgs args)

View File

@ -174,16 +174,6 @@ namespace Remotely.Server.Components.Devices
AddScriptResult(result);
}
break;
case CircuitEventName.UnattendedSessionReady:
{
var sessionId = (string)args.Params[0];
var accessKey = (string)args.Params[1];
var deviceId = (string)args.Params[2];
var viewOnly = (bool)args.Params[3];
JsInterop.OpenWindow($"/RemoteControl/Viewer?mode=Unattended&sessionId={sessionId}&accessKey={accessKey}&viewonly={viewOnly}", "_blank");
}
break;
default:
break;
}

View File

@ -1,5 +1,6 @@
using Immense.RemoteControl.Server.Abstractions;
using Immense.RemoteControl.Server.Services;
using Immense.RemoteControl.Shared;
using Immense.RemoteControl.Shared.Helpers;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization;
@ -40,7 +41,7 @@ namespace Remotely.Server.Hubs
Task InvokeCircuitEvent(CircuitEventName eventName, params object[] args);
Task ReinstallAgents(string[] deviceIDs);
Task<bool> RemoteControl(string deviceID, bool viewOnly);
Task<Result<RemoteControlSessionEx>> RemoteControl(string deviceID, bool viewOnly);
Task RemoveDevices(string[] deviceIDs);
@ -206,7 +207,7 @@ namespace Remotely.Server.Hubs
return Task.CompletedTask;
}
public async Task<bool> RemoteControl(string deviceId, bool viewOnly)
public async Task<Result<RemoteControlSessionEx>> RemoteControl(string deviceId, bool viewOnly)
{
if (!_serviceSessionCache.TryGetByDeviceId(deviceId, out var targetDevice))
{
@ -214,66 +215,65 @@ namespace Remotely.Server.Hubs
"The selected device is not online.",
"Device is not online.",
"bg-warning"));
return false;
return Result.Fail<RemoteControlSessionEx>("Device is not online.");
}
if (_dataService.DoesUserHaveAccessToDevice(deviceId, User))
{
var sessionCount = _desktopSessionCache.Sessions.Values
.OfType<RemoteControlSessionEx>()
.Count(x => x.OrganizationId == User.OrganizationID);
if (sessionCount >= _appConfig.RemoteControlSessionLimit)
{
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
"There are already the maximum amount of active remote control sessions for your organization.",
"Max number of concurrent sessions reached.",
"bg-warning"));
return false;
}
if (!_serviceSessionCache.TryGetConnectionId(targetDevice.ID, out var serviceConnectionId))
{
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
"Service connection not found.",
"Service connection not found.",
"bg-warning"));
return false;
}
var sessionId = Guid.NewGuid().ToString();
var accessKey = RandomGenerator.GenerateAccessKey();
var session = new RemoteControlSessionEx()
{
UnattendedSessionId = sessionId,
UserConnectionId = ConnectionId,
ServiceConnectionId = serviceConnectionId,
DeviceId = deviceId,
ViewOnly = viewOnly,
OrganizationId = User.OrganizationID
};
_desktopSessionCache.Sessions.AddOrUpdate(sessionId, session, (k, v) => session);
var organization = _dataService.GetOrganizationNameByUserName(User.UserName);
await _agentHubContext.Clients.Client(serviceConnectionId).SendAsync("RemoteControl",
sessionId,
accessKey,
ConnectionId,
User.UserOptions.DisplayName,
organization,
User.OrganizationID);
return true;
}
else
if (!_dataService.DoesUserHaveAccessToDevice(deviceId, User))
{
var device = _dataService.GetDevice(targetDevice.ID);
_dataService.WriteEvent($"Remote control attempted by unauthorized user. Device ID: {deviceId}. User Name: {User.UserName}.", EventType.Warning, device?.OrganizationID);
return false;
return Result.Fail<RemoteControlSessionEx>("Unauthorized.");
}
var sessionCount = _desktopSessionCache.Sessions
.OfType<RemoteControlSessionEx>()
.Count(x => x.OrganizationId == User.OrganizationID);
if (sessionCount >= _appConfig.RemoteControlSessionLimit)
{
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
"There are already the maximum amount of active remote control sessions for your organization.",
"Max number of concurrent sessions reached.",
"bg-warning"));
return Result.Fail<RemoteControlSessionEx>("Max number of concurrent sessions reached.");
}
if (!_serviceSessionCache.TryGetConnectionId(targetDevice.ID, out var serviceConnectionId))
{
MessageReceived?.Invoke(this, new CircuitEvent(CircuitEventName.DisplayMessage,
"Service connection not found.",
"Service connection not found.",
"bg-warning"));
return Result.Fail<RemoteControlSessionEx>("Service connection not found.");
}
var sessionId = Guid.NewGuid();
var accessKey = RandomGenerator.GenerateAccessKey();
var session = new RemoteControlSessionEx()
{
UnattendedSessionId = sessionId,
UserConnectionId = ConnectionId,
ServiceConnectionId = serviceConnectionId,
DeviceId = deviceId,
ViewOnly = viewOnly,
OrganizationId = User.OrganizationID
};
_desktopSessionCache.AddOrUpdate($"{sessionId}", session);
var organization = _dataService.GetOrganizationNameByUserName(User.UserName);
await _agentHubContext.Clients.Client(serviceConnectionId).SendAsync("RemoteControl",
sessionId,
accessKey,
ConnectionId,
User.UserOptions.DisplayName,
organization,
User.OrganizationID);
return Result.Ok(session);
}
public Task RemoveDevices(string[] deviceIDs)

View File

@ -8,7 +8,6 @@ namespace Remotely.Server.Models
public enum CircuitEventName
{
DisplayMessage,
UnattendedSessionReady,
ChatReceived,
CommandResult,
DeviceUpdate,

View File

@ -34,6 +34,7 @@ using Immense.RemoteControl.Server.Abstractions;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Remotely.Shared.Services;
using System;
using Immense.RemoteControl.Server.Services;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
@ -182,7 +183,6 @@ services.AddScoped<ApiAuthorizationFilter>();
services.AddScoped<ExpiringTokenFilter>();
services.AddHostedService<DbCleanupService>();
services.AddHostedService<ScriptScheduler>();
services.AddHostedService<DesktopHubSessionCleanup>();
services.AddSingleton<IUpgradeService, UpgradeService>();
services.AddScoped<IToastService, ToastService>();
services.AddScoped<IModalService, ModalService>();
@ -199,15 +199,12 @@ services.AddSingleton<IEmbeddedServerDataSearcher, EmbeddedServerDataSearcher>()
services.AddRemoteControlServer(config =>
{
config.AddHubEventHandler<HubEventHandlerEx>();
config.AddHubEventHandler<HubEventHandler>();
config.AddViewerAuthorizer<ViewerAuthorizer>();
config.AddViewerHubDataProvider<ViewerHubDataProvider>();
config.AddViewerPageDataProvider<ViewerPageDataProvider>();
});
services.RemoveAll<IHubEventHandler>();
services.AddScoped<IHubEventHandlerEx, HubEventHandlerEx>();
services.AddScoped<IHubEventHandler>(s => s.GetRequiredService<IHubEventHandlerEx>());
services.AddSingleton<IServiceHubSessionCache, ServiceHubSessionCache>();
var app = builder.Build();
@ -255,13 +252,11 @@ app.UseCors("TrustedOriginPolicy");
app.UseRemoteControlServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<AgentHub>("/hubs/service");
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
app.MapHub<AgentHub>("/hubs/service");
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
using (var scope = app.Services.CreateScope())
{

View File

@ -1,61 +0,0 @@
using Immense.RemoteControl.Server.Services;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Linq;
using Timer = System.Timers.Timer;
using Immense.RemoteControl.Shared.Services;
using Microsoft.Build.Framework;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using System.Collections.Generic;
using Immense.RemoteControl.Server.Models;
namespace Remotely.Server.Services
{
public class DesktopHubSessionCleanup : BackgroundService
{
private readonly IDesktopHubSessionCache _sessionCache;
private readonly ISystemTime _systemTime;
private readonly ILogger<DesktopHubSessionCleanup> _logger;
private Task _cleanupTask;
private CancellationToken _stoppingToken;
public DesktopHubSessionCleanup(
IDesktopHubSessionCache sessionCache,
ISystemTime systemTime,
ILogger<DesktopHubSessionCleanup> logger)
{
_sessionCache = sessionCache;
_systemTime = systemTime;
_logger = logger;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_stoppingToken = stoppingToken;
_cleanupTask = Task.Run(CleanupSessions, stoppingToken);
return Task.CompletedTask;
}
private async Task CleanupSessions()
{
while (!_stoppingToken.IsCancellationRequested)
{
foreach (var session in _sessionCache.Sessions)
{
if (session.Value.Mode == RemoteControlMode.Unattended &&
!session.Value.ViewerList.Any() &&
session.Value.Created < _systemTime.Now.AddMinutes(-1))
{
_logger.LogWarning("Removing expired session: {session}", JsonSerializer.Serialize(session.Value));
_sessionCache.Sessions.Remove(session.Key, out _);
}
}
await Task.Delay(30_000, _stoppingToken);
}
}
}
}

View File

@ -17,25 +17,15 @@ using System.Threading.Tasks;
namespace Remotely.Server.Services.RcImplementations
{
public interface IHubEventHandlerEx : IHubEventHandler
public class HubEventHandler : IHubEventHandler
{
Task<bool> TryWaitForSession(string sessionId, Func<Task> createSessionFunc);
}
public class HubEventHandlerEx : IHubEventHandlerEx
{
private static readonly ConcurrentDictionary<string, SemaphoreSlim> _sessionWaitHandlers = new();
private readonly ICircuitManager _circuitManager;
private readonly IHubContext<AgentHub> _serviceHub;
private readonly ILogger<HubEventHandlerEx> _logger;
private readonly ILogger<HubEventHandler> _logger;
public HubEventHandlerEx(
ICircuitManager circuitManager,
public HubEventHandler(
IHubContext<AgentHub> serviceHub,
ILogger<HubEventHandlerEx> logger)
ILogger<HubEventHandler> logger)
{
_circuitManager = circuitManager;
_serviceHub = serviceHub;
_logger = logger;
}
@ -108,29 +98,6 @@ namespace Remotely.Server.Services.RcImplementations
return Task.CompletedTask;
}
public Task NotifyUnattendedSessionReady(RemoteControlSession session, string relativeAccessUrl)
{
if (_sessionWaitHandlers.TryGetValue(session.UnattendedSessionId, out var waitHandle))
{
waitHandle.Release();
return Task.CompletedTask;
}
if (session is not RemoteControlSessionEx ex)
{
_logger.LogError("Event should have been for RemoteControlSessionEx.");
return Task.CompletedTask;
}
return _circuitManager.InvokeOnConnection(
ex.UserConnectionId,
CircuitEventName.UnattendedSessionReady,
session.UnattendedSessionId,
session.AccessKey,
ex.DeviceId,
ex.ViewOnly);
}
public Task RestartScreenCaster(RemoteControlSession session, HashSet<string> viewerList)
{
@ -151,29 +118,5 @@ namespace Remotely.Server.Services.RcImplementations
ex.OrganizationName,
ex.OrganizationId);
}
public async Task<bool> TryWaitForSession(string sessionId, Func<Task> createSessionFunc)
{
try
{
var waitHandle = _sessionWaitHandlers.AddOrUpdate(sessionId, new SemaphoreSlim(0, 1), (k, v) =>
{
v.Release();
return new SemaphoreSlim(0, 1);
});
await createSessionFunc();
return waitHandle.Wait(TimeSpan.FromSeconds(30));
}
finally
{
if (_sessionWaitHandlers.TryRemove(sessionId, out var result))
{
result.Dispose();
}
}
}
}
}

View File

@ -12,31 +12,37 @@ namespace Remotely.Server.Services.RcImplementations
{
public class ViewerPageDataProvider : IViewerPageDataProvider
{
private readonly IDataService _dataService;
private readonly IApplicationConfig _appConfig;
private readonly IDataService _dataService;
public ViewerPageDataProvider(IDataService dataService, IApplicationConfig appConfig)
{
_dataService = dataService;
_appConfig = appConfig;
}
public Task<string> GetUserDisplayName(PageModel pageModel)
public Task<string> GetFaviconUrl(ViewerModel viewerModel)
{
if (string.IsNullOrWhiteSpace(pageModel?.User?.Identity?.Name))
{
return Task.FromResult(string.Empty);
}
return Task.FromResult("/_content/Immense.RemoteControl.Server/favicon.ico");
}
var user = _dataService.GetUserByNameWithOrg(pageModel.User.Identity.Name);
public Task<string> GetPageDescription(ViewerModel viewerModel)
{
return Task.FromResult("Open-source remote support tools.");
}
if (user is null)
{
return Task.FromResult(string.Empty);
}
public Task<string> GetPageTitle(PageModel pageModel)
{
return Task.FromResult("Remotely Remote Control");
}
var displayName = user.UserOptions?.DisplayName ?? user.UserName ?? string.Empty;
return Task.FromResult(displayName);
public Task<string> GetProductName(PageModel pageModel)
{
return Task.FromResult("Remotely");
}
public Task<string> GetProductSubtitle(PageModel pageModel)
{
return Task.FromResult("Remote Control");
}
public Task<ViewerPageTheme> GetTheme(PageModel pageModel)
@ -63,24 +69,22 @@ namespace Remotely.Server.Services.RcImplementations
return Task.FromResult(appTheme);
}
public Task<string> GetPageTitle(PageModel pageModel)
public Task<string> GetUserDisplayName(PageModel pageModel)
{
return Task.FromResult("Remotely Remote Control");
}
if (string.IsNullOrWhiteSpace(pageModel?.User?.Identity?.Name))
{
return Task.FromResult(string.Empty);
}
public Task<string> GetProductName(PageModel pageModel)
{
return Task.FromResult("Remotely");
}
var user = _dataService.GetUserByNameWithOrg(pageModel.User.Identity.Name);
public Task<string> GetProductSubtitle(PageModel pageModel)
{
return Task.FromResult("Remote Control");
}
if (user is null)
{
return Task.FromResult(string.Empty);
}
public Task<string> GetPageDescription(ViewerModel viewerModel)
{
return Task.FromResult("Open-source remote support tools.");
var displayName = user.UserOptions?.DisplayName ?? user.UserName ?? string.Empty;
return Task.FromResult(displayName);
}
}
}

View File

@ -7,6 +7,10 @@
<RootNamespace>Remotely.Tests.LoadTester</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.18.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Agent\Agent.csproj" />
<ProjectReference Include="..\..\Desktop.Win\Desktop.Win.csproj" />

View File

@ -1,4 +1,7 @@
using Microsoft.AspNetCore.SignalR.Client;
using Castle.Core.Logging;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using Moq;
using Remotely.Agent.Services;
using Remotely.Agent.Services.Windows;
using System;
@ -15,11 +18,17 @@ namespace Remotely.Tests.LoadTester
private static int _agentCount;
private static string _organizationId;
private static string _serverurl;
private static Mock<ICpuUtilizationSampler> _cpuSampler;
private static Mock<ILogger<DeviceInfoGeneratorWin>> _logger;
private static DeviceInfoGeneratorWin _deviceInfo;
private static void Main(string[] args)
{
_deviceInfo = new DeviceInfoGeneratorWin();
_cpuSampler = new Mock<ICpuUtilizationSampler>();
_cpuSampler.Setup(x => x.CurrentUtilization).Returns(0);
_logger = new Mock<ILogger<DeviceInfoGeneratorWin>>();
_deviceInfo = new DeviceInfoGeneratorWin(_cpuSampler.Object, _logger.Object);
ConnectAgents();
Console.Write("Press Enter to exit...");

View File

@ -92,25 +92,25 @@ if ([string]::IsNullOrWhiteSpace($MSBuildPath) -or !(Test-Path -Path $MSBuildPat
# Clear publish folders.
if ((Test-Path -Path "$Root\Agent\bin\Release\.net7.0\win10-x64\publish") -eq $true) {
Get-ChildItem -Path "$Root\Agent\bin\Release\.net7.0\win10-x64\publish" | Remove-Item -Force -Recurse
if ((Test-Path -Path "$Root\Agent\bin\Release\net7.0\win10-x64\publish") -eq $true) {
Get-ChildItem -Path "$Root\Agent\bin\Release\net7.0\win10-x64\publish" | Remove-Item -Force -Recurse
}
if ((Test-Path -Path "$Root\Agent\bin\Release\.net7.0\win10-x86\publish" ) -eq $true) {
Get-ChildItem -Path "$Root\Agent\bin\Release\.net7.0\win10-x86\publish" | Remove-Item -Force -Recurse
if ((Test-Path -Path "$Root\Agent\bin\Release\net7.0\win10-x86\publish" ) -eq $true) {
Get-ChildItem -Path "$Root\Agent\bin\Release\net7.0\win10-x86\publish" | Remove-Item -Force -Recurse
}
if ((Test-Path -Path "$Root\Agent\bin\Release\.net7.0\linux-x64\publish") -eq $true) {
Get-ChildItem -Path "$Root\Agent\bin\Release\.net7.0\linux-x64\publish" | Remove-Item -Force -Recurse
if ((Test-Path -Path "$Root\Agent\bin\Release\net7.0\linux-x64\publish") -eq $true) {
Get-ChildItem -Path "$Root\Agent\bin\Release\net7.0\linux-x64\publish" | Remove-Item -Force -Recurse
}
# Publish Core clients.
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x64 --self-contained --configuration Release --output "$Root\Agent\bin\Release\.net7.0\win10-x64\publish" "$Root\Agent"
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --self-contained --configuration Release --output "$Root\Agent\bin\Release\.net7.0\linux-x64\publish" "$Root\Agent"
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x86 --self-contained --configuration Release --output "$Root\Agent\bin\Release\.net7.0\win10-x86\publish" "$Root\Agent"
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x64 --self-contained --configuration Release --output "$Root\Agent\bin\Release\net7.0\win10-x64\publish" "$Root\Agent"
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --self-contained --configuration Release --output "$Root\Agent\bin\Release\net7.0\linux-x64\publish" "$Root\Agent"
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win10-x86 --self-contained --configuration Release --output "$Root\Agent\bin\Release\net7.0\win10-x86\publish" "$Root\Agent"
New-Item -Path "$Root\Agent\bin\Release\.net7.0\win10-x64\publish\Desktop\" -ItemType Directory -Force
New-Item -Path "$Root\Agent\bin\Release\.net7.0\win10-x86\publish\Desktop\" -ItemType Directory -Force
New-Item -Path "$Root\Agent\bin\Release\.net7.0\linux-x64\publish\Desktop\" -ItemType Directory -Force
New-Item -Path "$Root\Agent\bin\Release\net7.0\win10-x64\publish\Desktop\" -ItemType Directory -Force
New-Item -Path "$Root\Agent\bin\Release\net7.0\win10-x86\publish\Desktop\" -ItemType Directory -Force
New-Item -Path "$Root\Agent\bin\Release\net7.0\linux-x64\publish\Desktop\" -ItemType Directory -Force
# Publish Linux ScreenCaster
@ -155,7 +155,7 @@ if ($SignAssemblies) {
}
# Compress Core clients.
$PublishDir = "$Root\Agent\bin\Release\.net7.0\win10-x64\publish"
$PublishDir = "$Root\Agent\bin\Release\net7.0\win10-x64\publish"
Compress-Archive -Path "$PublishDir\*" -DestinationPath "$PublishDir\Remotely-Win10-x64.zip" -Force
while ((Test-Path -Path "$PublishDir\Remotely-Win10-x64.zip") -eq $false){
Write-Host "Waiting for archive to finish: $PublishDir\Remotely-Win10-x64.zip"
@ -163,7 +163,7 @@ while ((Test-Path -Path "$PublishDir\Remotely-Win10-x64.zip") -eq $false){
}
Move-Item -Path "$PublishDir\Remotely-Win10-x64.zip" -Destination "$Root\Server\wwwroot\Content\Remotely-Win10-x64.zip" -Force
$PublishDir = "$Root\Agent\bin\Release\.net7.0\win10-x86\publish"
$PublishDir = "$Root\Agent\bin\Release\net7.0\win10-x86\publish"
Compress-Archive -Path "$PublishDir\*" -DestinationPath "$PublishDir\Remotely-Win10-x86.zip" -Force
while ((Test-Path -Path "$PublishDir\Remotely-Win10-x86.zip") -eq $false){
Write-Host "Waiting for archive to finish: $PublishDir\Remotely-Win10-x86.zip"
@ -171,7 +171,7 @@ while ((Test-Path -Path "$PublishDir\Remotely-Win10-x86.zip") -eq $false){
}
Move-Item -Path "$PublishDir\Remotely-Win10-x86.zip" -Destination "$Root\Server\wwwroot\Content\Remotely-Win10-x86.zip" -Force
$PublishDir = "$Root\Agent\bin\Release\.net7.0\linux-x64\publish"
$PublishDir = "$Root\Agent\bin\Release\net7.0\linux-x64\publish"
Compress-Archive -Path "$PublishDir\*" -DestinationPath "$PublishDir\Remotely-Linux.zip" -Force
while ((Test-Path -Path "$PublishDir\Remotely-Linux.zip") -eq $false){
Write-Host "Waiting for archive to finish: $PublishDir\Remotely-Win10-x86.zip"

@ -1 +1 @@
Subproject commit c2f38702bbba6db17b73b5578b0173e1dd81ac39
Subproject commit 39d4f853eaa2a87590fb3a56e35db244f6eae08e