diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index de28124c76..b4d68b1139 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -39,6 +39,11 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly IRestartRegistration restartRegistration; + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + /// /// The for the /// @@ -109,18 +114,20 @@ namespace Tgstation.Server.Host.Components.Chat /// /// The value of /// The value of - /// The value of - /// The value of /// The value of /// The to populate with + /// The value of + /// The value of + /// The value of /// The used to populate - public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, ILoggerFactory loggerFactory, ILogger logger, IEnumerable initialChatBots) + public Chat(IProviderFactory providerFactory, IIOManager ioManager, ICommandFactory commandFactory, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory, ILogger logger, IEnumerable initialChatBots) { this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.commandFactory = commandFactory ?? throw new ArgumentNullException(nameof(commandFactory)); if (serverControl == null) throw new ArgumentNullException(nameof(serverControl)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.initialChatBots = initialChatBots?.ToList() ?? throw new ArgumentNullException(nameof(initialChatBots)); @@ -344,7 +351,7 @@ namespace Tgstation.Server.Host.Components.Chat if (messageTasks.Count == 0) { - await Task.Delay(1000, cancellationToken).ConfigureAwait(false); + await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); continue; } diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs index 28e1323dde..15d353e644 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatFactory.cs @@ -25,20 +25,27 @@ namespace Tgstation.Server.Host.Components.Chat /// readonly IServerControl serverControl; + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + /// /// Construct a /// - /// The value of /// The value of /// The value of - public ChatFactory(ILoggerFactory loggerFactory, IProviderFactory providerFactory, IServerControl serverControl) + /// The value of + /// The value of + public ChatFactory(IProviderFactory providerFactory, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory) { - this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.providerFactory = providerFactory ?? throw new ArgumentNullException(nameof(providerFactory)); this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); + this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } /// - public IChat CreateChat(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, serverControl, loggerFactory, loggerFactory.CreateLogger(), initialChatBots); + public IChat CreateChat(IIOManager ioManager, ICommandFactory commandFactory, IEnumerable initialChatBots) => new Chat(providerFactory, ioManager, commandFactory, serverControl, asyncDelayer, loggerFactory, loggerFactory.CreateLogger(), initialChatBots); } } diff --git a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs index aee9d43965..14df6b306c 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs @@ -11,24 +11,31 @@ namespace Tgstation.Server.Host.Components.Chat /// sealed class ProviderFactory : IProviderFactory { - /// - /// The for the - /// - readonly ILoggerFactory loggerFactory; - /// /// The for the /// readonly IApplication application; + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + + /// + /// The for the + /// + readonly ILoggerFactory loggerFactory; + /// /// Construct a /// - /// The value of /// The value of - public ProviderFactory(ILoggerFactory loggerFactory, IApplication application) + /// The value of + /// The value of + public ProviderFactory(IApplication application, IAsyncDelayer asyncDelayer, ILoggerFactory loggerFactory) { this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.application = application ?? throw new ArgumentNullException(nameof(application)); } @@ -44,7 +51,7 @@ namespace Tgstation.Server.Host.Components.Chat { case ChatProvider.Irc: var ircBuilder = (IrcConnectionStringBuilder)builder; - return new IrcProvider(loggerFactory.CreateLogger(), application, ircBuilder.Address, ircBuilder.Port.Value, ircBuilder.Nickname, ircBuilder.Password, ircBuilder.PasswordType, ircBuilder.UseSsl.Value); + return new IrcProvider(application, asyncDelayer, loggerFactory.CreateLogger(), ircBuilder.Address, ircBuilder.Port.Value, ircBuilder.Nickname, ircBuilder.Password, ircBuilder.PasswordType, ircBuilder.UseSsl.Value); case ChatProvider.Discord: var discordBuilder = (DiscordConnectionStringBuilder)builder; return new DiscordProvider(loggerFactory.CreateLogger(), discordBuilder.BotToken); diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index ea2ff03a1c..cd08c1760e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -25,6 +25,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public override string BotMention => client.Nickname; + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + /// /// The for the /// @@ -84,19 +89,21 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// Construct an /// - /// The value of logger /// The to get the from + /// The value of + /// The value of logger /// The value of /// The value of /// The value of /// The value of /// The value of /// If should be used - public IrcProvider(ILogger logger, IApplication application, string address, ushort port, string nickname, string password, IrcPasswordType? passwordType, bool useSsl) + public IrcProvider(IApplication application, IAsyncDelayer asyncDelayer, ILogger logger, string address, ushort port, string nickname, string password, IrcPasswordType? passwordType, bool useSsl) { - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); if (application == null) throw new ArgumentNullException(nameof(application)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.address = address ?? throw new ArgumentNullException(nameof(address)); this.port = port; @@ -267,13 +274,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers var startTime = DateTimeOffset.Now; var endTime = DateTimeOffset.Now.AddSeconds(TimeoutSeconds); cancellationToken.ThrowIfCancellationRequested(); - for (; !recievedAck && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult()) + + var listenTimeSpan = TimeSpan.FromMilliseconds(10); + for (; !recievedAck && DateTimeOffset.Now <= endTime; asyncDelayer.Delay(listenTimeSpan, cancellationToken).GetAwaiter().GetResult()) client.Listen(false); client.WriteLine("AUTHENTICATE PLAIN", Priority.Critical); cancellationToken.ThrowIfCancellationRequested(); - for (; !recievedPlus && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult()) + for (; !recievedPlus && DateTimeOffset.Now <= endTime; asyncDelayer.Delay(listenTimeSpan, cancellationToken).GetAwaiter().GetResult()) client.Listen(false); //Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196 diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index 7d087d1c08..b54cbc4d68 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -103,6 +103,11 @@ namespace Tgstation.Server.Host.Components /// readonly IGitHubClientFactory gitHubClientFactory; + /// + /// The for the + /// + readonly IPlatformIdentifier platformIdentifier; + /// /// Construct an /// @@ -123,7 +128,8 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of - public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider, INetworkPromptReaper networkPromptReaper, IGitHubClientFactory gitHubClientFactory) + /// The value of + public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider, INetworkPromptReaper networkPromptReaper, IGitHubClientFactory gitHubClientFactory, IPlatformIdentifier platformIdentifier) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -142,6 +148,7 @@ namespace Tgstation.Server.Host.Components this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider)); this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper)); this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory)); + this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); } /// @@ -156,7 +163,7 @@ namespace Tgstation.Server.Host.Components var gameIoManager = new ResolvingIOManager(instanceIoManager, "Game"); var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration"); - var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, loggerFactory.CreateLogger()); + var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, platformIdentifier, loggerFactory.CreateLogger()); var eventConsumer = new EventConsumer(configuration); var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger(), metadata.CloneMetadata()); @@ -172,7 +179,7 @@ namespace Tgstation.Server.Host.Components var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings); try { - var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, loggerFactory, metadata.CloneMetadata()); + var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, platformIdentifier, loggerFactory, metadata.CloneMetadata()); var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger(), metadata.CloneMetadata()); var watchdog = watchdogFactory.CreateWatchdog(chat, dmbFactory, reattachInfoHandler, configuration, sessionControllerFactory, metadata.CloneMetadata(), metadata.DreamDaemonSettings); eventConsumer.SetWatchdog(watchdog); diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index dcf009a8f6..bb19579a21 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -38,8 +38,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles { EventType.RepoPreSynchronize, "PreSynchronize" } }; - static readonly string SystemScriptFileExtension = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "bat" : "sh"; - /// /// The for /// @@ -65,6 +63,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// readonly IPostWriteHandler postWriteHandler; + /// + /// The for + /// + readonly IPlatformIdentifier platformIdentifier; + /// /// The for /// @@ -83,14 +86,16 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// The value of /// The value of /// The value of + /// The value of /// The value of - public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, ILogger logger) + public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IPlatformIdentifier platformIdentifier, ILogger logger) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager)); this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory)); this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler)); + this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); semaphore = new SemaphoreSlim(1); @@ -312,7 +317,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles var fileName = ioManager.GetFileName(x); bool ignored; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (platformIdentifier.IsWindows) //need to normalize ignored = ignoreFiles.Any(y => fileName.ToUpperInvariant() == y.ToUpperInvariant()); else @@ -438,7 +443,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles //always execute in serial using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) { - var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, SystemScriptFileExtension, cancellationToken).ConfigureAwait(false); + var files = await ioManager.GetFilesWithExtension(EventScriptsSubdirectory, platformIdentifier.ScriptFileExtension, cancellationToken).ConfigureAwait(false); var resolvedScriptsDir = ioManager.ResolvePath(EventScriptsSubdirectory); foreach (var I in files.Select(x => ioManager.GetFileName(x)).Where(x => x.StartsWith(scriptName, StringComparison.Ordinal))) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index 63ee94c543..4715f6443e 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -63,6 +63,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly INetworkPromptReaper networkPromptReaper; + /// + /// The for the + /// + readonly IPlatformIdentifier platformIdentifier; + /// /// The for the /// @@ -105,8 +110,9 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of + /// The value of /// The value of - public SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, INetworkPromptReaper networkPromptReaper, ILoggerFactory loggerFactory, Api.Models.Instance instance) + public SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, INetworkPromptReaper networkPromptReaper, IPlatformIdentifier platformIdentifier, ILoggerFactory loggerFactory, Api.Models.Instance instance) { this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor)); this.byond = byond ?? throw new ArgumentNullException(nameof(byond)); @@ -117,6 +123,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper)); + this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } @@ -211,7 +218,7 @@ namespace Tgstation.Server.Host.Components.Watchdog parameters); //See #719 - var noShellExecute = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var noShellExecute = !platformIdentifier.IsWindows; //launch dd var process = processExecutor.LaunchProcess(byondLock.DreamDaemonPath, basePath, arguments, noShellExecute: noShellExecute); try diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs index b79b9f5363..0302b24fce 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs @@ -60,11 +60,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IDmbFactory dmbFactory; - /// - /// The for the - /// - readonly ILogger logger; - /// /// The for the /// @@ -95,6 +90,16 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IRestartRegistration restartRegistration; + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + + /// + /// The for the + /// + readonly ILogger logger; + /// /// The for the /// @@ -145,27 +150,29 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - /// The value of /// The value of /// The value of /// The value of /// The value of /// The value of /// The to populate with + /// The value of + /// The value of /// The initial value of . May be modified /// The value of /// The value of - public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, ILogger logger, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) + public Watchdog(IChat chat, ISessionControllerFactory sessionControllerFactory, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IEventConsumer eventConsumer, IJobManager jobManager, IServerControl serverControl, IAsyncDelayer asyncDelayer, ILogger logger, DreamDaemonLaunchParameters initialLaunchParameters, Api.Models.Instance instance, bool autoStart) { this.chat = chat ?? throw new ArgumentNullException(nameof(chat)); this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory)); this.dmbFactory = dmbFactory ?? throw new ArgumentNullException(nameof(dmbFactory)); - this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); this.reattachInfoHandler = reattachInfoHandler ?? throw new ArgumentNullException(nameof(reattachInfoHandler)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); + this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); ActiveLaunchParameters = initialLaunchParameters ?? throw new ArgumentNullException(nameof(initialLaunchParameters)); this.instance = instance ?? throw new ArgumentNullException(nameof(instance)); this.autoStart = autoStart; @@ -635,7 +642,7 @@ namespace Tgstation.Server.Host.Components.Watchdog logger.LogWarning("Failed to automatically restart the watchdog! Attempt: {0}, Exception: {1}", retryAttempts, launchException); var retryDelay = Math.Min(Math.Pow(2, retryAttempts), 3600); //max of one hour, increasing by a power of 2 each time chatTask = chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Failed to restart watchdog (Attempt: {0}), retrying in {1} seconds...", retryAttempts, retryDelay), cancellationToken); - await Task.WhenAll(Task.Delay((int)retryDelay, cancellationToken), chatTask).ConfigureAwait(false); + await Task.WhenAll(asyncDelayer.Delay(TimeSpan.FromSeconds(retryDelay), cancellationToken), chatTask).ConfigureAwait(false); } } } @@ -751,7 +758,7 @@ namespace Tgstation.Server.Host.Components.Watchdog //definitely not if reattaching though if (reattachInfo == null && delay.TotalSeconds < AlphaBravoStartupSeperationInterval) - await Task.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false); + await asyncDelayer.Delay(startTime.AddSeconds(AlphaBravoStartupSeperationInterval) - now, cancellationToken).ConfigureAwait(false); //now bring bravo up if (!doesntNeedNewDmb) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs index b2d33d838d..1c03523ee7 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogFactory.cs @@ -36,6 +36,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IJobManager jobManager; + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + /// /// Construct a /// @@ -44,16 +49,18 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of - public WatchdogFactory(IServerControl serverControl, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager) + /// The value of + public WatchdogFactory(IServerControl serverControl, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager, IAsyncDelayer asyncDelayer) { this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender)); this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); } /// - public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, loggerFactory.CreateLogger(), reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, settings, instance, settings.AutoStart.Value); + public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings) => new Watchdog(chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, asyncDelayer, loggerFactory.CreateLogger(), settings, instance, settings.AutoStart.Value); } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs index ef11168489..719eb9f4f8 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsNetworkPromptReaper.cs @@ -25,6 +25,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// const int RecheckDelayMs = 250; + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + /// /// The for the /// @@ -78,9 +83,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// Construct a /// + /// The value of /// The value of - public WindowsNetworkPromptReaper(ILogger logger) + public WindowsNetworkPromptReaper(IAsyncDelayer asyncDelayer, ILogger logger) { + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); registeredProcesses = new List(); @@ -97,7 +104,7 @@ namespace Tgstation.Server.Host.Components.Watchdog { while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(TimeSpan.FromMilliseconds(RecheckDelayMs), cancellationToken).ConfigureAwait(false); + await asyncDelayer.Delay(TimeSpan.FromMilliseconds(RecheckDelayMs), cancellationToken).ConfigureAwait(false); IntPtr window; int processId; diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs index a7ed6593d4..0899ab7db5 100644 --- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs +++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs @@ -52,6 +52,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly IIOManager ioManager; + /// + /// The for the + /// + readonly IPlatformIdentifier platformIdentifier; + /// /// The for the /// @@ -71,15 +76,17 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of + /// The value of /// The for the /// The containing value of /// The containing value of - public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClientFactory gitHubClientFactory, IServerControl serverUpdater, IApplication application, IIOManager ioManager, ILogger logger, IOptions updatesConfigurationOptions, IOptions generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false) + public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClientFactory gitHubClientFactory, IServerControl serverUpdater, IApplication application, IIOManager ioManager, IPlatformIdentifier platformIdentifier, ILogger logger, IOptions updatesConfigurationOptions, IOptions generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false) { this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory)); this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); + this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); updatesConfiguration = updatesConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(updatesConfigurationOptions)); generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } @@ -123,7 +130,7 @@ namespace Tgstation.Server.Host.Controllers { LatestVersion = greatestVersion, TrackedRepositoryUrl = repoUrl, - WindowsHost = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + WindowsHost = platformIdentifier.IsWindows }); } catch (RateLimitExceededException e) diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index 5d6515dc7b..5db9cd5897 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -40,10 +40,12 @@ namespace Tgstation.Server.Host.Controllers /// The for the /// readonly IJobManager jobManager; + /// /// The for the /// readonly IInstanceManager instanceManager; + /// /// The for the /// @@ -54,6 +56,11 @@ namespace Tgstation.Server.Host.Controllers /// readonly IApplication application; + /// + /// The for the + /// + readonly IPlatformIdentifier platformIdentifier; + /// /// Construct a /// @@ -63,13 +70,15 @@ namespace Tgstation.Server.Host.Controllers /// The value of /// The value of /// The value of + /// The value of /// The for the - public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager, IApplication application, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, false) + public InstanceController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IJobManager jobManager, IInstanceManager instanceManager, IIOManager ioManager, IApplication application, IPlatformIdentifier platformIdentifier, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, false) { this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.application = application ?? throw new ArgumentNullException(nameof(application)); + this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); } void NormalizeModelPath(Api.Models.Instance model, out string absolutePath) @@ -80,7 +89,7 @@ namespace Tgstation.Server.Host.Controllers return; } absolutePath = ioManager.ResolvePath(model.Path); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (platformIdentifier.IsWindows) model.Path = absolutePath.ToUpperInvariant(); else model.Path = absolutePath; diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 1288324842..05b5234e0c 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -60,6 +60,11 @@ namespace Tgstation.Server.Host.Core /// readonly TaskCompletionSource startupTcs; + /// + /// The for the + /// + ITokenFactory tokenFactory; + /// /// Construct an /// @@ -97,21 +102,19 @@ namespace Tgstation.Server.Host.Core //enable options which give us config reloading services.AddOptions(); - //setup stuff for setup wizard + //other stuff needed for for setup wizard and configuration services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - - //needed here for JWT configuration - //we use a manually instatiated token factory to prevent it from regenerating the signing key after we configure it - services.AddSingleton(new TokenFactory()); + services.AddSingleton(); + services.AddSingleton(); GeneralConfiguration generalConfiguration; DatabaseConfiguration databaseConfiguration; FileLoggingConfiguration fileLoggingConfiguration; - ITokenFactory tokenFactory; IIOManager ioManager; + IPlatformIdentifier platformIdentifier; //temporarily build the service provider in it's current state //do it here so we can run the setup wizard if necessary @@ -138,8 +141,8 @@ namespace Tgstation.Server.Host.Core var loggingOptions = provider.GetRequiredService>(); fileLoggingConfiguration = loggingOptions.Value; - tokenFactory = provider.GetRequiredService(); ioManager = provider.GetRequiredService(); + platformIdentifier = provider.GetRequiredService(); } //setup file logging via serilog @@ -195,6 +198,8 @@ namespace Tgstation.Server.Host.Core //configure bearer token validation services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions => { + //this line isn't actually run until the first request is made + //at that point tokenFactory will be populated jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters; jwtBearerOptions.Events = new JwtBearerEvents { @@ -251,10 +256,11 @@ namespace Tgstation.Server.Host.Core services.AddScoped(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton, PasswordHasher>(); //configure platform specific services - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (platformIdentifier.IsWindows) { services.AddSingleton(); services.AddSingleton(); @@ -303,17 +309,21 @@ namespace Tgstation.Server.Host.Core /// Configure the /// /// The to configure + /// The for the + /// The value of /// The for the - /// The for the application - public void Configure(IApplicationBuilder applicationBuilder, ILogger logger, IServerControl serverControl) + public void Configure(IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, ILogger logger) { if (applicationBuilder == null) throw new ArgumentNullException(nameof(applicationBuilder)); - if (logger == null) - throw new ArgumentNullException(nameof(logger)); if (serverControl == null) throw new ArgumentNullException(nameof(serverControl)); + this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); + + if (logger == null) + throw new ArgumentNullException(nameof(logger)); + logger.LogInformation(VersionString); //attempt to restart the server if the configuration changes diff --git a/src/Tgstation.Server.Host/Core/AsyncDelayer.cs b/src/Tgstation.Server.Host/Core/AsyncDelayer.cs new file mode 100644 index 0000000000..1e874366d3 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/AsyncDelayer.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class AsyncDelayer : IAsyncDelayer + { + /// + public Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken) => Task.Delay(timeSpan, cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs b/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs new file mode 100644 index 0000000000..ae0da1284d --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IAsyncDelayer.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + /// For waiting asynchronously + /// + interface IAsyncDelayer + { + /// + /// Create a that completes after a given + /// + /// The that must elapse + /// The for the operation + /// A representing the running operation + Task Delay(TimeSpan timeSpan, CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Core/IPlatformIdentifier.cs b/src/Tgstation.Server.Host/Core/IPlatformIdentifier.cs new file mode 100644 index 0000000000..300ca29866 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IPlatformIdentifier.cs @@ -0,0 +1,18 @@ +namespace Tgstation.Server.Host.Core +{ + /// + /// For identifying the current platform + /// + public interface IPlatformIdentifier + { + /// + /// If the current platform is a Windows platform + /// + bool IsWindows { get; } + + /// + /// The extension of executable script files for the system + /// + string ScriptFileExtension { get; } + } +} diff --git a/src/Tgstation.Server.Host/Core/PlatformIdentifier.cs b/src/Tgstation.Server.Host/Core/PlatformIdentifier.cs new file mode 100644 index 0000000000..df59c83356 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/PlatformIdentifier.cs @@ -0,0 +1,23 @@ +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class PlatformIdentifier : IPlatformIdentifier + { + /// + public bool IsWindows { get; } + + /// + public string ScriptFileExtension { get; } + + /// + /// Construct a + /// + public PlatformIdentifier() + { + IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + ScriptFileExtension = IsWindows ? "bat" : "sh"; + } + } +} diff --git a/src/Tgstation.Server.Host/Core/SetupWizard.cs b/src/Tgstation.Server.Host/Core/SetupWizard.cs index 1d3cb0bd65..83b6456fe3 100644 --- a/src/Tgstation.Server.Host/Core/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Core/SetupWizard.cs @@ -45,6 +45,16 @@ namespace Tgstation.Server.Host.Core /// readonly IDBConnectionFactory dbConnectionFactory; + /// + /// The for the + /// + readonly IPlatformIdentifier platformIdentifier; + + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + /// /// The for the /// @@ -63,15 +73,19 @@ namespace Tgstation.Server.Host.Core /// The value of /// The value of /// The value of + /// The value of + /// The value of /// The value of /// The containing the value of - public SetupWizard(IIOManager ioManager, IConsole console, IHostingEnvironment hostingEnvironment, IApplication application, IDBConnectionFactory dbConnectionFactory, ILogger logger, IOptions generalConfigurationOptions) + public SetupWizard(IIOManager ioManager, IConsole console, IHostingEnvironment hostingEnvironment, IApplication application, IDBConnectionFactory dbConnectionFactory, IPlatformIdentifier platformIdentifier, IAsyncDelayer asyncDelayer, ILogger logger, IOptions generalConfigurationOptions) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.console = console ?? throw new ArgumentNullException(nameof(console)); this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); this.application = application ?? throw new ArgumentNullException(nameof(application)); this.dbConnectionFactory = dbConnectionFactory ?? throw new ArgumentNullException(nameof(dbConnectionFactory)); + this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); } @@ -170,7 +184,7 @@ namespace Tgstation.Server.Host.Core var dbExists = await PromptYesNo("Does this database already exist? (y/n): ", cancellationToken).ConfigureAwait(false); bool useWinAuth; - if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer && platformIdentifier.IsWindows) useWinAuth = await PromptYesNo("Use Windows Authentication? (y/n): ", cancellationToken).ConfigureAwait(false); else useWinAuth = false; @@ -494,7 +508,7 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync("Waiting for configuration changes to reload...", true, cancellationToken).ConfigureAwait(false); //we need to wait for the configuration's file system watcher to read and reload the changes - await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false); + await asyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false); } /// @@ -577,7 +591,7 @@ namespace Tgstation.Server.Host.Core } //flush the logs to prevent console conflicts - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); + await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false); return true; diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs index b702fc0475..4cdd6be43a 100644 --- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -9,18 +9,9 @@ namespace Tgstation.Server.Host.Security sealed class CryptographySuite : ICryptographySuite { /// - /// Generates a secure set of s + /// Length in s of generated base64 secure string /// - /// A secure set of s - public static byte[] GetSecureBytes(int amount) - { - using (var rng = new RNGCryptoServiceProvider()) - { - var byt = new byte[amount]; - rng.GetBytes(byt); - return byt; - } - } + const uint SecureStringLength = 30; /// /// The for the @@ -31,14 +22,28 @@ namespace Tgstation.Server.Host.Security /// Construct a /// /// The value of - public CryptographySuite(IPasswordHasher passwordHasher) => this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); + public CryptographySuite(IPasswordHasher passwordHasher) + { + this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher)); + } + + /// + public byte[] GetSecureBytes(uint amount) + { + using (var rng = new RNGCryptoServiceProvider()) + { + var byt = new byte[amount]; + rng.GetBytes(byt); + return byt; + } + } /// public void SetUserPassword(User user, string newPassword, bool newUser) { if (user == null) throw new ArgumentNullException(nameof(user)); - if (String.IsNullOrEmpty(newPassword)) + if (newPassword == null) throw new ArgumentNullException(nameof(newPassword)); user.PasswordHash = passwordHasher.HashPassword(user, newPassword); if (!newUser) @@ -53,14 +58,13 @@ namespace Tgstation.Server.Host.Security case PasswordVerificationResult.Failed: return false; case PasswordVerificationResult.SuccessRehashNeeded: - user.PasswordHash = passwordHasher.HashPassword(user, password); - //don't update LastPasswordUpdate since it hasn't actually changed + SetUserPassword(user, password, false); break; } return true; } /// - public string GetSecureString() => Convert.ToBase64String(GetSecureBytes(30)); + public string GetSecureString() => Convert.ToBase64String(GetSecureBytes(SecureStringLength)); } } diff --git a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs index 87e64f7978..540fb60e9f 100644 --- a/src/Tgstation.Server.Host/Security/ICryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/ICryptographySuite.cs @@ -7,6 +7,13 @@ namespace Tgstation.Server.Host.Security /// public interface ICryptographySuite { + /// + /// Generates a secure set of s + /// + /// The amount of s to generate + /// A secure set of s + byte[] GetSecureBytes(uint amount); + /// /// Sets a for a given /// diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs index cb50e7ccb5..43a2b84302 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -9,6 +10,11 @@ namespace Tgstation.Server.Host.Security /// sealed class IdentityCache : IIdentityCache, IDisposable { + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + /// /// The for the /// @@ -22,8 +28,11 @@ namespace Tgstation.Server.Host.Security /// /// Construct an /// - public IdentityCache(ILogger logger) + /// The value of + /// The value of + public IdentityCache(IAsyncDelayer asyncDelayer, ILogger logger) { + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.logger = logger ?? throw new ArgumentNullException(nameof(logger)); cachedIdentities = new Dictionary(); @@ -54,7 +63,7 @@ namespace Tgstation.Server.Host.Security logger.LogTrace("Expiring previously cached identity..."); identCache.Dispose(); //also clears it out } - identCache = new IdentityCacheObject(systemIdentity.Clone(), () => + identCache = new IdentityCacheObject(systemIdentity.Clone(), asyncDelayer, () => { logger.LogDebug("Expiring system identity cache for user {1}", uid, user.Id); lock (cachedIdentities) diff --git a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs index 02ea54ed04..9b54996f93 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCacheObject.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Security { @@ -28,11 +29,16 @@ namespace Tgstation.Server.Host.Security /// Construct an /// /// The value of + /// The used to delay the expiry /// The to take on expiry /// The - public IdentityCacheObject(ISystemIdentity systemIdentity, Action onExpiry, DateTimeOffset expiry) + public IdentityCacheObject(ISystemIdentity systemIdentity, IAsyncDelayer asyncDelayer, Action onExpiry, DateTimeOffset expiry) { SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity)); + + if (asyncDelayer == null) + throw new ArgumentNullException(nameof(asyncDelayer)); + if (onExpiry == null) throw new ArgumentNullException(nameof(onExpiry)); var now = DateTimeOffset.Now; @@ -46,7 +52,7 @@ namespace Tgstation.Server.Host.Security using (SystemIdentity) try { - await Task.Delay(expiry - now, cancellationToken).ConfigureAwait(false); + await asyncDelayer.Delay(expiry - now, cancellationToken).ConfigureAwait(false); } finally { diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 8fa34381ff..661f52f91a 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -7,6 +7,7 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Core; namespace Tgstation.Server.Host.Security { @@ -16,20 +17,37 @@ namespace Tgstation.Server.Host.Security /// /// Amount of minutes until generated s expire /// - const int TokenExpiryMinutes = 15; + const uint TokenExpiryMinutes = 15; + + /// + /// Amount of minutes to skew the clock for validation + /// + const uint TokenClockSkewMinutes = 1; + + /// + /// Amount of bytes to use in the + /// + const uint TokenSigningKeyByteAmount = 256; /// public TokenValidationParameters ValidationParameters { get; } + /// + /// The for the + /// + readonly IAsyncDelayer asyncDelayer; + /// /// Construct a /// - public TokenFactory() + /// The value of + /// The used for generating the + public TokenFactory(IAsyncDelayer asyncDelayer, ICryptographySuite cryptographySuite) { ValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(CryptographySuite.GetSecureBytes(256)), + IssuerSigningKey = new SymmetricSecurityKey(cryptographySuite.GetSecureBytes(TokenSigningKeyByteAmount)), ValidateIssuer = true, ValidIssuer = Assembly.GetExecutingAssembly().GetName().Name, @@ -38,12 +56,14 @@ namespace Tgstation.Server.Host.Security ValidateAudience = true, ValidAudience = typeof(Token).Assembly.GetName().Name, - ClockSkew = TimeSpan.FromMinutes(1), + ClockSkew = TimeSpan.FromMinutes(TokenClockSkewMinutes), RequireSignedTokens = true, RequireExpirationTime = true }; + + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); } /// @@ -56,9 +76,13 @@ namespace Tgstation.Server.Host.Security var nowUnix = now.ToUnixTimeSeconds(); //this prevents validation conflicts down the line + //tldr we can (theoretically) send a token the same second we receive it + //since unix time rounds down, it looks like it came from before the user changed their password + //this happens occasionally in unit tests + //just delay a second so we can force a round up var lpuUnix = user.LastPasswordUpdate?.ToUnixTimeSeconds(); if (nowUnix == lpuUnix) - await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); + await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); var expiry = now.AddMinutes(TokenExpiryMinutes); var claims = new Claim[] diff --git a/tests/Tgstation.Server.Host.Tests/Components/Watchdog/TestWatchdog.cs b/tests/Tgstation.Server.Host.Tests/Components/Watchdog/TestWatchdog.cs index 3326386343..ff95faf9f4 100644 --- a/tests/Tgstation.Server.Host.Tests/Components/Watchdog/TestWatchdog.cs +++ b/tests/Tgstation.Server.Host.Tests/Components/Watchdog/TestWatchdog.cs @@ -19,47 +19,50 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests [TestMethod] public void TestConstruction() { - Assert.ThrowsException(() => new Watchdog(null, null, null, null, null, null, null, null, null, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(null, null, null, null, null, null, null, null, null, null, null, null, null, default)); var mockChat = new Mock(); mockChat.Setup(x => x.RegisterCommandHandler(It.IsNotNull())).Verifiable(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, null, null, null, null, null, null, null, null, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, null, null, null, null, null, null, null, null, null, null, null, null, default)); var mockSessionControllerFactory = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, null, null, null, null, null, null, null, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, null, null, null, null, null, null, null, null, null, null, null, default)); var mockDmbFactory = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, null, null, null, null, null, null, null, null, null, null, default)); - - var mockLogger = new Mock>(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, null, null, null, null, null, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, null, null, null, null, null, null, null, null, null, null, default)); var mockReattachInfoHandler = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, null, null, null, null, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, null, null, null, null, null, null, null, null, null, default)); var mockDatabaseContextFactory = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, null, null, null, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, null, null, null, null, null, null, null, null, default)); var mockByondTopicSender = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, null, null, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, null, null, null, null, null, null, null, default)); var mockEventConsumer = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, null, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, null, null, null, null, null, null, default)); var mockJobManager = new Mock(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, null, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, null, null, null, null, null, default)); var mockRestartRegistration = new Mock(); mockRestartRegistration.Setup(x => x.Dispose()).Verifiable(); var mockServerControl = new Mock(); mockServerControl.Setup(x => x.RegisterForRestart(It.IsNotNull())).Returns(mockRestartRegistration.Object).Verifiable(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, null, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, null, null, null, null, default)); + + var mockAsyncDelayer = new Mock(); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, null, null, null, default)); + + var mockLogger = new Mock>(); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, null, null, default)); var mockLaunchParameters = new DreamDaemonLaunchParameters(); - Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockLaunchParameters, null, default)); + Assert.ThrowsException(() => new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, null, default)); var mockInstance = new Models.Instance(); - new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockLaunchParameters, mockInstance, default).Dispose(); + new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, mockInstance, default).Dispose(); mockRestartRegistration.VerifyAll(); mockServerControl.VerifyAll(); @@ -85,8 +88,9 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests mockServerControl.Setup(x => x.RegisterForRestart(It.IsNotNull())).Returns(mockRestartRegistration.Object).Verifiable(); var mockLaunchParameters = new DreamDaemonLaunchParameters(); var mockInstance = new Models.Instance(); + var mockAsyncDelayer = new Mock(); - using (var wd = new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockLogger.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockLaunchParameters, mockInstance, default)) + using (var wd = new Watchdog(mockChat.Object, mockSessionControllerFactory.Object, mockDmbFactory.Object, mockReattachInfoHandler.Object, mockDatabaseContextFactory.Object, mockByondTopicSender.Object, mockEventConsumer.Object, mockJobManager.Object, mockServerControl.Object, mockAsyncDelayer.Object, mockLogger.Object, mockLaunchParameters, mockInstance, default)) using (var cts = new CancellationTokenSource()) { var mockCompileJob = new Models.CompileJob(); @@ -115,6 +119,7 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests sessionsToVerify.Add(mockSession); return Task.FromResult(mockSession.Object); }).Verifiable(); + mockAsyncDelayer.Setup(x => x.Delay(It.IsAny(), cancellationToken)).Returns(Task.CompletedTask).Verifiable(); cts.CancelAfter(TimeSpan.FromSeconds(15)); @@ -138,6 +143,7 @@ namespace Tgstation.Server.Host.Components.Watchdog.Tests mockRestartRegistration.VerifyAll(); mockServerControl.VerifyAll(); mockChat.VerifyAll(); + mockAsyncDelayer.VerifyAll(); } } } diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs index f3747f0fe7..191b5d05b4 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -11,6 +10,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; +using Tgstation.Server.Host.Security; namespace Tgstation.Server.Host.Core.Tests { @@ -32,13 +32,16 @@ namespace Tgstation.Server.Host.Core.Tests var app = new Application(mockConfiguration.Object, mockHostingEnvironment.Object); Assert.ThrowsException(() => app.ConfigureServices(null)); - Assert.ThrowsException(() => app.Configure(null, null, null)); + Assert.ThrowsException(() => app.Configure(null, null, null, null)); var mockAppBuilder = new Mock(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, null, null)); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, null, null, null)); - var mockLogger = new Mock>(); - Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockLogger.Object, null)); + var mockServerControl = new Mock(); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, null, null)); + + var mockTokenFactory = new Mock(); + Assert.ThrowsException(() => app.Configure(mockAppBuilder.Object, mockServerControl.Object, mockTokenFactory.Object, null)); } class MockSetupWizard : ISetupWizard diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestAsyncDelayer.cs b/tests/Tgstation.Server.Host.Tests/Core/TestAsyncDelayer.cs new file mode 100644 index 0000000000..c4927e3dee --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Core/TestAsyncDelayer.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core.Tests +{ + [TestClass] + public sealed class TestAsyncDelayer + { + [TestMethod] + public async Task TestDelay() + { + var delayer = new AsyncDelayer(); + var startDelay = delayer.Delay(TimeSpan.FromSeconds(1), default); + var checkDelay = Task.Delay(TimeSpan.FromSeconds(1) - TimeSpan.FromMilliseconds(10), default); + await startDelay.ConfigureAwait(false); + Assert.IsTrue(checkDelay.IsCompleted); + } + + [TestMethod] + public async Task TestCancel() + { + var delayer = new AsyncDelayer(); + using (var cts = new CancellationTokenSource()) + { + cts.Cancel(); + await Assert.ThrowsExceptionAsync(() => delayer.Delay(TimeSpan.FromSeconds(1), cts.Token)).ConfigureAwait(false); + } + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestPlatformIdentifier.cs b/tests/Tgstation.Server.Host.Tests/Core/TestPlatformIdentifier.cs new file mode 100644 index 0000000000..26d7459ed6 --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Core/TestPlatformIdentifier.cs @@ -0,0 +1,24 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Runtime.InteropServices; + +namespace Tgstation.Server.Host.Core.Tests +{ + [TestClass] + public sealed class TestPlatformIdentifier + { + [TestMethod] + public void TestCorrectPlatform() + { + var identifier = new PlatformIdentifier(); + + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + const string WindowsScriptExtension = "bat"; + const string PosixScriptExtension = "sh"; + + + Assert.AreEqual(isWindows, identifier.IsWindows); + + Assert.AreEqual(isWindows ? WindowsScriptExtension : PosixScriptExtension, identifier.ScriptFileExtension); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs b/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs index 2aaed750da..7824a93c27 100644 --- a/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs +++ b/tests/Tgstation.Server.Host.Tests/Core/TestSetupWizard.cs @@ -22,19 +22,23 @@ namespace Tgstation.Server.Host.Core.Tests [TestMethod] public void TestConstructionThrows() { - Assert.ThrowsException(() => new SetupWizard(null, null, null, null, null, null, null)); + Assert.ThrowsException(() => new SetupWizard(null, null, null, null, null, null, null, null, null)); var mockIOManager = new Mock(); - Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, null, null, null, null, null, null)); + Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, null, null, null, null, null, null, null, null)); var mockConsole = new Mock(); - Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, null, null, null, null, null)); + Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, null, null, null, null, null, null, null)); var mockHostingEnvironment = new Mock(); - Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, null, null, null, null)); + Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, null, null, null, null, null, null)); var mockApplication = new Mock(); - Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, null, null, null)); + Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, null, null, null, null, null)); var mockDBConnectionFactory = new Mock(); - Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, null, null)); + Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, null, null, null, null)); + var mockPlatformIdentifier = new Mock(); + Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, null, null, null)); + var mockAsyncDelayer = new Mock(); + Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, mockAsyncDelayer.Object, null, null)); var mockLogger = new Mock>(); - Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockLogger.Object, null)); + Assert.ThrowsException(() => new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, mockAsyncDelayer.Object, mockLogger.Object, null)); } [TestMethod] @@ -47,6 +51,8 @@ namespace Tgstation.Server.Host.Core.Tests var mockDBConnectionFactory = new Mock(); var mockLogger = new Mock>(); var mockGeneralConfigurationOptions = new Mock>(); + var mockPlatformIdentifier = new Mock(); + var mockAsyncDelayer = new Mock(); var testGeneralConfig = new GeneralConfiguration { @@ -54,7 +60,10 @@ namespace Tgstation.Server.Host.Core.Tests }; mockGeneralConfigurationOptions.SetupGet(x => x.Value).Returns(testGeneralConfig).Verifiable(); - var wizard = new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockLogger.Object, mockGeneralConfigurationOptions.Object); + var wizard = new SetupWizard(mockIOManager.Object, mockConsole.Object, mockHostingEnvironment.Object, mockApplication.Object, mockDBConnectionFactory.Object, mockPlatformIdentifier.Object, mockAsyncDelayer.Object, mockLogger.Object, mockGeneralConfigurationOptions.Object); + + mockPlatformIdentifier.SetupGet(x => x.IsWindows).Returns(true).Verifiable(); + mockAsyncDelayer.Setup(x => x.Delay(It.IsAny(), It.IsAny())).Returns(Task.CompletedTask).Verifiable(); Assert.IsFalse(await wizard.CheckRunWizard(default).ConfigureAwait(false)); @@ -116,18 +125,8 @@ namespace Tgstation.Server.Host.Core.Tests "this isn't validated", "nor is this", "no", - }; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) //test winauth - finalInputSequence.Add("yes"); - else - finalInputSequence.AddRange(new List - { - "username", - "password" - }); - finalInputSequence.AddRange(new List - { + "yes", //sql server will always fail so reconfigure with maria nameof(DatabaseType.MariaDB), "bleh", @@ -183,7 +182,7 @@ namespace Tgstation.Server.Host.Core.Tests "fake", "None", "Critical" - }); + }; var inputPos = 0; @@ -234,6 +233,8 @@ namespace Tgstation.Server.Host.Core.Tests mockUglyDbConnection.VerifyAll(); mockDBConnectionFactory.VerifyAll(); mockApplication.VerifyAll(); + mockPlatformIdentifier.VerifyAll(); + mockAsyncDelayer.VerifyAll(); } } }