From ac6487c6c5f101453190a51687f306f080572ed5 Mon Sep 17 00:00:00 2001 From: Jared Goodwin Date: Wed, 26 Jul 2023 11:31:34 -0700 Subject: [PATCH] Another pass of nullable refactoring. --- .../Services/InstallerService.cs | 32 +++--- .../ViewModels/MainWindowViewModel.cs | 8 +- Agent/Models/ChatSession.cs | 2 +- Agent/Services/AgentHubConnection.cs | 108 +++++++++++++++--- Agent/Services/ChatClientService.cs | 6 +- Agent/Services/ConfigService.cs | 77 ++++++++----- Agent/Services/DeviceInfoGeneratorBase.cs | 7 +- Agent/Services/ExternalScriptingShell.cs | 38 +++--- Agent/Services/Linux/AppLauncherLinux.cs | 2 +- .../Linux/DeviceInfoGeneratorLinux.cs | 28 ++--- Agent/Services/Linux/UpdaterLinux.cs | 4 +- .../Services/MacOS/DeviceInfoGeneratorMac.cs | 2 +- Agent/Services/MacOS/UpdaterMac.cs | 4 +- Agent/Services/PsCoreShell.cs | 20 ++-- Agent/Services/ScriptExecutor.cs | 11 +- .../Windows/DeviceInfoGeneratorWin.cs | 2 +- Server/API/LoginController.cs | 2 +- .../API/OrganizationManagementController.cs | 2 +- Server/API/RemoteControlController.cs | 7 ++ .../Pages/Account/ConfirmEmail.cshtml.cs | 2 +- .../Account/ConfirmEmailChange.cshtml.cs | 2 +- .../Pages/Account/ForgotPassword.cshtml.cs | 6 +- .../Identity/Pages/Account/Login.cshtml.cs | 25 ++-- .../Identity/Pages/Account/Logout.cshtml | 2 +- .../Manage/EnableAuthenticator.cshtml.cs | 19 +-- .../Pages/Account/Manage/Index.cshtml.cs | 8 +- .../Pages/Account/Manage/ManageNavPages.cs | 2 +- Server/Auth/LocalOnlyFilter.cs | 2 +- Server/Auth/TwoFactorRequiredHandler.cs | 4 +- Server/Components/AlertBanner.razor | 4 +- Server/Components/AlertsFrame.razor | 5 +- Server/Components/ColorPicker.razor | 2 +- Server/Components/Devices/ChatCard.razor | 4 +- Server/Components/Devices/ChatCard.razor.cs | 23 ++-- Server/Components/Devices/ChatFrame.razor.cs | 8 +- Server/Components/Devices/DeviceCard.razor.cs | 35 +++--- Server/Components/Devices/Terminal.razor.cs | 41 ++++--- Server/Components/DropdownButton.razor | 14 +-- Server/Components/FileInputButton.razor | 6 +- .../ModalContents/EditDeviceGroup.razor | 8 +- Server/Components/ModalHarness.razor | 8 +- Server/Components/Scripts/SavedScripts.razor | 2 +- Server/Components/TabControl/TabContent.razor | 7 +- Server/Components/TabControl/TabControl.razor | 8 +- Server/Components/TabControl/TabHeader.razor | 14 +-- Server/Components/ToastHarness.razor | 2 +- Server/Components/TreeView/TreeView.razor.cs | 24 ++-- .../Components/TreeView/TreeViewItem.razor.cs | 22 ++-- Server/Hubs/AgentHub.cs | 21 ++-- Server/Hubs/CircuitConnection.cs | 2 +- Server/Models/ApiLogin.cs | 4 +- Server/Models/ModalButton.cs | 6 +- Server/Models/RemoteControlRequest.cs | 6 +- Server/Pages/Branding.razor | 8 +- Server/Pages/DeviceDetails.razor | 6 +- Server/Pages/Error.cshtml.cs | 9 +- Server/Pages/GetSupport.cshtml.cs | 4 +- Server/Pages/ScriptsPage.razor | 4 +- Server/Pages/ServerConfig.razor.cs | 98 ++++++++++------ Server/Pages/UserOptions.razor | 4 +- Server/Services/AgentHubSessionCache.cs | 17 +-- Server/Services/ApplicationConfig.cs | 16 +-- Server/Services/ClientAppState.cs | 4 +- Server/Services/DataService.cs | 2 +- Server/Services/EmailSender.cs | 13 ++- Server/Services/ModalService.cs | 25 ++-- Server/Services/OtpProvider.cs | 3 +- Server/Services/ScriptScheduler.cs | 19 +-- Server/Services/ToastService.cs | 16 +-- Server/Services/UpgradeService.cs | 18 ++- Shared/Models/ConnectionInfo.cs | 2 +- Shared/Models/Device.cs | 1 - Shared/Models/PwshCommandCompletion.cs | 8 +- Shared/Models/RemotelyUserOptions.cs | 2 +- Shared/Services/ProcessInvoker.cs | 4 +- Shared/Utilities/AppVersionHelper.cs | 2 +- Shared/Utilities/ConsoleHelper.cs | 4 +- Shared/Utilities/Logger.cs | 4 +- Shared/ViewModels/ChatHistoryItem.cs | 2 +- Shared/ViewModels/ChatSession.cs | 4 +- Shared/ViewModels/InviteViewModel.cs | 4 +- Shared/ViewModels/OrganizationUser.cs | 4 +- Shared/ViewModels/TerminalLineItem.cs | 6 +- Tests/LoadTester/CommandLineParser.cs | 20 +++- Tests/LoadTester/Program.cs | 18 +-- Tests/Server.Tests/AgentHubTests.cs | 20 ++-- Tests/Server.Tests/CircuitConnectionTests.cs | 10 +- Tests/Server.Tests/IoCActivator.cs | 12 +- .../ScriptScheduleDispatcherTests.cs | 20 ++-- 89 files changed, 665 insertions(+), 458 deletions(-) diff --git a/Agent.Installer.Win/Services/InstallerService.cs b/Agent.Installer.Win/Services/InstallerService.cs index 110587166..3ac20966f 100644 --- a/Agent.Installer.Win/Services/InstallerService.cs +++ b/Agent.Installer.Win/Services/InstallerService.cs @@ -1,4 +1,5 @@ -using IWshRuntimeLibrary; +#nullable enable +using IWshRuntimeLibrary; using Microsoft.VisualBasic.FileIO; using Microsoft.Win32; using Remotely.Agent.Installer.Win.Utilities; @@ -26,14 +27,14 @@ public class InstallerService private readonly string _platform = Environment.Is64BitOperatingSystem ? "x64" : "x86"; private readonly JavaScriptSerializer _serializer = new JavaScriptSerializer(); - public event EventHandler ProgressMessageChanged; - public event EventHandler ProgressValueChanged; + public event EventHandler? ProgressMessageChanged; + public event EventHandler? ProgressValueChanged; public async Task Install(string serverUrl, string organizationId, - string deviceGroup, - string deviceAlias, - string deviceUuid, + string? deviceGroup, + string? deviceAlias, + string? deviceUuid, bool createSupportShortcut) { try @@ -174,8 +175,8 @@ private void ClearInstallDirectory() private async Task CreateDeviceOnServer(string deviceUuid, string serverUrl, - string deviceGroup, - string deviceAlias, + string? deviceGroup, + string? deviceAlias, string organizationId) { try @@ -199,9 +200,10 @@ private async Task CreateDeviceOnServer(string deviceUuid, { await sw.WriteAsync(_serializer.Serialize(setupOptions)); } - using (var response = await wr.GetResponseAsync() as HttpWebResponse) - { - Logger.Write($"Create device response: {response.StatusCode}"); + using var response = await wr.GetResponseAsync(); + if (response is HttpWebResponse httpResponse) + { + Logger.Write($"Create device response: {httpResponse.StatusCode}"); } } } @@ -261,7 +263,7 @@ private async Task DownloadRemotelyAgent(string serverUrl) } else { - ProgressMessageChanged.Invoke(this, "Downloading Remotely agent."); + ProgressMessageChanged?.Invoke(this, "Downloading Remotely agent."); using (var client = new WebClient()) { client.DownloadProgressChanged += (sender, args) => @@ -273,7 +275,7 @@ private async Task DownloadRemotelyAgent(string serverUrl) } } - ProgressMessageChanged.Invoke(this, "Extracting Remotely files."); + ProgressMessageChanged?.Invoke(this, "Extracting Remotely files."); ProgressValueChanged?.Invoke(this, 0); var tempDir = Path.Combine(Path.GetTempPath(), "RemotelyUpdate"); @@ -321,7 +323,7 @@ private async Task DownloadRemotelyAgent(string serverUrl) ProgressValueChanged?.Invoke(this, 0); } - private ConnectionInfo GetConnectionInfo(string organizationId, string serverUrl, string deviceUuid) + private ConnectionInfo GetConnectionInfo(string organizationId, string serverUrl, string? deviceUuid) { ConnectionInfo connectionInfo; var connectionInfoPath = Path.Combine(_installPath, "ConnectionInfo.json"); @@ -345,7 +347,7 @@ private ConnectionInfo GetConnectionInfo(string organizationId, string serverUrl { connectionInfo.ServerVerificationToken = null; } - connectionInfo.DeviceID = deviceUuid; + connectionInfo.DeviceID = deviceUuid!; } connectionInfo.OrganizationID = organizationId; connectionInfo.Host = serverUrl; diff --git a/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs b/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs index 94141a9c2..5b04f78d4 100644 --- a/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs +++ b/Agent.Installer.Win/ViewModels/MainWindowViewModel.cs @@ -465,7 +465,13 @@ private async Task Install() HeaderMessage = "Installing Remotely..."; - if (await _installer.Install(ServerUrl, OrganizationID, DeviceGroup, DeviceAlias, DeviceUuid, CreateSupportShortcut)) + if (await _installer.Install( + ServerUrl, + OrganizationID!, + DeviceGroup, + DeviceAlias, + DeviceUuid, + CreateSupportShortcut)) { IsServiceInstalled = true; Progress = 0; diff --git a/Agent/Models/ChatSession.cs b/Agent/Models/ChatSession.cs index 7178d7c28..9743a1e76 100644 --- a/Agent/Models/ChatSession.cs +++ b/Agent/Models/ChatSession.cs @@ -5,5 +5,5 @@ namespace Remotely.Agent.Models; public class ChatSession { public int ProcessID { get; set; } - public NamedPipeClientStream PipeStream { get; set; } + public NamedPipeClientStream? PipeStream { get; set; } } diff --git a/Agent/Services/AgentHubConnection.cs b/Agent/Services/AgentHubConnection.cs index 97aa999fe..40f102696 100644 --- a/Agent/Services/AgentHubConnection.cs +++ b/Agent/Services/AgentHubConnection.cs @@ -10,12 +10,15 @@ using Remotely.Shared.Services; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Net.Http; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Timers; +using Timer = System.Timers.Timer; namespace Remotely.Agent.Services; @@ -36,15 +39,16 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable private readonly IHttpClientFactory _httpFactory; private readonly IWakeOnLanService _wakeOnLanService; private readonly ILogger _logger; - private readonly ILogger _fileLogger; + private readonly IEnumerable _loggerProviders; private readonly IScriptExecutor _scriptExecutor; private readonly IUninstaller _uninstaller; private readonly IUpdater _updater; - private ConnectionInfo _connectionInfo; - private HubConnection _hubConnection; - private Timer _heartbeatTimer; + private ConnectionInfo? _connectionInfo; + private HubConnection? _hubConnection; + private Timer? _heartbeatTimer; private bool _isServerVerified; + private FileLogger? _fileLogger; public AgentHubConnection( IConfigService configService, @@ -69,24 +73,41 @@ public AgentHubConnection( _httpFactory = httpFactory; _wakeOnLanService = wakeOnLanService; _logger = logger; - _fileLogger = loggerProviders - .OfType() - .FirstOrDefault() - ?.CreateLogger(nameof(AgentHubConnection)); + _loggerProviders = loggerProviders; } public bool IsConnected => _hubConnection?.State == HubConnectionState.Connected; public async Task Connect() { + using var throttle = new SemaphoreSlim(1, 1); + var count = 1; + while (true) { try { + var waitSeconds = Math.Min(60, Math.Pow(count, 2)); + // This will allow the first attempt to go through immediately, but + // subsequent attempts will have an exponential delay. + _ = await throttle.WaitAsync(TimeSpan.FromSeconds(waitSeconds)); + _logger.LogInformation("Attempting to connect to server."); _connectionInfo = _configService.GetConnectionInfo(); + if (string.IsNullOrWhiteSpace(_connectionInfo.OrganizationID)) + { + _logger.LogError("Organization ID is not set. Please set it in the config file."); + continue; + } + + if (string.IsNullOrWhiteSpace(_connectionInfo.Host)) + { + _logger.LogError("Host (server URL) is not set. Please set it in the config file."); + continue; + } + if (_hubConnection is not null) { await _hubConnection.DisposeAsync(); @@ -116,13 +137,11 @@ public async Task Connect() // The above can be caused by temporary issues on the server. So we'll do // nothing here and wait for it to get resolved. _logger.LogError("There was an issue registering with the server. The server might be undergoing maintenance, or the supplied organization ID might be incorrect."); - await Task.Delay(TimeSpan.FromMinutes(1)); continue; } if (!await VerifyServer()) { - await Task.Delay(TimeSpan.FromMinutes(1)); continue; } @@ -144,10 +163,7 @@ public async Task Connect() catch (Exception ex) { _logger.LogError(ex, "Error while connecting to server."); - await Task.Delay(5_000); } - - } } @@ -161,6 +177,17 @@ public async Task SendHeartbeat() { try { + if (_connectionInfo is null || _hubConnection is null) + { + return; + } + + if (string.IsNullOrWhiteSpace(_connectionInfo.OrganizationID)) + { + _logger.LogError("Organization ID is not set. Please set it in the config file."); + return; + } + var currentInfo = await _deviceInfoService.CreateDevice(_connectionInfo.DeviceID, _connectionInfo.OrganizationID); await _hubConnection.SendAsync("DeviceHeartbeat", currentInfo); } @@ -172,6 +199,11 @@ public async Task SendHeartbeat() private async Task CheckForServerMigration() { + if (_connectionInfo is null || _hubConnection is null) + { + return false; + } + var serverUrl = await _hubConnection.InvokeAsync("GetServerUrl"); if (Uri.TryCreate(serverUrl, UriKind.Absolute, out var serverUri) && @@ -187,17 +219,22 @@ private async Task CheckForServerMigration() return false; } - private async void HeartbeatTimer_Elapsed(object sender, ElapsedEventArgs e) + private async void HeartbeatTimer_Elapsed(object? sender, ElapsedEventArgs e) { await SendHeartbeat(); } - private async Task HubConnection_Reconnected(string arg) + private async Task HubConnection_Reconnected(string? arg) { + if (_connectionInfo is null || _hubConnection is null) + { + return; + } + _logger.LogInformation("Reconnected to server."); await _updater.CheckForUpdates(); - var device = await _deviceInfoService.CreateDevice(_connectionInfo.DeviceID, _connectionInfo.OrganizationID); + var device = await _deviceInfoService.CreateDevice(_connectionInfo.DeviceID, $"{_connectionInfo.OrganizationID}"); if (!await _hubConnection.InvokeAsync("DeviceCameOnline", device)) { @@ -214,6 +251,10 @@ private async Task HubConnection_Reconnected(string arg) private void RegisterMessageHandlers() { + if (_hubConnection is null) + { + throw new InvalidOperationException("Hub connection is null."); + } _hubConnection.On("ChangeWindowsSession", async (string viewerConnectionId, string sessionId, string accessKey, string userConnectionId, string requesterName, string orgName, string orgId, int targetSessionID) => { @@ -263,6 +304,10 @@ private void RegisterMessageHandlers() _hubConnection.On("DeleteLogs", () => { + if (TryGetFileLogger(out var fileLogger)) + { + fileLogger.DeleteLogs(); + } if (_fileLogger is FileLogger logger) { logger.DeleteLogs(); @@ -466,13 +511,14 @@ private void RegisterMessageHandlers() foreach (var fileID in fileIDs) { - var url = $"{_connectionInfo.Host}/API/FileSharing/{fileID}"; + var url = $"{_connectionInfo?.Host}/API/FileSharing/{fileID}"; using var client = _httpFactory.CreateClient(); client.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken); using var response = await client.GetAsync(url); - var filename = response.Content.Headers.ContentDisposition.FileName; - var legalChars = filename.ToCharArray().Where(x => !Path.GetInvalidFileNameChars().Any(y => x == y)); + var filename = response.Content.Headers.ContentDisposition?.FileName ?? Path.GetRandomFileName(); + var invalidChars = Path.GetInvalidFileNameChars().ToHashSet(); + var legalChars = filename.ToCharArray().Where(x => !invalidChars.Contains(x)); filename = new string(legalChars.ToArray()); @@ -499,8 +545,32 @@ private void RegisterMessageHandlers() }); } + private bool TryGetFileLogger([NotNullWhen(true)] out FileLogger? fileLogger) + { + if (_fileLogger is null) + { + var logger = _loggerProviders + .OfType() + .FirstOrDefault() + ?.CreateLogger(nameof(AgentHubConnection)); + + if (logger is FileLogger loggerImpl) + { + _fileLogger = loggerImpl; + } + } + + fileLogger = _fileLogger; + return fileLogger is not null; + } + private async Task VerifyServer() { + if (_connectionInfo is null || _hubConnection is null) + { + return false; + } + if (string.IsNullOrWhiteSpace(_connectionInfo.ServerVerificationToken)) { _isServerVerified = true; diff --git a/Agent/Services/ChatClientService.cs b/Agent/Services/ChatClientService.cs index 864bbe359..a6dfc65ce 100644 --- a/Agent/Services/ChatClientService.cs +++ b/Agent/Services/ChatClientService.cs @@ -39,7 +39,7 @@ public class ChatClientService : IChatClientService { return; } - chatSession.PipeStream.Dispose(); + chatSession.PipeStream?.Dispose(); var chatProcess = Process.GetProcessById(chatSession.ProcessID); if (chatProcess?.HasExited == false) { @@ -111,7 +111,7 @@ public async Task SendMessage( chatSession = (ChatSession)_chatClients.Get(senderConnectionID); - if (!chatSession.PipeStream.IsConnected) + if (chatSession.PipeStream?.IsConnected != true) { _chatClients.Remove(senderConnectionID); await hubConnection.SendAsync("DisplayMessage", "Chat disconnected. Please try again.", "Chat disconnected.", "bg-warning", senderConnectionID); @@ -142,7 +142,7 @@ private async Task ReadFromStream(NamedPipeClientStream clientPipe, string sende if (!string.IsNullOrWhiteSpace(messageJson)) { var chatMessage = JsonSerializer.Deserialize(messageJson); - await hubConnection.SendAsync("Chat", chatMessage.Message, false, senderConnectionID); + await hubConnection.SendAsync("Chat", $"{chatMessage?.Message}", false, senderConnectionID); } } await hubConnection.SendAsync("Chat", string.Empty, true, senderConnectionID); diff --git a/Agent/Services/ConfigService.cs b/Agent/Services/ConfigService.cs index fe8c80564..6eaea12c6 100644 --- a/Agent/Services/ConfigService.cs +++ b/Agent/Services/ConfigService.cs @@ -1,10 +1,12 @@ -using Microsoft.Extensions.Logging; +using Immense.RemoteControl.Shared; +using Microsoft.Extensions.Logging; using Remotely.Shared.Models; using Remotely.Shared.Utilities; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text.Json; namespace Remotely.Agent.Services; @@ -18,16 +20,17 @@ public interface IConfigService public class ConfigService : IConfigService { private static readonly object _fileLock = new(); - private ConnectionInfo _connectionInfo; private readonly string _debugGuid = "f2b0a595-5ea8-471b-975f-12e70e0f3497"; private readonly ILogger _logger; + private ConnectionInfo? _connectionInfo; public ConfigService(ILogger logger) { _logger = logger; } - private Dictionary _commandLineArgs; + private Dictionary? _commandLineArgs; + private Dictionary CommandLineArgs { get @@ -36,13 +39,14 @@ private Dictionary CommandLineArgs { _commandLineArgs = new Dictionary(); var args = Environment.GetCommandLineArgs(); + for (var i = 1; i < args.Length; i += 2) { - var key = args?[i]; + var key = args[i]; if (key != null) { key = key.Trim().Replace("-", "").ToLower(); - var value = args?[i + 1]; + var value = args[i + 1]; if (value != null) { _commandLineArgs[key] = args[i + 1].Trim(); @@ -57,42 +61,53 @@ private Dictionary CommandLineArgs public ConnectionInfo GetConnectionInfo() { - // For debugging purposes (i.e. launch of a bunch of instances). - if (CommandLineArgs.TryGetValue("organization", out var orgID) && - CommandLineArgs.TryGetValue("host", out var hostName) && - CommandLineArgs.TryGetValue("device", out var deviceID)) - { - return new ConnectionInfo() + try + { // For debugging purposes (i.e. launch of a bunch of instances). + if (CommandLineArgs.TryGetValue("organization", out var orgID) && + CommandLineArgs.TryGetValue("host", out var hostName) && + CommandLineArgs.TryGetValue("device", out var deviceID)) { - DeviceID = deviceID, - Host = hostName, - OrganizationID = orgID - }; - } + return new ConnectionInfo() + { + DeviceID = deviceID, + Host = hostName, + OrganizationID = orgID + }; + } - if (Environment.UserInteractive && Debugger.IsAttached) - { - return new ConnectionInfo() + if (Environment.UserInteractive && Debugger.IsAttached) { - DeviceID = _debugGuid, - Host = "http://localhost:5000", - OrganizationID = orgID - }; - } + return new ConnectionInfo() + { + DeviceID = _debugGuid, + Host = "http://localhost:5000", + OrganizationID = orgID + }; + } - if (_connectionInfo == null) - { - lock (_fileLock) + if (_connectionInfo == null) { - if (!File.Exists("ConnectionInfo.json")) + lock (_fileLock) { - _logger.LogError("No connection info available. Please create ConnectionInfo.json file with appropriate values."); - return null; + if (!File.Exists("ConnectionInfo.json")) + { + _logger.LogError("No connection info available. Please create ConnectionInfo.json file with appropriate values."); + throw new InvalidOperationException("Config file does not exist."); + } + _connectionInfo = JsonSerializer.Deserialize(File.ReadAllText("ConnectionInfo.json")); } - _connectionInfo = JsonSerializer.Deserialize(File.ReadAllText("ConnectionInfo.json")); } } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to retrieve connection info."); + } + + if (_connectionInfo is null) + { + throw new InvalidOperationException("Unable to load config data."); + } return _connectionInfo; } diff --git a/Agent/Services/DeviceInfoGeneratorBase.cs b/Agent/Services/DeviceInfoGeneratorBase.cs index 595dbfa5d..937e31666 100644 --- a/Agent/Services/DeviceInfoGeneratorBase.cs +++ b/Agent/Services/DeviceInfoGeneratorBase.cs @@ -45,14 +45,15 @@ protected DeviceClientDto GetDeviceBase(string deviceID, string orgID) { try { - DriveInfo systemDrive; + DriveInfo? systemDrive; var allDrives = DriveInfo.GetDrives(); if (EnvironmentHelper.IsWindows) { + var rootPath = Path.GetPathRoot(Environment.SystemDirectory) ?? "C:\\"; systemDrive = allDrives.FirstOrDefault(x => x.IsReady && - x.RootDirectory.FullName.Contains(Path.GetPathRoot(Environment.SystemDirectory ?? Environment.CurrentDirectory))); + x.RootDirectory.FullName.Contains(rootPath)); } else { @@ -97,7 +98,7 @@ protected List GetAllDrives() catch (Exception ex) { _logger.LogError(ex, "Error getting drive info."); - return null; + return new(); } } diff --git a/Agent/Services/ExternalScriptingShell.cs b/Agent/Services/ExternalScriptingShell.cs index 45f1a7a3e..37f7a58fa 100644 --- a/Agent/Services/ExternalScriptingShell.cs +++ b/Agent/Services/ExternalScriptingShell.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using Immense.RemoteControl.Shared.Extensions; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Remotely.Shared.Enums; using Remotely.Shared.Models; @@ -8,12 +9,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Timers; namespace Remotely.Agent.Services; public interface IExternalScriptingShell { - Process ShellProcess { get; } + Process? ShellProcess { get; } Task Init(ScriptingShell shell, string shellProcessName, string lineEnding, string connectionId); Task WriteInput(string input, TimeSpan timeout); } @@ -27,13 +29,13 @@ public class ExternalScriptingShell : IExternalScriptingShell private readonly SemaphoreSlim _writeLock = new(1, 1); private string _errorOut = string.Empty; private string _lastInputID = string.Empty; - private string _lineEnding; + private string _lineEnding = Environment.NewLine; private System.Timers.Timer _processIdleTimeout = new(TimeSpan.FromMinutes(10)) { AutoReset = false }; - private string _senderConnectionId; + private string? _senderConnectionId; private ScriptingShell _shell; private string _standardOut = string.Empty; @@ -44,7 +46,8 @@ public ExternalScriptingShell( _configService = configService; _logger = logger; } - public Process ShellProcess { get; set; } + + public Process? ShellProcess { get; private set; } // TODO: Turn into cache and factory. @@ -94,9 +97,9 @@ public async Task Init(ScriptingShell shell, string shellProcessName, string lin RedirectStandardOutput = true }; - var connectionInfo = _configService.GetConnectionInfo(); - psi.Environment.Add("DeviceId", connectionInfo.DeviceID); - psi.Environment.Add("ServerUrl", connectionInfo.Host); + var configInfo = _configService.GetConnectionInfo(); + psi.Environment.Add("DeviceId", configInfo.DeviceID); + psi.Environment.Add("ServerUrl", configInfo.Host); ShellProcess = new Process { @@ -133,6 +136,11 @@ public async Task WriteInput(string input, TimeSpan timeout) try { + if (ShellProcess?.HasExited != false) + { + throw new InvalidOperationException("Shell process is not running."); + } + _processIdleTimeout.Stop(); _processIdleTimeout.Start(); _outputDone.Reset(); @@ -190,7 +198,7 @@ private ScriptResult GenerateCompletedResult(string input, TimeSpan runtime) StandardOutput = _standardOut.Split(Environment.NewLine), ErrorOutput = _errorOut.Split(Environment.NewLine), HadErrors = !string.IsNullOrWhiteSpace(_errorOut) || - (ShellProcess.HasExited && ShellProcess.ExitCode != 0) + (ShellProcess?.HasExited == true && ShellProcess.ExitCode != 0) }; } @@ -210,12 +218,12 @@ private ScriptResult GeneratePartialResult(string input, TimeSpan runtime) .Concat(_errorOut.Split(Environment.NewLine)) .ToArray(), HadErrors = !string.IsNullOrWhiteSpace(_errorOut) || - (ShellProcess.HasExited && ShellProcess.ExitCode != 0) + (ShellProcess?.HasExited == true && ShellProcess.ExitCode != 0) }; - ProcessIdleTimeout_Elapsed(this, null); + RemoveSession(); return partialResult; } - private void ProcessIdleTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + private void ProcessIdleTimeout_Elapsed(object? sender, ElapsedEventArgs e) { RemoveSession(); } @@ -223,6 +231,10 @@ private void ProcessIdleTimeout_Elapsed(object sender, System.Timers.ElapsedEven private void RemoveSession() { ShellProcess?.Kill(); + if (_senderConnectionId is null) + { + return; + } _sessions.TryRemove(_senderConnectionId, out _); } @@ -236,7 +248,7 @@ private void ShellProcess_ErrorDataReceived(object sender, DataReceivedEventArgs private void ShellProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) { - if (e?.Data?.Contains(_lastInputID) == true) + if (e.Data?.Contains(_lastInputID) == true) { _outputDone.Set(); } diff --git a/Agent/Services/Linux/AppLauncherLinux.cs b/Agent/Services/Linux/AppLauncherLinux.cs index 6eb14e53f..53e3106c7 100644 --- a/Agent/Services/Linux/AppLauncherLinux.cs +++ b/Agent/Services/Linux/AppLauncherLinux.cs @@ -76,7 +76,7 @@ private int StartLinuxDesktopApp(string args) xauthority, display, args); - return Process.Start(psi).Id; + return Process.Start(psi)?.Id ?? throw new InvalidOperationException("Failed to launch desktop app."); } private string GetXorgAuth() diff --git a/Agent/Services/Linux/DeviceInfoGeneratorLinux.cs b/Agent/Services/Linux/DeviceInfoGeneratorLinux.cs index eedce258b..2a8e13232 100644 --- a/Agent/Services/Linux/DeviceInfoGeneratorLinux.cs +++ b/Agent/Services/Linux/DeviceInfoGeneratorLinux.cs @@ -56,7 +56,7 @@ public Task CreateDevice(string deviceId, string orgId) private string GetCurrentUser() { var users = _processInvoker.InvokeProcessOutput("users", ""); - return users?.Split()?.FirstOrDefault()?.Trim(); + return users?.Split()?.FirstOrDefault()?.Trim() ?? string.Empty; } public (double usedGB, double totalGB) GetMemoryInGB() @@ -67,21 +67,23 @@ private string GetCurrentUser() var resultsArr = results.Split("\n".ToCharArray()); var freeKB = resultsArr .FirstOrDefault(x => x.Trim().StartsWith("MemAvailable")) - .Trim() - .Split(" ".ToCharArray(), 2) - .Last() // 9168236 kB - .Trim() - .Split(' ') - .First(); // 9168236 + ?.Trim() + ?.Split(" ".ToCharArray(), 2) + ?.Last() // 9168236 kB + ?.Trim() + ?.Split(' ') + ?.First() // 9168236 + ?? "0"; var totalKB = resultsArr .FirstOrDefault(x => x.Trim().StartsWith("MemTotal")) - .Trim() - .Split(" ".ToCharArray(), 2) - .Last() // 16637468 kB - .Trim() - .Split(' ') - .First(); // 16637468 + ?.Trim() + ?.Split(" ".ToCharArray(), 2) + ?.Last() // 16637468 kB + ?.Trim() + ?.Split(' ') + ?.First() // 16637468 + ?? "0"; var freeGB = Math.Round(double.Parse(freeKB) / 1024 / 1024, 2); var totalGB = Math.Round(double.Parse(totalKB) / 1024 / 1024, 2); diff --git a/Agent/Services/Linux/UpdaterLinux.cs b/Agent/Services/Linux/UpdaterLinux.cs index 141c0e4cd..88262c949 100644 --- a/Agent/Services/Linux/UpdaterLinux.cs +++ b/Agent/Services/Linux/UpdaterLinux.cs @@ -105,7 +105,7 @@ public async Task CheckForUpdates() await InstallLatestVersion(); } - catch (WebException ex) when ((ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotModified) + catch (WebException ex) when (ex.Response is HttpWebResponse http && http.StatusCode == HttpStatusCode.NotModified) { _logger.LogInformation("Service Updater: Version is current."); return; @@ -180,7 +180,7 @@ await _updateDownloader.DownloadFile( } } - private async void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + private async void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { await CheckForUpdates(); } diff --git a/Agent/Services/MacOS/DeviceInfoGeneratorMac.cs b/Agent/Services/MacOS/DeviceInfoGeneratorMac.cs index cfa646f87..23f083750 100644 --- a/Agent/Services/MacOS/DeviceInfoGeneratorMac.cs +++ b/Agent/Services/MacOS/DeviceInfoGeneratorMac.cs @@ -114,6 +114,6 @@ private Task GetCpuUtilization() private string GetCurrentUser() { var users = _processInvoker.InvokeProcessOutput("users", ""); - return users?.Split()?.FirstOrDefault()?.Trim(); + return users?.Split()?.FirstOrDefault()?.Trim() ?? string.Empty; } } diff --git a/Agent/Services/MacOS/UpdaterMac.cs b/Agent/Services/MacOS/UpdaterMac.cs index 5e9641bf6..a131efb26 100644 --- a/Agent/Services/MacOS/UpdaterMac.cs +++ b/Agent/Services/MacOS/UpdaterMac.cs @@ -106,7 +106,7 @@ public async Task CheckForUpdates() await InstallLatestVersion(); } - catch (WebException ex) when ((ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.NotModified) + catch (WebException ex) when (ex.Response is HttpWebResponse http && http.StatusCode == HttpStatusCode.NotModified) { _logger.LogInformation("Service Updater: Version is current."); return; @@ -166,7 +166,7 @@ await _updateDownloader.DownloadFile( } } - private async void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + private async void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { await CheckForUpdates(); } diff --git a/Agent/Services/PsCoreShell.cs b/Agent/Services/PsCoreShell.cs index e3044798f..d115e50f4 100644 --- a/Agent/Services/PsCoreShell.cs +++ b/Agent/Services/PsCoreShell.cs @@ -12,7 +12,7 @@ namespace Remotely.Agent.Services; public interface IPsCoreShell { - string SenderConnectionId { get; set; } + string? SenderConnectionId { get; set; } CommandCompletion GetCompletions(string inputText, int currentIndex, bool? forward); Task WriteInput(string input); @@ -25,15 +25,16 @@ public class PsCoreShell : IPsCoreShell private readonly ConnectionInfo _connectionInfo; private readonly ILogger _logger; private readonly PowerShell _powershell; - private CommandCompletion _lastCompletion; - private string _lastInputText; + private CommandCompletion? _lastCompletion; + private string? _lastInputText; + public PsCoreShell( IConfigService configService, ILogger logger) { _configService = configService; - _logger = logger; _connectionInfo = _configService.GetConnectionInfo(); + _logger = logger; _powershell = PowerShell.Create(); @@ -47,7 +48,8 @@ public PsCoreShell( _powershell.Invoke(); } - public string SenderConnectionId { get; set; } + public string? SenderConnectionId { get; set; } + // TODO: Turn into cache and factory. public static IPsCoreShell GetCurrent(string senderConnectionId) { @@ -99,7 +101,10 @@ public async Task WriteInput(string input) ps.AddScript("$args[0] | Out-String"); ps.AddArgument(results); var result = await ps.InvokeAsync(); - var hostOutput = result[0].BaseObject.ToString(); + + var hostOutput = result.Count > 0 ? + $"{result[0].BaseObject}" : + string.Empty; var verboseOut = _powershell.Streams.Verbose.ReadAll().Select(x => x.Message); var debugOut = _powershell.Streams.Debug.ReadAll().Select(x => x.Message); @@ -110,7 +115,8 @@ public async Task WriteInput(string input) var standardOut = hostOutput.Split(Environment.NewLine) .Concat(infoOut) .Concat(debugOut) - .Concat(verboseOut); + .Concat(verboseOut) + .Select(x => $"{x}"); var errorAndWarningOut = errorOut.Concat(warningOut).ToArray(); diff --git a/Agent/Services/ScriptExecutor.cs b/Agent/Services/ScriptExecutor.cs index 71ca180d7..1e983fd8c 100644 --- a/Agent/Services/ScriptExecutor.cs +++ b/Agent/Services/ScriptExecutor.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.SignalR.Client; +using Immense.RemoteControl.Shared.Extensions; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Remotely.Shared; using Remotely.Shared.Enums; @@ -103,8 +105,7 @@ public async Task RunScript(Guid savedScriptId, scriptRunId, initiator); - var connectionInfo = _configService.GetConnectionInfo(); - var url = $"{connectionInfo.Host}/API/SavedScripts/{savedScriptId}"; + var url = $"{_configService.GetConnectionInfo().Host}/API/SavedScripts/{savedScriptId}"; using var hc = new HttpClient(); hc.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken); var response = await hc.GetAsync(url); @@ -193,9 +194,9 @@ private async Task ExecuteScriptContent( default: break; } - return null; + throw new InvalidOperationException($"Unknown shell type: {shell}"); } - private async Task SendResultsToApi(object result, string expiringToken) + private async Task SendResultsToApi(object result, string expiringToken) { var targetURL = _configService.GetConnectionInfo().Host + $"/API/ScriptResults"; diff --git a/Agent/Services/Windows/DeviceInfoGeneratorWin.cs b/Agent/Services/Windows/DeviceInfoGeneratorWin.cs index 32ae5e029..65b4ecdff 100644 --- a/Agent/Services/Windows/DeviceInfoGeneratorWin.cs +++ b/Agent/Services/Windows/DeviceInfoGeneratorWin.cs @@ -30,7 +30,7 @@ public Task CreateDevice(string deviceId, string orgId) var (usedStorage, totalStorage) = GetSystemDriveInfo(); var (usedMemory, totalMemory) = GetMemoryInGB(); - device.CurrentUser = Win32Interop.GetActiveSessions().LastOrDefault()?.Username; + device.CurrentUser = Win32Interop.GetActiveSessions().LastOrDefault()?.Username ?? string.Empty; device.Drives = GetAllDrives(); device.UsedStorage = usedStorage; device.TotalStorage = totalStorage; diff --git a/Server/API/LoginController.cs b/Server/API/LoginController.cs index ddbee3b5f..fa8f3cf23 100644 --- a/Server/API/LoginController.cs +++ b/Server/API/LoginController.cs @@ -81,7 +81,7 @@ public async Task Post([FromBody] ApiLogin login) return NotFound(); } - var result = await _signInManager.PasswordSignInAsync(login.Email, login.Password, false, true); + var result = await _signInManager.PasswordSignInAsync($"{login.Email}", $"{login.Password}", false, true); if (result.Succeeded) { _logger.LogInformation("API login successful for {loginEmail}.", login.Email); diff --git a/Server/API/OrganizationManagementController.cs b/Server/API/OrganizationManagementController.cs index 5381190ef..323f1f601 100644 --- a/Server/API/OrganizationManagementController.cs +++ b/Server/API/OrganizationManagementController.cs @@ -288,7 +288,7 @@ public async Task SendInvite([FromBody] InviteViewModel invite) return Unauthorized(); } - if (!ModelState.IsValid) + if (!ModelState.IsValid || string.IsNullOrWhiteSpace(invite.InvitedUser)) { return BadRequest(); } diff --git a/Server/API/RemoteControlController.cs b/Server/API/RemoteControlController.cs index a20be9cff..aba579bdb 100644 --- a/Server/API/RemoteControlController.cs +++ b/Server/API/RemoteControlController.cs @@ -75,6 +75,13 @@ public async Task Post([FromBody] RemoteControlRequest rcRequest) return NotFound(); } + if (string.IsNullOrWhiteSpace(rcRequest.Email) || + string.IsNullOrWhiteSpace(rcRequest.Password) || + string.IsNullOrWhiteSpace(rcRequest.DeviceID)) + { + return BadRequest("Request body is missing required values."); + } + var userResult = await _dataService.GetUserByName(rcRequest.Email); if (!userResult.IsSuccess) { diff --git a/Server/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs b/Server/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs index ed2436b8a..46058053f 100644 --- a/Server/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs @@ -23,7 +23,7 @@ public ConfirmEmailModel(UserManager userManager) } [TempData] - public string StatusMessage { get; set; } + public string? StatusMessage { get; set; } public async Task OnGetAsync(string userId, string code) { diff --git a/Server/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs b/Server/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs index c1b7b2985..983f20391 100644 --- a/Server/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs @@ -25,7 +25,7 @@ public ConfirmEmailChangeModel(UserManager userManager, SignInMana } [TempData] - public string StatusMessage { get; set; } + public string? StatusMessage { get; set; } public async Task OnGetAsync(string userId, string email, string code) { diff --git a/Server/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs b/Server/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs index 3a33dce38..a4fb87fc4 100644 --- a/Server/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs @@ -37,13 +37,13 @@ public ForgotPasswordModel( } [BindProperty] - public InputModel Input { get; set; } + public InputModel Input { get; set; } = new(); public class InputModel { [Required] [EmailAddress] - public string Email { get; set; } + public string Email { get; set; } = string.Empty; } public async Task OnPostAsync() @@ -65,7 +65,7 @@ public async Task OnPostAsync() "/Account/ResetPassword", pageHandler: null, values: new { area = "Identity", code }, - protocol: Request.Scheme); + protocol: Request.Scheme)!; _logger.LogInformation( "Sending password reset for user {username}. Reset URL: {callbackUrl}", user.UserName, callbackUrl); diff --git a/Server/Areas/Identity/Pages/Account/Login.cshtml.cs b/Server/Areas/Identity/Pages/Account/Login.cshtml.cs index cbe109dd6..d433aa522 100644 --- a/Server/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -38,30 +38,30 @@ public LoginModel(SignInManager signInManager, } [BindProperty] - public InputModel Input { get; set; } + public InputModel Input { get; set; } = null!; - public IList ExternalLogins { get; set; } + public IList? ExternalLogins { get; set; } - public string ReturnUrl { get; set; } + public string? ReturnUrl { get; set; } [TempData] - public string ErrorMessage { get; set; } + public string? ErrorMessage { get; set; } public class InputModel { [Required] [EmailAddress] - public string Email { get; set; } + public required string Email { get; set; } [Required] [DataType(DataType.Password)] - public string Password { get; set; } + public required string Password { get; set; } [Display(Name = "Remember me?")] public bool RememberMe { get; set; } } - public async Task OnGetAsync(string returnUrl = null) + public async Task OnGetAsync(string? returnUrl = null) { if (!string.IsNullOrEmpty(ErrorMessage)) { @@ -78,7 +78,7 @@ public async Task OnGetAsync(string returnUrl = null) ReturnUrl = returnUrl; } - public async Task OnPostAsync(string returnUrl = null) + public async Task OnPostAsync(string? returnUrl = null) { returnUrl ??= Url.Content("~/"); @@ -96,7 +96,7 @@ public async Task OnPostAsync(string returnUrl = null) } if (result.RequiresTwoFactor) { - return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); + return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, Input.RememberMe }); } if (result.IsLockedOut) { @@ -108,6 +108,11 @@ public async Task OnPostAsync(string returnUrl = null) if (await _dataService.TempPasswordSignIn(Input.Email, Input.Password)) { var user = await _userManager.FindByNameAsync(Input.Email); + if (user is null) + { + ModelState.AddModelError(string.Empty, "Account not found."); + return Page(); + } var code = await _userManager.GeneratePasswordResetTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Page( @@ -116,7 +121,7 @@ public async Task OnPostAsync(string returnUrl = null) values: new { area = "Identity", code }, protocol: Request.Scheme); - return Redirect(callbackUrl); + return Redirect(callbackUrl!); } ModelState.AddModelError(string.Empty, "Invalid login attempt."); diff --git a/Server/Areas/Identity/Pages/Account/Logout.cshtml b/Server/Areas/Identity/Pages/Account/Logout.cshtml index e84347e52..f320b0194 100644 --- a/Server/Areas/Identity/Pages/Account/Logout.cshtml +++ b/Server/Areas/Identity/Pages/Account/Logout.cshtml @@ -16,7 +16,7 @@ { if (SignInManager.IsSignedIn(User)) { - var activeSessions = RemoteControlSessionCache.Sessions.Where(x => x.RequesterUserName == HttpContext.User.Identity.Name); + var activeSessions = RemoteControlSessionCache.Sessions.Where(x => x.RequesterUserName == HttpContext.User.Identity?.Name); foreach (var session in activeSessions) { await DesktopHubContext.Clients.Client(session.DesktopConnectionId).SendAsync("Disconnect", "User logged out."); diff --git a/Server/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs b/Server/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs index 9148edc86..7a64628e2 100644 --- a/Server/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs @@ -32,18 +32,19 @@ public EnableAuthenticatorModel( _urlEncoder = urlEncoder; } - public string SharedKey { get; set; } + public string? SharedKey { get; set; } + + public string? AuthenticatorUri { get; set; } - public string AuthenticatorUri { get; set; } [TempData] - public string[] RecoveryCodes { get; set; } + public string[]? RecoveryCodes { get; set; } [TempData] - public string StatusMessage { get; set; } + public string? StatusMessage { get; set; } [BindProperty] - public InputModel Input { get; set; } + public InputModel Input { get; set; } = new(); public class InputModel { @@ -51,7 +52,7 @@ public class InputModel [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Text)] [Display(Name = "Verification Code")] - public string Code { get; set; } + public string Code { get; set; } = string.Empty; } public async Task OnGetAsync() @@ -103,7 +104,7 @@ public async Task OnPostAsync() if (await _userManager.CountRecoveryCodesAsync(user) == 0) { var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); - RecoveryCodes = recoveryCodes.ToArray(); + RecoveryCodes = recoveryCodes?.ToArray(); return RedirectToPage("./ShowRecoveryCodes"); } else @@ -122,10 +123,10 @@ private async Task LoadSharedKeyAndQrCodeUriAsync(RemotelyUser user) unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); } - SharedKey = FormatKey(unformattedKey); + SharedKey = FormatKey($"{unformattedKey}"); var email = await _userManager.GetEmailAsync(user); - AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey); + AuthenticatorUri = GenerateQrCodeUri($"{email}", $"{unformattedKey}"); } private string FormatKey(string unformattedKey) diff --git a/Server/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs b/Server/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs index b10322bc0..9ee3bd79b 100644 --- a/Server/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs +++ b/Server/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs @@ -23,19 +23,19 @@ public IndexModel( _signInManager = signInManager; } - public string Username { get; set; } + public string? Username { get; set; } [TempData] - public string StatusMessage { get; set; } + public string? StatusMessage { get; set; } [BindProperty] - public InputModel Input { get; set; } + public InputModel Input { get; set; } = new(); public class InputModel { [Phone] [Display(Name = "Phone number")] - public string PhoneNumber { get; set; } + public string? PhoneNumber { get; set; } } private async Task LoadAsync(RemotelyUser user) diff --git a/Server/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs b/Server/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs index aa0c95ff0..4d1010386 100644 --- a/Server/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs +++ b/Server/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs @@ -44,6 +44,6 @@ private static string PageNavClass(ViewContext viewContext, string page) { var activePage = viewContext.ViewData["ActivePage"] as string ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName); - return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; + return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : ""; } } diff --git a/Server/Auth/LocalOnlyFilter.cs b/Server/Auth/LocalOnlyFilter.cs index ca32340e0..f4a5aae1b 100644 --- a/Server/Auth/LocalOnlyFilter.cs +++ b/Server/Auth/LocalOnlyFilter.cs @@ -9,7 +9,7 @@ public class LocalOnlyFilter : IAuthorizationFilter public void OnAuthorization(AuthorizationFilterContext context) { var remoteIp = context.HttpContext.Connection.RemoteIpAddress; - if (!IPAddress.IsLoopback(remoteIp)) + if (remoteIp is null || !IPAddress.IsLoopback(remoteIp)) { context.Result = new UnauthorizedResult(); return; diff --git a/Server/Auth/TwoFactorRequiredHandler.cs b/Server/Auth/TwoFactorRequiredHandler.cs index 692c352eb..beb431f9d 100644 --- a/Server/Auth/TwoFactorRequiredHandler.cs +++ b/Server/Auth/TwoFactorRequiredHandler.cs @@ -22,10 +22,10 @@ public TwoFactorRequiredHandler(UserManager userManager, IApplicat protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, TwoFactorRequiredRequirement requirement) { - if (context.User.Identity.IsAuthenticated && _appConfig.Require2FA) + if (context.User.Identity?.IsAuthenticated == true && _appConfig.Require2FA) { var user = await _userManager.GetUserAsync(context.User); - if (!user.TwoFactorEnabled) + if (user?.TwoFactorEnabled != true) { context.Fail(); return; diff --git a/Server/Components/AlertBanner.razor b/Server/Components/AlertBanner.razor index f5fb13be0..a1552ee1b 100644 --- a/Server/Components/AlertBanner.razor +++ b/Server/Components/AlertBanner.razor @@ -9,10 +9,10 @@ @code { [Parameter] - public string Message { get; set; } + public string? Message { get; set; } [Parameter] - public string StatusClass { get; set; } + public string? StatusClass { get; set; } [Parameter] public EventCallback OnClose { get; set; } diff --git a/Server/Components/AlertsFrame.razor b/Server/Components/AlertsFrame.razor index adb2c96f3..922ae35a2 100644 --- a/Server/Components/AlertsFrame.razor +++ b/Server/Components/AlertsFrame.razor @@ -57,7 +57,7 @@ private readonly List _alerts = new(); private bool _isOpen; - private string FrameClass => _isOpen ? "open" : null; + private string? FrameClass => _isOpen ? "open" : null; protected override async Task OnInitializedAsync() { @@ -93,7 +93,8 @@ private void ShowAlertDetails(Alert alert) { - ModalService.ShowModal($"Alert Details for {alert.Device?.DeviceName}", alert.Details.Split('\n')); + var body = alert.Details?.Split('\n') ?? Array.Empty(); + ModalService.ShowModal($"Alert Details for {alert.Device?.DeviceName}", body); } private void ToggleOpen() diff --git a/Server/Components/ColorPicker.razor b/Server/Components/ColorPicker.razor index 40c3615ee..d6fd662db 100644 --- a/Server/Components/ColorPicker.razor +++ b/Server/Components/ColorPicker.razor @@ -20,7 +20,7 @@ @code { - private ColorPickerModel _color; + private ColorPickerModel _color = new(); [Parameter] public ColorPickerModel Color diff --git a/Server/Components/Devices/ChatCard.razor b/Server/Components/Devices/ChatCard.razor index c89da48d3..4cf7ef115 100644 --- a/Server/Components/Devices/ChatCard.razor +++ b/Server/Components/Devices/ChatCard.razor @@ -6,9 +6,9 @@
- @Session?.MissedChats + @Session.MissedChats - @Session?.DeviceName + @Session.DeviceName
@code { - private System.Timers.Timer _collapseTimer; - private string _showClass; + private System.Timers.Timer? _collapseTimer; + private string? _showClass; private bool _isExpanded; [Parameter] - public string ButtonClass { get; set; } + public string? ButtonClass { get; set; } [Parameter] - public RenderFragment ButtonContent { get; set; } + public RenderFragment? ButtonContent { get; set; } [Parameter] - public string DropDownClass { get; set; } + public string? DropDownClass { get; set; } [Parameter] - public string DropDownMenuClass { get; set; } + public string? DropDownMenuClass { get; set; } [Parameter] - public RenderFragment ChildListItems { get; set; } + public RenderFragment? ChildListItems { get; set; } private void ToggleShown() { diff --git a/Server/Components/FileInputButton.razor b/Server/Components/FileInputButton.razor index 4e1bae25a..cbd2dd74a 100644 --- a/Server/Components/FileInputButton.razor +++ b/Server/Components/FileInputButton.razor @@ -8,14 +8,14 @@ @code { [Parameter] - public string ClassNames { get; set; } + public string? ClassNames { get; set; } [Parameter] - public RenderFragment ButtonContent { get; set; } + public RenderFragment? ButtonContent { get; set; } [Parameter] - public Func OnChanged { get; set; } + public Func? OnChanged { get; set; } [Parameter] public bool Multiple { get; set; } diff --git a/Server/Components/ModalContents/EditDeviceGroup.razor b/Server/Components/ModalContents/EditDeviceGroup.razor index 95603a8a7..3d81f6755 100644 --- a/Server/Components/ModalContents/EditDeviceGroup.razor +++ b/Server/Components/ModalContents/EditDeviceGroup.razor @@ -21,10 +21,10 @@ public static string DeviceGroupsPropName => nameof(DeviceGroups); [Parameter] - public RemotelyUser EditUser { get; set; } + public required RemotelyUser EditUser { get; set; } [Parameter] - public DeviceGroup[] DeviceGroups { get; set; } + public required DeviceGroup[] DeviceGroups { get; set; } private bool DoesGroupContainUser(DeviceGroup group) @@ -34,7 +34,9 @@ private async Task GroupCheckChanged(ChangeEventArgs args, DeviceGroup group) { - if ((bool)args.Value) + if (!string.IsNullOrWhiteSpace(EditUser.UserName) && + args.Value is bool boolValue && + boolValue) { if (!DataService.AddUserToDeviceGroup(EditUser.OrganizationID, group.ID, EditUser.UserName, out var result)) { diff --git a/Server/Components/ModalHarness.razor b/Server/Components/ModalHarness.razor index 910aa2f6a..6d2843639 100644 --- a/Server/Components/ModalHarness.razor +++ b/Server/Components/ModalHarness.razor @@ -11,13 +11,13 @@ @code { - private string _showClass; - private string _displayStyle; + private string? _showClass; + private string? _displayStyle; protected override Task OnAfterRenderAsync(bool firstRender) { diff --git a/Server/Components/Scripts/SavedScripts.razor b/Server/Components/Scripts/SavedScripts.razor index ea70493e8..d688e8817 100644 --- a/Server/Components/Scripts/SavedScripts.razor +++ b/Server/Components/Scripts/SavedScripts.razor @@ -13,7 +13,7 @@
- +
diff --git a/Server/Components/TabControl/TabContent.razor b/Server/Components/TabControl/TabContent.razor index aa9a60b0f..9713a215a 100644 --- a/Server/Components/TabControl/TabContent.razor +++ b/Server/Components/TabControl/TabContent.razor @@ -8,13 +8,14 @@ @code { [CascadingParameter] - public TabControl Parent { get; set; } + public required TabControl Parent { get; init; } [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } [Parameter] - public string Name { get; set; } + [EditorRequired] + public required string Name { get; set; } private bool IsActive => Parent.ActiveTab == Name; diff --git a/Server/Components/TabControl/TabControl.razor b/Server/Components/TabControl/TabControl.razor index 4b140ab4b..139f7d6e7 100644 --- a/Server/Components/TabControl/TabControl.razor +++ b/Server/Components/TabControl/TabControl.razor @@ -12,15 +12,15 @@ @code { [Parameter] - public RenderFragment TabHeaders { get; set; } + public RenderFragment? TabHeaders { get; set; } [Parameter] - public RenderFragment TabContents { get; set; } + public RenderFragment? TabContents { get; set; } [Parameter] - public string InitialActiveTab { get; set; } + public string? InitialActiveTab { get; set; } - public string ActiveTab { get; set; } + public string? ActiveTab { get; set; } protected override void OnInitialized() { diff --git a/Server/Components/TabControl/TabHeader.razor b/Server/Components/TabControl/TabHeader.razor index ebbf6fad4..80238cd3c 100644 --- a/Server/Components/TabControl/TabHeader.razor +++ b/Server/Components/TabControl/TabHeader.razor @@ -8,21 +8,21 @@ @code { [CascadingParameter] - public TabControl Parent { get; set; } + public TabControl? Parent { get; set; } [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } [Parameter] - public Action OnActivated { get; set; } + public Action? OnActivated { get; set; } [Parameter] - public string NavigationUri { get; set; } + public string? NavigationUri { get; set; } [Parameter] - public string Name { get; set; } + public string? Name { get; set; } - private string ActiveClass => Parent.ActiveTab == Name ? "active" : ""; + private string ActiveClass => Parent?.ActiveTab == Name ? "active" : ""; protected override async Task OnInitializedAsync() { @@ -46,7 +46,7 @@ } else { - Parent.SetActiveTab(this); + Parent?.SetActiveTab(this); StateHasChanged(); OnActivated?.Invoke(); } diff --git a/Server/Components/ToastHarness.razor b/Server/Components/ToastHarness.razor index 79dc35b4e..85484e060 100644 --- a/Server/Components/ToastHarness.razor +++ b/Server/Components/ToastHarness.razor @@ -2,7 +2,7 @@ @inject IToastService ToastService
- @foreach (var toast in ToastService?.Toasts) + @foreach (var toast in ToastService.Toasts) {
@toast.Message diff --git a/Server/Components/TreeView/TreeView.razor.cs b/Server/Components/TreeView/TreeView.razor.cs index 261e72fb9..694cf9046 100644 --- a/Server/Components/TreeView/TreeView.razor.cs +++ b/Server/Components/TreeView/TreeView.razor.cs @@ -9,35 +9,41 @@ namespace Remotely.Server.Components.TreeView; public partial class TreeView : ComponentBase { [Parameter] - public IEnumerable DataSource { get; set; } + [EditorRequired] + public required IEnumerable DataSource { get; set; } [Parameter] - public Func> ChildItemSelector { get; set; } + [EditorRequired] + public required Func> ChildItemSelector { get; set; } [Parameter] - public Func ItemHeaderSelector { get; set; } + [EditorRequired] + public required Func ItemHeaderSelector { get; set; } [Parameter] - public Func KeySelector { get; set; } + [EditorRequired] + public required Func KeySelector { get; set; } [Parameter] public EventCallback ItemSelected { get; set; } [Parameter] - public string WrapperStyle { get; set; } + public string? WrapperStyle { get; set; } [Parameter] - public string ChildItemStyle { get; set; } + public string? ChildItemStyle { get; set; } [Parameter] public int IndentLevel { get; set; } [Parameter] - public Func ItemTypeSelector { get; set; } + [EditorRequired] + public required Func ItemTypeSelector { get; set; } [Parameter] - public Func ItemIconCssSelector { get; set; } + [EditorRequired] + public required Func? ItemIconCssSelector { get; set; } - public TreeViewItem SelectedNode { get; set; } + public TreeViewItem? SelectedNode { get; set; } } diff --git a/Server/Components/TreeView/TreeViewItem.razor.cs b/Server/Components/TreeView/TreeViewItem.razor.cs index 7c6e6f138..7eb1462ef 100644 --- a/Server/Components/TreeView/TreeViewItem.razor.cs +++ b/Server/Components/TreeView/TreeViewItem.razor.cs @@ -9,34 +9,40 @@ namespace Remotely.Server.Components.TreeView; public partial class TreeViewItem : ComponentBase { [CascadingParameter] - public TreeView ParentTree { get; set; } + public required TreeView ParentTree { get; set; } [Parameter] - public T Source { get; set; } + [EditorRequired] + public required T Source { get; set; } [Parameter] - public Func> ChildItemSelector { get; set; } + [EditorRequired] + public required Func> ChildItemSelector { get; set; } [Parameter] - public Func HeaderSelector { get; set; } + [EditorRequired] + public required Func HeaderSelector { get; set; } [Parameter] - public Func ItemIconCssSelector { get; set; } + [EditorRequired] + public required Func ItemIconCssSelector { get; set; } [Parameter] - public string Style { get; set; } + public string? Style { get; set; } [Parameter] public int IndentLevel { get; set; } [Parameter] - public Func ItemTypeSelector { get; set; } + [EditorRequired] + public required Func ItemTypeSelector { get; set; } [Parameter] public EventCallback ItemSelected { get; set; } [Parameter] - public Func KeySelector { get; set; } + [EditorRequired] + public required Func KeySelector { get; set; } public bool IsExpanded { get; set; } diff --git a/Server/Hubs/AgentHub.cs b/Server/Hubs/AgentHub.cs index 811620c33..e24ec7df2 100644 --- a/Server/Hubs/AgentHub.cs +++ b/Server/Hubs/AgentHub.cs @@ -10,9 +10,7 @@ using Remotely.Shared.Models; using Remotely.Shared.Utilities; using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; @@ -52,7 +50,14 @@ private Device Device { get { - return Context.Items["Device"] as Device; + if (Context.Items["Device"] is Device device) + { + return device; + } + else + { + throw new InvalidOperationException("Device not set."); + } } set { @@ -64,7 +69,7 @@ public Task Chat(string message, bool disconnected, string browserConnectionId) { if (_circuitManager.TryGetConnection(browserConnectionId, out var connection)) { - return connection.InvokeCircuitEvent(CircuitEventName.ChatReceived, Device.ID, Device.DeviceName, message, disconnected); + return connection.InvokeCircuitEvent(CircuitEventName.ChatReceived, Device.ID, $"{Device.DeviceName}", message, disconnected); } else { @@ -102,7 +107,7 @@ public async Task DeviceCameOnline(DeviceClientDto device) { ip = ip.MapToIPv4(); } - device.PublicIP = ip?.ToString(); + device.PublicIP = $"{ip}"; if (CheckForDeviceBan(device.PublicIP)) { @@ -157,7 +162,7 @@ public async Task DeviceHeartbeat(DeviceClientDto device) { ip = ip.MapToIPv4(); } - device.PublicIP = ip?.ToString(); + device.PublicIP = $"{ip}"; if (CheckForDeviceBan(device.PublicIP)) { @@ -216,10 +221,10 @@ public string GetServerUrl() public string GetServerVerificationToken() { - return Device.ServerVerificationToken; + return $"{Device.ServerVerificationToken}"; } - public override Task OnDisconnectedAsync(Exception exception) + public override Task OnDisconnectedAsync(Exception? exception) { try { diff --git a/Server/Hubs/CircuitConnection.cs b/Server/Hubs/CircuitConnection.cs index bde56c2bd..e50ee796a 100644 --- a/Server/Hubs/CircuitConnection.cs +++ b/Server/Hubs/CircuitConnection.cs @@ -28,7 +28,7 @@ namespace Remotely.Server.Hubs; public interface ICircuitConnection { event EventHandler? MessageReceived; - RemotelyUser? User { get; } + RemotelyUser User { get; } Task DeleteRemoteLogs(string deviceId); diff --git a/Server/Models/ApiLogin.cs b/Server/Models/ApiLogin.cs index da4c37b30..45dfc014c 100644 --- a/Server/Models/ApiLogin.cs +++ b/Server/Models/ApiLogin.cs @@ -2,6 +2,6 @@ public class ApiLogin { - public string Email { get; set; } - public string Password { get; set; } + public string? Email { get; set; } + public string? Password { get; set; } } diff --git a/Server/Models/ModalButton.cs b/Server/Models/ModalButton.cs index 81f28fc0e..88ff556c3 100644 --- a/Server/Models/ModalButton.cs +++ b/Server/Models/ModalButton.cs @@ -4,8 +4,8 @@ namespace Remotely.Server.Models; public class ModalButton { - public string Class { get; set; } - public string Text { get; set; } + public string Class { get; init; } = string.Empty; + public string Text { get; init; } = string.Empty; - public Action OnClick { get; set; } + public required Action OnClick { get; init; } } diff --git a/Server/Models/RemoteControlRequest.cs b/Server/Models/RemoteControlRequest.cs index 43d0c14fd..0b104f1ad 100644 --- a/Server/Models/RemoteControlRequest.cs +++ b/Server/Models/RemoteControlRequest.cs @@ -2,7 +2,7 @@ public class RemoteControlRequest { - public string DeviceID { get; set; } - public string Email { get; set; } - public string Password { get; set; } + public string? DeviceID { get; set; } + public string? Email { get; set; } + public string? Password { get; set; } } diff --git a/Server/Pages/Branding.razor b/Server/Pages/Branding.razor index 2b9a26f72..f0ae890db 100644 --- a/Server/Pages/Branding.razor +++ b/Server/Pages/Branding.razor @@ -65,8 +65,8 @@
@code { - private string _alertMessage; - private string _base64Icon; + private string? _alertMessage; + private string? _base64Icon; private InputModel _inputModel = new(); private class InputModel @@ -74,13 +74,13 @@ [StringLength(25)] [Required] [Display(Name = "Product Name")] - public string ProductName { get; set; } + public string ProductName { get; set; } = string.Empty; public ColorPickerModel TitleForegroundColor { get; set; } = new(); public ColorPickerModel TitleBackgroundColor { get; set; } = new(); public ColorPickerModel TitleButtonColor { get; set; } = new(); - public byte[] IconBytes { get; set; } + public byte[] IconBytes { get; set; } = Array.Empty(); } protected override async Task OnInitializedAsync() diff --git a/Server/Pages/DeviceDetails.razor b/Server/Pages/DeviceDetails.razor index f8979bc2c..c6a97118f 100644 --- a/Server/Pages/DeviceDetails.razor +++ b/Server/Pages/DeviceDetails.razor @@ -255,9 +255,9 @@ else @scriptResult.TimeStamp @scriptResult.SenderUserName @scriptResult.RunTime - @GetTrimmedText(scriptResult.ScriptInput, 25) - @GetTrimmedText(scriptResult.StandardOutput, 25) - @GetTrimmedText(scriptResult.ErrorOutput, 25) + @GetTrimmedText($"{scriptResult.ScriptInput}", 25) + @GetTrimmedText($"{scriptResult.StandardOutput}", 25) + @GetTrimmedText($"{scriptResult.ErrorOutput}", 25) diff --git a/Server/Pages/Error.cshtml.cs b/Server/Pages/Error.cshtml.cs index 236f89a17..556e40456 100644 --- a/Server/Pages/Error.cshtml.cs +++ b/Server/Pages/Error.cshtml.cs @@ -13,17 +13,10 @@ namespace Remotely.Server.Pages; [IgnoreAntiforgeryToken] public class ErrorModel : PageModel { - public string RequestId { get; set; } + public string? RequestId { get; set; } public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - public void OnGet() { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; diff --git a/Server/Pages/GetSupport.cshtml.cs b/Server/Pages/GetSupport.cshtml.cs index 216eb0415..b9216631b 100644 --- a/Server/Pages/GetSupport.cshtml.cs +++ b/Server/Pages/GetSupport.cshtml.cs @@ -21,7 +21,7 @@ public GetSupportModel(IDataService dataService, IEmailSenderEx emailSender) public string? StatusMessage { get; set; } [BindProperty] - public InputModel Input { get; set; } + public InputModel Input { get; set; } = new(); public IActionResult OnGet() { @@ -76,7 +76,7 @@ public class InputModel { [StringLength(150)] [Required] - public required string Name { get; set; } + public string Name { get; set; } = string.Empty; public string? Email { get; set; } public string? Phone { get; set; } public bool ChatResponseOk { get; set; } diff --git a/Server/Pages/ScriptsPage.razor b/Server/Pages/ScriptsPage.razor index b9459bc72..6e7cfaf05 100644 --- a/Server/Pages/ScriptsPage.razor +++ b/Server/Pages/ScriptsPage.razor @@ -39,7 +39,7 @@ private bool _showOnlyMyScripts = true; [Parameter] - public string ActiveTab { get; set; } + public string? ActiveTab { get; set; } public bool ShowOnlyMyScripts { @@ -56,7 +56,7 @@ { get { - if (_treeNodes?.Any() == true) + if (_treeNodes.Any() == true) { return _treeNodes; } diff --git a/Server/Pages/ServerConfig.razor.cs b/Server/Pages/ServerConfig.razor.cs index e8f3584c4..c6d7f6945 100644 --- a/Server/Pages/ServerConfig.razor.cs +++ b/Server/Pages/ServerConfig.razor.cs @@ -58,7 +58,7 @@ public class AppSettingsModel [Display(Name = "Max Organizations")] public int MaxOrganizationCount { get; set; } [Display(Name = "Message of the Day")] - public string MessageOfTheDay { get; set; } + public string? MessageOfTheDay { get; set; } [Display(Name = "Redirect To HTTPS")] public bool RedirectToHttps { get; set; } @@ -76,28 +76,28 @@ public class AppSettingsModel public bool Require2FA { get; set; } [Display(Name = "SMTP Display Name")] - public string SmtpDisplayName { get; set; } + public string? SmtpDisplayName { get; set; } [Display(Name = "SMTP Email")] [EmailAddress] - public string SmtpEmail { get; set; } + public string? SmtpEmail { get; set; } [Display(Name = "SMTP Host")] - public string SmtpHost { get; set; } + public string? SmtpHost { get; set; } [Display(Name = "SMTP Local Domain")] - public string SmtpLocalDomain { get; set; } + 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; } + public string? SmtpPassword { get; set; } [Display(Name = "SMTP Port")] public int SmtpPort { get; set; } [Display(Name = "SMTP Username")] - public string SmtpUserName { get; set; } + public string? SmtpUserName { get; set; } [Display(Name = "Theme")] [JsonConverter(typeof(JsonStringEnumConverter))] @@ -116,72 +116,74 @@ public class AppSettingsModel public class ConnectionStringsModel { [Display(Name = "PostgreSQL")] - public string PostgreSQL { get; set; } + public string? PostgreSQL { get; set; } [Display(Name = "SQLite")] - public string SQLite { get; set; } + public string? SQLite { get; set; } [Display(Name = "SQL Server")] - public string SQLServer { get; set; } + public string? SQLServer { get; set; } } public partial class ServerConfig : AuthComponentBase { - private string _alertMessage; - private string _bannedDeviceSelected; - private string _bannedDeviceToAdd; + private string? _alertMessage; + private string? _bannedDeviceSelected; + private string? _bannedDeviceToAdd; - private string _knownProxySelected; - private string _knownProxyToAdd; + private string? _knownProxySelected; + private string? _knownProxyToAdd; private bool _showMyOrgAdminsOnly = true; private bool _showAdminsOnly; - private string _trustedCorsOriginSelected; - private string _trustedCorsOriginToAdd; + private string? _trustedCorsOriginSelected; + private string? _trustedCorsOriginToAdd; private readonly List _userList = new(); [Inject] - private IHubContext AgentHubContext { get; set; } + private IHubContext AgentHubContext { get; init; } = null!; [Inject] - private IConfiguration Configuration { get; set; } + private IConfiguration Configuration { get; init; } = null!; private ConnectionStringsModel ConnectionStrings { get; } = new(); [Inject] - private IDataService DataService { get; set; } + private IDataService DataService { get; init; } = null!; [Inject] - private IEmailSenderEx EmailSender { get; set; } + private IEmailSenderEx EmailSender { get; init; } = null!; [Inject] - private IWebHostEnvironment HostEnv { get; set; } + private IWebHostEnvironment HostEnv { get; init; } = null!; [Inject] - private ILogger Logger { get; set; } + private ILogger Logger { get; init; } = null!; [Inject] - private IAgentHubSessionCache ServiceSessionCache { get; init; } + private IAgentHubSessionCache ServiceSessionCache { get; init; } = null!; private AppSettingsModel Input { get; } = new(); [Inject] - private IModalService ModalService { get; set; } + private IModalService ModalService { get; init; } = null!; [Inject] - private IUpgradeService UpgradeService { get; init; } + private IUpgradeService UpgradeService { get; init; } = null!; [Inject] - private ICircuitManager CircuitManager { get; set; } + private ICircuitManager CircuitManager { get; init; } = null!; private IEnumerable OutdatedDevices => GetOutdatedDevices(); [Inject] - private IToastService ToastService { get; set; } + private IToastService ToastService { get; init; } = null!; + private int TotalDevices => DataService.GetTotalDevices(); + private IEnumerable UserList { get @@ -338,6 +340,12 @@ private async Task SaveAndTestSmtpSettings() { await SaveInputToAppSettings(); + if (string.IsNullOrWhiteSpace(User.Email)) + { + ToastService.ShowToast2("User email is not set.", Enums.ToastType.Warning); + return; + } + var success = await EmailSender.SendEmailAsync(User.Email, "Remotely Test Email", "Congratulations! Your SMTP settings are working!", User.OrganizationID); if (success) { @@ -359,19 +367,27 @@ private async Task SaveInputToAppSettings() var devSettings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.Development.json"); var settings = HostEnv.ContentRootFileProvider.GetFileInfo("appsettings.json"); - if (HostEnv.IsProduction() && prodSettings.Exists) + if (HostEnv.IsProduction() + && prodSettings.Exists && + !string.IsNullOrWhiteSpace(prodSettings.PhysicalPath)) { savePath = prodSettings.PhysicalPath; } - else if (HostEnv.IsStaging() && stagingSettings.Exists) + else if ( + HostEnv.IsStaging() && + stagingSettings.Exists && + !string.IsNullOrWhiteSpace(stagingSettings.PhysicalPath)) { savePath = stagingSettings.PhysicalPath; } - else if (HostEnv.IsDevelopment() && devSettings.Exists) + else if ( + HostEnv.IsDevelopment() && + devSettings.Exists && + !string.IsNullOrWhiteSpace(devSettings.PhysicalPath)) { savePath = devSettings.PhysicalPath; } - else if (settings.Exists) + else if (settings.Exists && !string.IsNullOrWhiteSpace(settings.PhysicalPath)) { savePath = settings.PhysicalPath; } @@ -381,6 +397,10 @@ private async Task SaveInputToAppSettings() } var settingsJson = JsonSerializer.Deserialize>(await System.IO.File.ReadAllTextAsync(savePath)); + if (settingsJson is null) + { + return; + } settingsJson["ApplicationOptions"] = Input; settingsJson["ConnectionStrings"] = ConnectionStrings; @@ -393,7 +413,10 @@ private async Task SaveInputToAppSettings() } private void SetIsServerAdmin(ChangeEventArgs ev, RemotelyUser user) { - var isAdmin = (bool)ev.Value; + if (ev.Value is not bool isAdmin) + { + return; + } DataService.SetIsServerAdmin(user.Id, isAdmin, User.Id); ToastService.ShowToast("Server admins updated."); } @@ -404,10 +427,13 @@ private void ShowOutdatedDevices() { var outdatedDeviceNames = DataService .GetDevices(OutdatedDevices) - .Select(x => x.DeviceName); + .Select(x => $"{x.DeviceName}"); + + var body = (new[] { "Outdated Devices:" }) + .Concat(outdatedDeviceNames) + .ToArray(); - ModalService.ShowModal("Outdated Devices", - (new[] { "Outdated Devices:" }).Concat(outdatedDeviceNames).ToArray()); + ModalService.ShowModal("Outdated Devices", body); } else { diff --git a/Server/Pages/UserOptions.razor b/Server/Pages/UserOptions.razor index 19e1b8930..3f5fa6ae4 100644 --- a/Server/Pages/UserOptions.razor +++ b/Server/Pages/UserOptions.razor @@ -92,7 +92,7 @@ @code { private RemotelyUserOptions _options = new(); - private string _alertMessage; + private string? _alertMessage; protected override async Task OnInitializedAsync() { @@ -119,7 +119,7 @@ _options.CommandModeShortcutWinPS = "/" + _options.CommandModeShortcutWinPS; } - DataService.UpdateUserOptions(User.UserName, _options); + DataService.UpdateUserOptions(UserName, _options); _alertMessage = "Options saved"; diff --git a/Server/Services/AgentHubSessionCache.cs b/Server/Services/AgentHubSessionCache.cs index 1f7b98abe..4ad82cbdf 100644 --- a/Server/Services/AgentHubSessionCache.cs +++ b/Server/Services/AgentHubSessionCache.cs @@ -2,6 +2,7 @@ using Remotely.Shared.Models; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Remotely.Server.Services; @@ -13,9 +14,9 @@ public interface IAgentHubSessionCache ICollection GetAllDevices(); IEnumerable GetConnectionIdsByDeviceIds(IEnumerable deviceIds); - bool TryGetByDeviceId(string deviceId, out Device device); - bool TryGetConnectionId(string deviceId, out string serviceConnectionId); - bool TryRemoveByConnectionId(string connectionId, out Device device); + bool TryGetByDeviceId(string deviceId, [NotNullWhen(true)] out Device? device); + bool TryGetConnectionId(string deviceId, [NotNullWhen(true)] out string? serviceConnectionId); + bool TryRemoveByConnectionId(string connectionId, [NotNullWhen(true)] out Device? device); } public class AgentHubSessionCache : IAgentHubSessionCache @@ -55,23 +56,23 @@ public IEnumerable GetConnectionIdsByDeviceIds(IEnumerable devic } } - public bool TryGetByDeviceId(string deviceId, out Device device) + public bool TryGetByDeviceId(string deviceId, [NotNullWhen(true)] out Device? device) { if (_deviceIdToConnectionIdLookup.TryGetValue(deviceId, out var connectionId) && _connectionIdToDeviceLookup.TryGetValue(connectionId, out device)) { return true; } - device = Device.Empty; + device = default; return false; } - public bool TryGetConnectionId(string deviceId, out string serviceConnectionId) + public bool TryGetConnectionId(string deviceId, [NotNullWhen(true)] out string? serviceConnectionId) { return _deviceIdToConnectionIdLookup.TryGetValue(deviceId, out serviceConnectionId); } - public bool TryRemoveByConnectionId(string connectionId, out Device device) + public bool TryRemoveByConnectionId(string connectionId, [NotNullWhen(true)] out Device? device) { if (_connectionIdToDeviceLookup.TryRemove(connectionId, out var lookupResult)) { @@ -80,7 +81,7 @@ public bool TryRemoveByConnectionId(string connectionId, out Device device) return true; } - device = Device.Empty; + device = null; return false; } } diff --git a/Server/Services/ApplicationConfig.cs b/Server/Services/ApplicationConfig.cs index 24deb626a..9fd583882 100644 --- a/Server/Services/ApplicationConfig.cs +++ b/Server/Services/ApplicationConfig.cs @@ -59,21 +59,21 @@ public ApplicationConfig(IConfiguration config) public string[] KnownProxies => _config.GetSection("ApplicationOptions:KnownProxies").Get() ?? System.Array.Empty(); public int MaxConcurrentUpdates => int.TryParse(_config["ApplicationOptions:MaxConcurrentUpdates"], out var result) ? result : 10; public int MaxOrganizationCount => int.TryParse(_config["ApplicationOptions:MaxOrganizationCount"], out var result) ? result : 1; - public string MessageOfTheDay => _config["ApplicationOptions:MessageOfTheDay"]; + public 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"]; + 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"]; - public string SmtpEmail => _config["ApplicationOptions:SmtpEmail"]; - public string SmtpHost => _config["ApplicationOptions:SmtpHost"]; - public string SmtpLocalDomain => _config["ApplicationOptions:SmtpLocalDomain"]; - public string SmtpPassword => _config["ApplicationOptions:SmtpPassword"]; + public 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"]; + public string SmtpUserName => _config["ApplicationOptions:SmtpUserName"] ?? string.Empty; public Theme Theme => Enum.TryParse(_config["ApplicationOptions:Theme"], out var result) ? result : Theme.Dark; public string[] TrustedCorsOrigins => _config.GetSection("ApplicationOptions:TrustedCorsOrigins").Get() ?? System.Array.Empty(); public bool UseHsts => bool.TryParse(_config["ApplicationOptions:UseHsts"], out var result) && result; diff --git a/Server/Services/ClientAppState.cs b/Server/Services/ClientAppState.cs index 95d283d3b..0994f2ecd 100644 --- a/Server/Services/ClientAppState.cs +++ b/Server/Services/ClientAppState.cs @@ -14,7 +14,7 @@ public interface IClientAppState : INotifyPropertyChanged, IInvokePropertyChange { ConcurrentList DevicesFrameChatSessions { get; } DeviceCardState DevicesFrameFocusedCardState { get; set; } - string DevicesFrameFocusedDevice { get; set; } + string? DevicesFrameFocusedDevice { get; set; } ConcurrentList DevicesFrameSelectedDevices { get; } ConcurrentQueue TerminalLines { get; } @@ -49,7 +49,7 @@ public DeviceCardState DevicesFrameFocusedCardState set => Set(value); } - public string DevicesFrameFocusedDevice + public string? DevicesFrameFocusedDevice { get => Get(); set => Set(value); diff --git a/Server/Services/DataService.cs b/Server/Services/DataService.cs index bf6dc3842..8e3ef98b3 100644 --- a/Server/Services/DataService.cs +++ b/Server/Services/DataService.cs @@ -352,7 +352,7 @@ public async Task> AddInvite(string orgId, InviteViewModel in var inviteLink = new InviteLink() { - InvitedUser = invite.InvitedUser.ToLower(), + InvitedUser = invite.InvitedUser?.ToLower(), DateSent = DateTimeOffset.Now, IsAdmin = invite.IsAdmin, Organization = organization, diff --git a/Server/Services/EmailSender.cs b/Server/Services/EmailSender.cs index ed2eeb693..656cc85d0 100644 --- a/Server/Services/EmailSender.cs +++ b/Server/Services/EmailSender.cs @@ -13,8 +13,8 @@ namespace Remotely.Server.Services; public interface IEmailSenderEx { - Task SendEmailAsync(string email, string replyTo, string subject, string htmlMessage, string organizationID = null); - Task SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null); + Task SendEmailAsync(string email, string replyTo, string subject, string htmlMessage, string? organizationID = null); + Task SendEmailAsync(string email, string subject, string htmlMessage, string? organizationID = null); } public class EmailSender : IEmailSender @@ -44,7 +44,12 @@ public EmailSenderEx( _appConfig = appConfig; _logger = logger; } - public async Task SendEmailAsync(string toEmail, string replyTo, string subject, string htmlMessage, string organizationID = null) + public async Task SendEmailAsync( + string toEmail, + string replyTo, + string subject, + string htmlMessage, + string? organizationID = null) { try { @@ -89,7 +94,7 @@ public async Task SendEmailAsync(string toEmail, string replyTo, string su } } - public Task SendEmailAsync(string email, string subject, string htmlMessage, string organizationID = null) + public Task SendEmailAsync(string email, string subject, string htmlMessage, string? organizationID = null) { return SendEmailAsync(email, _appConfig.SmtpEmail, subject, htmlMessage, organizationID); } diff --git a/Server/Services/ModalService.cs b/Server/Services/ModalService.cs index 29c22323b..b6d297c9a 100644 --- a/Server/Services/ModalService.cs +++ b/Server/Services/ModalService.cs @@ -11,24 +11,25 @@ public interface IModalService { event EventHandler ModalShown; List Buttons { get; } - string[] Body { get; } - RenderFragment RenderBody { get; } + string[]? Body { get; } + RenderFragment? RenderBody { get; } string Title { get; } - Task ShowModal(string title, string[] body, ModalButton[] buttons = null); - Task ShowModal(string title, RenderFragment body, ModalButton[] buttons = null); + Task ShowModal(string title, string[] body, ModalButton[]? buttons = null); + Task ShowModal(string title, RenderFragment body, ModalButton[]? buttons = null); } public class ModalService : IModalService { private readonly SemaphoreSlim _modalLock = new(1, 1); - public event EventHandler ModalShown; + public event EventHandler? ModalShown; + public List Buttons { get; } = new List(); - public string[] Body { get; private set; } - public RenderFragment RenderBody { get; private set; } + public string[]? Body { get; private set; } + public RenderFragment? RenderBody { get; private set; } public bool ShowInput { get; private set; } - public string Title { get; private set; } - public async Task ShowModal(string title, string[] body, ModalButton[] buttons = null) + public string Title { get; private set; } = string.Empty; + public async Task ShowModal(string title, string[] body, ModalButton[]? buttons = null) { try { @@ -41,7 +42,7 @@ public async Task ShowModal(string title, string[] body, ModalButton[] buttons = { Buttons.AddRange(buttons); } - ModalShown?.Invoke(this, null); + ModalShown?.Invoke(this, EventArgs.Empty); } finally { @@ -49,7 +50,7 @@ public async Task ShowModal(string title, string[] body, ModalButton[] buttons = } } - public async Task ShowModal(string title, RenderFragment body, ModalButton[] buttons = null) + public async Task ShowModal(string title, RenderFragment body, ModalButton[]? buttons = null) { try { @@ -62,7 +63,7 @@ public async Task ShowModal(string title, RenderFragment body, ModalButton[] but { Buttons.AddRange(buttons); } - ModalShown?.Invoke(this, null); + ModalShown?.Invoke(this, EventArgs.Empty); } finally { diff --git a/Server/Services/OtpProvider.cs b/Server/Services/OtpProvider.cs index ad11051d0..525f3e282 100644 --- a/Server/Services/OtpProvider.cs +++ b/Server/Services/OtpProvider.cs @@ -30,7 +30,8 @@ public string GetOtp(string deviceId) public bool OtpMatchesDevice(string otp, string deviceId) { - if (_otpCache.TryGetValue(otp, out string cachedDevice) && + if (_otpCache.TryGetValue(otp, out var cachedItem) && + cachedItem is string cachedDevice && cachedDevice == deviceId) { return true; diff --git a/Server/Services/ScriptScheduler.cs b/Server/Services/ScriptScheduler.cs index 680c9edf4..9b5d0fc94 100644 --- a/Server/Services/ScriptScheduler.cs +++ b/Server/Services/ScriptScheduler.cs @@ -21,8 +21,8 @@ public class ScriptScheduler : IHostedService, IDisposable TimeSpan.FromSeconds(30) : TimeSpan.FromMinutes(10); - private IServiceProvider _serviceProvider; - private System.Timers.Timer _schedulerTimer; + private readonly IServiceProvider _serviceProvider; + private System.Timers.Timer? _schedulerTimer; public ScriptScheduler(IServiceProvider serviceProvider) @@ -40,7 +40,7 @@ public void Dispose() public Task StartAsync(CancellationToken cancellationToken) { _schedulerTimer?.Dispose(); - _schedulerTimer = new System.Timers.Timer(_timerInterval.TotalMilliseconds); + _schedulerTimer = new System.Timers.Timer(_timerInterval); _schedulerTimer.Elapsed += SchedulerTimer_Elapsed; _schedulerTimer.Start(); return Task.CompletedTask; @@ -52,7 +52,7 @@ public Task StopAsync(CancellationToken cancellationToken) return Task.CompletedTask; } - private void SchedulerTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + private void SchedulerTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { _ = DispatchScriptRuns(); } @@ -63,13 +63,14 @@ public async Task DispatchScriptRuns() var scriptScheduleDispatcher = scope.ServiceProvider.GetRequiredService(); var logger = scope.ServiceProvider.GetRequiredService>(); + if (!await _dispatchLock.WaitAsync(0)) + { + logger.LogWarning("Script schedule dispatcher is already running. Returning."); + return; + } + try { - if (!await _dispatchLock.WaitAsync(0)) - { - logger.LogWarning("Script schedule dispatcher is already running. Returning."); - return; - } await scriptScheduleDispatcher.DispatchPendingScriptRuns(); } diff --git a/Server/Services/ToastService.cs b/Server/Services/ToastService.cs index de943060e..a42af683b 100644 --- a/Server/Services/ToastService.cs +++ b/Server/Services/ToastService.cs @@ -14,25 +14,25 @@ public interface IToastService void ShowToast( string message, int expirationMillisecond = 3000, - string classString = null, - string styleOverrides = null); + string classString = "", + string styleOverrides = ""); void ShowToast2( string message, ToastType toastType = ToastType.Info, int expirationMillisecond = 3000, - string styleOverrides = null); + string styleOverrides = ""); } public class ToastService : IToastService { - public event EventHandler OnToastsChanged; + public event EventHandler? OnToastsChanged; public ConcurrentList Toasts { get; } = new(); public void ShowToast(string message, int expirationMillisecond = 3000, - string classString = null, - string styleOverrides = null) + string classString = "", + string styleOverrides = "") { if (string.IsNullOrWhiteSpace(classString)) @@ -57,7 +57,7 @@ public void ShowToast(string message, removeToastTimer.Elapsed += (s, e) => { Toasts.Remove(toastModel); - OnToastsChanged?.Invoke(this, null); + OnToastsChanged?.Invoke(this, EventArgs.Empty); removeToastTimer.Dispose(); }; removeToastTimer.Start(); @@ -67,7 +67,7 @@ public void ShowToast2( string message, ToastType toastType, int expirationMillisecond = 3000, - string styleOverrides = null) + string styleOverrides = "") { var classString = toastType switch { diff --git a/Server/Services/UpgradeService.cs b/Server/Services/UpgradeService.cs index 333c9e591..81eb2747e 100644 --- a/Server/Services/UpgradeService.cs +++ b/Server/Services/UpgradeService.cs @@ -20,7 +20,7 @@ public class UpgradeService : IUpgradeService { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; - private Version _currentVersion; + private Version? _currentVersion; public UpgradeService(IHttpClientFactory httpClientFactory, ILogger logger) { @@ -59,10 +59,22 @@ public async Task IsNewVersionAvailable() { using var client = _httpClientFactory.CreateClient(); var response = await client.GetAsync("https://github.com/immense/Remotely/releases/latest"); - var versionString = response.RequestMessage.RequestUri.ToString().Split("/").Last()[1..]; + var versionTag = $"{response.RequestMessage?.RequestUri}".Split("/").LastOrDefault(); + if (string.IsNullOrWhiteSpace(versionTag)) + { + return false; + } + var versionString = versionTag[1..]; var remoteVersion = Version.Parse(versionString); + var filePath = Directory.GetFiles(Directory.GetCurrentDirectory(), "Remotely_Server.dll", SearchOption.AllDirectories).First(); - var localVersion = Version.Parse(System.Diagnostics.FileVersionInfo.GetVersionInfo(filePath).FileVersion); + var fileVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(filePath).FileVersion; + if (string.IsNullOrWhiteSpace(fileVersion)) + { + return false; + } + var localVersion = Version.Parse(fileVersion); + if (remoteVersion > localVersion) { return true; diff --git a/Shared/Models/ConnectionInfo.cs b/Shared/Models/ConnectionInfo.cs index 17165ac35..e5292b888 100644 --- a/Shared/Models/ConnectionInfo.cs +++ b/Shared/Models/ConnectionInfo.cs @@ -16,7 +16,7 @@ public string? Host } set { - _host = value?.Trim()?.TrimEnd('/') ?? string.Empty; + _host = value?.Trim()?.TrimEnd('/'); } } public string? OrganizationID { get; set; } diff --git a/Shared/Models/Device.cs b/Shared/Models/Device.cs index 865aed44c..3720bad53 100644 --- a/Shared/Models/Device.cs +++ b/Shared/Models/Device.cs @@ -11,7 +11,6 @@ namespace Remotely.Shared.Models; public class Device { - public static Device Empty { get; } = new(); [Sortable] [Display(Name = "Agent Version")] diff --git a/Shared/Models/PwshCommandCompletion.cs b/Shared/Models/PwshCommandCompletion.cs index b9fddf06f..6c71fdaf2 100644 --- a/Shared/Models/PwshCommandCompletion.cs +++ b/Shared/Models/PwshCommandCompletion.cs @@ -11,7 +11,7 @@ public class PwshCommandCompletion public int CurrentMatchIndex { get; set; } public int ReplacementIndex { get; set; } public int ReplacementLength { get; set; } - public List CompletionMatches { get; set; } + public List CompletionMatches { get; set; } = new(); } @@ -26,13 +26,13 @@ public PwshCompletionResult(string completionText, string listItemText, PwshComp ToolTip = toolTip; } - public string CompletionText { get; set; } + public string CompletionText { get; set; } = string.Empty; - public string ListItemText { get; set; } + public string ListItemText { get; set; } = string.Empty; public PwshCompletionResultType ResultType { get; set; } - public string ToolTip { get; set; } + public string ToolTip { get; set; } = string.Empty; } public enum PwshCompletionResultType diff --git a/Shared/Models/RemotelyUserOptions.cs b/Shared/Models/RemotelyUserOptions.cs index 6d128477a..56bd6d103 100644 --- a/Shared/Models/RemotelyUserOptions.cs +++ b/Shared/Models/RemotelyUserOptions.cs @@ -7,7 +7,7 @@ public class RemotelyUserOptions { [Display(Name = "Display Name")] [StringLength(100)] - public string DisplayName { get; set; } + public string? DisplayName { get; set; } [Display(Name = "PS Core Shortcut")] [StringLength(10)] diff --git a/Shared/Services/ProcessInvoker.cs b/Shared/Services/ProcessInvoker.cs index 58ede284b..23e83d615 100644 --- a/Shared/Services/ProcessInvoker.cs +++ b/Shared/Services/ProcessInvoker.cs @@ -32,9 +32,9 @@ public string InvokeProcessOutput(string command, string arguments) }; var proc = Process.Start(psi); - proc.WaitForExit(); + proc?.WaitForExit(); - return proc.StandardOutput.ReadToEnd(); + return proc?.StandardOutput.ReadToEnd() ?? string.Empty; } catch (Exception ex) { diff --git a/Shared/Utilities/AppVersionHelper.cs b/Shared/Utilities/AppVersionHelper.cs index bcd3ce3c7..1ffb1c964 100644 --- a/Shared/Utilities/AppVersionHelper.cs +++ b/Shared/Utilities/AppVersionHelper.cs @@ -34,7 +34,7 @@ public static string GetAppVersion(string defaultVersion = "1.0.0") } } - private static bool TryGetFileVersion(string filePath, out string version) + private static bool TryGetFileVersion(string? filePath, out string version) { try { diff --git a/Shared/Utilities/ConsoleHelper.cs b/Shared/Utilities/ConsoleHelper.cs index 5d4da78b5..ee7b8750b 100644 --- a/Shared/Utilities/ConsoleHelper.cs +++ b/Shared/Utilities/ConsoleHelper.cs @@ -28,7 +28,7 @@ public static string GetSelection(string promptMessage, params string[] options) Console.Write("Enter Response: "); Console.ForegroundColor = ConsoleColor.Gray; - return Console.ReadLine().Trim(); + return Console.ReadLine()?.Trim() ?? string.Empty; } public static string ReadLine(string prompt, ConsoleColor promptColor = ConsoleColor.Cyan, string subprompt = "") @@ -50,7 +50,7 @@ public static string ReadLine(string prompt, ConsoleColor promptColor = ConsoleC var response = Console.ReadLine(); Console.WriteLine(); - return response; + return response ?? string.Empty; } public static bool TryParseBoolLike(string value, out bool result) diff --git a/Shared/Utilities/Logger.cs b/Shared/Utilities/Logger.cs index 5ab3f5125..b56087f31 100644 --- a/Shared/Utilities/Logger.cs +++ b/Shared/Utilities/Logger.cs @@ -15,7 +15,7 @@ namespace Remotely.Shared.Utilities; [Obsolete("Please use ILogger via dependency injection.")] public static class Logger { - private static string _logDir; + private static string? _logDir; private static string LogDir { @@ -130,7 +130,7 @@ public static void Write(Exception ex, EventType eventType = EventType.Error, [C while (exception != null) { - File.AppendAllText(LogPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[{eventType}]\t[{callerName}]\t{exception?.Message}\t{exception?.StackTrace}\t{exception?.Source}{Environment.NewLine}"); + File.AppendAllText(LogPath, $"{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}\t[{eventType}]\t[{callerName}]\t{exception.Message}\t{exception.StackTrace}\t{exception.Source}{Environment.NewLine}"); Console.WriteLine(exception.Message); exception = exception.InnerException; } diff --git a/Shared/ViewModels/ChatHistoryItem.cs b/Shared/ViewModels/ChatHistoryItem.cs index 8dd32afab..9b5db9cc9 100644 --- a/Shared/ViewModels/ChatHistoryItem.cs +++ b/Shared/ViewModels/ChatHistoryItem.cs @@ -10,7 +10,7 @@ namespace Remotely.Shared.ViewModels; public class ChatHistoryItem { public ChatHistoryItemOrigin Origin { get; init; } - public string Message { get; init; } + public string? Message { get; init; } public DateTimeOffset Timestamp { get; init; } = DateTime.Now; diff --git a/Shared/ViewModels/ChatSession.cs b/Shared/ViewModels/ChatSession.cs index 4bf5c2dc1..3d7a30f8c 100644 --- a/Shared/ViewModels/ChatSession.cs +++ b/Shared/ViewModels/ChatSession.cs @@ -6,8 +6,8 @@ namespace Remotely.Shared.ViewModels; public class ChatSession { public ConcurrentList ChatHistory { get; } = new(); - public string DeviceId { get; set; } - public string DeviceName { get; set; } + public string? DeviceId { get; set; } + public string? DeviceName { get; set; } public string ExpandedClass => IsExpanded ? "expanded" : ""; public bool IsExpanded { get; set; } diff --git a/Shared/ViewModels/InviteViewModel.cs b/Shared/ViewModels/InviteViewModel.cs index 4d59b5e47..96eef5b4f 100644 --- a/Shared/ViewModels/InviteViewModel.cs +++ b/Shared/ViewModels/InviteViewModel.cs @@ -5,9 +5,9 @@ namespace Remotely.Shared.ViewModels; public class InviteViewModel { - public string ID { get; set; } + public string? ID { get; set; } public bool IsAdmin { get; set; } public DateTimeOffset DateSent { get; set; } [EmailAddress] - public string InvitedUser { get; set; } + public string? InvitedUser { get; set; } } diff --git a/Shared/ViewModels/OrganizationUser.cs b/Shared/ViewModels/OrganizationUser.cs index 537a78d4e..860604bf7 100644 --- a/Shared/ViewModels/OrganizationUser.cs +++ b/Shared/ViewModels/OrganizationUser.cs @@ -2,7 +2,7 @@ public class OrganizationUser { - public string ID { get; set; } - public string UserName { get; set; } + public string? ID { get; set; } + public string? UserName { get; set; } public bool IsAdmin { get; set; } } diff --git a/Shared/ViewModels/TerminalLineItem.cs b/Shared/ViewModels/TerminalLineItem.cs index 00751df29..bcef221da 100644 --- a/Shared/ViewModels/TerminalLineItem.cs +++ b/Shared/ViewModels/TerminalLineItem.cs @@ -10,7 +10,7 @@ namespace Remotely.Shared.ViewModels; public class TerminalLineItem { public Guid Id { get; } = Guid.NewGuid(); - public string Text { get; set; } - public string ClassName { get; set; } - public string Title { get; set; } + public string? Text { get; set; } + public string? ClassName { get; set; } + public string? Title { get; set; } } diff --git a/Tests/LoadTester/CommandLineParser.cs b/Tests/LoadTester/CommandLineParser.cs index ce2e5c6f7..0cb914b29 100644 --- a/Tests/LoadTester/CommandLineParser.cs +++ b/Tests/LoadTester/CommandLineParser.cs @@ -1,24 +1,32 @@ using System; +using System.Linq; using System.Collections.Generic; namespace Remotely.Tests.LoadTester; public class CommandLineParser { - private static Dictionary commandLineArgs; + private static Dictionary? _commandLineArgs; + public static Dictionary CommandLineArgs { get { - if (commandLineArgs is null) + if (_commandLineArgs is null) { - commandLineArgs = new Dictionary(); + _commandLineArgs = new Dictionary(); + var args = Environment.GetCommandLineArgs(); + if (args?.Any() != true) + { + return _commandLineArgs; + } + for (var i = 1; i < args.Length; i += 2) { try { - var key = args?[i]; + var key = args[i]; if (key != null) { if (!key.Contains("-")) @@ -29,14 +37,14 @@ public static Dictionary CommandLineArgs key = key.Trim().Replace("-", "").ToLower(); - commandLineArgs.Add(key, args[i + 1]); + _commandLineArgs.Add(key, args[i + 1]); } } catch { } } } - return commandLineArgs; + return _commandLineArgs; } } } diff --git a/Tests/LoadTester/Program.cs b/Tests/LoadTester/Program.cs index 3b341c0c0..00d078cdb 100644 --- a/Tests/LoadTester/Program.cs +++ b/Tests/LoadTester/Program.cs @@ -17,12 +17,12 @@ internal class Program { private static readonly double _heartbeatMs = TimeSpan.FromMinutes(1).TotalMilliseconds; private static int _agentCount; - private static string _organizationId; - private static string _serverurl; - private static Mock _cpuSampler; - private static Mock> _logger; - private static DeviceInfoGeneratorWin _deviceInfo; - private static Stopwatch _stopwatch; + private static string? _organizationId; + private static string? _serverurl; + private static Mock? _cpuSampler; + private static Mock>? _logger; + private static DeviceInfoGeneratorWin? _deviceInfo; + private static Stopwatch? _stopwatch; private static int _connectedCount; private static void Main(string[] args) @@ -96,7 +96,7 @@ private static async Task StartAgent(int i) Console.WriteLine($"Connecting device number {i}"); await hubConnection.StartAsync(); - var device = await _deviceInfo.CreateDevice(deviceId, _organizationId); + var device = await _deviceInfo!.CreateDevice(deviceId, _organizationId!); device.DeviceName = "TestDevice-" + Guid.NewGuid(); var result = await hubConnection.InvokeAsync("DeviceCameOnline", device); @@ -116,7 +116,7 @@ private static async Task StartAgent(int i) { try { - var currentInfo = await _deviceInfo.CreateDevice(device.ID, _organizationId); + var currentInfo = await _deviceInfo.CreateDevice(device.ID, _organizationId!); currentInfo.DeviceName = device.DeviceName; await hubConnection.SendAsync("DeviceHeartbeat", currentInfo); } @@ -130,7 +130,7 @@ private static async Task StartAgent(int i) Interlocked.Increment(ref _connectedCount); if (_connectedCount == _agentCount) { - Console.WriteLine($"Finished connecting all devices. Elapsed: {_stopwatch.Elapsed}"); + Console.WriteLine($"Finished connecting all devices. Elapsed: {_stopwatch!.Elapsed}"); } break; diff --git a/Tests/Server.Tests/AgentHubTests.cs b/Tests/Server.Tests/AgentHubTests.cs index 94929bae5..e5a7283bf 100644 --- a/Tests/Server.Tests/AgentHubTests.cs +++ b/Tests/Server.Tests/AgentHubTests.cs @@ -24,9 +24,8 @@ namespace Remotely.Tests; [TestClass] public class AgentHubTests { - private TestData _testData; - - public IDataService DataService { get; private set; } + private TestData _testData = null!; + private IDataService _dataService = null!; [TestMethod] [DoNotParallelize] @@ -42,10 +41,10 @@ public async Task DeviceCameOnline_BannedByName() var serviceSessionCache = new Mock(); var logger = new Mock>(); - appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Org1Device1.DeviceName }); + appConfig.Setup(x => x.BannedDevices).Returns(new string[] { $"{_testData.Org1Device1.DeviceName}" }); var hub = new AgentHub( - DataService, + _dataService, appConfig.Object, serviceSessionCache.Object, viewerHub.Object, @@ -82,7 +81,7 @@ public async Task DeviceCameOnline_BannedById() appConfig.Setup(x => x.BannedDevices).Returns(new string[] { _testData.Org1Device1.ID }); var hub = new AgentHub( - DataService, + _dataService, appConfig.Object, serviceSessionCache.Object, viewerHub.Object, @@ -111,18 +110,17 @@ public async Task TestInit() { _testData = new TestData(); await _testData.Init(); - DataService = IoCActivator.ServiceProvider.GetRequiredService(); + _dataService = IoCActivator.ServiceProvider.GetRequiredService(); } private class CallerContext : HubCallerContext { public override string ConnectionId => "test-id"; - public override string UserIdentifier => null; - - public override ClaimsPrincipal User => null; + public override string? UserIdentifier => null; + public override ClaimsPrincipal? User => null; - public override IDictionary Items { get; } = new Dictionary(); + public override IDictionary Items { get; } = new Dictionary(); public override IFeatureCollection Features { get; } = new FeatureCollection(); diff --git a/Tests/Server.Tests/CircuitConnectionTests.cs b/Tests/Server.Tests/CircuitConnectionTests.cs index b9da16089..37a104085 100644 --- a/Tests/Server.Tests/CircuitConnectionTests.cs +++ b/Tests/Server.Tests/CircuitConnectionTests.cs @@ -133,7 +133,7 @@ public async Task WakeDevice_GivenMatchingPeerByIp_UsesCorrectPeer() var addToGroupResult = _dataService.AddUserToDeviceGroup( _testData.Org1Id, _testData.Org1Group1.ID, - _testData.Org1User1.UserName, + $"{_testData.Org1User1.UserName}", out _); var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto()); @@ -199,7 +199,7 @@ public async Task WakeDevice_GivenMatchingPeerByGroupId_UsesCorrectPeer() var addToGroupResult = _dataService.AddUserToDeviceGroup( _testData.Org1Id, _testData.Org1Group1.ID, - _testData.Org1User1.UserName, + $"{_testData.Org1User1.UserName}", out _); var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto()); @@ -278,7 +278,7 @@ public async Task WakeDevice_GivenNoMatchingGroupOrIp_DoesNotSend() var addToGroupResult = _dataService.AddUserToDeviceGroup( _testData.Org1Id, _testData.Org1Group1.ID, - _testData.Org1User1.UserName, + $"{_testData.Org1User1.UserName}", out _); var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto()); @@ -346,7 +346,7 @@ public async Task WakeDevices_GivenPeerIpMatches_UsesCorrectPeer() var addToGroupResult = _dataService.AddUserToDeviceGroup( _testData.Org1Id, _testData.Org1Group1.ID, - _testData.Org1User1.UserName, + $"{_testData.Org1User1.UserName}", out _); var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto()); @@ -417,7 +417,7 @@ public async Task WakeDevices_GivenMatchingPeerByGroupId_UsesCorrectPeer() var addToGroupResult = _dataService.AddUserToDeviceGroup( _testData.Org1Id, _testData.Org1Group1.ID, - _testData.Org1User1.UserName, + $"{_testData.Org1User1.UserName}", out _); var updateResult = await _dataService.AddOrUpdateDevice(_testData.Org1Device1.ToDto()); diff --git a/Tests/Server.Tests/IoCActivator.cs b/Tests/Server.Tests/IoCActivator.cs index 19d050745..7c0e84f65 100644 --- a/Tests/Server.Tests/IoCActivator.cs +++ b/Tests/Server.Tests/IoCActivator.cs @@ -21,25 +21,25 @@ namespace Remotely.Tests; [TestClass] public class IoCActivator { - public static IServiceProvider ServiceProvider { get; set; } - private static IWebHostBuilder builder; + public static IServiceProvider ServiceProvider { get; set; } = null!; + private static IWebHostBuilder? _builder; public static void Activate() { - if (builder is null) + if (_builder is null) { - builder = WebHost.CreateDefaultBuilder() + _builder = WebHost.CreateDefaultBuilder() .UseStartup() .CaptureStartupErrors(true) .ConfigureAppConfiguration(config => { - config.AddInMemoryCollection(new Dictionary() + config.AddInMemoryCollection(new Dictionary() { ["ApplicationOptions:DBProvider"] = "InMemory" }); }); - builder.Build(); + _builder.Build(); } } diff --git a/Tests/Server.Tests/ScriptScheduleDispatcherTests.cs b/Tests/Server.Tests/ScriptScheduleDispatcherTests.cs index c55234fa2..be1d0f926 100644 --- a/Tests/Server.Tests/ScriptScheduleDispatcherTests.cs +++ b/Tests/Server.Tests/ScriptScheduleDispatcherTests.cs @@ -19,14 +19,14 @@ namespace Remotely.Tests; [TestClass] public class ScriptScheduleDispatcherTests { - private ScriptSchedule _schedule1; - private Mock _dataService; - private Mock _circuitConnection; - private Mock _serviceSessionCache; - private Mock> _logger; - private ScriptScheduleDispatcher _dispatcher; - private TestData _testData; - private SavedScript _savedScript; + private ScriptSchedule _schedule1 = null!; + private Mock _dataService = null!; + private Mock _circuitConnection = null!; + private Mock _serviceSessionCache = null!; + private Mock> _logger = null!; + private ScriptScheduleDispatcher _dispatcher = null!; + private TestData _testData = null!; + private SavedScript _savedScript = null!; [TestInitialize] public async Task Init() @@ -112,8 +112,8 @@ public async Task DispatchPendingScriptRuns_GivenSchedulesDue_CreatesScriptRuns( x.Contains(_schedule1.Devices.First().ID)))); _dataService.Verify(x => x.AddScriptRun(It.Is(x => x.ScheduleId == _schedule1.Id && - x.Devices.Exists(d => d.ID == _testData.Org1Device1.ID) && - x.Devices.Exists(d => d.ID == _testData.Org1Device2.ID)))); + x.Devices!.Exists(d => d.ID == _testData.Org1Device1.ID) && + x.Devices!.Exists(d => d.ID == _testData.Org1Device2.ID)))); _dataService.VerifyNoOtherCalls(); _circuitConnection.Verify(x => x.RunScript(