mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Merge pull request #678 from immense/feature/server-side-recording
Server-side remote control session recording.
This commit is contained in:
commit
567c6afd7b
@ -23,7 +23,7 @@ namespace Remotely.Server.API
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IHubContext<DesktopHub> _desktopHub;
|
||||
private readonly IDesktopHubSessionCache _desktopSessionCache;
|
||||
private readonly IRemoteControlSessionCache _remoteControlSessionCache;
|
||||
private readonly SignInManager<RemotelyUser> _signInManager;
|
||||
private readonly IHubContext<ViewerHub> _viewerHub;
|
||||
private readonly ILogger<LoginController> _logger;
|
||||
@ -33,7 +33,7 @@ namespace Remotely.Server.API
|
||||
IDataService dataService,
|
||||
IApplicationConfig appConfig,
|
||||
IHubContext<DesktopHub> casterHubContext,
|
||||
IDesktopHubSessionCache desktopSessionCache,
|
||||
IRemoteControlSessionCache remoteControlSessionCache,
|
||||
IHubContext<ViewerHub> viewerHubContext,
|
||||
ILogger<LoginController> logger)
|
||||
{
|
||||
@ -41,7 +41,7 @@ namespace Remotely.Server.API
|
||||
_dataService = dataService;
|
||||
_appConfig = appConfig;
|
||||
_desktopHub = casterHubContext;
|
||||
_desktopSessionCache = desktopSessionCache;
|
||||
_remoteControlSessionCache = remoteControlSessionCache;
|
||||
_viewerHub = viewerHubContext;
|
||||
_logger = logger;
|
||||
}
|
||||
@ -54,7 +54,7 @@ namespace Remotely.Server.API
|
||||
if (HttpContext?.User?.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
orgId = _dataService.GetUserByNameWithOrg(HttpContext.User.Identity.Name)?.OrganizationID;
|
||||
var activeSessions = _desktopSessionCache
|
||||
var activeSessions = _remoteControlSessionCache
|
||||
.Sessions
|
||||
.Where(x => x.RequesterUserName == HttpContext.User.Identity.Name);
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ namespace Remotely.Server.API
|
||||
public class RemoteControlController : ControllerBase
|
||||
{
|
||||
private readonly IHubContext<AgentHub> _serviceHub;
|
||||
private readonly IDesktopHubSessionCache _desktopSessionCache;
|
||||
private readonly IRemoteControlSessionCache _remoteControlSessionCache;
|
||||
private readonly IAgentHubSessionCache _serviceSessionCache;
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IOtpProvider _otpProvider;
|
||||
@ -39,7 +39,7 @@ namespace Remotely.Server.API
|
||||
public RemoteControlController(
|
||||
SignInManager<RemotelyUser> signInManager,
|
||||
IDataService dataService,
|
||||
IDesktopHubSessionCache desktopSessionCache,
|
||||
IRemoteControlSessionCache remoteControlSessionCache,
|
||||
IHubContext<AgentHub> serviceHub,
|
||||
IAgentHubSessionCache serviceSessionCache,
|
||||
IOtpProvider otpProvider,
|
||||
@ -49,7 +49,7 @@ namespace Remotely.Server.API
|
||||
{
|
||||
_dataService = dataService;
|
||||
_serviceHub = serviceHub;
|
||||
_desktopSessionCache = desktopSessionCache;
|
||||
_remoteControlSessionCache = remoteControlSessionCache;
|
||||
_serviceSessionCache = serviceSessionCache;
|
||||
_appConfig = appConfig;
|
||||
_otpProvider = otpProvider;
|
||||
@ -117,7 +117,7 @@ namespace Remotely.Server.API
|
||||
}
|
||||
|
||||
|
||||
var sessionCount = _desktopSessionCache.Sessions
|
||||
var sessionCount = _remoteControlSessionCache.Sessions
|
||||
.OfType<RemoteControlSessionEx>()
|
||||
.Count(x => x.OrganizationId == orgID);
|
||||
|
||||
@ -138,7 +138,7 @@ namespace Remotely.Server.API
|
||||
OrganizationId = orgID
|
||||
};
|
||||
|
||||
_desktopSessionCache.AddOrUpdate($"{sessionId}", session, (k, v) =>
|
||||
_remoteControlSessionCache.AddOrUpdate($"{sessionId}", session, (k, v) =>
|
||||
{
|
||||
if (v is RemoteControlSessionEx ex)
|
||||
{
|
||||
|
||||
@ -9,14 +9,14 @@
|
||||
@inject SignInManager<RemotelyUser> SignInManager
|
||||
@inject IHubContext<DesktopHub> DesktopHubContext
|
||||
@inject IHubContext<ViewerHub> ViewerHubContext
|
||||
@inject IDesktopHubSessionCache DesktopSessionCache
|
||||
@inject IRemoteControlSessionCache RemoteControlSessionCache
|
||||
|
||||
@functions {
|
||||
public async Task<IActionResult> OnPost()
|
||||
{
|
||||
if (SignInManager.IsSignedIn(User))
|
||||
{
|
||||
var activeSessions = DesktopSessionCache.Sessions.Where(x => x.RequesterUserName == HttpContext.User.Identity.Name);
|
||||
var activeSessions = RemoteControlSessionCache.Sessions.Where(x => x.RequesterUserName == HttpContext.User.Identity.Name);
|
||||
foreach (var session in activeSessions)
|
||||
{
|
||||
await DesktopHubContext.Clients.Client(session.DesktopConnectionId).SendAsync("Disconnect", "User logged out.");
|
||||
|
||||
@ -79,7 +79,7 @@ namespace Remotely.Server.Hubs
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ICircuitManager _circuitManager;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IDesktopHubSessionCache _desktopSessionCache;
|
||||
private readonly IRemoteControlSessionCache _remoteControlSessionCache;
|
||||
private readonly ConcurrentQueue<CircuitEvent> _eventQueue = new();
|
||||
private readonly IExpiringTokenService _expiringTokenService;
|
||||
private readonly ILogger<CircuitConnection> _logger;
|
||||
@ -94,7 +94,7 @@ namespace Remotely.Server.Hubs
|
||||
ICircuitManager circuitManager,
|
||||
IToastService toastService,
|
||||
IExpiringTokenService expiringTokenService,
|
||||
IDesktopHubSessionCache desktopSessionCache,
|
||||
IRemoteControlSessionCache remoteControlSessionCache,
|
||||
IAgentHubSessionCache agentSessionCache,
|
||||
ILogger<CircuitConnection> logger)
|
||||
{
|
||||
@ -106,7 +106,7 @@ namespace Remotely.Server.Hubs
|
||||
_circuitManager = circuitManager;
|
||||
_toastService = toastService;
|
||||
_expiringTokenService = expiringTokenService;
|
||||
_desktopSessionCache = desktopSessionCache;
|
||||
_remoteControlSessionCache = remoteControlSessionCache;
|
||||
_agentSessionCache = agentSessionCache;
|
||||
_logger = logger;
|
||||
}
|
||||
@ -244,7 +244,7 @@ namespace Remotely.Server.Hubs
|
||||
|
||||
}
|
||||
|
||||
var sessionCount = _desktopSessionCache.Sessions
|
||||
var sessionCount = _remoteControlSessionCache.Sessions
|
||||
.OfType<RemoteControlSessionEx>()
|
||||
.Count(x => x.OrganizationId == User.OrganizationID);
|
||||
|
||||
@ -281,7 +281,7 @@ namespace Remotely.Server.Hubs
|
||||
NotifyUserOnStart = _appConfig.RemoteControlNotifyUser
|
||||
};
|
||||
|
||||
_desktopSessionCache.AddOrUpdate($"{sessionId}", session);
|
||||
_remoteControlSessionCache.AddOrUpdate($"{sessionId}", session);
|
||||
|
||||
var organization = _dataService.GetOrganizationNameByUserName(User.UserName);
|
||||
await _agentHubContext.Clients.Client(serviceConnectionId).SendAsync("RemoteControl",
|
||||
|
||||
@ -134,6 +134,13 @@
|
||||
<br />
|
||||
<ValidationMessage For="() => Input.DBProvider" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Enable Remote Control Recording</label>
|
||||
<br />
|
||||
<InputCheckbox @bind-Value="Input.EnableRemoteControlRecording" autocomplete="off" />
|
||||
<br />
|
||||
<ValidationMessage For="() => Input.EnableRemoteControlRecording" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Enable Windows Event Log</label>
|
||||
<br />
|
||||
|
||||
@ -37,6 +37,8 @@ namespace Remotely.Server.Pages
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public DbProvider DBProvider { get; set; }
|
||||
|
||||
[Display(Name = "Enable Remote Control Recording")]
|
||||
public bool EnableRemoteControlRecording { get; set; }
|
||||
|
||||
[Display(Name = "Enable Windows Event Log")]
|
||||
public bool EnableWindowsEventLog { get; set; }
|
||||
|
||||
@ -226,6 +226,8 @@ services.AddRemoteControlServer(config =>
|
||||
config.AddHubEventHandler<HubEventHandler>();
|
||||
config.AddViewerAuthorizer<ViewerAuthorizer>();
|
||||
config.AddViewerPageDataProvider<ViewerPageDataProvider>();
|
||||
config.AddViewerOptionsProvider<ViewerOptionsProvider>();
|
||||
config.AddSessionRecordingSink<SessionRecordingSink>();
|
||||
});
|
||||
|
||||
services.AddSingleton<IAgentHubSessionCache, AgentHubSessionCache>();
|
||||
@ -292,7 +294,7 @@ using (var scope = app.Services.CreateScope())
|
||||
}
|
||||
|
||||
await dataService.SetAllDevicesNotOnline();
|
||||
dataService.CleanupOldRecords();
|
||||
await dataService.CleanupOldRecords();
|
||||
}
|
||||
|
||||
await app.RunAsync();
|
||||
|
||||
@ -11,6 +11,7 @@ namespace Remotely.Server.Services
|
||||
string[] BannedDevices { get; }
|
||||
double DataRetentionInDays { get; }
|
||||
string DBProvider { get; }
|
||||
bool EnableRemoteControlRecording { get; }
|
||||
bool EnableWindowsEventLog { get; }
|
||||
bool EnforceAttendedAccess { get; }
|
||||
bool ForceClientHttps { get; }
|
||||
@ -40,40 +41,42 @@ namespace Remotely.Server.Services
|
||||
|
||||
public class ApplicationConfig : IApplicationConfig
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public ApplicationConfig(IConfiguration config)
|
||||
{
|
||||
Config = config;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public bool AllowApiLogin => bool.Parse(Config["ApplicationOptions:AllowApiLogin"] ?? "false");
|
||||
public string[] BannedDevices => Config.GetSection("ApplicationOptions:BannedDevices").Get<string[]>() ?? System.Array.Empty<string>();
|
||||
public double DataRetentionInDays => double.Parse(Config["ApplicationOptions:DataRetentionInDays"] ?? "30");
|
||||
public string DBProvider => Config["ApplicationOptions:DBProvider"] ?? "SQLite";
|
||||
public bool EnableWindowsEventLog => bool.Parse(Config["ApplicationOptions:EnableWindowsEventLog"]);
|
||||
public bool EnforceAttendedAccess => bool.Parse(Config["ApplicationOptions:EnforceAttendedAccess"] ?? "false");
|
||||
public bool ForceClientHttps => bool.Parse(Config["ApplicationOptions:ForceClientHttps"] ?? "false");
|
||||
public string[] KnownProxies => Config.GetSection("ApplicationOptions:KnownProxies").Get<string[]>() ?? System.Array.Empty<string>();
|
||||
public int MaxConcurrentUpdates => int.Parse(Config["ApplicationOptions:MaxConcurrentUpdates"] ?? "10");
|
||||
public int MaxOrganizationCount => int.Parse(Config["ApplicationOptions:MaxOrganizationCount"] ?? "1");
|
||||
public string MessageOfTheDay => Config["ApplicationOptions:MessageOfTheDay"];
|
||||
public bool RedirectToHttps => bool.Parse(Config["ApplicationOptions:RedirectToHttps"] ?? "true");
|
||||
public bool RemoteControlNotifyUser => bool.Parse(Config["ApplicationOptions:RemoteControlNotifyUser"] ?? "true");
|
||||
public bool RemoteControlRequiresAuthentication => bool.Parse(Config["ApplicationOptions:RemoteControlRequiresAuthentication"] ?? "true");
|
||||
public int RemoteControlSessionLimit => int.Parse(Config["ApplicationOptions:RemoteControlSessionLimit"] ?? "3");
|
||||
public bool Require2FA => bool.Parse(Config["ApplicationOptions:Require2FA"] ?? "false");
|
||||
public string ServerUrl => Config["ApplicationOptions:ServerUrl"];
|
||||
public bool SmtpCheckCertificateRevocation => bool.Parse(Config["ApplicationOptions:SmtpCheckCertificateRevocation"] ?? "true");
|
||||
public string SmtpDisplayName => Config["ApplicationOptions:SmtpDisplayName"];
|
||||
public string SmtpEmail => Config["ApplicationOptions:SmtpEmail"];
|
||||
public string SmtpHost => Config["ApplicationOptions:SmtpHost"];
|
||||
public string SmtpLocalDomain => Config["ApplicationOptions:SmtpLocalDomain"];
|
||||
public string SmtpPassword => Config["ApplicationOptions:SmtpPassword"];
|
||||
public int SmtpPort => int.Parse(Config["ApplicationOptions:SmtpPort"] ?? "25");
|
||||
public string SmtpUserName => Config["ApplicationOptions:SmtpUserName"];
|
||||
public Theme Theme => Enum.Parse<Theme>(Config["ApplicationOptions:Theme"] ?? "Dark", true);
|
||||
public string[] TrustedCorsOrigins => Config.GetSection("ApplicationOptions:TrustedCorsOrigins").Get<string[]>() ?? System.Array.Empty<string>();
|
||||
public bool UseHsts => bool.Parse(Config["ApplicationOptions:UseHsts"] ?? "false");
|
||||
public bool UseHttpLogging => bool.Parse(Config["ApplicationOptions:UseHttpLogging"] ?? "false");
|
||||
private IConfiguration Config { get; set; }
|
||||
public bool AllowApiLogin => bool.TryParse(_config["ApplicationOptions:AllowApiLogin"], out var result) && result;
|
||||
public string[] BannedDevices => _config.GetSection("ApplicationOptions:BannedDevices").Get<string[]>() ?? System.Array.Empty<string>();
|
||||
public double DataRetentionInDays => double.TryParse(_config["ApplicationOptions:DataRetentionInDays"], out var result) ? result : 30;
|
||||
public string DBProvider => _config["ApplicationOptions:DBProvider"] ?? "SQLite";
|
||||
public bool EnableRemoteControlRecording => bool.TryParse(_config["ApplicationOptions:EnableRemoteControlRecording"], out var result) && result;
|
||||
public bool EnableWindowsEventLog => bool.TryParse(_config["ApplicationOptions:EnableWindowsEventLog"], out var result) && result;
|
||||
public bool EnforceAttendedAccess => bool.TryParse(_config["ApplicationOptions:EnforceAttendedAccess"], out var result) && result;
|
||||
public bool ForceClientHttps => bool.TryParse(_config["ApplicationOptions:ForceClientHttps"], out var result) && result;
|
||||
public string[] KnownProxies => _config.GetSection("ApplicationOptions:KnownProxies").Get<string[]>() ?? System.Array.Empty<string>();
|
||||
public int MaxConcurrentUpdates => int.TryParse(_config["ApplicationOptions:MaxConcurrentUpdates"], out var result) ? result : 10;
|
||||
public int MaxOrganizationCount => int.TryParse(_config["ApplicationOptions:MaxOrganizationCount"], out var result) ? result : 1;
|
||||
public string MessageOfTheDay => _config["ApplicationOptions:MessageOfTheDay"];
|
||||
public bool RedirectToHttps => bool.TryParse(_config["ApplicationOptions:RedirectToHttps"], out var result) && result;
|
||||
public bool RemoteControlNotifyUser => bool.TryParse(_config["ApplicationOptions:RemoteControlNotifyUser"], out var result) && result;
|
||||
public bool RemoteControlRequiresAuthentication => bool.TryParse(_config["ApplicationOptions:RemoteControlRequiresAuthentication"], out var result) && result;
|
||||
public int RemoteControlSessionLimit => int.TryParse(_config["ApplicationOptions:RemoteControlSessionLimit"], out var result) ? result : 3;
|
||||
public bool Require2FA => bool.TryParse(_config["ApplicationOptions:Require2FA"], out var result) && result;
|
||||
public string ServerUrl => _config["ApplicationOptions:ServerUrl"];
|
||||
public bool SmtpCheckCertificateRevocation => !bool.TryParse(_config["ApplicationOptions:SmtpCheckCertificateRevocation"], out var result) || result;
|
||||
public string SmtpDisplayName => _config["ApplicationOptions:SmtpDisplayName"];
|
||||
public string SmtpEmail => _config["ApplicationOptions:SmtpEmail"];
|
||||
public string SmtpHost => _config["ApplicationOptions:SmtpHost"];
|
||||
public string SmtpLocalDomain => _config["ApplicationOptions:SmtpLocalDomain"];
|
||||
public string SmtpPassword => _config["ApplicationOptions:SmtpPassword"];
|
||||
public int SmtpPort => int.TryParse(_config["ApplicationOptions:SmtpPort"], out var result) ? result : 25;
|
||||
public string SmtpUserName => _config["ApplicationOptions:SmtpUserName"];
|
||||
public Theme Theme => Enum.TryParse<Theme>(_config["ApplicationOptions:Theme"], out var result) ? result : Theme.Dark;
|
||||
public string[] TrustedCorsOrigins => _config.GetSection("ApplicationOptions:TrustedCorsOrigins").Get<string[]>() ?? System.Array.Empty<string>();
|
||||
public bool UseHsts => bool.TryParse(_config["ApplicationOptions:UseHsts"], out var result) && result;
|
||||
public bool UseHttpLogging => bool.TryParse(_config["ApplicationOptions:UseHttpLogging"], out var result) && result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,62 +1,113 @@
|
||||
using Microsoft.Build.Framework;
|
||||
using Immense.RemoteControl.Shared.Services;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Identity.Client;
|
||||
using Remotely.Server.Services.RcImplementations;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace Remotely.Server.Services
|
||||
{
|
||||
public class DataCleanupService : IHostedService, IDisposable
|
||||
public class DataCleanupService : BackgroundService, IDisposable
|
||||
{
|
||||
private readonly ILogger<DataCleanupService> _logger;
|
||||
|
||||
private readonly IServiceProvider _services;
|
||||
|
||||
private System.Timers.Timer _cleanupTimer = new(TimeSpan.FromDays(1));
|
||||
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ISystemTime _systemTime;
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
|
||||
public DataCleanupService(
|
||||
IServiceProvider serviceProvider,
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ISystemTime systemTime,
|
||||
IApplicationConfig appConfig,
|
||||
ILogger<DataCleanupService> logger)
|
||||
{
|
||||
_services = serviceProvider;
|
||||
_scopeFactory = scopeFactory;
|
||||
_systemTime = systemTime;
|
||||
_appConfig = appConfig;
|
||||
_logger = logger;
|
||||
|
||||
_cleanupTimer.Elapsed += CleanupTimer_Elapsed;
|
||||
}
|
||||
public void Dispose()
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_cleanupTimer?.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
await Task.Yield();
|
||||
|
||||
await PerformCleanup();
|
||||
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromDays(1));
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = await timer.WaitForNextTickAsync(stoppingToken);
|
||||
await PerformCleanup();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("Application is shutting down. Stopping data cleanup service.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cleanupTimer.Start();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_cleanupTimer.Stop();
|
||||
_cleanupTimer.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void CleanupTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
private async Task PerformCleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _services.CreateScope();
|
||||
var dataService = scope.ServiceProvider.GetRequiredService<IDataService>();
|
||||
dataService.CleanupOldRecords();
|
||||
await RemoveExpiredDbRecords();
|
||||
await RemoveExpiredRecordings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during data cleanup.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveExpiredDbRecords()
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dataService = scope.ServiceProvider.GetRequiredService<IDataService>();
|
||||
await dataService.CleanupOldRecords();
|
||||
}
|
||||
|
||||
private Task RemoveExpiredRecordings()
|
||||
{
|
||||
if (!Directory.Exists(SessionRecordingSink.RecordingsDirectory))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var expirationDate = _systemTime.Now.UtcDateTime - TimeSpan.FromDays(_appConfig.DataRetentionInDays);
|
||||
|
||||
var files = Directory
|
||||
.GetFiles(
|
||||
SessionRecordingSink.RecordingsDirectory,
|
||||
"*.webm",
|
||||
SearchOption.AllDirectories)
|
||||
.Select(x => new FileInfo(x))
|
||||
.Where(x => x.CreationTimeUtc < expirationDate)
|
||||
.ToList();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
_logger.LogInformation("Expired recording deleted: {file}", file.FullName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while deleting expired recording: {file}", file);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ namespace Remotely.Server.Services
|
||||
|
||||
void ChangeUserIsAdmin(string organizationID, string targetUserID, bool isAdmin);
|
||||
|
||||
void CleanupOldRecords();
|
||||
Task CleanupOldRecords();
|
||||
|
||||
Task<ApiToken> CreateApiToken(string userName, string tokenName, string secretHash);
|
||||
|
||||
@ -623,7 +623,7 @@ namespace Remotely.Server.Services
|
||||
}
|
||||
}
|
||||
|
||||
public void CleanupOldRecords()
|
||||
public async Task CleanupOldRecords()
|
||||
{
|
||||
using var dbContext = _appDbFactory.GetContext();
|
||||
|
||||
@ -631,11 +631,12 @@ namespace Remotely.Server.Services
|
||||
{
|
||||
var expirationDate = DateTimeOffset.Now - TimeSpan.FromDays(_appConfig.DataRetentionInDays);
|
||||
|
||||
var scriptRuns = dbContext.ScriptRuns
|
||||
var scriptRuns = await dbContext.ScriptRuns
|
||||
.Include(x => x.Results)
|
||||
.Include(x => x.Devices)
|
||||
.Include(x => x.DevicesCompleted)
|
||||
.Where(x => x.RunAt < expirationDate);
|
||||
.Where(x => x.RunAt < expirationDate)
|
||||
.ToArrayAsync();
|
||||
|
||||
foreach (var run in scriptRuns)
|
||||
{
|
||||
@ -656,7 +657,7 @@ namespace Remotely.Server.Services
|
||||
|
||||
dbContext.RemoveRange(sharedFiles);
|
||||
|
||||
dbContext.SaveChanges();
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
60
Server/Services/RcImplementations/SessionRecordingSink.cs
Normal file
60
Server/Services/RcImplementations/SessionRecordingSink.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using Immense.RemoteControl.Server.Abstractions;
|
||||
using Immense.RemoteControl.Server.Models;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.Services.RcImplementations
|
||||
{
|
||||
public class SessionRecordingSink : ISessionRecordingSink
|
||||
{
|
||||
private readonly ILogger<SessionRecordingSink> _logger;
|
||||
|
||||
public SessionRecordingSink(ILogger<SessionRecordingSink> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public static string RecordingsDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
var logsDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "recordings");
|
||||
if (Directory.Exists("/remotely-data"))
|
||||
{
|
||||
logsDir = "/remotely-data/recordings";
|
||||
}
|
||||
return logsDir;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SinkWebmStream(IAsyncEnumerable<byte[]> webmStream, RemoteControlSession session)
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetDir = Path.Combine(RecordingsDirectory, $"{DateTime.Now:yyyy-MM-dd}");
|
||||
_ = Directory.CreateDirectory(targetDir);
|
||||
|
||||
var viewerName = !string.IsNullOrWhiteSpace(session.RequesterName) ?
|
||||
session.RequesterName :
|
||||
"AnonymousUser";
|
||||
|
||||
var fileName = $"{viewerName}_{DateTime.Now:yyyyMMdd_HHmmssfff}.webm";
|
||||
|
||||
using var fs = new FileStream(Path.Combine(targetDir, fileName), FileMode.Create);
|
||||
|
||||
await foreach (var chunk in webmStream)
|
||||
{
|
||||
await fs.WriteAsync(chunk);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while sinking webm stream.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Server/Services/RcImplementations/ViewerOptionsProvider.cs
Normal file
26
Server/Services/RcImplementations/ViewerOptionsProvider.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Immense.RemoteControl.Server.Abstractions;
|
||||
using Immense.RemoteControl.Server.Models;
|
||||
using Immense.RemoteControl.Shared.Models;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.Services.RcImplementations
|
||||
{
|
||||
public class ViewerOptionsProvider : IViewerOptionsProvider
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
|
||||
public ViewerOptionsProvider(IApplicationConfig appConfig)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
public Task<RemoteControlViewerOptions> GetViewerOptions()
|
||||
{
|
||||
var options = new RemoteControlViewerOptions()
|
||||
{
|
||||
ShouldRecordSession = _appConfig.EnableRemoteControlRecording
|
||||
};
|
||||
return Task.FromResult(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@
|
||||
"BannedDevices": [],
|
||||
"DataRetentionInDays": 90,
|
||||
"DBProvider": "SQLite",
|
||||
"EnableRemoteControlRecording": false,
|
||||
"EnableWindowsEventLog": false,
|
||||
"EnforceAttendedAccess": false,
|
||||
"ForceClientHttps": false,
|
||||
|
||||
@ -35,7 +35,7 @@ namespace Remotely.Server.Tests
|
||||
private Mock<ICircuitManager> _circuitManager;
|
||||
private Mock<IToastService> _toastService;
|
||||
private Mock<IExpiringTokenService> _expiringTokenService;
|
||||
private Mock<IDesktopHubSessionCache> _desktopSessionCache;
|
||||
private Mock<IRemoteControlSessionCache> _remoteControlSessionCache;
|
||||
private Mock<IAgentHubSessionCache> _agentSessionCache;
|
||||
private Mock<ILogger<CircuitConnection>> _logger;
|
||||
private CircuitConnection _circuitConnection;
|
||||
@ -55,7 +55,7 @@ namespace Remotely.Server.Tests
|
||||
_circuitManager = new Mock<ICircuitManager>();
|
||||
_toastService = new Mock<IToastService>();
|
||||
_expiringTokenService = new Mock<IExpiringTokenService>();
|
||||
_desktopSessionCache = new Mock<IDesktopHubSessionCache>();
|
||||
_remoteControlSessionCache = new Mock<IRemoteControlSessionCache>();
|
||||
_agentSessionCache = new Mock<IAgentHubSessionCache>();
|
||||
_logger = new Mock<ILogger<CircuitConnection>>();
|
||||
|
||||
@ -68,7 +68,7 @@ namespace Remotely.Server.Tests
|
||||
_circuitManager.Object,
|
||||
_toastService.Object,
|
||||
_expiringTokenService.Object,
|
||||
_desktopSessionCache.Object,
|
||||
_remoteControlSessionCache.Object,
|
||||
_agentSessionCache.Object,
|
||||
_logger.Object);
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 2c722cecdd6502b2524526e5e15564a8db9f4bad
|
||||
Subproject commit 85292122ef84efe5811e59cdcc208941c2a93037
|
||||
Loading…
Reference in New Issue
Block a user