mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Move appsettings to DB.
This commit is contained in:
parent
2af48bf663
commit
8afdd97640
@ -7,7 +7,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PublishDir>..\Server\wwwroot\Content\Win-x64\</PublishDir>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
|
||||
@ -7,7 +7,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PublishDir>..\Server\wwwroot\Content\Win-x86\</PublishDir>
|
||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
|
||||
@ -7,7 +7,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PublishDir>..\Agent\bin\publish\win-x64\Desktop</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
@ -7,7 +7,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PublishDir>..\Agent\bin\publish\win-x64\Desktop</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
@ -7,7 +7,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x86</Platform>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<PublishDir>..\Agent\bin\publish\win-x86\Desktop</PublishDir>
|
||||
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
|
||||
@ -21,18 +21,18 @@ public class AgentUpdateController : ControllerBase
|
||||
{
|
||||
private readonly IHubContext<AgentHub, IAgentHubClient> _agentHubContext;
|
||||
private readonly ILogger<AgentUpdateController> _logger;
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IWebHostEnvironment _hostEnv;
|
||||
private readonly IAgentHubSessionCache _serviceSessionCache;
|
||||
|
||||
public AgentUpdateController(IWebHostEnvironment hostingEnv,
|
||||
IApplicationConfig appConfig,
|
||||
IDataService dataService,
|
||||
IAgentHubSessionCache serviceSessionCache,
|
||||
IHubContext<AgentHub, IAgentHubClient> agentHubContext,
|
||||
ILogger<AgentUpdateController> logger)
|
||||
{
|
||||
_hostEnv = hostingEnv;
|
||||
_appConfig = appConfig;
|
||||
_dataService = dataService;
|
||||
_serviceSessionCache = serviceSessionCache;
|
||||
_agentHubContext = agentHubContext;
|
||||
_logger = logger;
|
||||
@ -105,7 +105,8 @@ public class AgentUpdateController : ControllerBase
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_appConfig.BannedDevices.Contains(deviceIp))
|
||||
var settings = await _dataService.GetSettings();
|
||||
if (settings.BannedDevices.Contains(deviceIp))
|
||||
{
|
||||
_logger.LogInformation("Device IP ({deviceIp}) is banned. Sending uninstall command.", deviceIp);
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ namespace Remotely.Server.API;
|
||||
[ApiController]
|
||||
public class ClientDownloadsController : ControllerBase
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IEmbeddedServerDataSearcher _embeddedDataSearcher;
|
||||
private readonly SemaphoreSlim _fileLock = new(1, 1);
|
||||
private readonly IWebHostEnvironment _hostEnv;
|
||||
@ -28,12 +28,12 @@ public class ClientDownloadsController : ControllerBase
|
||||
public ClientDownloadsController(
|
||||
IWebHostEnvironment hostEnv,
|
||||
IEmbeddedServerDataSearcher embeddedDataSearcher,
|
||||
IApplicationConfig appConfig,
|
||||
IDataService dataService,
|
||||
ILogger<ClientDownloadsController> logger)
|
||||
{
|
||||
_hostEnv = hostEnv;
|
||||
_embeddedDataSearcher = embeddedDataSearcher;
|
||||
_appConfig = appConfig;
|
||||
_dataService = dataService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -133,7 +133,8 @@ public class ClientDownloadsController : ControllerBase
|
||||
var hostIndex = fileContents.IndexOf("HostName=");
|
||||
var orgIndex = fileContents.IndexOf("Organization=");
|
||||
|
||||
var effectiveScheme = _appConfig.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var settings = await _dataService.GetSettings();
|
||||
var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme;
|
||||
|
||||
fileContents[hostIndex] = $"HostName=\"{effectiveScheme}://{Request.Host}\"";
|
||||
fileContents[orgIndex] = $"Organization=\"{organizationId}\"";
|
||||
@ -143,9 +144,10 @@ public class ClientDownloadsController : ControllerBase
|
||||
|
||||
private async Task<IActionResult> GetDesktopFile(string filePath, string? organizationId = null)
|
||||
{
|
||||
LogRequest(nameof(GetDesktopFile));
|
||||
var settings = await _dataService.GetSettings();
|
||||
await LogRequest(nameof(GetDesktopFile));
|
||||
|
||||
var effectiveScheme = _appConfig.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var serverUrl = $"{effectiveScheme}://{Request.Host}";
|
||||
var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId);
|
||||
var result = await _embeddedDataSearcher.GetRewrittenStream(filePath, embeddedData);
|
||||
@ -160,7 +162,8 @@ public class ClientDownloadsController : ControllerBase
|
||||
|
||||
private async Task<IActionResult> GetInstallFile(string organizationId, string platformID)
|
||||
{
|
||||
LogRequest(nameof(GetInstallFile));
|
||||
var settings = await _dataService.GetSettings();
|
||||
await LogRequest(nameof(GetInstallFile));
|
||||
|
||||
if (!await _fileLock.WaitAsync(TimeSpan.FromSeconds(15)))
|
||||
{
|
||||
@ -173,7 +176,7 @@ public class ClientDownloadsController : ControllerBase
|
||||
{
|
||||
case "WindowsInstaller":
|
||||
{
|
||||
var effectiveScheme = _appConfig.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var serverUrl = $"{effectiveScheme}://{Request.Host}";
|
||||
var filePath = Path.Combine(_hostEnv.WebRootPath, "Content", "Remotely_Installer.exe");
|
||||
var embeddedData = new EmbeddedServerData(new Uri(serverUrl), organizationId);
|
||||
@ -220,9 +223,10 @@ public class ClientDownloadsController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
private void LogRequest(string methodName)
|
||||
private async Task LogRequest(string methodName)
|
||||
{
|
||||
if (_appConfig.UseHttpLogging)
|
||||
var settings = await _dataService.GetSettings();
|
||||
if (settings.UseHttpLogging)
|
||||
{
|
||||
var ip = Request.HttpContext.Connection.RemoteIpAddress;
|
||||
if (ip?.IsIPv4MappedToIPv6 == true)
|
||||
@ -230,7 +234,7 @@ public class ClientDownloadsController : ControllerBase
|
||||
ip = ip.MapToIPv4();
|
||||
}
|
||||
|
||||
var effectiveScheme = _appConfig.ForceClientHttps ? "https" : Request.Scheme;
|
||||
var effectiveScheme = settings.ForceClientHttps ? "https" : Request.Scheme;
|
||||
|
||||
_logger.LogInformation(
|
||||
"Started client download via {methodName}. Effective Scheme: {scheme}. Effective Host: {host}. Remote IP: {ip}.",
|
||||
|
||||
@ -20,7 +20,6 @@ namespace Remotely.Server.API;
|
||||
[Obsolete("This controller is here only for legacy purposes. For new integrations, use API tokens.")]
|
||||
public class LoginController : ControllerBase
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IHubContext<DesktopHub> _desktopHub;
|
||||
private readonly IRemoteControlSessionCache _remoteControlSessionCache;
|
||||
@ -31,7 +30,6 @@ public class LoginController : ControllerBase
|
||||
public LoginController(
|
||||
SignInManager<RemotelyUser> signInManager,
|
||||
IDataService dataService,
|
||||
IApplicationConfig appConfig,
|
||||
IHubContext<DesktopHub> casterHubContext,
|
||||
IRemoteControlSessionCache remoteControlSessionCache,
|
||||
IHubContext<ViewerHub> viewerHubContext,
|
||||
@ -39,7 +37,6 @@ public class LoginController : ControllerBase
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_dataService = dataService;
|
||||
_appConfig = appConfig;
|
||||
_desktopHub = casterHubContext;
|
||||
_remoteControlSessionCache = remoteControlSessionCache;
|
||||
_viewerHub = viewerHubContext;
|
||||
@ -76,7 +73,8 @@ public class LoginController : ControllerBase
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Post([FromBody] ApiLogin login)
|
||||
{
|
||||
if (!_appConfig.AllowApiLogin)
|
||||
var settings = await _dataService.GetSettings();
|
||||
if (!settings.AllowApiLogin)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
@ -27,10 +27,9 @@ public class RemoteControlController : ControllerBase
|
||||
private readonly IHubContext<AgentHub, IAgentHubClient> _agentHub;
|
||||
private readonly IRemoteControlSessionCache _remoteControlSessionCache;
|
||||
private readonly IAgentHubSessionCache _serviceSessionCache;
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IOtpProvider _otpProvider;
|
||||
private readonly IHubEventHandler _hubEvents;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly SignInManager<RemotelyUser> _signInManager;
|
||||
private readonly ILogger<RemoteControlController> _logger;
|
||||
|
||||
@ -42,14 +41,12 @@ public class RemoteControlController : ControllerBase
|
||||
IAgentHubSessionCache serviceSessionCache,
|
||||
IOtpProvider otpProvider,
|
||||
IHubEventHandler hubEvents,
|
||||
IApplicationConfig appConfig,
|
||||
ILogger<RemoteControlController> logger)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_agentHub = agentHub;
|
||||
_remoteControlSessionCache = remoteControlSessionCache;
|
||||
_serviceSessionCache = serviceSessionCache;
|
||||
_appConfig = appConfig;
|
||||
_otpProvider = otpProvider;
|
||||
_hubEvents = hubEvents;
|
||||
_signInManager = signInManager;
|
||||
@ -72,7 +69,8 @@ public class RemoteControlController : ControllerBase
|
||||
[Obsolete("This method is deprecated. Use the GET method along with API keys instead.")]
|
||||
public async Task<IActionResult> Post([FromBody] RemoteControlRequest rcRequest)
|
||||
{
|
||||
if (!_appConfig.AllowApiLogin)
|
||||
var settings = await _dataService.GetSettings();
|
||||
if (!settings.AllowApiLogin)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
@ -145,7 +143,8 @@ public class RemoteControlController : ControllerBase
|
||||
.OfType<RemoteControlSessionEx>()
|
||||
.Count(x => x.OrganizationId == orgId);
|
||||
|
||||
if (sessionCount > _appConfig.RemoteControlSessionLimit)
|
||||
var settings = await _dataService.GetSettings();
|
||||
if (sessionCount > settings.RemoteControlSessionLimit)
|
||||
{
|
||||
return BadRequest("There are already the maximum amount of active remote control sessions for your organization.");
|
||||
}
|
||||
|
||||
@ -12,19 +12,18 @@ namespace Remotely.Server.Auth;
|
||||
public class TwoFactorRequiredHandler : AuthorizationHandler<TwoFactorRequiredRequirement>
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
|
||||
public TwoFactorRequiredHandler(IDataService dataService, IApplicationConfig appConfig)
|
||||
public TwoFactorRequiredHandler(IDataService dataService)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, TwoFactorRequiredRequirement requirement)
|
||||
{
|
||||
var settings = await _dataService.GetSettings();
|
||||
if (context.User.Identity?.IsAuthenticated == true &&
|
||||
context.User.Identity.Name is not null &&
|
||||
_appConfig.Require2FA)
|
||||
settings.Require2FA)
|
||||
{
|
||||
var userResult = await _dataService.GetUserByName(context.User.Identity.Name);
|
||||
|
||||
|
||||
@ -16,13 +16,12 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IdentityRedirectManager RedirectManager
|
||||
@inject IDataService DataService
|
||||
@inject IApplicationConfig AppConfig
|
||||
@inject IWebHostEnvironment HostEnv
|
||||
|
||||
<PageTitle>Register</PageTitle>
|
||||
<h1>Register</h1>
|
||||
|
||||
@if (!IsRegistrationEnabled())
|
||||
@if (!_registrationEnabled)
|
||||
{
|
||||
<h2>Registration is disabled.</h2>
|
||||
}
|
||||
@ -69,6 +68,7 @@ else
|
||||
@code {
|
||||
private IEnumerable<IdentityError>? identityErrors;
|
||||
private int _organizationCount;
|
||||
private bool _registrationEnabled;
|
||||
|
||||
[SupplyParameterFromForm]
|
||||
private InputModel Input { get; set; } = new();
|
||||
@ -78,12 +78,13 @@ else
|
||||
|
||||
private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}";
|
||||
|
||||
protected override void OnInitialized()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_registrationEnabled = await IsRegistrationEnabled();
|
||||
_organizationCount = DataService.GetOrganizationCount();
|
||||
base.OnInitialized();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
|
||||
public async Task RegisterUser(EditContext editContext)
|
||||
{
|
||||
var user = CreateUser();
|
||||
@ -155,9 +156,10 @@ else
|
||||
return (IUserEmailStore<RemotelyUser>)UserStore;
|
||||
}
|
||||
|
||||
private bool IsRegistrationEnabled()
|
||||
private async Task<bool> IsRegistrationEnabled()
|
||||
{
|
||||
return AppConfig.MaxOrganizationCount < 0 || _organizationCount < AppConfig.MaxOrganizationCount;
|
||||
var settings = await DataService.GetSettings();
|
||||
return settings.MaxOrganizationCount < 0 || _organizationCount < settings.MaxOrganizationCount;
|
||||
}
|
||||
|
||||
private sealed class InputModel
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
@inject AuthenticationStateProvider AuthProvider
|
||||
@inject IDataService DataService
|
||||
@inject IApplicationConfig AppConfig
|
||||
@inject IDataService DataService
|
||||
@inject IThemeProvider ThemeProvider
|
||||
|
||||
<!DOCTYPE html>
|
||||
@ -28,19 +28,6 @@
|
||||
<HeadOutlet @rendermode="RenderModeForPage" />
|
||||
</head>
|
||||
<body>
|
||||
<component type="typeof(App)" render-mode="ServerPrerendered" />
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
<environment include="Staging,Production">
|
||||
An error has occurred. This application may no longer respond until reloaded.
|
||||
</environment>
|
||||
<environment include="Development">
|
||||
An unhandled exception has occurred. See browser dev tools for details.
|
||||
</environment>
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<Routes @rendermode="RenderModeForPage" />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
@inherits AuthComponentBase
|
||||
|
||||
@inject NavigationManager NavManager
|
||||
@inject IApplicationConfig AppConfig
|
||||
@inject IDataService DataService
|
||||
@inject SignInManager<RemotelyUser> SignInManager
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(AppConfig.MessageOfTheDay))
|
||||
@if (!string.IsNullOrWhiteSpace(_settings?.MessageOfTheDay))
|
||||
{
|
||||
<div class="me-5">
|
||||
<AlertBanner Message="@AppConfig.MessageOfTheDay" StatusClass="info" />
|
||||
<AlertBanner Message="@_settings?.MessageOfTheDay" StatusClass="info" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -17,11 +17,12 @@
|
||||
<ChatFrame />
|
||||
|
||||
@code {
|
||||
private SettingsModel? _settings;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (User is not null &&
|
||||
AppConfig.Require2FA &&
|
||||
_settings?.Require2FA == true &&
|
||||
!User.TwoFactorEnabled)
|
||||
{
|
||||
NavManager.NavigateTo("/TwoFactorRequired");
|
||||
@ -31,6 +32,7 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_settings = await DataService.GetSettings();
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
var isAuthenticated = await AuthService.IsAuthenticated();
|
||||
|
||||
@ -91,11 +91,6 @@ public partial class Terminal : AuthComponentBase, IDisposable
|
||||
[Inject]
|
||||
private IToastService ToastService { get; init; } = null!;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Messenger.Unregister<PowerShellCompletionsMessage, string>(this, CircuitConnection.ConnectionId);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected override Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
|
||||
@ -34,6 +34,12 @@
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<ToastHarness />
|
||||
<LoaderHarness />
|
||||
<ModalHarness />
|
||||
@ -1,7 +1,7 @@
|
||||
@implements IDisposable
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IAuthService AuthService
|
||||
@inject IApplicationConfig AppConfig
|
||||
@inject IDataService DataService
|
||||
@inject IDataService DataService
|
||||
|
||||
<div class="ps-4 pb-1 pe-0 pt-0 navbar navbar-dark" style="background-color: rgba(0,0,0,0.3)">
|
||||
@ -122,7 +122,7 @@
|
||||
<li class="px-3 mt-3">
|
||||
<a class="btn btn-link text-light" href="Account/Login">Log in</a>
|
||||
</li>
|
||||
@if (AppConfig.MaxOrganizationCount < 0 || DataService.GetOrganizationCount() < AppConfig.MaxOrganizationCount)
|
||||
@if (_isRegistrationEnabled)
|
||||
{
|
||||
<li class="px-3">
|
||||
<a class="btn btn-link text-light" href="Account/Register">Register</a>
|
||||
@ -139,7 +139,7 @@
|
||||
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
|
||||
private bool _isRegistrationEnabled;
|
||||
private RemotelyUser? _user;
|
||||
private Organization? _organization;
|
||||
private string? _currentUrl;
|
||||
@ -151,6 +151,9 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var settings = await DataService.GetSettings();
|
||||
_isRegistrationEnabled = settings.MaxOrganizationCount < 0 || DataService.GetOrganizationCount() < settings.MaxOrganizationCount;
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
_currentUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri);
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
@page "/"
|
||||
@inject IApplicationConfig AppConfig
|
||||
@inject IDataService DataService
|
||||
|
||||
<AuthorizeView>
|
||||
@ -13,7 +12,7 @@
|
||||
<p class="lead">
|
||||
<a class="btn btn-primary btn-lg me-2" href="Account/Login" role="button">Log In</a>
|
||||
|
||||
@if (AppConfig.MaxOrganizationCount < 0 || DataService.GetOrganizationCount() < AppConfig.MaxOrganizationCount)
|
||||
@if (_isRegistrationEnabled)
|
||||
{
|
||||
<a class="btn btn-primary btn-lg" href="Account/Register" role="button">Register</a>
|
||||
}
|
||||
@ -24,3 +23,15 @@
|
||||
<AuthorizedIndex />
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@code
|
||||
{
|
||||
private bool _isRegistrationEnabled;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var settings = await DataService.GetSettings();
|
||||
_isRegistrationEnabled = settings.MaxOrganizationCount < 0 || DataService.GetOrganizationCount() < settings.MaxOrganizationCount;
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
}
|
||||
@ -121,18 +121,6 @@
|
||||
<br />
|
||||
<ValidationMessage For="() => Input.DataRetentionInDays" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">Database Provider</label>
|
||||
<br />
|
||||
<select class="form-control" @bind="Input.DBProvider">
|
||||
@foreach (var provider in Enum.GetValues<DbProvider>())
|
||||
{
|
||||
<option @key="provider" value="@provider">@provider</option>
|
||||
}
|
||||
</select>
|
||||
<br />
|
||||
<ValidationMessage For="() => Input.DBProvider" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Enable Remote Control Recording</label>
|
||||
<br />
|
||||
@ -357,34 +345,6 @@
|
||||
<ValidationMessage For="() => Input.UseHttpLogging" />
|
||||
</div>
|
||||
|
||||
<h4>Connection Strings</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">PostgreSQL</label>
|
||||
<br />
|
||||
<InputText @bind-Value="ConnectionStrings.PostgreSQL" class="form-control" autocomplete="off" />
|
||||
<br />
|
||||
<ValidationMessage For="() => ConnectionStrings.PostgreSQL" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">SQLite</label>
|
||||
<br />
|
||||
<InputText @bind-Value="ConnectionStrings.SQLite" class="form-control" autocomplete="off" />
|
||||
<br />
|
||||
<ValidationMessage For="() => ConnectionStrings.SQLite" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">SQL Server</label>
|
||||
<br />
|
||||
<InputText @bind-Value="ConnectionStrings.SQLServer" class="form-control" autocomplete="off" />
|
||||
<br />
|
||||
<ValidationMessage For="() => ConnectionStrings.SQLServer" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group mt-3">
|
||||
<button type="button" class="btn btn-primary" @onclick="Save">Save</button>
|
||||
</div>
|
||||
|
||||
@ -2,123 +2,18 @@
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Remotely.Server.Components;
|
||||
using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Interfaces;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Remotely.Server.Components.Pages;
|
||||
|
||||
public class AppSettingsModel
|
||||
{
|
||||
[Display(Name = "Allow API Login")]
|
||||
public bool AllowApiLogin { get; set; }
|
||||
|
||||
[Display(Name = "Banned Devices")]
|
||||
public List<string> BannedDevices { get; set; } = new();
|
||||
|
||||
[Display(Name = "Data Retention (days)")]
|
||||
public double DataRetentionInDays { get; set; }
|
||||
|
||||
[Display(Name = "Database Provider")]
|
||||
[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; }
|
||||
|
||||
[Display(Name = "Enforce Attended Access")]
|
||||
public bool EnforceAttendedAccess { get; set; }
|
||||
|
||||
[Display(Name = "Force Client HTTPS")]
|
||||
public bool ForceClientHttps { get; set; }
|
||||
|
||||
[Display(Name = "Known Proxies")]
|
||||
public List<string> KnownProxies { get; set; } = new();
|
||||
|
||||
[Display(Name = "Max Concurrent Updates")]
|
||||
public int MaxConcurrentUpdates { get; set; }
|
||||
|
||||
[Display(Name = "Max Organizations")]
|
||||
public int MaxOrganizationCount { get; set; }
|
||||
[Display(Name = "Message of the Day")]
|
||||
public string? MessageOfTheDay { get; set; }
|
||||
|
||||
[Display(Name = "Redirect To HTTPS")]
|
||||
public bool RedirectToHttps { get; set; }
|
||||
|
||||
[Display(Name = "Remote Control Notify User")]
|
||||
public bool RemoteControlNotifyUser { get; set; }
|
||||
|
||||
[Display(Name = "Remote Control Requires Authentication")]
|
||||
public bool RemoteControlRequiresAuthentication { get; set; }
|
||||
|
||||
[Display(Name = "Remote Control Session Limit")]
|
||||
public double RemoteControlSessionLimit { get; set; }
|
||||
|
||||
[Display(Name = "Require 2FA")]
|
||||
public bool Require2FA { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Display Name")]
|
||||
public string? SmtpDisplayName { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Email")]
|
||||
[EmailAddress]
|
||||
public string? SmtpEmail { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Host")]
|
||||
public string? SmtpHost { get; set; }
|
||||
[Display(Name = "SMTP Local Domain")]
|
||||
public string? SmtpLocalDomain { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Check Certificate Revocation")]
|
||||
public bool SmtpCheckCertificateRevocation { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Password")]
|
||||
public string? SmtpPassword { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Port")]
|
||||
public int SmtpPort { get; set; }
|
||||
|
||||
[Display(Name = "SMTP Username")]
|
||||
public string? SmtpUserName { get; set; }
|
||||
|
||||
[Display(Name = "Theme")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public Theme Theme { get; set; }
|
||||
|
||||
[Display(Name = "Trusted CORS Origins")]
|
||||
public List<string> TrustedCorsOrigins { get; set; } = new();
|
||||
|
||||
[Display(Name = "Use HSTS")]
|
||||
public bool UseHsts { get; set; }
|
||||
|
||||
[Display(Name = "Use HTTP Logging")]
|
||||
public bool UseHttpLogging { get; set; }
|
||||
}
|
||||
|
||||
public class ConnectionStringsModel
|
||||
{
|
||||
[Display(Name = "PostgreSQL")]
|
||||
public string? PostgreSQL { get; set; }
|
||||
|
||||
[Display(Name = "SQLite")]
|
||||
public string? SQLite { get; set; }
|
||||
|
||||
[Display(Name = "SQL Server")]
|
||||
public string? SQLServer { get; set; }
|
||||
}
|
||||
|
||||
public partial class ServerConfig : AuthComponentBase
|
||||
{
|
||||
private readonly List<RemotelyUser> _userList = new();
|
||||
private string? _alertMessage;
|
||||
private string? _bannedDeviceSelected;
|
||||
private string? _bannedDeviceToAdd;
|
||||
@ -126,54 +21,44 @@ public partial class ServerConfig : AuthComponentBase
|
||||
private string? _knownProxySelected;
|
||||
private string? _knownProxyToAdd;
|
||||
|
||||
private bool _showMyOrgAdminsOnly = true;
|
||||
private bool _showAdminsOnly;
|
||||
|
||||
private bool _showMyOrgAdminsOnly = true;
|
||||
private string? _trustedCorsOriginSelected;
|
||||
private string? _trustedCorsOriginToAdd;
|
||||
|
||||
private readonly List<RemotelyUser> _userList = new();
|
||||
|
||||
[Inject]
|
||||
public required IHubContext<AgentHub, IAgentHubClient> AgentHubContext { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IHubContext<AgentHub, IAgentHubClient> AgentHubContext { get; init; } = null!;
|
||||
public required ICircuitManager CircuitManager { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IConfiguration Configuration { get; init; } = null!;
|
||||
|
||||
private ConnectionStringsModel ConnectionStrings { get; } = new();
|
||||
public required IDataService DataService { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IDataService DataService { get; init; } = null!;
|
||||
public required IEmailSenderEx EmailSender { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IEmailSenderEx EmailSender { get; init; } = null!;
|
||||
public required IWebHostEnvironment HostEnv { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IWebHostEnvironment HostEnv { get; init; } = null!;
|
||||
public required ILogger<ServerConfig> Logger { get; init; }
|
||||
|
||||
[Inject]
|
||||
private ILogger<ServerConfig> Logger { get; init; } = null!;
|
||||
public required IModalService ModalService { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IAgentHubSessionCache ServiceSessionCache { get; init; } = null!;
|
||||
|
||||
private AppSettingsModel Input { get; } = new();
|
||||
public required IAgentHubSessionCache ServiceSessionCache { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IModalService ModalService { get; init; } = null!;
|
||||
public required IToastService ToastService { get; init; }
|
||||
|
||||
[Inject]
|
||||
private IUpgradeService UpgradeService { get; init; } = null!;
|
||||
public required IUpgradeService UpgradeService { get; init; }
|
||||
|
||||
[Inject]
|
||||
private ICircuitManager CircuitManager { get; init; } = null!;
|
||||
|
||||
private SettingsModel Input { get; set; } = new();
|
||||
private IEnumerable<string> OutdatedDevices => GetOutdatedDevices();
|
||||
|
||||
[Inject]
|
||||
private IToastService ToastService { get; init; } = null!;
|
||||
|
||||
private int TotalDevices => DataService.GetTotalDevices();
|
||||
|
||||
private IEnumerable<RemotelyUser> UserList
|
||||
@ -182,7 +67,7 @@ public partial class ServerConfig : AuthComponentBase
|
||||
{
|
||||
if (User is null)
|
||||
{
|
||||
return Enumerable.Empty<RemotelyUser>();
|
||||
return [];
|
||||
}
|
||||
|
||||
EnsureUserSet();
|
||||
@ -202,8 +87,8 @@ public partial class ServerConfig : AuthComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
Configuration.Bind("ApplicationOptions", Input);
|
||||
Configuration.Bind("ConnectionStrings", ConnectionStrings);
|
||||
Input = await DataService.GetSettings();
|
||||
|
||||
_userList.AddRange(DataService.GetAllUsersForServer().OrderBy(x => x.UserName));
|
||||
}
|
||||
|
||||
@ -320,25 +205,18 @@ public partial class ServerConfig : AuthComponentBase
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
var resetEvent = new ManualResetEventSlim();
|
||||
|
||||
Configuration.GetReloadToken().RegisterChangeCallback((e) =>
|
||||
{
|
||||
resetEvent.Set();
|
||||
}, null);
|
||||
|
||||
await SaveInputToAppSettings();
|
||||
|
||||
resetEvent.Wait(5_000);
|
||||
|
||||
await DataService.SaveSettings(Input);
|
||||
|
||||
ToastService.ShowToast("Configuration saved.");
|
||||
_alertMessage = "Configuration saved.";
|
||||
}
|
||||
|
||||
private async Task SaveAndTestSmtpSettings()
|
||||
{
|
||||
EnsureUserSet();
|
||||
await SaveInputToAppSettings();
|
||||
|
||||
await DataService.SaveSettings(Input);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(User.Email))
|
||||
{
|
||||
ToastService.ShowToast2("User email is not set.", Enums.ToastType.Warning);
|
||||
@ -358,58 +236,6 @@ public partial class ServerConfig : AuthComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveInputToAppSettings()
|
||||
{
|
||||
string savePath;
|
||||
var prodSettings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Production.json");
|
||||
var stagingSettings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Staging.json");
|
||||
var devSettings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Development.json");
|
||||
var settings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.json");
|
||||
|
||||
if (HostEnv.IsProduction()
|
||||
&& prodSettings.Exists &&
|
||||
!string.IsNullOrWhiteSpace(prodSettings.PhysicalPath))
|
||||
{
|
||||
savePath = prodSettings.PhysicalPath;
|
||||
}
|
||||
else if (
|
||||
HostEnv.IsStaging() &&
|
||||
stagingSettings.Exists &&
|
||||
!string.IsNullOrWhiteSpace(stagingSettings.PhysicalPath))
|
||||
{
|
||||
savePath = stagingSettings.PhysicalPath;
|
||||
}
|
||||
else if (
|
||||
HostEnv.IsDevelopment() &&
|
||||
devSettings.Exists &&
|
||||
!string.IsNullOrWhiteSpace(devSettings.PhysicalPath))
|
||||
{
|
||||
savePath = devSettings.PhysicalPath;
|
||||
}
|
||||
else if (settings.Exists && !string.IsNullOrWhiteSpace(settings.PhysicalPath))
|
||||
{
|
||||
savePath = settings.PhysicalPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var settingsJson = JsonSerializer.Deserialize<IDictionary<string, object>>(await File.ReadAllTextAsync(savePath));
|
||||
if (settingsJson is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
settingsJson["ApplicationOptions"] = Input;
|
||||
settingsJson["ConnectionStrings"] = ConnectionStrings;
|
||||
|
||||
await File.WriteAllTextAsync(savePath, JsonSerializer.Serialize(settingsJson, new JsonSerializerOptions() { WriteIndented = true }));
|
||||
|
||||
if (Configuration is IConfigurationRoot root)
|
||||
{
|
||||
root.Reload();
|
||||
}
|
||||
}
|
||||
private void SetIsServerAdmin(ChangeEventArgs ev, RemotelyUser user)
|
||||
{
|
||||
if (ev.Value is not bool isAdmin)
|
||||
|
||||
@ -24,4 +24,5 @@
|
||||
@using Remotely.Server.Components.Scripts
|
||||
@using Remotely.Server.Components.TreeView
|
||||
@using Remotely.Server.Auth
|
||||
@using Remotely.Shared.Entities
|
||||
@using Remotely.Shared.Entities
|
||||
@using Remotely.Server.Models
|
||||
@ -1,17 +1,12 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Remotely.Server.Converters;
|
||||
using Remotely.Shared.Entities;
|
||||
using Remotely.Shared.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Remotely.Server.Data;
|
||||
@ -37,6 +32,7 @@ public class AppDb : IdentityDbContext
|
||||
public DbSet<DeviceGroup> DeviceGroups { get; set; }
|
||||
public DbSet<Device> Devices { get; set; }
|
||||
public DbSet<InviteLink> InviteLinks { get; set; }
|
||||
public DbSet<KeyValueRecord> KeyValueRecords { get; set; }
|
||||
public DbSet<Organization> Organizations { get; set; }
|
||||
public DbSet<SavedScript> SavedScripts { get; set; }
|
||||
public DbSet<ScriptResult> ScriptResults { get; set; }
|
||||
@ -45,7 +41,6 @@ public class AppDb : IdentityDbContext
|
||||
public DbSet<SharedFile> SharedFiles { get; set; }
|
||||
public new DbSet<RemotelyUser> Users { get; set; }
|
||||
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
options.ConfigureWarnings(x => x.Ignore(RelationalEventId.MultipleCollectionIncludeWarning));
|
||||
@ -284,13 +279,13 @@ public class AppDb : IdentityDbContext
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
return [];
|
||||
}
|
||||
return JsonSerializer.Deserialize<string[]>(value, jsonOptions) ?? Array.Empty<string>();
|
||||
return JsonSerializer.Deserialize<string[]>(value, jsonOptions) ?? [];
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -10,31 +10,10 @@ public interface IAppDbFactory
|
||||
AppDb GetContext();
|
||||
}
|
||||
|
||||
public class AppDbFactory : IAppDbFactory
|
||||
public class AppDbFactory(IServiceProvider _services) : IAppDbFactory
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IWebHostEnvironment _hostEnv;
|
||||
|
||||
public AppDbFactory(
|
||||
IApplicationConfig appConfig,
|
||||
IConfiguration configuration,
|
||||
IWebHostEnvironment hostEnv)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_configuration = configuration;
|
||||
_hostEnv = hostEnv;
|
||||
}
|
||||
|
||||
public AppDb GetContext()
|
||||
{
|
||||
return _appConfig.DBProvider.ToLower() switch
|
||||
{
|
||||
"sqlite" => new SqliteDbContext(_configuration, _hostEnv),
|
||||
"sqlserver" => new SqlServerDbContext(_configuration, _hostEnv),
|
||||
"postgresql" => new PostgreSqlDbContext(_configuration, _hostEnv),
|
||||
"inmemory" => new TestingDbContext(_hostEnv),
|
||||
_ => throw new ArgumentException("Unknown DB provider."),
|
||||
};
|
||||
return _services.GetRequiredService<AppDb>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,22 +1,38 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
|
||||
|
||||
USER app
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
RUN apt -y update && apt -y install curl
|
||||
RUN mkdir -p /app/AppData
|
||||
RUN chown app:app -R /app/AppData
|
||||
|
||||
WORKDIR /app
|
||||
EXPOSE 5000
|
||||
EXPOSE 5001
|
||||
|
||||
COPY /_immense.Remotely/Server/linux-x64/Server /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["Directory.Build.props", "."]
|
||||
COPY ["Server/Server.csproj", "Server/"]
|
||||
COPY ["Shared/Shared.csproj", "Shared/"]
|
||||
COPY ["submodules/Immense.RemoteControl/Immense.RemoteControl.Shared/Immense.RemoteControl.Shared.csproj", "submodules/Immense.RemoteControl/Immense.RemoteControl.Shared/"]
|
||||
COPY ["submodules/Immense.RemoteControl/Immense.RemoteControl.Server/Immense.RemoteControl.Server.csproj", "submodules/Immense.RemoteControl/Immense.RemoteControl.Server/"]
|
||||
RUN dotnet restore "./Server/./Server.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/Server"
|
||||
|
||||
RUN dotnet build "./Server.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./Server.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
|
||||
RUN \
|
||||
apt-get -y update && \
|
||||
apt-get -y install curl && \
|
||||
mkdir -p /remotely-data && \
|
||||
sed -i 's/DataSource=Remotely.db/DataSource=\/app\/AppData\/Remotely.db/' /app/appsettings.json
|
||||
|
||||
USER app
|
||||
ENTRYPOINT ["dotnet", "Remotely_Server.dll"]
|
||||
|
||||
HEALTHCHECK --interval=5m --timeout=3s \
|
||||
|
||||
@ -7,15 +7,15 @@ SHELL ["/bin/bash", "-c"]
|
||||
EXPOSE 5000
|
||||
EXPOSE 5001
|
||||
|
||||
COPY ./bin/publish /app
|
||||
COPY /_immense.Remotely/Server/linux-x64/Server /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN \
|
||||
apt-get -y update && \
|
||||
apt-get -y install curl && \
|
||||
mkdir -p /remotely-data && \
|
||||
sed -i 's/DataSource=Remotely.db/DataSource=\/app\/AppData\/Remotely.db/' ./appsettings.json
|
||||
mkdir -p /app/AppData && \
|
||||
sed -i 's/DataSource=Remotely.db/DataSource=\/app\/AppData\/Remotely.db/' /app/appsettings.json
|
||||
|
||||
ENTRYPOINT ["dotnet", "Remotely_Server.dll"]
|
||||
|
||||
31
Server/Extensions/AppDbExtensions.cs
Normal file
31
Server/Extensions/AppDbExtensions.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Remotely.Server.Data;
|
||||
using Remotely.Server.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Remotely.Server.Extensions;
|
||||
|
||||
public static class AppDbExtensions
|
||||
{
|
||||
public static async Task<SettingsModel> GetAppSettings(this AppDb dbContext)
|
||||
{
|
||||
var record = await dbContext.KeyValueRecords.FindAsync(SettingsModel.DbKey);
|
||||
if (record is null)
|
||||
{
|
||||
record = new()
|
||||
{
|
||||
Key = SettingsModel.DbKey,
|
||||
};
|
||||
await dbContext.KeyValueRecords.AddAsync(record);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(record.Value))
|
||||
{
|
||||
var settings = new SettingsModel();
|
||||
record.Value = JsonSerializer.Serialize(settings);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<SettingsModel>(record.Value) ?? new();
|
||||
}
|
||||
}
|
||||
@ -23,9 +23,8 @@ namespace Remotely.Server.Hubs;
|
||||
|
||||
public class AgentHub : Hub<IAgentHubClient>
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly ICircuitManager _circuitManager;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly ICircuitManager _circuitManager;
|
||||
private readonly IExpiringTokenService _expiringTokenService;
|
||||
private readonly ILogger<AgentHub> _logger;
|
||||
private readonly IMessenger _messenger;
|
||||
@ -33,8 +32,8 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
private readonly IAgentHubSessionCache _serviceSessionCache;
|
||||
private readonly IHubContext<ViewerHub> _viewerHubContext;
|
||||
|
||||
public AgentHub(IDataService dataService,
|
||||
IApplicationConfig appConfig,
|
||||
public AgentHub(
|
||||
IDataService dataService,
|
||||
IAgentHubSessionCache serviceSessionCache,
|
||||
IHubContext<ViewerHub> viewerHubContext,
|
||||
ICircuitManager circuitManager,
|
||||
@ -46,7 +45,6 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
_dataService = dataService;
|
||||
_serviceSessionCache = serviceSessionCache;
|
||||
_viewerHubContext = viewerHubContext;
|
||||
_appConfig = appConfig;
|
||||
_circuitManager = circuitManager;
|
||||
_expiringTokenService = expiringTokenService;
|
||||
_remoteControlSessions = remoteControlSessionCache;
|
||||
@ -288,9 +286,10 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
return _messenger.Send(message, requesterId);
|
||||
}
|
||||
|
||||
public string GetServerUrl()
|
||||
public async Task<string> GetServerUrl()
|
||||
{
|
||||
return _appConfig.ServerUrl;
|
||||
var settings = await _dataService.GetSettings();
|
||||
return settings.ServerUrl;
|
||||
}
|
||||
|
||||
public string GetServerVerificationToken()
|
||||
@ -382,6 +381,7 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
|
||||
private async Task<bool> CheckForDeviceBan(params string[] deviceIdNameOrIPs)
|
||||
{
|
||||
var settings = await _dataService.GetSettings();
|
||||
foreach (var device in deviceIdNameOrIPs)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(device))
|
||||
@ -389,7 +389,7 @@ public class AgentHub : Hub<IAgentHubClient>
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_appConfig.BannedDevices.Any(x => !string.IsNullOrWhiteSpace(x) &&
|
||||
if (settings.BannedDevices.Any(x => !string.IsNullOrWhiteSpace(x) &&
|
||||
x.Equals(device, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
_logger.LogWarning("Device ID/name/IP ({device}) is banned. Sending uninstall command.", device);
|
||||
|
||||
@ -71,11 +71,10 @@ public interface ICircuitConnection
|
||||
public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
{
|
||||
private readonly IHubContext<AgentHub, IAgentHubClient> _agentHubContext;
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly ISelectedCardsStore _cardStore;
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ICircuitManager _circuitManager;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IRemoteControlSessionCache _remoteControlSessionCache;
|
||||
private readonly IExpiringTokenService _expiringTokenService;
|
||||
private readonly ILogger<CircuitConnection> _logger;
|
||||
@ -89,7 +88,6 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
IDataService dataService,
|
||||
ISelectedCardsStore cardStore,
|
||||
IHubContext<AgentHub, IAgentHubClient> agentHubContext,
|
||||
IApplicationConfig appConfig,
|
||||
ICircuitManager circuitManager,
|
||||
IToastService toastService,
|
||||
IExpiringTokenService expiringTokenService,
|
||||
@ -101,7 +99,6 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
_dataService = dataService;
|
||||
_agentHubContext = agentHubContext;
|
||||
_cardStore = cardStore;
|
||||
_appConfig = appConfig;
|
||||
_authService = authService;
|
||||
_circuitManager = circuitManager;
|
||||
_toastService = toastService;
|
||||
@ -228,6 +225,8 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
|
||||
public async Task<Result<RemoteControlSessionEx>> RemoteControl(string deviceId, bool viewOnly)
|
||||
{
|
||||
var settings = await _dataService.GetSettings();
|
||||
|
||||
if (!_agentSessionCache.TryGetByDeviceId(deviceId, out var targetDevice))
|
||||
{
|
||||
var message = new DisplayNotificationMessage(
|
||||
@ -256,7 +255,7 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
.OfType<RemoteControlSessionEx>()
|
||||
.Count(x => x.OrganizationId == User.OrganizationID);
|
||||
|
||||
if (sessionCount >= _appConfig.RemoteControlSessionLimit)
|
||||
if (sessionCount >= settings.RemoteControlSessionLimit)
|
||||
{
|
||||
var message = new DisplayNotificationMessage(
|
||||
"There are already the maximum amount of active remote control sessions for your organization.",
|
||||
@ -290,8 +289,8 @@ public class CircuitConnection : CircuitHandler, ICircuitConnection
|
||||
DeviceId = deviceId,
|
||||
ViewOnly = viewOnly,
|
||||
OrganizationId = User.OrganizationID,
|
||||
RequireConsent = _appConfig.EnforceAttendedAccess,
|
||||
NotifyUserOnStart = _appConfig.RemoteControlNotifyUser
|
||||
RequireConsent = settings.EnforceAttendedAccess,
|
||||
NotifyUserOnStart = settings.RemoteControlNotifyUser
|
||||
};
|
||||
|
||||
_remoteControlSessionCache.AddOrUpdate($"{sessionId}", session);
|
||||
|
||||
39
Server/Models/SettingsModel.cs
Normal file
39
Server/Models/SettingsModel.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Remotely.Shared.Enums;
|
||||
|
||||
namespace Remotely.Server.Models;
|
||||
|
||||
public class SettingsModel
|
||||
{
|
||||
public static Guid DbKey { get; } = Guid.Parse("a35d6212-c0b7-49b2-89e1-7ba497f94a35");
|
||||
|
||||
public bool AllowApiLogin { get; set; }
|
||||
public List<string> BannedDevices { get; set; } = [];
|
||||
public double DataRetentionInDays { get; set; } = 90;
|
||||
public string DbProvider { get; set; } = "SQLite";
|
||||
public bool EnableRemoteControlRecording { get; set; }
|
||||
public bool EnableWindowsEventLog { get; set; }
|
||||
public bool EnforceAttendedAccess { get; set; }
|
||||
public bool ForceClientHttps { get; set; }
|
||||
public List<string> KnownProxies { get; set; } = [];
|
||||
public int MaxConcurrentUpdates { get; set; } = 10;
|
||||
public int MaxOrganizationCount { get; set; } = 1;
|
||||
public string MessageOfTheDay { get; set; } = string.Empty;
|
||||
public bool RedirectToHttps { get; set; } = true;
|
||||
public bool RemoteControlNotifyUser { get; set; } = true;
|
||||
public bool RemoteControlRequiresAuthentication { get; set; } = true;
|
||||
public int RemoteControlSessionLimit { get; set; } = 5;
|
||||
public bool Require2FA { get; set; }
|
||||
public string ServerUrl { get; set; } = string.Empty;
|
||||
public bool SmtpCheckCertificateRevocation { get; set; } = true;
|
||||
public string SmtpDisplayName { get; set; } = string.Empty;
|
||||
public string SmtpEmail { get; set; } = string.Empty;
|
||||
public string SmtpHost { get; set; } = string.Empty;
|
||||
public string SmtpLocalDomain { get; set; } = string.Empty;
|
||||
public string SmtpPassword { get; set; } = string.Empty;
|
||||
public int SmtpPort { get; set; } = 587;
|
||||
public string SmtpUserName { get; set; } = string.Empty;
|
||||
public Theme Theme { get; set; } = Theme.Dark;
|
||||
public List<string> TrustedCorsOrigins { get; set; } = [];
|
||||
public bool UseHsts { get; set; }
|
||||
public bool UseHttpLogging { get; set; }
|
||||
}
|
||||
7
Server/Options/ApplicationOptions.cs
Normal file
7
Server/Options/ApplicationOptions.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Remotely.Server.Options;
|
||||
|
||||
public class ApplicationOptions
|
||||
{
|
||||
public const string SectionKey = "ApplicationOptions";
|
||||
public string DbProvider { get; set; } = "SQLite";
|
||||
}
|
||||
@ -23,6 +23,9 @@ using Immense.SimpleMessenger;
|
||||
using Remotely.Server.Services.Stores;
|
||||
using Remotely.Server.Components.Account;
|
||||
using Remotely.Server.Components;
|
||||
using Remotely.Server.Options;
|
||||
using Remotely.Server.Extensions;
|
||||
using Remotely.Server.Models;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var configuration = builder.Configuration;
|
||||
@ -30,6 +33,9 @@ var services = builder.Services;
|
||||
|
||||
configuration.AddEnvironmentVariables("Remotely_");
|
||||
|
||||
services.Configure<ApplicationOptions>(
|
||||
configuration.GetSection(ApplicationOptions.SectionKey));
|
||||
|
||||
services
|
||||
.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
@ -41,21 +47,29 @@ services.AddScoped<IdentityUserAccessor>();
|
||||
services.AddScoped<IdentityRedirectManager>();
|
||||
services.AddScoped<AuthenticationStateProvider, IdentityRevalidatingAuthenticationStateProvider>();
|
||||
|
||||
ConfigureSerilog(builder);
|
||||
var dbProvider = configuration["ApplicationOptions:DbProvider"]?.ToLower();
|
||||
|
||||
builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
|
||||
|
||||
if (OperatingSystem.IsWindows() &&
|
||||
bool.TryParse(builder.Configuration["ApplicationOptions:EnableWindowsEventLog"], out var enableEventLog) &&
|
||||
enableEventLog)
|
||||
switch (dbProvider)
|
||||
{
|
||||
builder.Logging.AddEventLog();
|
||||
}
|
||||
|
||||
var dbProvider = configuration["ApplicationOptions:DBProvider"]?.ToLower();
|
||||
if (string.IsNullOrWhiteSpace(dbProvider))
|
||||
{
|
||||
throw new InvalidOperationException("DBProvider is missing from appsettings.json.");
|
||||
case "sqlite":
|
||||
services.AddDbContext<AppDb, SqliteDbContext>(
|
||||
contextLifetime: ServiceLifetime.Transient,
|
||||
optionsLifetime: ServiceLifetime.Transient);
|
||||
break;
|
||||
case "sqlserver":
|
||||
services.AddDbContext<AppDb, SqlServerDbContext>(
|
||||
contextLifetime: ServiceLifetime.Transient,
|
||||
optionsLifetime: ServiceLifetime.Transient);
|
||||
break;
|
||||
case "postgresql":
|
||||
services.AddDbContext<AppDb, PostgreSqlDbContext>(
|
||||
contextLifetime: ServiceLifetime.Transient,
|
||||
optionsLifetime: ServiceLifetime.Transient);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(
|
||||
$"Invalid DBProvider: {dbProvider}. Ensure a valid value " +
|
||||
$"is set in appsettings.json or environment variables.");
|
||||
}
|
||||
|
||||
if (dbProvider == "sqlite")
|
||||
@ -71,6 +85,26 @@ else if (dbProvider == "postgresql")
|
||||
services.AddDbContext<AppDb, PostgreSqlDbContext>();
|
||||
}
|
||||
|
||||
using AppDb appDb = dbProvider switch
|
||||
{
|
||||
"sqlite" => new SqliteDbContext(builder.Configuration, builder.Environment),
|
||||
"sqlserver" => new SqlServerDbContext(builder.Configuration, builder.Environment),
|
||||
"postgresql" => new PostgreSqlDbContext(builder.Configuration, builder.Environment),
|
||||
_ => throw new InvalidOperationException($"Invalid DBProvider: {dbProvider}")
|
||||
};
|
||||
|
||||
await appDb.Database.MigrateAsync();
|
||||
var settings = await appDb.GetAppSettings();
|
||||
|
||||
ConfigureSerilog(builder, settings);
|
||||
|
||||
builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
|
||||
|
||||
if (OperatingSystem.IsWindows() && settings.EnableWindowsEventLog)
|
||||
{
|
||||
builder.Logging.AddEventLog();
|
||||
}
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = IdentityConstants.ApplicationScheme;
|
||||
@ -113,8 +147,7 @@ services.AddAuthorization(options =>
|
||||
|
||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
if (bool.TryParse(configuration["ApplicationOptions:UseHttpLogging"], out var useHttpLogging) &&
|
||||
useHttpLogging)
|
||||
if (settings.UseHttpLogging)
|
||||
{
|
||||
services.AddHttpLogging(options =>
|
||||
{
|
||||
@ -128,14 +161,12 @@ if (bool.TryParse(configuration["ApplicationOptions:UseHttpLogging"], out var us
|
||||
});
|
||||
}
|
||||
|
||||
var trustedOrigins = configuration.GetSection("ApplicationOptions:TrustedCorsOrigins").Get<string[]>();
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
if (trustedOrigins != null)
|
||||
if (settings.TrustedCorsOrigins is { Count: > 0} trustedOrigins)
|
||||
{
|
||||
options.AddPolicy("TrustedOriginPolicy", builder => builder
|
||||
.WithOrigins(trustedOrigins)
|
||||
.WithOrigins(trustedOrigins.ToArray())
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials()
|
||||
@ -144,7 +175,6 @@ services.AddCors(options =>
|
||||
});
|
||||
|
||||
|
||||
var knownProxies = configuration.GetSection("ApplicationOptions:KnownProxies").Get<string[]>();
|
||||
services.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.All;
|
||||
@ -153,7 +183,7 @@ services.Configure<ForwardedHeadersOptions>(options =>
|
||||
// Default Docker host. We want to allow forwarded headers from this address.
|
||||
options.KnownProxies.Add(IPAddress.Parse("172.17.0.1"));
|
||||
|
||||
if (knownProxies?.Any() == true)
|
||||
if (settings.KnownProxies is { Count: >0 } knownProxies)
|
||||
{
|
||||
foreach (var proxy in knownProxies)
|
||||
{
|
||||
@ -180,13 +210,10 @@ services.AddRateLimiter(options =>
|
||||
{
|
||||
clOptions.QueueLimit = int.MaxValue;
|
||||
|
||||
var concurrentPermits = configuration.GetSection("ApplicationOptions:MaxConcurrentUpdates").Get<int>();
|
||||
if (concurrentPermits <= 0)
|
||||
{
|
||||
concurrentPermits = 10;
|
||||
}
|
||||
|
||||
clOptions.PermitLimit = concurrentPermits;
|
||||
clOptions.PermitLimit =
|
||||
settings.MaxConcurrentUpdates <= 0 ?
|
||||
10 :
|
||||
settings.MaxConcurrentUpdates;
|
||||
});
|
||||
});
|
||||
services.AddHttpClient();
|
||||
@ -202,7 +229,6 @@ else
|
||||
}
|
||||
services.AddScoped<IAppDbFactory, AppDbFactory>();
|
||||
services.AddTransient<IDataService, DataService>();
|
||||
services.AddSingleton<IApplicationConfig, ApplicationConfig>();
|
||||
services.AddScoped<ApiAuthorizationFilter>();
|
||||
services.AddScoped<LocalOnlyFilter>();
|
||||
services.AddScoped<ExpiringTokenFilter>();
|
||||
@ -246,9 +272,7 @@ var app = builder.Build();
|
||||
|
||||
app.UseRateLimiter();
|
||||
|
||||
var appConfig = app.Services.GetRequiredService<IApplicationConfig>();
|
||||
|
||||
if (appConfig.UseHttpLogging)
|
||||
if (settings.UseHttpLogging)
|
||||
{
|
||||
app.UseHttpLogging();
|
||||
}
|
||||
@ -265,11 +289,11 @@ if (app.Environment.IsDevelopment())
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
if (bool.TryParse(app.Configuration["ApplicationOptions:UseHsts"], out var hsts) && hsts)
|
||||
if (settings.UseHsts)
|
||||
{
|
||||
app.UseHsts();
|
||||
}
|
||||
if (bool.TryParse(app.Configuration["ApplicationOptions:RedirectToHttps"], out var redirect) && redirect)
|
||||
if (settings.RedirectToHttps)
|
||||
{
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
@ -297,14 +321,8 @@ app.MapAdditionalIdentityEndpoints();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
using var context = scope.ServiceProvider.GetRequiredService<AppDb>();
|
||||
var dataService = scope.ServiceProvider.GetRequiredService<IDataService>();
|
||||
|
||||
if (context.Database.IsRelational())
|
||||
{
|
||||
await context.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
await dataService.SetAllDevicesNotOnline();
|
||||
await dataService.CleanupOldRecords();
|
||||
}
|
||||
@ -348,16 +366,17 @@ void ConfigureStaticFiles()
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigureSerilog(WebApplicationBuilder webAppBuilder)
|
||||
void ConfigureSerilog(WebApplicationBuilder webAppBuilder, SettingsModel settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dataRetentionDays = 7;
|
||||
if (int.TryParse(webAppBuilder.Configuration["ApplicationOptions:DataRetentionInDays"], out var retentionSetting))
|
||||
{
|
||||
dataRetentionDays = retentionSetting;
|
||||
}
|
||||
|
||||
var dataRetentionDays = settings.DataRetentionInDays;
|
||||
if (dataRetentionDays <= 0)
|
||||
{
|
||||
dataRetentionDays = 7;
|
||||
}
|
||||
|
||||
var logPath = LogsManager.DefaultLogsDirectory;
|
||||
|
||||
void ApplySharedLoggerConfig(LoggerConfiguration loggerConfiguration)
|
||||
@ -366,8 +385,8 @@ void ConfigureSerilog(WebApplicationBuilder webAppBuilder)
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithThreadId()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}")
|
||||
.WriteTo.File($"{logPath}/Remotely_Server.log",
|
||||
rollingInterval: RollingInterval.Day,
|
||||
.WriteTo.File($"{logPath}/Remotely_Server.log",
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileTimeLimit: TimeSpan.FromDays(dataRetentionDays),
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj} {Properties}{NewLine}{Exception}",
|
||||
shared: true);
|
||||
|
||||
@ -1,81 +0,0 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Models;
|
||||
using System;
|
||||
|
||||
namespace Remotely.Server.Services;
|
||||
|
||||
public interface IApplicationConfig
|
||||
{
|
||||
bool AllowApiLogin { get; }
|
||||
string[] BannedDevices { get; }
|
||||
double DataRetentionInDays { get; }
|
||||
string DBProvider { get; }
|
||||
bool EnableRemoteControlRecording { get; }
|
||||
bool EnableWindowsEventLog { get; }
|
||||
bool EnforceAttendedAccess { get; }
|
||||
bool ForceClientHttps { get; }
|
||||
string[] KnownProxies { get; }
|
||||
int MaxConcurrentUpdates { get; }
|
||||
int MaxOrganizationCount { get; }
|
||||
string MessageOfTheDay { get; }
|
||||
bool RedirectToHttps { get; }
|
||||
bool RemoteControlNotifyUser { get; }
|
||||
bool RemoteControlRequiresAuthentication { get; }
|
||||
int RemoteControlSessionLimit { get; }
|
||||
bool Require2FA { get; }
|
||||
string ServerUrl { get; }
|
||||
bool SmtpCheckCertificateRevocation { get; }
|
||||
string SmtpDisplayName { get; }
|
||||
string SmtpEmail { get; }
|
||||
string SmtpHost { get; }
|
||||
string SmtpLocalDomain { get; }
|
||||
string SmtpPassword { get; }
|
||||
int SmtpPort { get; }
|
||||
string SmtpUserName { get; }
|
||||
Theme Theme { get; }
|
||||
string[] TrustedCorsOrigins { get; }
|
||||
bool UseHsts { get; }
|
||||
bool UseHttpLogging { get; }
|
||||
}
|
||||
|
||||
public class ApplicationConfig : IApplicationConfig
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public ApplicationConfig(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
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"] ?? string.Empty;
|
||||
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"] ?? string.Empty;
|
||||
public bool SmtpCheckCertificateRevocation => !bool.TryParse(_config["ApplicationOptions:SmtpCheckCertificateRevocation"], out var result) || result;
|
||||
public string SmtpDisplayName => _config["ApplicationOptions:SmtpDisplayName"] ?? string.Empty;
|
||||
public string SmtpEmail => _config["ApplicationOptions:SmtpEmail"] ?? string.Empty;
|
||||
public string SmtpHost => _config["ApplicationOptions:SmtpHost"] ?? string.Empty;
|
||||
public string SmtpLocalDomain => _config["ApplicationOptions:SmtpLocalDomain"] ?? string.Empty;
|
||||
public string SmtpPassword => _config["ApplicationOptions:SmtpPassword"] ?? string.Empty;
|
||||
public int SmtpPort => int.TryParse(_config["ApplicationOptions:SmtpPort"], out var result) ? result : 25;
|
||||
public string SmtpUserName => _config["ApplicationOptions:SmtpUserName"] ?? string.Empty;
|
||||
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;
|
||||
}
|
||||
@ -19,17 +19,17 @@ public class DataCleanupService : BackgroundService, IDisposable
|
||||
private readonly ILogger<DataCleanupService> _logger;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ISystemTime _systemTime;
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
|
||||
public DataCleanupService(
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ISystemTime systemTime,
|
||||
IApplicationConfig appConfig,
|
||||
IDataService dataService,
|
||||
ILogger<DataCleanupService> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_systemTime = systemTime;
|
||||
_appConfig = appConfig;
|
||||
_dataService = dataService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -76,14 +76,18 @@ public class DataCleanupService : BackgroundService, IDisposable
|
||||
await dataService.CleanupOldRecords();
|
||||
}
|
||||
|
||||
private Task RemoveExpiredRecordings()
|
||||
private async Task RemoveExpiredRecordings()
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dataService = scope.ServiceProvider.GetRequiredService<IDataService>();
|
||||
var settings = await dataService.GetSettings();
|
||||
|
||||
if (!Directory.Exists(SessionRecordingSink.RecordingsDirectory))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
return;
|
||||
}
|
||||
|
||||
var expirationDate = _systemTime.Now.UtcDateTime - TimeSpan.FromDays(_appConfig.DataRetentionInDays);
|
||||
var expirationDate = _systemTime.Now.UtcDateTime - TimeSpan.FromDays(settings.DataRetentionInDays);
|
||||
|
||||
var files = Directory
|
||||
.GetFiles(
|
||||
@ -106,7 +110,5 @@ public class DataCleanupService : BackgroundService, IDisposable
|
||||
_logger.LogError(ex, "Error while deleting expired recording: {file}", file);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ using Remotely.Shared.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.Services;
|
||||
@ -171,6 +172,8 @@ public interface IDataService
|
||||
|
||||
List<string> GetServerAdmins();
|
||||
|
||||
Task<SettingsModel> GetSettings();
|
||||
|
||||
Task<Result<SharedFile>> GetSharedFiled(string fileId);
|
||||
|
||||
int GetTotalDevices();
|
||||
@ -193,6 +196,8 @@ public interface IDataService
|
||||
|
||||
Task ResetBranding(string organizationId);
|
||||
|
||||
Task SaveSettings(SettingsModel settings);
|
||||
|
||||
Task SetAllDevicesNotOnline();
|
||||
|
||||
Task SetDisplayName(RemotelyUser user, string displayName);
|
||||
@ -224,18 +229,16 @@ public interface IDataService
|
||||
|
||||
public class DataService : IDataService
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IAppDbFactory _appDbFactory;
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
private readonly ILogger<DataService> _logger;
|
||||
private readonly SemaphoreSlim _settingsLock = new(1, 1);
|
||||
|
||||
public DataService(
|
||||
IApplicationConfig appConfig,
|
||||
IHostEnvironment hostEnvironment,
|
||||
IAppDbFactory appDbFactory,
|
||||
ILogger<DataService> logger)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
_appDbFactory = appDbFactory;
|
||||
_logger = logger;
|
||||
@ -639,14 +642,15 @@ public class DataService : IDataService
|
||||
|
||||
public async Task CleanupOldRecords()
|
||||
{
|
||||
var settings = await GetSettings();
|
||||
using var dbContext = _appDbFactory.GetContext();
|
||||
|
||||
if (_appConfig.DataRetentionInDays < 0)
|
||||
if (settings.DataRetentionInDays < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var expirationDate = DateTimeOffset.Now - TimeSpan.FromDays(_appConfig.DataRetentionInDays);
|
||||
var expirationDate = DateTimeOffset.Now - TimeSpan.FromDays(settings.DataRetentionInDays);
|
||||
|
||||
var scriptRuns = await dbContext.ScriptRuns
|
||||
.Include(x => x.Results)
|
||||
@ -1711,6 +1715,25 @@ public class DataService : IDataService
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<SettingsModel> GetSettings()
|
||||
{
|
||||
await _settingsLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
using var dbContext = _appDbFactory.GetContext();
|
||||
return await dbContext.GetAppSettings();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while getting settings from database.");
|
||||
return new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_settingsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<SharedFile>> GetSharedFiled(string fileId)
|
||||
{
|
||||
using var dbContext = _appDbFactory.GetContext();
|
||||
@ -1930,6 +1953,35 @@ public class DataService : IDataService
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task SaveSettings(SettingsModel settings)
|
||||
{
|
||||
await _settingsLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
using var dbContext = _appDbFactory.GetContext();
|
||||
var record = await dbContext.KeyValueRecords.FindAsync(SettingsModel.DbKey);
|
||||
if (record is null)
|
||||
{
|
||||
record = new()
|
||||
{
|
||||
Key = SettingsModel.DbKey,
|
||||
};
|
||||
await dbContext.KeyValueRecords.AddAsync(record);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
record.Value = JsonSerializer.Serialize(settings);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while saving settings to database.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_settingsLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetAllDevicesNotOnline()
|
||||
{
|
||||
using var dbContext = _appDbFactory.GetContext();
|
||||
@ -2188,14 +2240,15 @@ public class DataService : IDataService
|
||||
}
|
||||
|
||||
private async Task<string> AddSharedFileImpl(
|
||||
string fileName,
|
||||
string fileName,
|
||||
byte[] fileContents,
|
||||
string contentType,
|
||||
string organizationId)
|
||||
{
|
||||
var settings = await GetSettings();
|
||||
using var dbContext = _appDbFactory.GetContext();
|
||||
|
||||
var expirationDate = DateTimeOffset.Now.AddDays(-_appConfig.DataRetentionInDays);
|
||||
var expirationDate = DateTimeOffset.Now.AddDays(-settings.DataRetentionInDays);
|
||||
var expiredFiles = dbContext.SharedFiles.Where(x => x.Timestamp < expirationDate);
|
||||
dbContext.RemoveRange(expiredFiles);
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Identity.Client;
|
||||
using MimeKit;
|
||||
using MimeKit.Text;
|
||||
using NuGet.Configuration;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
@ -35,14 +36,14 @@ public class EmailSender : IEmailSender
|
||||
|
||||
public class EmailSenderEx : IEmailSenderEx
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly ILogger<EmailSenderEx> _logger;
|
||||
|
||||
public EmailSenderEx(
|
||||
IApplicationConfig appConfig,
|
||||
IDataService dataService,
|
||||
ILogger<EmailSenderEx> logger)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_dataService = dataService;
|
||||
_logger = logger;
|
||||
}
|
||||
public async Task<bool> SendEmailAsync(
|
||||
@ -54,8 +55,10 @@ public class EmailSenderEx : IEmailSenderEx
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await _dataService.GetSettings();
|
||||
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress(_appConfig.SmtpDisplayName, _appConfig.SmtpEmail));
|
||||
message.From.Add(new MailboxAddress(settings.SmtpDisplayName, settings.SmtpEmail));
|
||||
message.To.Add(MailboxAddress.Parse(toEmail));
|
||||
message.ReplyTo.Add(MailboxAddress.Parse(replyTo));
|
||||
message.Subject = subject;
|
||||
@ -66,19 +69,19 @@ public class EmailSenderEx : IEmailSenderEx
|
||||
|
||||
using var client = new SmtpClient();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_appConfig.SmtpLocalDomain))
|
||||
if (!string.IsNullOrWhiteSpace(settings.SmtpLocalDomain))
|
||||
{
|
||||
client.LocalDomain = _appConfig.SmtpLocalDomain;
|
||||
client.LocalDomain = settings.SmtpLocalDomain;
|
||||
}
|
||||
|
||||
client.CheckCertificateRevocation = _appConfig.SmtpCheckCertificateRevocation;
|
||||
client.CheckCertificateRevocation = settings.SmtpCheckCertificateRevocation;
|
||||
|
||||
await client.ConnectAsync(_appConfig.SmtpHost, _appConfig.SmtpPort);
|
||||
await client.ConnectAsync(settings.SmtpHost, settings.SmtpPort);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_appConfig.SmtpUserName) &&
|
||||
!string.IsNullOrWhiteSpace(_appConfig.SmtpPassword))
|
||||
if (!string.IsNullOrWhiteSpace(settings.SmtpUserName) &&
|
||||
!string.IsNullOrWhiteSpace(settings.SmtpPassword))
|
||||
{
|
||||
await client.AuthenticateAsync(_appConfig.SmtpUserName, _appConfig.SmtpPassword);
|
||||
await client.AuthenticateAsync(settings.SmtpUserName, settings.SmtpPassword);
|
||||
}
|
||||
|
||||
await client.SendAsync(message);
|
||||
@ -95,9 +98,10 @@ public class EmailSenderEx : IEmailSenderEx
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string? organizationID = null)
|
||||
public async Task<bool> SendEmailAsync(string email, string subject, string htmlMessage, string? organizationID = null)
|
||||
{
|
||||
return SendEmailAsync(email, _appConfig.SmtpEmail, subject, htmlMessage, organizationID);
|
||||
var settings = await _dataService.GetSettings();
|
||||
return await SendEmailAsync(email, settings.SmtpEmail, subject, htmlMessage, organizationID);
|
||||
}
|
||||
}
|
||||
public class EmailSenderFake(ILogger<EmailSenderFake> _logger) : IEmailSenderEx
|
||||
|
||||
@ -10,35 +10,36 @@ namespace Remotely.Server.Services.RcImplementations;
|
||||
|
||||
public class ViewerAuthorizer : IViewerAuthorizer
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IOtpProvider _otpProvider;
|
||||
|
||||
public ViewerAuthorizer(IApplicationConfig appConfig, IOtpProvider otpProvider)
|
||||
public ViewerAuthorizer(IDataService dataService, IOtpProvider otpProvider)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_dataService = dataService;
|
||||
_otpProvider = otpProvider;
|
||||
}
|
||||
|
||||
public string UnauthorizedRedirectUrl { get; } = "/Account/Login";
|
||||
|
||||
public Task<bool> IsAuthorized(AuthorizationFilterContext context)
|
||||
public async Task<bool> IsAuthorized(AuthorizationFilterContext context)
|
||||
{
|
||||
if (!_appConfig.RemoteControlRequiresAuthentication)
|
||||
var settings = await _dataService.GetSettings();
|
||||
if (!settings.RemoteControlRequiresAuthentication)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.HttpContext.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.HttpContext.Request.Query.TryGetValue("otp", out var otp) &&
|
||||
_otpProvider.Exists($"{otp}"))
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return Task.FromResult(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,18 +8,19 @@ namespace Remotely.Server.Services.RcImplementations;
|
||||
|
||||
public class ViewerOptionsProvider : IViewerOptionsProvider
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
|
||||
public ViewerOptionsProvider(IApplicationConfig appConfig)
|
||||
public ViewerOptionsProvider(IDataService dataService)
|
||||
{
|
||||
_appConfig = appConfig;
|
||||
_dataService = dataService;
|
||||
}
|
||||
public Task<RemoteControlViewerOptions> GetViewerOptions()
|
||||
public async Task<RemoteControlViewerOptions> GetViewerOptions()
|
||||
{
|
||||
var settings = await _dataService.GetSettings();
|
||||
var options = new RemoteControlViewerOptions()
|
||||
{
|
||||
ShouldRecordSession = _appConfig.EnableRemoteControlRecording
|
||||
ShouldRecordSession = settings.EnableRemoteControlRecording
|
||||
};
|
||||
return Task.FromResult(options);
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,12 +12,10 @@ namespace Remotely.Server.Services.RcImplementations;
|
||||
|
||||
public class ViewerPageDataProvider : IViewerPageDataProvider
|
||||
{
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
public ViewerPageDataProvider(IDataService dataService, IApplicationConfig appConfig)
|
||||
public ViewerPageDataProvider(IDataService dataService)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
public Task<string> GetFaviconUrl(PageModel viewerModel)
|
||||
|
||||
@ -12,25 +12,26 @@ public interface IThemeProvider
|
||||
public class ThemeProvider : IThemeProvider
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly IApplicationConfig _appConfig;
|
||||
private readonly IDataService _dataService;
|
||||
|
||||
public ThemeProvider(IAuthService authService, IApplicationConfig appConfig)
|
||||
public ThemeProvider(IAuthService authService, IDataService dataService)
|
||||
{
|
||||
_authService = authService;
|
||||
_appConfig = appConfig;
|
||||
_dataService = dataService;
|
||||
}
|
||||
|
||||
public async Task<Theme> GetEffectiveTheme()
|
||||
{
|
||||
var settings = await _dataService.GetSettings();
|
||||
if (await _authService.IsAuthenticated())
|
||||
{
|
||||
var userResult = await _authService.GetUser();
|
||||
if (userResult.IsSuccess)
|
||||
{
|
||||
return userResult.Value.UserOptions?.Theme ?? _appConfig.Theme;
|
||||
return userResult.Value.UserOptions?.Theme ?? settings.Theme;
|
||||
}
|
||||
}
|
||||
|
||||
return _appConfig.Theme;
|
||||
return settings.Theme;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,34 +20,6 @@
|
||||
}
|
||||
},
|
||||
"ApplicationOptions": {
|
||||
"AllowApiLogin": false,
|
||||
"BannedDevices": [],
|
||||
"DataRetentionInDays": 90,
|
||||
"DBProvider": "SQLite",
|
||||
"EnableRemoteControlRecording": false,
|
||||
"EnableWindowsEventLog": false,
|
||||
"EnforceAttendedAccess": false,
|
||||
"ForceClientHttps": false,
|
||||
"KnownProxies": [],
|
||||
"MaxConcurrentUpdates": 10,
|
||||
"MaxOrganizationCount": 1,
|
||||
"MessageOfTheDay": "",
|
||||
"RedirectToHttps": true,
|
||||
"RemoteControlNotifyUser": true,
|
||||
"RemoteControlRequiresAuthentication": true,
|
||||
"RemoteControlSessionLimit": 3,
|
||||
"Require2FA": false,
|
||||
"SmtpDisplayName": "",
|
||||
"SmtpEmail": "",
|
||||
"SmtpHost": "",
|
||||
"SmtpLocalDomain": "",
|
||||
"SmtpCheckCertificateRevocation": true,
|
||||
"SmtpPassword": "",
|
||||
"SmtpPort": 587,
|
||||
"SmtpUserName": "",
|
||||
"Theme": "Dark",
|
||||
"TrustedCorsOrigins": [],
|
||||
"UseHsts": false,
|
||||
"UseHttpLogging": false
|
||||
"DBProvider": "SQLite"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Shared/Entities/KeyValueRecord.cs
Normal file
15
Shared/Entities/KeyValueRecord.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Shared.Entities;
|
||||
|
||||
public class KeyValueRecord
|
||||
{
|
||||
[Key]
|
||||
public Guid Key { get; set; }
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using Remotely.Server.Hubs;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Extensions;
|
||||
using Remotely.Shared.Interfaces;
|
||||
@ -32,7 +33,6 @@ public class AgentHubTests
|
||||
var circuitConnection = new Mock<ICircuitConnection>();
|
||||
circuitManager.Setup(x => x.Connections).Returns(new[] { circuitConnection.Object });
|
||||
circuitConnection.Setup(x => x.User).Returns(_testData.Org1Admin1);
|
||||
var appConfig = new Mock<IApplicationConfig>();
|
||||
var viewerHub = new Mock<IHubContext<ViewerHub>>();
|
||||
var expiringTokenService = new Mock<IExpiringTokenService>();
|
||||
var serviceSessionCache = new Mock<IAgentHubSessionCache>();
|
||||
@ -40,11 +40,12 @@ public class AgentHubTests
|
||||
var messenger = new Mock<IMessenger>();
|
||||
var logger = new Mock<ILogger<AgentHub>>();
|
||||
|
||||
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { $"{_testData.Org1Device1.DeviceName}" });
|
||||
var settings = await _dataService.GetSettings();
|
||||
settings.BannedDevices = [_testData.Org1Device1.DeviceName!];
|
||||
await _dataService.SaveSettings(settings);
|
||||
|
||||
var hub = new AgentHub(
|
||||
_dataService,
|
||||
appConfig.Object,
|
||||
serviceSessionCache.Object,
|
||||
viewerHub.Object,
|
||||
circuitManager.Object,
|
||||
@ -58,7 +59,8 @@ public class AgentHubTests
|
||||
hubClients.Setup(x => x.Caller).Returns(caller.Object);
|
||||
hub.Clients = hubClients.Object;
|
||||
|
||||
Assert.IsFalse(await hub.DeviceCameOnline(_testData.Org1Device1.ToDto()));
|
||||
var result = await hub.DeviceCameOnline(_testData.Org1Device1.ToDto());
|
||||
Assert.IsFalse(result);
|
||||
hubClients.Verify(x => x.Caller, Times.Once);
|
||||
caller.Verify(x => x.UninstallAgent(), Times.Once);
|
||||
}
|
||||
@ -73,7 +75,6 @@ public class AgentHubTests
|
||||
var circuitConnection = new Mock<ICircuitConnection>();
|
||||
circuitManager.Setup(x => x.Connections).Returns(new[] { circuitConnection.Object });
|
||||
circuitConnection.Setup(x => x.User).Returns(_testData.Org1Admin1);
|
||||
var appConfig = new Mock<IApplicationConfig>();
|
||||
var viewerHub = new Mock<IHubContext<ViewerHub>>();
|
||||
var expiringTokenService = new Mock<IExpiringTokenService>();
|
||||
var serviceSessionCache = new Mock<IAgentHubSessionCache>();
|
||||
@ -81,11 +82,13 @@ public class AgentHubTests
|
||||
var messenger = new Mock<IMessenger>();
|
||||
var logger = new Mock<ILogger<AgentHub>>();
|
||||
|
||||
appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Org1Device1.ID });
|
||||
|
||||
var settings = await _dataService.GetSettings();
|
||||
settings.BannedDevices = [$"{_testData.Org1Device1.ID}"];
|
||||
await _dataService.SaveSettings(settings);
|
||||
|
||||
var hub = new AgentHub(
|
||||
_dataService,
|
||||
appConfig.Object,
|
||||
serviceSessionCache.Object,
|
||||
viewerHub.Object,
|
||||
circuitManager.Object,
|
||||
@ -99,7 +102,8 @@ public class AgentHubTests
|
||||
hubClients.Setup(x => x.Caller).Returns(caller.Object);
|
||||
hub.Clients = hubClients.Object;
|
||||
|
||||
Assert.IsFalse(await hub.DeviceCameOnline(_testData.Org1Device1.ToDto()));
|
||||
var result = await hub.DeviceCameOnline(_testData.Org1Device1.ToDto());
|
||||
Assert.IsFalse(result);
|
||||
hubClients.Verify(x => x.Caller, Times.Once);
|
||||
caller.Verify(x => x.UninstallAgent(), Times.Once);
|
||||
}
|
||||
@ -117,23 +121,4 @@ public class AgentHubTests
|
||||
await _testData.Init();
|
||||
_dataService = IoCActivator.ServiceProvider.GetRequiredService<IDataService>();
|
||||
}
|
||||
|
||||
private class CallerContext : HubCallerContext
|
||||
{
|
||||
public override string ConnectionId => "test-id";
|
||||
|
||||
public override string? UserIdentifier => null;
|
||||
public override ClaimsPrincipal? User => null;
|
||||
|
||||
public override IDictionary<object, object?> Items { get; } = new Dictionary<object, object?>();
|
||||
|
||||
public override IFeatureCollection Features { get; } = new FeatureCollection();
|
||||
|
||||
public override CancellationToken ConnectionAborted => CancellationToken.None;
|
||||
|
||||
public override void Abort()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ public class CircuitConnectionTests
|
||||
private Mock<IAuthService> _authService;
|
||||
private Mock<ISelectedCardsStore> _clientAppState;
|
||||
private HubContextFixture<AgentHub, IAgentHubClient> _agentHubContextFixture;
|
||||
private Mock<IApplicationConfig> _appConfig;
|
||||
private Mock<ICircuitManager> _circuitManager;
|
||||
private Mock<IToastService> _toastService;
|
||||
private Mock<IExpiringTokenService> _expiringTokenService;
|
||||
@ -45,7 +44,6 @@ public class CircuitConnectionTests
|
||||
_authService = new Mock<IAuthService>();
|
||||
_clientAppState = new Mock<ISelectedCardsStore>();
|
||||
_agentHubContextFixture = new HubContextFixture<AgentHub, IAgentHubClient>();
|
||||
_appConfig = new Mock<IApplicationConfig>();
|
||||
_circuitManager = new Mock<ICircuitManager>();
|
||||
_toastService = new Mock<IToastService>();
|
||||
_expiringTokenService = new Mock<IExpiringTokenService>();
|
||||
@ -59,7 +57,6 @@ public class CircuitConnectionTests
|
||||
_dataService,
|
||||
_clientAppState.Object,
|
||||
_agentHubContextFixture.HubContextMock.Object,
|
||||
_appConfig.Object,
|
||||
_circuitManager.Object,
|
||||
_toastService.Object,
|
||||
_expiringTokenService.Object,
|
||||
|
||||
@ -22,54 +22,29 @@ namespace Remotely.Server.Tests;
|
||||
public class IoCActivator
|
||||
{
|
||||
public static IServiceProvider ServiceProvider { get; set; } = null!;
|
||||
private static IWebHostBuilder? _builder;
|
||||
|
||||
public static void Activate()
|
||||
{
|
||||
if (_builder is null)
|
||||
{
|
||||
_builder = WebHost.CreateDefaultBuilder()
|
||||
.UseStartup<Startup>()
|
||||
.CaptureStartupErrors(true)
|
||||
.ConfigureAppConfiguration(config =>
|
||||
{
|
||||
config.AddInMemoryCollection(new Dictionary<string, string?>()
|
||||
{
|
||||
["ApplicationOptions:DBProvider"] = "InMemory"
|
||||
});
|
||||
});
|
||||
|
||||
_builder.Build();
|
||||
}
|
||||
}
|
||||
|
||||
private static WebApplicationBuilder? _builder;
|
||||
private static WebApplication? _webApp;
|
||||
|
||||
[AssemblyInitialize]
|
||||
public static void AssemblyInit(TestContext context)
|
||||
{
|
||||
Activate();
|
||||
_builder = WebApplication.CreateBuilder();
|
||||
|
||||
_builder.Services.AddDbContext<AppDb, TestingDbContext>(
|
||||
contextLifetime: ServiceLifetime.Transient,
|
||||
optionsLifetime: ServiceLifetime.Transient);
|
||||
|
||||
_builder.Services.
|
||||
AddIdentity<RemotelyUser, IdentityRole>(options => options.Stores.MaxLengthForKeys = 128)
|
||||
.AddEntityFrameworkStores<AppDb>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
_builder.Services.AddTransient<IAppDbFactory, AppDbFactory>();
|
||||
_builder.Services.AddTransient<IDataService, DataService>();
|
||||
_builder.Services.AddTransient<IEmailSenderEx, EmailSenderEx>();
|
||||
|
||||
_webApp = _builder.Build();
|
||||
ServiceProvider = _webApp.Services;
|
||||
}
|
||||
}
|
||||
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<AppDb, TestingDbContext>();
|
||||
|
||||
services.AddIdentity<RemotelyUser, IdentityRole>(options => options.Stores.MaxLengthForKeys = 128)
|
||||
.AddEntityFrameworkStores<AppDb>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.AddTransient<IAppDbFactory, AppDbFactory>();
|
||||
services.AddTransient<IDataService, DataService>();
|
||||
services.AddTransient<IApplicationConfig, ApplicationConfig>();
|
||||
services.AddTransient<IEmailSenderEx, EmailSenderEx>();
|
||||
|
||||
IoCActivator.ServiceProvider = services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +103,7 @@ if ((Test-Path -Path "$Root\Agent\bin\publish\linux-x64") -eq $true) {
|
||||
}
|
||||
|
||||
|
||||
# Publish Core clients.
|
||||
# Publish agents.
|
||||
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win-x64 --self-contained --configuration Release --output "$Root\Agent\bin\publish\win-x64" "$Root\Agent"
|
||||
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime linux-x64 --self-contained --configuration Release --output "$Root\Agent\bin\publish\linux-x64" "$Root\Agent"
|
||||
dotnet publish /p:Version=$CurrentVersion /p:FileVersion=$CurrentVersion --runtime win-x86 --self-contained --configuration Release --output "$Root\Agent\bin\publish\win-x86" "$Root\Agent"
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectVersion>2.1</ProjectVersion>
|
||||
<DockerComposeProjectName>remotely</DockerComposeProjectName>
|
||||
<DockerComposeProjectName>remotely</DockerComposeProjectName>
|
||||
<DockerTargetOS>Linux</DockerTargetOS>
|
||||
<DockerPublishLocally>False</DockerPublishLocally>
|
||||
<ProjectGuid>90ec49b2-b56a-4ecd-8f63-2162dd140f7c</ProjectGuid>
|
||||
<DockerLaunchAction>LaunchBrowser</DockerLaunchAction>
|
||||
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>
|
||||
<DockerServiceName>server</DockerServiceName>
|
||||
<DockerServiceName>remotely</DockerServiceName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="docker-compose.override.yml">
|
||||
|
||||
@ -1,51 +1,14 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
server:
|
||||
build:
|
||||
context: ../Server
|
||||
dockerfile: Dockerfile.local
|
||||
remotely:
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ASPNETCORE_HTTP_PORTS=5000
|
||||
- ASPNETCORE_HTTPS_PORTS=5001
|
||||
- Remotely_ApplicationOptions__AllowApiLogin=false,
|
||||
#- Remotely_ApplicationOptions__BannedDevices__0=,
|
||||
- Remotely_ApplicationOptions__DataRetentionInDays=90,
|
||||
- Remotely_ApplicationOptions__DBProvider=SQLite,
|
||||
- Remotely_ApplicationOptions__EnableRemoteControlRecording=false,
|
||||
- Remotely_ApplicationOptions__EnableWindowsEventLog=false,
|
||||
- Remotely_ApplicationOptions__EnforceAttendedAccess=false,
|
||||
- Remotely_ApplicationOptions__ForceClientHttps=false,
|
||||
#- Remotely_ApplicationOptions__KnownProxies__0=,
|
||||
- Remotely_ApplicationOptions__MaxConcurrentUpdates=10,
|
||||
- Remotely_ApplicationOptions__MaxOrganizationCount=1,
|
||||
- Remotely_ApplicationOptions__MessageOfTheDay=,
|
||||
- Remotely_ApplicationOptions__RedirectToHttps=true,
|
||||
- Remotely_ApplicationOptions__RemoteControlNotifyUser=true,
|
||||
- Remotely_ApplicationOptions__RemoteControlRequiresAuthentication=true,
|
||||
- Remotely_ApplicationOptions__RemoteControlSessionLimit=3,
|
||||
- Remotely_ApplicationOptions__Require2FA=false,
|
||||
- Remotely_ApplicationOptions__SmtpDisplayName=,
|
||||
- Remotely_ApplicationOptions__SmtpEmail=,
|
||||
- Remotely_ApplicationOptions__SmtpHost=,
|
||||
- Remotely_ApplicationOptions__SmtpLocalDomain=,
|
||||
- Remotely_ApplicationOptions__SmtpCheckCertificateRevocation=true,
|
||||
- Remotely_ApplicationOptions__SmtpPassword=,
|
||||
- Remotely_ApplicationOptions__SmtpPort=587,
|
||||
- Remotely_ApplicationOptions__SmtpUserName=,
|
||||
- Remotely_ApplicationOptions__Theme=Dark,
|
||||
#- Remotely_ApplicationOptions__TrustedCorsOrigins__0=,
|
||||
- Remotely_ApplicationOptions__UseHsts=false,
|
||||
- Remotely_ApplicationOptions__UseHttpLogging=false
|
||||
ports:
|
||||
- "5000"
|
||||
- "5001"
|
||||
- "5000:5000"
|
||||
- "5001:5001"
|
||||
volumes:
|
||||
- ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro
|
||||
- ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
|
||||
- remotely-data:/app/AppData
|
||||
|
||||
volumes:
|
||||
remotely-data:
|
||||
name: remotely-data
|
||||
- ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro
|
||||
@ -1,45 +1,62 @@
|
||||
version: '3.4'
|
||||
|
||||
volumes:
|
||||
remotely-data:
|
||||
name: remotely-data
|
||||
|
||||
services:
|
||||
remotely:
|
||||
image: immybot/remotely
|
||||
image: immybot/remotely:latest
|
||||
volumes:
|
||||
- /remotely-data:/app/AppData
|
||||
- remotely-data:/app/AppData
|
||||
build:
|
||||
context: ../Server
|
||||
dockerfile: Dockerfile
|
||||
context: ../
|
||||
dockerfile: Server/Dockerfile
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- "5001:5001"
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Production
|
||||
- ASPNETCORE_HTTP_PORTS=5000
|
||||
- ASPNETCORE_HTTPS_PORTS=5001
|
||||
- Remotely_ApplicationOptions__AllowApiLogin=false,
|
||||
#- Remotely_ApplicationOptions__BannedDevices__0=,
|
||||
- Remotely_ApplicationOptions__DataRetentionInDays=90,
|
||||
- Remotely_ApplicationOptions__DBProvider=SQLite,
|
||||
- Remotely_ApplicationOptions__EnableRemoteControlRecording=false,
|
||||
- Remotely_ApplicationOptions__EnableWindowsEventLog=false,
|
||||
- Remotely_ApplicationOptions__EnforceAttendedAccess=false,
|
||||
- Remotely_ApplicationOptions__ForceClientHttps=false,
|
||||
#- Remotely_ApplicationOptions__KnownProxies__0=,
|
||||
- Remotely_ApplicationOptions__MaxConcurrentUpdates=10,
|
||||
- Remotely_ApplicationOptions__MaxOrganizationCount=1,
|
||||
- Remotely_ApplicationOptions__MessageOfTheDay=,
|
||||
- Remotely_ApplicationOptions__RedirectToHttps=true,
|
||||
- Remotely_ApplicationOptions__RemoteControlNotifyUser=true,
|
||||
- Remotely_ApplicationOptions__RemoteControlRequiresAuthentication=true,
|
||||
- Remotely_ApplicationOptions__RemoteControlSessionLimit=3,
|
||||
- Remotely_ApplicationOptions__Require2FA=false,
|
||||
- Remotely_ApplicationOptions__SmtpDisplayName=,
|
||||
- Remotely_ApplicationOptions__SmtpEmail=,
|
||||
- Remotely_ApplicationOptions__SmtpHost=,
|
||||
- Remotely_ApplicationOptions__SmtpLocalDomain=,
|
||||
- Remotely_ApplicationOptions__SmtpCheckCertificateRevocation=true,
|
||||
- Remotely_ApplicationOptions__SmtpPassword=,
|
||||
- Remotely_ApplicationOptions__SmtpPort=587,
|
||||
- Remotely_ApplicationOptions__SmtpUserName=,
|
||||
- Remotely_ApplicationOptions__Theme=Dark,
|
||||
#- Remotely_ApplicationOptions__TrustedCorsOrigins__0=,
|
||||
- Remotely_ApplicationOptions__UseHsts=false,
|
||||
# Other ASP.NET Core configurations can be overridden here, such as Logging.
|
||||
# See https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0
|
||||
|
||||
# Values for DbProvider are SQLite, SQLServer, and PostgreSQL.
|
||||
- Remotely_ApplicationOptions__DbProvider=SQLite
|
||||
# This path shouldn't be changed. It points to the Docker volume.
|
||||
- Remotely_ConnectionStrings__SQLite=Data Source=/app/AppData/Remotely.db
|
||||
# If using SQL Server, change the connection string to point to your SQL Server instance.
|
||||
- Remotely_ConnectionStrings__SQLServer=Server=(localdb)\\mssqllocaldb;Database=Remotely-Server-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true
|
||||
# If using PostgreSQL, change the connection string to point to your PostgreSQL instance.
|
||||
- Remotely_ConnectionStrings__PostgreSQL=Server=Host=localhost;Database=Remotely;Username=postgres;
|
||||
|
||||
- Remotely_ApplicationOptions__AllowApiLogin=false
|
||||
#- Remotely_ApplicationOptions__BannedDevices__0=
|
||||
- Remotely_ApplicationOptions__DataRetentionInDays=90
|
||||
- Remotely_ApplicationOptions__DBProvider=SQLite
|
||||
- Remotely_ApplicationOptions__EnableRemoteControlRecording=false
|
||||
- Remotely_ApplicationOptions__EnableWindowsEventLog=false
|
||||
- Remotely_ApplicationOptions__EnforceAttendedAccess=false
|
||||
- Remotely_ApplicationOptions__ForceClientHttps=false
|
||||
#- Remotely_ApplicationOptions__KnownProxies__0=
|
||||
- Remotely_ApplicationOptions__MaxConcurrentUpdates=10
|
||||
- Remotely_ApplicationOptions__MaxOrganizationCount=1
|
||||
- Remotely_ApplicationOptions__MessageOfTheDay=
|
||||
- Remotely_ApplicationOptions__RedirectToHttps=true
|
||||
- Remotely_ApplicationOptions__RemoteControlNotifyUser=true
|
||||
- Remotely_ApplicationOptions__RemoteControlRequiresAuthentication=true
|
||||
- Remotely_ApplicationOptions__RemoteControlSessionLimit=3
|
||||
- Remotely_ApplicationOptions__Require2FA=false
|
||||
- Remotely_ApplicationOptions__SmtpDisplayName=
|
||||
- Remotely_ApplicationOptions__SmtpEmail=
|
||||
- Remotely_ApplicationOptions__SmtpHost=
|
||||
- Remotely_ApplicationOptions__SmtpLocalDomain=
|
||||
- Remotely_ApplicationOptions__SmtpCheckCertificateRevocation=true
|
||||
- Remotely_ApplicationOptions__SmtpPassword=
|
||||
- Remotely_ApplicationOptions__SmtpPort=587
|
||||
- Remotely_ApplicationOptions__SmtpUserName=
|
||||
- Remotely_ApplicationOptions__Theme=Dark
|
||||
#- Remotely_ApplicationOptions__TrustedCorsOrigins__0=
|
||||
- Remotely_ApplicationOptions__UseHsts=false
|
||||
- Remotely_ApplicationOptions__UseHttpLogging=false
|
||||
Loading…
Reference in New Issue
Block a user