diff --git a/build/analyzers.ruleset b/build/analyzers.ruleset index 79627376f3..88adfbf857 100644 --- a/build/analyzers.ruleset +++ b/build/analyzers.ruleset @@ -16,7 +16,6 @@ - @@ -744,10 +743,10 @@ - - - - + + + + @@ -968,7 +967,6 @@ - @@ -1028,168 +1026,29 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs index a1a3535225..403b1bfa6a 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondExecutableLock.cs @@ -27,8 +27,8 @@ namespace Tgstation.Server.Host.Components.Byond DreamMakerPath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath)); } - //at one point in design, byond versions were to delete themselves if they werent the active version - //That changed at some point so these functions are intentioanlly left blank + // at one point in design, byond versions were to delete themselves if they weren't the active version + // That changed at some point so these functions are intentioanlly left blank /// public void Dispose() { } diff --git a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs index 8c597e7d23..73c458f7f0 100644 --- a/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs +++ b/src/Tgstation.Server.Host/Components/Byond/ByondManager.cs @@ -24,6 +24,7 @@ namespace Tgstation.Server.Host.Components.Byond /// The file in which we store the for installations /// const string VersionFileName = "Version.txt"; + /// /// The file in which we store the for the active installation /// @@ -119,6 +120,7 @@ namespace Tgstation.Server.Host.Components.Byond if (!installed) installedVersions.Add(versionKey, ourTcs.Task); } + if (installed) using (cancellationToken.Register(() => ourTcs.SetCanceled())) { @@ -126,7 +128,8 @@ namespace Tgstation.Server.Host.Components.Byond cancellationToken.ThrowIfCancellationRequested(); return; } - //okay up to us to install it then + + // okay up to us to install it then try { await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List { versionKey }, cancellationToken).ConfigureAwait(false); @@ -141,12 +144,12 @@ namespace Tgstation.Server.Host.Components.Byond await ioManager.ZipToDirectory(versionKey, download, cancellationToken).ConfigureAwait(false); await byondInstaller.InstallByond(ioManager.ResolvePath(versionKey), version, cancellationToken).ConfigureAwait(false); - //make sure to do this last because this is what tells us we have a valid version in the future + // make sure to do this last because this is what tells us we have a valid version in the future await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false); } catch (WebException e) { - //since the user can easily provide non-exitent version numbers, we'll turn this into a JobException + // since the user can easily provide non-exitent version numbers, we'll turn this into a JobException throw new JobException(String.Format(CultureInfo.InvariantCulture, "Error downloading BYOND version: {0}", e.Message)); } catch (OperationCanceledException) @@ -205,9 +208,8 @@ namespace Tgstation.Server.Host.Components.Byond { async Task GetActiveVersion() { - if (!await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false)) - return null; - return await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); + var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); + return !activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false); } var activeVersionBytesTask = GetActiveVersion(); @@ -221,9 +223,10 @@ namespace Tgstation.Server.Host.Components.Byond if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(false)) { logger.LogInformation("Cleaning unparsable version path: {0}", ioManager.ResolvePath(path)); - await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); //cleanup + await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); // cleanup return; } + var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(false); var text = Encoding.UTF8.GetString(bytes); if (Version.TryParse(text, out var version)) @@ -236,8 +239,9 @@ namespace Tgstation.Server.Host.Components.Byond return; } } + await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); - }; + } await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs index 3c25ac9175..6d741bb952 100644 --- a/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/PosixByondInstaller.cs @@ -1,11 +1,9 @@ using Microsoft.Extensions.Logging; using System; using System.Globalization; -using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; -using Tgstation.Server.Host.Core; using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Components.Byond @@ -18,7 +16,8 @@ namespace Tgstation.Server.Host.Components.Byond /// /// The URL format string for getting BYOND linux version {0}.{1} zipfile /// - const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip"; + const string ByondRevisionsURLTemplate = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip"; + /// /// Path to the BYOND cache /// @@ -85,7 +84,7 @@ namespace Tgstation.Server.Host.Components.Byond if (version == null) throw new ArgumentNullException(nameof(version)); - var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor); + var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURLTemplate, version.Major, version.Minor); return await ioManager.DownloadFile(new Uri(url), cancellationToken).ConfigureAwait(false); } @@ -98,8 +97,8 @@ namespace Tgstation.Server.Host.Components.Byond if (version == null) throw new ArgumentNullException(nameof(version)); - //write the scripts for running the ting - //need to add $ORIGIN to LD_LIBRARY_PATH + // write the scripts for running the ting + // need to add $ORIGIN to LD_LIBRARY_PATH const string StandardScript = "#!/bin/sh\nexport LD_LIBRARY_PATH=\"\\$ORIGIN:$LD_LIBRARY_PATH\"\nBASEDIR=$(dirname \"$0\")\nexec \"$BASEDIR/{0}\" \"$@\"\n"; var dreamDaemonScript = String.Format(CultureInfo.InvariantCulture, StandardScript, DreamDaemonExecutableName); diff --git a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs index c3f051fa60..e4b46e5384 100644 --- a/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs +++ b/src/Tgstation.Server.Host/Components/Byond/WindowsByondInstaller.cs @@ -17,19 +17,23 @@ namespace Tgstation.Server.Host.Components.Byond /// /// The URL format string for getting BYOND windows version {0}.{1} zipfile /// - const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; + const string ByondRevisionsURLTemplate = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip"; + /// /// Directory to byond installation configuration /// const string ByondConfigDir = "byond/cfg"; + /// /// BYOND's DreamDaemon config file /// const string ByondDDConfig = "daemon.txt"; + /// /// Setting to add to to suppress an invisible user prompt for running a trusted mode .dmb /// const string ByondNoPromptTrustedMode = "trusted-check 0"; + /// /// The directory that contains the BYOND directx redistributable /// @@ -105,7 +109,7 @@ namespace Tgstation.Server.Host.Components.Byond /// public Task DownloadVersion(Version version, CancellationToken cancellationToken) { - var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor); + var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURLTemplate, version.Major, version.Minor); return ioManager.DownloadFile(new Uri(url), cancellationToken); } @@ -118,20 +122,21 @@ namespace Tgstation.Server.Host.Components.Byond var configPath = ioManager.ConcatPath(path, ByondConfigDir); await ioManager.CreateDirectory(configPath, cancellationToken).ConfigureAwait(false); await ioManager.WriteAllBytes(ioManager.ConcatPath(configPath, ByondDDConfig), Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken).ConfigureAwait(false); - }; + } var setNoPromptTrustedModeTask = SetNoPromptTrusted(); - //after this version lummox made DD depend of directx lol - //but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/ + // after this version lummox made DD depend of directx lol + // but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/ if (version.Major == 512 && version.Minor >= 1427 && version.Minor < 1452 && !installedDirectX) using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) - //check again because race conditions if (!installedDirectX) { - //always install it, it's pretty fast and will do better redundancy checking than us + // ^check again because race conditions + // always install it, it's pretty fast and will do better redundancy checking than us var rbdx = ioManager.ConcatPath(path, ByondDXDir); - //noShellExecute because we aren't doing runas shennanigans + + // noShellExecute because we aren't doing runas shennanigans IProcess directXInstaller; try { @@ -141,6 +146,7 @@ namespace Tgstation.Server.Host.Components.Byond { throw new JobException("Unable to start DirectX installer process! Is the server running with admin privileges?", e); } + using (directXInstaller) { int exitCode; diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index fc7321af84..7d44827d44 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -177,6 +177,7 @@ namespace Tgstation.Server.Host.Components.Chat else task = Task.CompletedTask; } + await task.ConfigureAwait(false); return provider; } @@ -190,7 +191,7 @@ namespace Tgstation.Server.Host.Components.Chat /// A representing the running operation async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken) { - //map the channel if it's private and we haven't seen it + // map the channel if it's private and we haven't seen it lock (providers) { var providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First(); @@ -219,7 +220,7 @@ namespace Tgstation.Server.Host.Components.Chat } else { - //need to add tag and isAdminChannel + // need to add tag and isAdminChannel var mapping = enumerable.First().Value; message.User.Channel.Id = mapping.Channel.Id; message.User.Channel.Tag = mapping.Channel.Tag; @@ -236,8 +237,8 @@ namespace Tgstation.Server.Host.Components.Chat var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant(); + // no mention if (!addressed && !message.User.Channel.IsPrivateChannel) - //no mention return; logger.LogTrace("Chat command: {0}. User (True provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User)); @@ -247,7 +248,7 @@ namespace Tgstation.Server.Host.Components.Chat if (splits.Count == 0) { - //just a mention + // just a mention await SendMessage("Hi!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } @@ -266,8 +267,9 @@ namespace Tgstation.Server.Host.Components.Chat await Task.WhenAll(tasks).ConfigureAwait(false); handler = tasks.SelectMany(x => x.Result).Where(x => x.Name.ToUpperInvariant() == commandName).FirstOrDefault(); } + return handler; - }; + } const string UnknownCommandMessage = "Unknown command! Type '?' or 'help' for available commands."; @@ -290,6 +292,7 @@ namespace Tgstation.Server.Host.Components.Chat else helpText = UnknownCommandMessage; } + await SendMessage(helpText, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } @@ -318,7 +321,7 @@ namespace Tgstation.Server.Host.Components.Chat } catch (Exception e) { - //error bc custom commands should reply about why it failed + // error bc custom commands should reply about why it failed logger.LogError("Error processing chat command: {0}", e); await SendMessage("Internal error processing command!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); } @@ -336,11 +339,11 @@ namespace Tgstation.Server.Host.Components.Chat { while (!cancellationToken.IsCancellationRequested) { - //prune disconnected providers + // prune disconnected providers foreach (var I in messageTasks.Where(x => !x.Key.Connected).ToList()) messageTasks.Remove(I.Key); - //add new ones + // add new ones Task updatedTask; lock (this) updatedTask = connectionsUpdated.Task; @@ -355,10 +358,10 @@ namespace Tgstation.Server.Host.Components.Chat continue; } - //wait for a message + // wait for a message await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value))).ConfigureAwait(false); - //process completed ones + // process completed ones foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) { var message = await I.Value.ConfigureAwait(false); @@ -385,7 +388,7 @@ namespace Tgstation.Server.Host.Components.Chat if (provider == null) return; var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false); - if (results == null) //aborted + if (results == null) // aborted return; var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping { @@ -407,7 +410,7 @@ namespace Tgstation.Server.Host.Components.Chat lock (mappedChannels) { lock (providers) - if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) //aborted again + if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) // aborted again return; foreach (var I in mappings) { @@ -419,6 +422,7 @@ namespace Tgstation.Server.Host.Components.Chat lock (trackingContexts) task = Task.WhenAll(trackingContexts.Select(x => x.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken))); } + await task.ConfigureAwait(false); } @@ -444,7 +448,7 @@ namespace Tgstation.Server.Host.Components.Chat Task disconnectTask; lock (providers) { - //raw settings changes forces a rebuild of the provider + // raw settings changes forces a rebuild of the provider if (providers.TryGetValue(newSettings.Id, out provider)) { providers.Remove(newSettings.Id); @@ -471,7 +475,7 @@ namespace Tgstation.Server.Host.Components.Chat await provider.Connect(cancellationToken).ConfigureAwait(false); lock (this) { - //same thread shennanigans + // same thread shennanigans var oldOne = connectionsUpdated; connectionsUpdated = new TaskCompletionSource(); oldOne.SetResult(null); @@ -506,7 +510,7 @@ namespace Tgstation.Server.Host.Components.Chat { List wdChannels; message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message); - lock (mappedChannels) //so it doesn't change while we're using it + lock (mappedChannels) // so it doesn't change while we're using it wdChannels = mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList(); return SendMessage(message, wdChannels, cancellationToken); } @@ -516,7 +520,7 @@ namespace Tgstation.Server.Host.Components.Chat { List wdChannels; message = String.Format(CultureInfo.InvariantCulture, "DM: {0}", message); - lock (mappedChannels) //so it doesn't change while we're using it + lock (mappedChannels) // so it doesn't change while we're using it wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList(); return SendMessage(message, wdChannels, cancellationToken); } @@ -547,11 +551,17 @@ namespace Tgstation.Server.Host.Components.Chat if (customCommandHandler == null) throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!"); JsonTrackingContext context = null; - context = new JsonTrackingContext(ioManager, customCommandHandler, loggerFactory.CreateLogger(), () => - { - lock (trackingContexts) - trackingContexts.Remove(context); - }, ioManager.ConcatPath(basePath, commandsJsonName), ioManager.ConcatPath(basePath, channelsJsonName)); + context = new JsonTrackingContext( + ioManager, + customCommandHandler, + loggerFactory.CreateLogger(), + () => + { + lock (trackingContexts) + trackingContexts.Remove(context); + }, + ioManager.ConcatPath(basePath, commandsJsonName), + ioManager.ConcatPath(basePath, channelsJsonName)); Task task; lock (trackingContexts) { @@ -559,6 +569,7 @@ namespace Tgstation.Server.Host.Components.Chat lock (mappedChannels) task = context.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken); } + await task.ConfigureAwait(false); return context; } @@ -598,7 +609,7 @@ namespace Tgstation.Server.Host.Components.Chat { var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion); List wdChannels; - lock (mappedChannels) //so it doesn't change while we're using it + lock (mappedChannels) // so it doesn't change while we're using it wdChannels = mappedChannels.Select(x => x.Key).ToList(); return SendMessage(message, wdChannels, cancellationToken); } diff --git a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs index 90db91a969..e619f6ffeb 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Commands/PullRequestsCommand.cs @@ -73,6 +73,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands return "Repository unavailable!"; head = repo.Head; } + await databaseContextFactory.UseContext(async db => results = await db.RevisionInformations.Where(x => x.Instance.Id == instance.Id && x.CommitSha == head) .SelectMany(x => x.ActiveTestMerges) .Select(x => x.TestMerge) @@ -89,10 +90,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands results = watchdog.ActiveCompileJob?.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList() ?? new List(); } - if (!results.Any()) - return "None!"; - - return String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7)))); + return !results.Any() ? "None!" : String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7)))); } } } diff --git a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs index 1aee8b6ea7..00f8ade0a5 100644 --- a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs +++ b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs @@ -90,6 +90,7 @@ namespace Tgstation.Server.Host.Components.Chat { logger.LogWarning("Error retrieving custom commands! Exception: {0}", e); } + return new List(); } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 50996ed74a..fcf6137f0e 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -54,7 +54,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// The mention provided by the Discord library /// The normalized mention - static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", "", StringComparison.Ordinal); + static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", String.Empty, StringComparison.Ordinal); /// /// Construct a @@ -100,7 +100,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers IsPrivateChannel = pm, ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", FriendlyName = e.Channel.Name - //isAdmin and Tag populated by manager + + // isAdmin and Tag populated by manager }, FriendlyName = e.Author.Username, Mention = NormalizeMention(e.Author.Mention) @@ -182,19 +183,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (!channel.DiscordChannelId.HasValue) throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); - if (!(client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel)) - return null; + if (client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel) + return new Channel + { + RealId = discordChannel.Id, + IsAdminChannel = channel.IsAdminChannel == true, + ConnectionName = discordChannel.Guild.Name, + FriendlyName = discordChannel.Name, + IsPrivateChannel = false, + Tag = channel.Tag + }; - return new Channel - { - RealId = discordChannel.Id, - IsAdminChannel = channel.IsAdminChannel == true, - ConnectionName = discordChannel.Guild.Name, - FriendlyName = discordChannel.Name, - IsPrivateChannel = false, - Tag = channel.Tag - }; - }; + return null; + } var enumerator = channels.Select(x => GetChannelForChatChannel(x)).Where(x => x != null).ToList(); diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 01fa89697e..8716ad3d0a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -44,18 +44,22 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// Address of the server to connect to /// readonly string address; + /// /// Port of the server to connect to /// readonly ushort port; + /// /// IRC nickname /// readonly string nickname; + /// /// Password which will used for authentication /// readonly string password; + /// /// The of /// @@ -134,7 +138,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers UseSsl = useSsl }; if (useSsl) - client.ValidateServerCertificate = true; //dunno if it defaults to that or what + client.ValidateServerCertificate = true; // dunno if it defaults to that or what client.OnChannelMessage += Client_OnChannelMessage; client.OnQueryMessage += Client_OnQueryMessage; @@ -151,7 +155,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (Connected) { disconnecting = true; - client.Disconnect(); //just closes the socket + client.Disconnect(); // just closes the socket } } @@ -184,8 +188,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (dicToCheck == queryChannelIdMap) channelIdMap.Add(resultId.Value, null); } + return resultId.Value; - }; + } ulong userId, channelId; lock (this) @@ -205,7 +210,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName, RealId = channelId, IsPrivateChannel = isPrivate - //isAdmin and Tag populated by manager + + // isAdmin and Tag populated by manager }, FriendlyName = username, RealId = userId, @@ -247,7 +253,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { if (passwordType == IrcPasswordType.Sasl) { - client.WriteLine("CAP REQ :sasl", Priority.Critical); //needs to be put in the buffer before anything else + client.WriteLine("CAP REQ :sasl", Priority.Critical); // needs to be put in the buffer before anything else cancellationToken.ThrowIfCancellationRequested(); } client.Login(nickname, nickname, 0, nickname); @@ -260,7 +266,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } else if (passwordType == IrcPasswordType.Sasl) { - //wait for the sasl ack or timeout + // wait for the sasl ack or timeout var recievedAck = false; var recievedPlus = false; client.OnReadLine += (sender, e) => @@ -285,7 +291,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers 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 + // Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196 var authString = String.Format(CultureInfo.InvariantCulture, "{0}{1}{0}{1}{2}", nickname, '\0', password); var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString)); var authLine = String.Format(CultureInfo.InvariantCulture, "AUTHENTICATE {0}", b64); @@ -306,7 +312,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (disconnecting || !client.IsConnected) break; client.Listen(false); - //ensure we have the correct nick + + // ensure we have the correct nick if (client.GetIrcUser(nickname) == null) client.RfcNick(nickname); } @@ -336,7 +343,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { try { - client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); //priocritical otherwise it wont go through + client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); // priocritical otherwise it wont go through } catch (Exception e) { @@ -363,7 +370,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers throw new InvalidOperationException("ChatChannel missing IrcChannel!"); lock (this) { - var hs = new HashSet(); //for unique inserts + var hs = new HashSet(); // for unique inserts foreach (var I in channels) hs.Add(I.IrcChannel); var toPart = new List(); diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs index 3b26db1875..b8d233a96c 100644 --- a/src/Tgstation.Server.Host/Controllers/ApiController.cs +++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs @@ -72,17 +72,16 @@ namespace Tgstation.Server.Host.Controllers /// public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - //ALL valid token and login requests that match a route go through this function - //404 is returned before - + // ALL valid token and login requests that match a route go through this function + // 404 is returned before if (AuthenticationContext != null && AuthenticationContext.User == null) { - //valid token, expired password + // valid token, expired password await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false); return; } - //validate the headers + // validate the headers try { ApiHeaders = new ApiHeaders(Request.GetTypedHeaders()); @@ -103,9 +102,10 @@ namespace Tgstation.Server.Host.Controllers await BadRequest(new ErrorMessage { Message = "Missing Instance header!" }).ExecuteResultAsync(context).ConfigureAwait(false); return; } + if (AuthenticationContext.InstanceUser == null) { - //accessing an instance they don't have access to or one that's disabled + // accessing an instance they don't have access to or one that's disabled await Forbid().ExecuteResultAsync(context).ConfigureAwait(false); return; } @@ -120,8 +120,9 @@ namespace Tgstation.Server.Host.Controllers if (ModelState?.IsValid == false) { var errorMessages = ModelState.SelectMany(x => x.Value.Errors).Select(x => x.ErrorMessage).ToList(); - //HACK - //do some fuckery to remove RequiredAttribute errors + + // HACK + // do some fuckery to remove RequiredAttribute errors for (var I = 0; I < errorMessages.Count; ++I) { var message = errorMessages[I]; @@ -131,6 +132,7 @@ namespace Tgstation.Server.Host.Controllers --I; } } + if (errorMessages.Count > 0) { await BadRequest(new ErrorMessage { Message = String.Join(Environment.NewLine, errorMessages) }).ExecuteResultAsync(context).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs index d6eb1b25b6..0d52ac6ae1 100644 --- a/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs +++ b/src/Tgstation.Server.Host/Controllers/TgsAuthorizeAttribute.cs @@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Controllers /// /// Helper for using the with the system /// + #pragma warning disable CA1019 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] sealed class TgsAuthorizeAttribute : AuthorizeAttribute { @@ -19,54 +20,81 @@ namespace Tgstation.Server.Host.Controllers /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(AdministrationRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(AdministrationRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(RepositoryRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(RepositoryRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ByondRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(ByondRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(DreamMakerRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(DreamMakerRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ChatBotRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(ChatBotRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(ConfigurationRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(ConfigurationRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } /// /// Construct a for /// /// The rights required - public TgsAuthorizeAttribute(InstanceUserRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights); + public TgsAuthorizeAttribute(InstanceUserRights requiredRights) + { + Roles = RightsHelper.RoleNames(requiredRights); + } } } diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 05b5234e0c..f8fe4f1536 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -19,7 +19,6 @@ using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Reflection; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Tgstation.Server.Host.Components; using Tgstation.Server.Host.Components.Byond; @@ -90,19 +89,19 @@ namespace Tgstation.Server.Host.Core if (services == null) throw new ArgumentNullException(nameof(services)); - //needful + // needful services.AddSingleton(this); - //configure configuration + // configure configuration services.Configure(configuration.GetSection(UpdatesConfiguration.Section)); services.Configure(configuration.GetSection(DatabaseConfiguration.Section)); services.Configure(configuration.GetSection(GeneralConfiguration.Section)); services.Configure(configuration.GetSection(FileLoggingConfiguration.Section)); - //enable options which give us config reloading + // enable options which give us config reloading services.AddOptions(); - - //other stuff needed for for setup wizard and configuration + + // other stuff needed for for setup wizard and configuration services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -116,24 +115,23 @@ namespace Tgstation.Server.Host.Core 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 - //also allows us to get some options and other services we need for continued configuration + // temporarily build the service provider in it's current state + // do it here so we can run the setup wizard if necessary + // also allows us to get some options and other services we need for continued configuration using (var provider = services.BuildServiceProvider()) { - //run the wizard if necessary + // run the wizard if necessary var setupWizard = provider.GetRequiredService(); var applicationLifetime = provider.GetRequiredService(); var setupWizardRan = setupWizard.CheckRunWizard(applicationLifetime.ApplicationStopping).GetAwaiter().GetResult(); - //load the configuration options we need + // load the configuration options we need var generalOptions = provider.GetRequiredService>(); generalConfiguration = generalOptions.Value; - //unless this is set, in which case, we leave + // unless this is set, in which case, we leave if (setupWizardRan && generalConfiguration.SetupWizardMode == SetupWizardMode.Only) - //we don't inject a logger in the constuctor to log this because it's not yet configured - throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!"); + throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!"); // we don't inject a logger in the constuctor to log this because it's not yet configured var dbOptions = provider.GetRequiredService>(); databaseConfiguration = dbOptions.Value; @@ -145,11 +143,11 @@ namespace Tgstation.Server.Host.Core platformIdentifier = provider.GetRequiredService(); } - //setup file logging via serilog + // setup file logging via serilog if (!fileLoggingConfiguration.Disable) services.AddLogging(builder => { - //common app data is C:/ProgramData on windows, else /usr/shar + // common app data is C:/ProgramData on windows, else /usr/shar var logPath = !String.IsNullOrEmpty(fileLoggingConfiguration.Directory) ? fileLoggingConfiguration.Directory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs"); logPath = ioManager.ConcatPath(logPath, "tgs-{Date}.log"); @@ -175,7 +173,7 @@ namespace Tgstation.Server.Host.Core default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid log level {0}", logLevel)); } - }; + } var logEventLevel = ConvertLogLevel(fileLoggingConfiguration.LogLevel); var microsoftEventLevel = ConvertLogLevel(fileLoggingConfiguration.MicrosoftLogLevel); @@ -195,23 +193,24 @@ namespace Tgstation.Server.Host.Core builder.AddSerilog(configuration.CreateLogger(), true); }); - //configure bearer token validation + // 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 + // 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 { - //Application is our composition root so this monstrosity of a line is okay + // Application is our composition root so this monstrosity of a line is okay OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted) }; }); - //fucking converts 'sub' to M$ bs - //can't be done in the above lambda, that's too late + + // fucking prevents converting 'sub' to M$ bs + // can't be done in the above lambda, that's too late JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - //add mvc, configure the json serializer settings + // add mvc, configure the json serializer settings services.AddMvc().AddJsonOptions(options => { options.AllowInputFormatterExceptionMessages = true; @@ -232,7 +231,7 @@ namespace Tgstation.Server.Host.Core services.AddScoped(x => x.GetRequiredService()); } - //add the correct database context type + // add the correct database context type var dbType = databaseConfiguration.DatabaseType; switch (dbType) { @@ -247,11 +246,11 @@ namespace Tgstation.Server.Host.Core throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType)); } - //configure other database services + // configure other database services services.AddSingleton(); services.AddSingleton(); - //configure security services + // configure security services services.AddScoped(); services.AddScoped(); services.AddSingleton(); @@ -259,7 +258,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton, PasswordHasher>(); - //configure platform specific services + // configure platform specific services if (platformIdentifier.IsWindows) { services.AddSingleton(); @@ -280,7 +279,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); } - //configure misc services + // configure misc services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -290,14 +289,14 @@ namespace Tgstation.Server.Host.Core SendTimeout = generalConfiguration.ByondTopicTimeout }); - //configure component services + // configure component services services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - //configure root services + // configure root services services.AddSingleton(); services.AddSingleton(x => x.GetRequiredService()); services.AddSingleton(x => x.GetRequiredService()); @@ -326,38 +325,38 @@ namespace Tgstation.Server.Host.Core logger.LogInformation(VersionString); - //attempt to restart the server if the configuration changes + // attempt to restart the server if the configuration changes if(serverControl.WatchdogPresent) ChangeToken.OnChange(configuration.GetReloadToken, () => serverControl.Restart()); - //now setup the HTTP request pipeline + // setup the HTTP request pipeline - //should anything after this throw an exception, catch it and display a detailed html page - applicationBuilder.UseDeveloperExceptionPage(); //it is not worth it to limit this, you should only ever get it if you're an authorized user - - //suppress OperationCancelledExceptions, they are just aborted HTTP requests + // should anything after this throw an exception, catch it and display a detailed html page + applicationBuilder.UseDeveloperExceptionPage(); // it is not worth it to limit this, you should only ever get it if you're an authorized user + + // suppress OperationCancelledExceptions, they are just aborted HTTP requests applicationBuilder.UseCancelledRequestSuppression(); - //Do not service requests until Ready is called, this will return 503 until that point + // Do not service requests until Ready is called, this will return 503 until that point applicationBuilder.UseAsyncInitialization(async cancellationToken => { using (cancellationToken.Register(() => startupTcs.SetCanceled())) await startupTcs.Task.ConfigureAwait(false); }); - //authenticate JWT tokens using our security pipeline if present, returns 401 if bad + // authenticate JWT tokens using our security pipeline if present, returns 401 if bad applicationBuilder.UseAuthentication(); - //suppress and log database exceptions + // suppress and log database exceptions applicationBuilder.UseDbConflictHandling(); - //majority of handling is done in the controllers + // majority of handling is done in the controllers applicationBuilder.UseMvc(); - //404 anything that gets this far + // 404 anything that gets this far } - /// + /// public void Ready(Exception initializationError) { lock (startupTcs) diff --git a/src/Tgstation.Server.Host/Core/JobHandler.cs b/src/Tgstation.Server.Host/Core/JobHandler.cs index c57c7bdc1f..e09f574581 100644 --- a/src/Tgstation.Server.Host/Core/JobHandler.cs +++ b/src/Tgstation.Server.Host/Core/JobHandler.cs @@ -13,6 +13,7 @@ namespace Tgstation.Server.Host.Core /// The for /// readonly CancellationTokenSource cancellationTokenSource; + /// /// The being run /// @@ -30,7 +31,7 @@ namespace Tgstation.Server.Host.Core task = job(cancellationTokenSource.Token); } - /// + /// public void Dispose() => cancellationTokenSource.Dispose(); /// diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index b3aeac331d..bd4d233f9d 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -69,14 +69,14 @@ namespace Tgstation.Server.Host.Core /// The for the operation /// A representing the running operation async Task RunJob(Job job, Func operation, CancellationToken cancellationToken) - { + { try { await databaseContextFactory.UseContext(async databaseContext => { async Task HandleExceptions(Task task) { - void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); + void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails); try { await task.ConfigureAwait(false); @@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Core await operation(job, databaseContext, cancellationToken).ConfigureAwait(false); logger.LogDebug("Job {0} completed!", job.Id); - }; + } await HandleExceptions(RunJobInternal()).ConfigureAwait(false); @@ -121,7 +121,7 @@ namespace Tgstation.Server.Host.Core bool JobErroredOrCancelled() => job.ExceptionDetails != null || job.Cancelled == true; - //ok so, now it's time for the post commit step if it exists + // ok so, now it's time for the post commit step if it exists if (!JobErroredOrCancelled() && job.PostComplete != null) { await HandleExceptions(job.PostComplete(cancellationToken)).ConfigureAwait(false); @@ -186,7 +186,7 @@ namespace Tgstation.Server.Host.Core logger.LogTrace("Starting job manager..."); await databaseContextFactory.UseContext(async databaseContext => { - //mark all jobs as cancelled + // mark all jobs as cancelled var badJobs = await databaseContext.Jobs.Where(y => !y.StoppedAt.HasValue).Select(y => y.Id).ToListAsync(cancellationToken).ConfigureAwait(false); if (badJobs.Count > 0) { @@ -229,10 +229,11 @@ namespace Tgstation.Server.Host.Core } catch (InvalidOperationException) { - //this is fine + // this is fine return false; } - handler.Cancel(); //this will ensure the db update is only done once + + handler.Cancel(); // this will ensure the db update is only done once await databaseContextFactory.UseContext(async databaseContext => { job = new Job { Id = job.Id }; @@ -240,7 +241,8 @@ namespace Tgstation.Server.Host.Core user = new User { Id = user.Id }; databaseContext.Users.Attach(user); job.CancelledBy = user; - //let either startup or cancellation set job.cancelled + + // let either startup or cancellation set job.cancelled await databaseContext.Save(cancellationToken).ConfigureAwait(false); }).ConfigureAwait(false); if (blocking) @@ -274,6 +276,7 @@ namespace Tgstation.Server.Host.Core if (!jobs.TryGetValue(job.Id, out handler)) return; } + Task cancelTask = null; using (jobCancellationToken.Register(() => cancelTask = CancelJob(job, canceller, true, cancellationToken))) await handler.Wait(cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs index 2786af6c83..7c1d5274eb 100644 --- a/src/Tgstation.Server.Host/Core/ProcessExecutor.cs +++ b/src/Tgstation.Server.Host/Core/ProcessExecutor.cs @@ -38,6 +38,7 @@ namespace Tgstation.Server.Host.Core { return; } + tcs.SetResult(exitCode); }; return tcs.Task; @@ -68,6 +69,7 @@ namespace Tgstation.Server.Host.Core logger.LogDebug("Unable to get process {0}! Exception: {1}", id, e); return null; } + try { return new Process(handle, AttachExitHandler(handle), null, null, null, loggerFactory.CreateLogger(), true); @@ -108,6 +110,7 @@ namespace Tgstation.Server.Host.Core outputStringBuilder.Append(e.Data); }; } + if (readError) { errorStringBuilder = new StringBuilder(); diff --git a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs index d16b7ae628..8c78b4570f 100644 --- a/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs +++ b/src/Tgstation.Server.Host/Core/SemaphoreSlimContext.cs @@ -38,10 +38,13 @@ namespace Tgstation.Server.Host.Core /// Construct a /// /// The value of - SemaphoreSlimContext(SemaphoreSlim lockedSemaphore) => this.lockedSemaphore = lockedSemaphore; + SemaphoreSlimContext(SemaphoreSlim lockedSemaphore) + { + this.lockedSemaphore = lockedSemaphore; + } /// - /// Finalize the + /// Finalizes an instance of the class. /// #pragma warning disable CA1821 // Remove empty Finalizers //TODO remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed ~SemaphoreSlimContext() => Dispose(); @@ -58,6 +61,7 @@ namespace Tgstation.Server.Host.Core return; disposed = true; } + GC.SuppressFinalize(this); lockedSemaphore.Release(); } diff --git a/src/Tgstation.Server.Host/Core/SetupWizard.cs b/src/Tgstation.Server.Host/Core/SetupWizard.cs index 83b6456fe3..df7df8e590 100644 --- a/src/Tgstation.Server.Host/Core/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Core/SetupWizard.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.Globalization; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -158,6 +157,7 @@ namespace Tgstation.Server.Host.Core databaseConfiguration.DatabaseType = databaseType; break; } + await console.WriteAsync("Invalid database type!", true, cancellationToken).ConfigureAwait(false); } while (true); @@ -171,7 +171,6 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Enter the database name (Can be from previous installation. Otherwise, should not exist): ", false, cancellationToken).ConfigureAwait(false); string databaseName; - do { databaseName = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false); @@ -206,6 +205,7 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync("The account it uses in MSSQL is usually \"NT AUTHORITY\\SYSTEM\" and the role it needs is usually \"dbcreator\".", true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("We'll run a sanity test here, but it won't be indicative of the service's permissions if that is the case", true, cancellationToken).ConfigureAwait(false); } + await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); DbConnection testConnection; @@ -261,7 +261,7 @@ namespace Tgstation.Server.Host.Core using (var command = testConnection.CreateCommand()) { command.CommandText = "SELECT VERSION()"; - var fullVersion = (string)(await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false)); + var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false); await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false); var splits = fullVersion.Split('-'); databaseConfiguration.MySqlServerVersion = splits[0]; @@ -276,6 +276,7 @@ namespace Tgstation.Server.Host.Core command.CommandText = String.Format(CultureInfo.InvariantCulture, "CREATE DATABASE {0}", databaseName); await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); } + await console.WriteAsync("Success!", true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false); using (var command = testConnection.CreateCommand()) @@ -313,7 +314,8 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Retrying database configuration...", true, cancellationToken).ConfigureAwait(false); } - } while (true); + } + while (true); } /// @@ -339,6 +341,7 @@ namespace Tgstation.Server.Host.Core newGeneralConfiguration.MinimumPasswordLength = passwordLength; break; } + await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false); } while (true); @@ -355,6 +358,7 @@ namespace Tgstation.Server.Host.Core newGeneralConfiguration.ByondTopicTimeout = topicTimeout; break; } + await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false); } while (true); @@ -390,7 +394,8 @@ namespace Tgstation.Server.Host.Core fileLoggingConfiguration.Directory = null; break; } - //test a write of it + + // test a write of it await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Testing directory access...", true, cancellationToken).ConfigureAwait(false); try @@ -412,6 +417,7 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); } + break; } catch (OperationCanceledException) @@ -424,7 +430,8 @@ namespace Tgstation.Server.Host.Core await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Please verify the path is valid and you have access to it!", true, cancellationToken).ConfigureAwait(false); } - } while (true); + } + while (true); async Task PromptLogLevel(string question) { @@ -432,19 +439,21 @@ namespace Tgstation.Server.Host.Core { await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync(question, true, cancellationToken).ConfigureAwait(false); - await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ",nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false); + await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false); var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false); if (String.IsNullOrWhiteSpace(responseString)) return null; if (Enum.TryParse(responseString, out var logLevel) && logLevel != LogLevel.None) return logLevel; await console.WriteAsync("Invalid log level!", true, cancellationToken).ConfigureAwait(false); - } while (true); + } + while (true); } fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.LogLevel; fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.MicrosoftLogLevel; } + return fileLoggingConfiguration; } @@ -507,7 +516,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 + // we need to wait for the configuration's file system watcher to read and reload the changes await asyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false); } @@ -519,7 +528,7 @@ namespace Tgstation.Server.Host.Core /// A representing the running operation async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken) { - //welcome message + // welcome message await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Welcome to tgstation-server 4!", true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("This wizard will help you configure your server.", true, cancellationToken).ConfigureAwait(false); @@ -576,7 +585,6 @@ namespace Tgstation.Server.Host.Core logger.LogTrace("No configuration json detected"); } - if (!shouldRunBasedOnAutodetect) { if (forceRun) @@ -586,11 +594,12 @@ namespace Tgstation.Server.Host.Core forceRun = await PromptYesNo("Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(false); } - if (!forceRun) + + if (!forceRun) return false; } - //flush the logs to prevent console conflicts + // flush the logs to prevent console conflicts await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/IO/Console.cs b/src/Tgstation.Server.Host/IO/Console.cs index 35d3b1e135..9f1fceba80 100644 --- a/src/Tgstation.Server.Host/IO/Console.cs +++ b/src/Tgstation.Server.Host/IO/Console.cs @@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.IO /// public Task ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - //TODO Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline + // TODO: Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline CheckAvailable(); if (!usePasswordChar) return System.Console.ReadLine(); @@ -46,13 +46,15 @@ namespace Tgstation.Server.Host.IO System.Console.Write("\b \b"); } } - else if (keyDescription.KeyChar != '\u0000') // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc + else if (keyDescription.KeyChar != '\u0000') { + // KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc passwordBuilder.Append(keyDescription.KeyChar); System.Console.Write('*'); } } while (!cancellationToken.IsCancellationRequested); + cancellationToken.ThrowIfCancellationRequested(); System.Console.WriteLine(); return passwordBuilder.ToString(); diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index d6cae80fde..6318a7b02a 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -5,7 +5,6 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Net; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -34,18 +33,21 @@ namespace Tgstation.Server.Host.IO foreach (var subDir in dir.EnumerateDirectories()) { cancellationToken.ThrowIfCancellationRequested(); + + // if below succeeds this is probably a symlink if (!subDir.Attributes.HasFlag(FileAttributes.Directory) || subDir.Attributes.HasFlag(FileAttributes.ReparsePoint)) - //this is probably a symlink subDir.Delete(); else tasks.Add(NormalizeAndDelete(subDir, cancellationToken)); } + foreach (var file in dir.EnumerateFiles()) { cancellationToken.ThrowIfCancellationRequested(); file.Attributes = FileAttributes.Normal; file.Delete(); } + await Task.WhenAll(tasks).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); dir.Delete(true); @@ -84,19 +86,19 @@ namespace Tgstation.Server.Host.IO async Task CopyThisDirectory() { if (!atLeastOneSubDir) - await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); //save on createdir calls + await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); // save on createdir calls var tasks = new List(); - await dir.EnumerateFiles().ToAsyncEnumerable().ForEachAsync(I => + await dir.EnumerateFiles().ToAsyncEnumerable().ForEachAsync(fileInfo => { - if (ignore != null && ignore.Contains(I.Name)) + if (ignore != null && ignore.Contains(fileInfo.Name)) return; - tasks.Add(CopyFile(I.FullName, Path.Combine(dest, I.Name), cancellationToken)); + tasks.Add(CopyFile(fileInfo.FullName, Path.Combine(dest, fileInfo.Name), cancellationToken)); }).ConfigureAwait(false); await Task.WhenAll(tasks).ConfigureAwait(false); - }; + } yield return CopyThisDirectory(); } @@ -115,18 +117,6 @@ namespace Tgstation.Server.Host.IO await directoryCopy.ConfigureAwait(false); } - /// - public async Task AppendAllText(string path, string additional_contents, CancellationToken cancellationToken) - { - if (additional_contents == null) - throw new ArgumentNullException(nameof(additional_contents)); - using (var destStream = new FileStream(ResolvePath(path), FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, DefaultBufferSize, true)) - { - var buf = Encoding.UTF8.GetBytes(additional_contents); - await destStream.WriteAsync(buf, 0, buf.Length, cancellationToken).ConfigureAwait(false); - } - } - /// public string ConcatPath(params string[] paths) { @@ -265,18 +255,10 @@ namespace Tgstation.Server.Host.IO return (IReadOnlyList)results; }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); - /// - public Task CreateSymlink(string target, string link, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - target = ResolvePath(target); - link = ResolvePath(link); - - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); - /// public async Task DownloadFile(Uri url, CancellationToken cancellationToken) { - //DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it + // DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it using (var wc = new WebClient()) { var tcs = new TaskCompletionSource(); diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index 5b457fd840..69f80050b5 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.IO bool DeleteDirectory(string path); /// - /// Write to a file at a given . + /// Write to a file at a given /// /// The path to the file to write /// The new contents of the file diff --git a/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs index 75eaa24bcd..2c9b6b74cf 100644 --- a/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs +++ b/src/Tgstation.Server.Host/IO/PosixPostWriteHandler.cs @@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.IO if (filePath == null) throw new ArgumentNullException(nameof(filePath)); - //set executable bit every time, don't want people calling me when their uploaded "sl" binary doesn't work + // set executable bit every time, don't want people calling me when their uploaded "sl" binary doesn't work if (Syscall.stat(filePath, out var stat) != 0) throw new UnixIOException(Stdlib.GetLastError()); diff --git a/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs b/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs index b00ee71b98..e06b5d98d3 100644 --- a/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ResolvingIOManager.cs @@ -17,15 +17,15 @@ namespace Tgstation.Server.Host.IO /// Construct a /// /// The that resolves to the directory to work out of - /// The value of - public ResolvingIOManager(IIOManager parent, string _subdirectory) + /// The value of + public ResolvingIOManager(IIOManager parent, string subdirectory) { if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (_subdirectory == null) - throw new ArgumentNullException(nameof(_subdirectory)); + if (subdirectory == null) + throw new ArgumentNullException(nameof(subdirectory)); - subdirectory = ConcatPath(parent.ResolvePath("."), _subdirectory); + this.subdirectory = ConcatPath(parent.ResolvePath("."), subdirectory); } /// diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index c3937d5a9c..2a6530faa0 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -86,8 +86,8 @@ namespace Tgstation.Server.Host.IO { cancellationToken.ThrowIfCancellationRequested(); - //as nice as it would be to not have to arrayify the memory stream, we have to - //because, oddly enough sha1(memorystream) != sha1(memorystream.ToArray()) + // as nice as it would be to not have to arrayify the memory stream, we have to + // because, oddly enough sha1(memorystream) != sha1(memorystream.ToArray()) // vOv byte[] originalBytes; using (var readMs = new MemoryStream()) @@ -95,24 +95,27 @@ namespace Tgstation.Server.Host.IO file.CopyTo(readMs); originalBytes = readMs.ToArray(); } + + // no sha1? no write if (originalBytes.Length != 0 && sha1InOut == null) - //no sha1? no write return false; - //suppressed due to only using for consistency checks + // suppressed due to only using for consistency checks #pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1. using (var sha1 = new SHA1Managed()) #pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1. { - string GetSha1(byte[] dataToHash) => dataToHash != null && dataToHash.Length != 0 ? String.Join("", sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; + string GetSha1(byte[] dataToHash) => dataToHash != null && dataToHash.Length != 0 ? String.Join(String.Empty, sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null; var originalSha1 = GetSha1(originalBytes); if (originalSha1 != sha1InOut) { sha1InOut = originalSha1; return false; } + sha1InOut = GetSha1(data); } + cancellationToken.ThrowIfCancellationRequested(); if (data != null) @@ -124,6 +127,7 @@ namespace Tgstation.Server.Host.IO file.Write(data, 0, data.Length); } } + if (data == null) File.Delete(path); return true; diff --git a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs index 761d37a574..b3a22fdaf0 100644 --- a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs @@ -19,17 +19,20 @@ namespace Tgstation.Server.Host.IO if (linkPath == null) throw new ArgumentNullException(nameof(linkPath)); - //check if its not a file + // check if its not a file var flags = File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory; - //no don't fucking use this - //sure it works in SOME cases - //i.e. win10 1803+ and IN DEVELOPER MODE - //other times it throws ERROR_INVALID_PARAMETER - //but the fucking worst is there is some configuration of windows that accept the argument and allow the function to succeed - //BUT IT DOESN'T CREATE THE FUCKING LINK - //I AM NOT DEBUGGING THAT SHIT AGAIN AHHH - //flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate; + /* + * no don't fucking use this + * sure it works in SOME cases + * i.e. win10 1803+ and IN DEVELOPER MODE + * other times it throws ERROR_INVALID_PARAMETER + * but the fucking worst is there is some configuration of windows that accept the argument and allow the function to succeed + * BUT IT DOESN'T CREATE THE FUCKING LINK + * I AM NOT DEBUGGING THAT SHIT AGAIN AHHH + + flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate; + */ cancellationToken.ThrowIfCancellationRequested(); if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index e113f1d999..c80e94d95d 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -190,10 +190,10 @@ namespace Tgstation.Server.Host.Models string targetMigration = null; - //Update this with new migrations as they are made - //Always use the MS class + // Update this with new migrations as they are made + // Always use the MS class - //TODO: Uncomment once #816 is merged + // TODO: Uncomment once #816 is merged /* if (version < new Version(4, 0, 2)) targetMigration = nameof(MSReattachCompileJobRequired); @@ -205,7 +205,7 @@ namespace Tgstation.Server.Host.Models if (UseMySQLMigrations()) targetMigration = String.Format(CultureInfo.InvariantCulture, "MY" + targetMigration.Substring(2)); - //even though it clearly implements it in the DatabaseFacade definition this won't work without casting (╯ಠ益ಠ)╯︵ ┻━┻ + // even though it clearly implements it in the DatabaseFacade definition this won't work without casting (╯ಠ益ಠ)╯︵ ┻━┻ var dbServiceProvider = ((IInfrastructure)Database).Instance; var migrator = dbServiceProvider.GetRequiredService(); diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs index d7a1c285c3..eaf2c50bde 100644 --- a/src/Tgstation.Server.Host/Models/Instance.cs +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -7,7 +7,6 @@ namespace Tgstation.Server.Host.Models /// public sealed class Instance : Api.Models.Instance { - /// /// The for the /// @@ -34,7 +33,7 @@ namespace Tgstation.Server.Host.Models public List InstanceUsers { get; set; } /// - /// The s for the + /// The s for the /// public List ChatSettings { get; set; } diff --git a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs index b4b4d13ef2..98af100f86 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs @@ -15,12 +15,16 @@ namespace Tgstation.Server.Host.Models.Migrations /// Path to the json file to use for migrations configuration /// const string RootJson = "appsettings.json"; + /// /// Path to the development json file to use for migrations configuration /// const string DevJson = "appsettings.Development.json"; - /// + /// + /// Get the for the + /// + /// The for the public static IOptions GetDbContextOptions() { var builder = new ConfigurationBuilder(); diff --git a/src/Tgstation.Server.Host/Models/RepositorySettings.cs b/src/Tgstation.Server.Host/Models/RepositorySettings.cs index 969e1ebb69..4c11820499 100644 --- a/src/Tgstation.Server.Host/Models/RepositorySettings.cs +++ b/src/Tgstation.Server.Host/Models/RepositorySettings.cs @@ -1,5 +1,4 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Tgstation.Server.Api.Models; namespace Tgstation.Server.Host.Models @@ -26,10 +25,10 @@ namespace Tgstation.Server.Host.Models /// /// Convert the to it's API form /// - /// A new + /// A new public Repository ToApi() => new Repository { - //AccessToken = AccessToken, //never show this + // AccessToken = AccessToken, //never show this AccessUser = AccessUser, AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges, AutoUpdatesSynchronize = AutoUpdatesSynchronize, @@ -37,7 +36,8 @@ namespace Tgstation.Server.Host.Models CommitterName = CommitterName, PushTestMergeCommits = PushTestMergeCommits, ShowTestMergeCommitters = ShowTestMergeCommitters - //revision information and the rest retrieved by controller + + // revision information and the rest retrieved by controller }; } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 896615150f..654f55ef1b 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -45,7 +45,7 @@ namespace Tgstation.Server.Host.Models ActiveTestMerges = ActiveTestMerges.Select(x => x.TestMerge.ToApi()).ToList(), CompileJobs = CompileJobs.Select(x => new Api.Models.CompileJob { - Id = x.Id //anti recursion measure + Id = x.Id // anti recursion measure }).ToList() }; } diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs index c8682b4e7f..bcbe8c23bf 100644 --- a/src/Tgstation.Server.Host/Models/User.cs +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Models }; /// - /// Convert the to it's API form + /// Convert the to it's API form /// /// If rights and system identifier should be shown /// A new diff --git a/src/Tgstation.Server.Host/Program.cs b/src/Tgstation.Server.Host/Program.cs index d7c28a061f..83895df62b 100644 --- a/src/Tgstation.Server.Host/Program.cs +++ b/src/Tgstation.Server.Host/Program.cs @@ -15,7 +15,9 @@ namespace Tgstation.Server.Host /// /// The to use /// - internal static IServerFactory serverFactory = new ServerFactory(); +#pragma warning disable SA1401 // Fields must be private + internal static IServerFactory ServerFactory = new ServerFactory(); +#pragma warning restore SA1401 // Fields must be private /// /// Entrypoint for the @@ -25,7 +27,8 @@ namespace Tgstation.Server.Host public static async Task Main(string[] args) { var listArgs = new List(args); - //first arg is 100% always the update path, starting it otherwise is solely for debugging purposes + + // first arg is 100% always the update path, starting it otherwise is solely for debugging purposes string updatePath; if (listArgs.Count > 0) { @@ -38,7 +41,7 @@ namespace Tgstation.Server.Host updatePath = null; try { - var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath); + var server = ServerFactory.CreateServer(listArgs.ToArray(), updatePath); try { using (var cts = new CancellationTokenSource()) @@ -70,6 +73,7 @@ namespace Tgstation.Server.Host File.WriteAllText(updatePath, e.ToString()); return 2; } + throw; } } diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs index 1ef6364651..b621a41c04 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContext.cs @@ -53,6 +53,7 @@ namespace Tgstation.Server.Host.Security if (isInstance && InstanceUser == null) return 0; var rightsEnum = RightsHelper.RightToType(rightsType); + // use the api versions because they're the ones that contain the actual properties var typeToCheck = isInstance ? typeof(InstanceUser) : typeof(User); diff --git a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs index 087a6d33dc..bfe70be61d 100644 --- a/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs +++ b/src/Tgstation.Server.Host/Security/AuthenticationContextFactory.cs @@ -76,6 +76,7 @@ namespace Tgstation.Server.Host.Security CurrentAuthenticationContext = new AuthenticationContext(); return; } + systemIdentity = null; } diff --git a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs index 3e7f202b2e..c3b67cb489 100644 --- a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs +++ b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs @@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Security if (tokenValidatedContext == null) throw new ArgumentNullException(nameof(tokenValidatedContext)); - //Find the user id in the token + // Find the user id in the token var userIdClaim = tokenValidatedContext.Principal.FindFirst(JwtRegisteredClaimNames.Sub); if (userIdClaim == default) throw new InvalidOperationException("Missing required claim!"); @@ -65,11 +65,11 @@ namespace Tgstation.Server.Host.Security } catch (InvalidOperationException) { - //we are not responsible for handling header validation issues + // we are not responsible for handling header validation issues return; } - //This populates the CurrentAuthenticationContext field for use by us and subsequent controllers + // This populates the CurrentAuthenticationContext field for use by us and subsequent controllers await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, tokenValidatedContext.SecurityToken.ValidFrom, cancellationToken).ConfigureAwait(false); var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext; @@ -78,9 +78,9 @@ namespace Tgstation.Server.Host.Security var claims = new List(); foreach (RightsType I in enumerator) { - //if there's no instance user, do a weird thing and add all the instance roles - //we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid - //if user is null that means they got the token with an expired password + // if there's no instance user, do a weird thing and add all the instance roles + // we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid + // if user is null that means they got the token with an expired password var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I); var rightEnum = RightsHelper.RightToType(I); var right = (Enum)Enum.ToObject(rightEnum, rightInt); diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs index 4cdd6be43a..77ab55db94 100644 --- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Identity; using System; +using System.Globalization; using System.Security.Cryptography; using Tgstation.Server.Host.Models; @@ -53,14 +54,20 @@ namespace Tgstation.Server.Host.Security /// public bool CheckUserPassword(User user, string password) { - switch (passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password)) + var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password); + switch (result) { case PasswordVerificationResult.Failed: return false; case PasswordVerificationResult.SuccessRehashNeeded: SetUserPassword(user, password, false); break; + case PasswordVerificationResult.Success: + break; + default: + throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Password hasher return unknown PasswordVerificationResult: {0}", result)); } + return true; } diff --git a/src/Tgstation.Server.Host/Security/IdentityCache.cs b/src/Tgstation.Server.Host/Security/IdentityCache.cs index 43a2b84302..2b4e751b08 100644 --- a/src/Tgstation.Server.Host/Security/IdentityCache.cs +++ b/src/Tgstation.Server.Host/Security/IdentityCache.cs @@ -61,8 +61,9 @@ namespace Tgstation.Server.Host.Security if (cachedIdentities.TryGetValue(user.Id, out var identCache)) { logger.LogTrace("Expiring previously cached identity..."); - identCache.Dispose(); //also clears it out + identCache.Dispose(); // also clears it out } + identCache = new IdentityCacheObject(systemIdentity.Clone(), asyncDelayer, () => { logger.LogDebug("Expiring system identity cache for user {1}", uid, user.Id); diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index 661f52f91a..2974f6d3b1 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -73,13 +73,13 @@ namespace Tgstation.Server.Host.Security throw new ArgumentNullException(nameof(user)); var now = DateTimeOffset.Now; - 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 + + // 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 asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs index 43065b1b5b..c71544cc35 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs @@ -63,13 +63,13 @@ namespace Tgstation.Server.Host.Security { if (identity != null) { - //var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841 - - var newIdentity = new WindowsIdentity(identity.Token); //the handle is cloned internally + // var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841 + var newIdentity = new WindowsIdentity(identity.Token); // the handle is cloned internally return new WindowsSystemIdentity(newIdentity); } - //can't clone a UP, shouldn't be trying to anyway, cloning is for impersonation + + // can't clone a UP, shouldn't be trying to anyway, cloning is for impersonation throw new InvalidOperationException("Cannot clone a UserPrincipal based WindowsSystemIdentity!"); } diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs index e1deb301f0..aec9d8c463 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs @@ -78,12 +78,13 @@ namespace Tgstation.Server.Host.Security } } return principal != null; - }; - + } + if (!TryGetPrincipalFromContextType(ContextType.Machine) && !TryGetPrincipalFromContextType(ContextType.Domain)) return null; return (ISystemIdentity)new WindowsSystemIdentity(principal); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, + cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -105,8 +106,8 @@ namespace Tgstation.Server.Host.Security logger.LogTrace("Successfully logged in username {0}!", originalUsername); - using (var handle = new SafeAccessTokenHandle(token)) //checked internally, windows identity always duplicates the handle when constructed with a userToken - return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); //https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271 + using (var handle = new SafeAccessTokenHandle(token)) // checked internally, windows identity always duplicates the handle when constructed with a userToken + return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); // https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271 }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Server.cs b/src/Tgstation.Server.Host/Server.cs index 1786fd6a19..1fa566b503 100644 --- a/src/Tgstation.Server.Host/Server.cs +++ b/src/Tgstation.Server.Host/Server.cs @@ -94,11 +94,11 @@ namespace Tgstation.Server.Host }; fsWatcher.EnableRaisingEvents = true; } + using (var webHost = webHostBuilder .UseStartup() .ConfigureServices(serviceCollection => serviceCollection.AddSingleton(this)) - .Build() - ) + .Build()) { logger = webHost.Services.GetRequiredService>(); await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false); @@ -127,6 +127,7 @@ namespace Tgstation.Server.Host logger.LogTrace("Aborted due to concurrency conflict!"); return false; } + updating = true; } @@ -153,13 +154,14 @@ namespace Tgstation.Server.Host updating = false; try { - //important to not leave this directory around if possible + // important to not leave this directory around if possible await ioManager.DeleteDirectory(updatePath, default).ConfigureAwait(false); } catch (Exception e2) { throw new AggregateException(e, e2); } + throw; } @@ -203,6 +205,7 @@ namespace Tgstation.Server.Host restartHandlers.Remove(handler); }); } + return new RestartRegistration(() => { }); } @@ -213,7 +216,7 @@ namespace Tgstation.Server.Host /// Implements /// /// The of any potential updates being applied - /// + /// A representing the running operation async Task Restart(Version newVersion) { CheckSanity(true); @@ -227,6 +230,7 @@ namespace Tgstation.Server.Host logger.LogTrace("Aborted due to concurrency conflict!"); return; } + RestartRequested = true; } @@ -237,7 +241,8 @@ namespace Tgstation.Server.Host logger.LogTrace("Running restart handlers..."); var cancellationToken = cts.Token; var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList()); - //YA GOT 10 SECONDS + + // YA GOT 10 SECONDS var expiryTask = Task.Delay(TimeSpan.FromSeconds(10)); await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false); logger.LogTrace("Joining restart handlers...");