From 78356eb68a4f49ce6da84d0c32638f5cdf93c77a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Aug 2020 13:00:53 -0400 Subject: [PATCH 1/8] Adds configuration option for swagger UI - Config version to 2.1.0 --- .../Configuration/GeneralConfiguration.cs | 9 +++++++-- src/Tgstation.Server.Host/Core/Application.cs | 3 ++- src/Tgstation.Server.Host/Setup/SetupWizard.cs | 4 +++- src/Tgstation.Server.Host/appsettings.json | 5 +++-- .../Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs | 5 ++++- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs index 69d5ec1b30..4b925e9342 100644 --- a/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/GeneralConfiguration.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; @@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.Configuration /// /// The current . /// - public static readonly Version CurrentConfigVersion = new Version(2, 0, 0); + public static readonly Version CurrentConfigVersion = new Version(2, 1, 0); /// /// The default value for . @@ -87,6 +87,11 @@ namespace Tgstation.Server.Host.Configuration /// public bool UseBasicWatchdog { get; set; } + /// + /// If the swagger UI should be made avaiable. + /// + public bool HostApiDocumemtation { get; set; } + /// /// Initializes a new instance of the . /// diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index a5104530bc..52a837f345 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -394,7 +394,8 @@ namespace Tgstation.Server.Host.Core // suppress OperationCancelledExceptions, they are just aborted HTTP requests applicationBuilder.UseCancelledRequestSuppression(); - if (hostingEnvironment.IsDevelopment()) + if (hostingEnvironment.IsDevelopment() + || generalConfiguration.HostApiDocumemtation) { applicationBuilder.UseSwagger(); applicationBuilder.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TGS API V4")); diff --git a/src/Tgstation.Server.Host/Setup/SetupWizard.cs b/src/Tgstation.Server.Host/Setup/SetupWizard.cs index fe7fdf4e00..b32bd9206d 100644 --- a/src/Tgstation.Server.Host/Setup/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Setup/SetupWizard.cs @@ -1,4 +1,4 @@ -using Microsoft.Data.Sqlite; +using Microsoft.Data.Sqlite; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -613,6 +613,8 @@ namespace Tgstation.Server.Host.Setup if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken)) newGeneralConfiguration.GitHubAccessToken = null; + newGeneralConfiguration.HostApiDocumemtation = await PromptYesNo("Host API Documentation? (y/n): ", cancellationToken).ConfigureAwait(false); + return newGeneralConfiguration; } diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 49409225a3..8bcf394cc2 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "General": { "MinimumPasswordLength": 15, "GitHubAccessToken": null, @@ -9,7 +9,8 @@ "UseBasicWatchdog": false, "UserLimit": 100, "InstanceLimit": 10, - "ValidInstancePaths": null + "ValidInstancePaths": null, + "HostApiDocumemtation": false }, "FileLogging": { "Directory": null, diff --git a/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs b/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs index 8d9d760faa..3d99525bdf 100644 --- a/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs +++ b/tests/Tgstation.Server.Host.Tests/Setup/TestSetupWizard.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -144,6 +144,7 @@ namespace Tgstation.Server.Host.Setup.Tests "-27", "5000", "fake token", + "y", //logging config "no", //cp config @@ -166,6 +167,7 @@ namespace Tgstation.Server.Host.Setup.Tests String.Empty, String.Empty, "n", + "n", //logging config "y", "not actually verified because lol mocks /../!@#$%^&*()/..///.", @@ -189,6 +191,7 @@ namespace Tgstation.Server.Host.Setup.Tests String.Empty, String.Empty, "y", + "y", "will faile", String.Empty, String.Empty, From 7ffcdf2ed3df22930823659813bd3ad4756b06fe Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Aug 2020 13:18:58 -0400 Subject: [PATCH 2/8] Option to not display DM output in Discord embed --- .../Models/DiscordConnectionStringBuilder.cs | 23 ++++++++++--- .../Models/DiscordDMOutputDisplayType.cs | 23 +++++++++++++ .../Chat/Providers/DiscordProvider.cs | 32 +++++++++++++++---- .../Instance/ChatTest.cs | 9 ++++-- 4 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 src/Tgstation.Server.Api/Models/DiscordDMOutputDisplayType.cs diff --git a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs index 2fe677ea02..57fba95013 100644 --- a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs +++ b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Linq; using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models @@ -12,11 +13,16 @@ namespace Tgstation.Server.Api.Models public override bool Valid => !String.IsNullOrEmpty(BotToken); /// - /// The Discord bot token + /// The Discord bot token. /// /// See https://discordapp.com/developers/docs/topics/oauth2#bots public string? BotToken { get; set; } + /// + /// The . + /// + public DiscordDMOutputDisplayType DMOutputDisplay { get; set; } + /// /// Construct a /// @@ -28,10 +34,17 @@ namespace Tgstation.Server.Api.Models /// The connection string public DiscordConnectionStringBuilder(string connectionString) { - BotToken = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); + if(connectionString == null) + throw new ArgumentNullException(nameof(connectionString)); + + var splits = connectionString.Split(';'); + BotToken = splits.First(); + if (splits.Length < 2 || !Enum.TryParse(splits[1], out var dMOutputDisplayType)) + dMOutputDisplayType = DiscordDMOutputDisplayType.Always; + DMOutputDisplay = dMOutputDisplayType; } /// - public override string ToString() => BotToken ?? "(null)"; + public override string ToString() => $"{BotToken};{(int)DMOutputDisplay}"; } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Api/Models/DiscordDMOutputDisplayType.cs b/src/Tgstation.Server.Api/Models/DiscordDMOutputDisplayType.cs new file mode 100644 index 0000000000..3af08a5daa --- /dev/null +++ b/src/Tgstation.Server.Api/Models/DiscordDMOutputDisplayType.cs @@ -0,0 +1,23 @@ +namespace Tgstation.Server.Api.Models +{ + /// + /// When the DM output section of Discord deployment embeds should be shown. + /// + public enum DiscordDMOutputDisplayType + { + /// + /// Always show. + /// + Always, + + /// + /// Only show if DM failed. + /// + OnError, + + /// + /// Never show. + /// + Never + } +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index e4b6aef02f..4488b7319a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -32,11 +32,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } } - /// - /// Gets the Discord bot token. - /// - string BotToken => ChatBot.ConnectionString; - /// /// The for the . /// @@ -52,6 +47,16 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// readonly List mappedChannels; + /// + /// The Discord bot token. + /// + readonly string botToken; + + /// + /// The . + /// + readonly DiscordDMOutputDisplayType outputDisplayType; + /// /// Normalize a discord mention string /// @@ -74,6 +79,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers : base(jobManager, logger, chatBot) { this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider)); + + var csb = new DiscordConnectionStringBuilder(chatBot.ConnectionString); + botToken = csb.BotToken; + outputDisplayType = csb.DMOutputDisplay; + client = new DiscordSocketClient(); client.MessageReceived += Client_MessageReceived; mappedChannels = new List(); @@ -150,7 +160,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { try { - await client.LoginAsync(TokenType.Bot, BotToken, true).ConfigureAwait(false); + await client.LoginAsync(TokenType.Bot, botToken, true).ConfigureAwait(false); Logger.LogTrace("Logged in."); cancellationToken.ThrowIfCancellationRequested(); @@ -379,7 +389,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers ? "The deployment completed successfully and will be available at the next server reboot." : "The deployment failed."; - if (dreamMakerOutput != null) + var showDMOutput = outputDisplayType switch + { + DiscordDMOutputDisplayType.Always => true, + DiscordDMOutputDisplayType.Never => false, + DiscordDMOutputDisplayType.OnError => errorMessage != null, + _ => throw new InvalidOperationException($"Invalid DiscordDMOutputDisplayType: {outputDisplayType}"), + }; + + if (showDMOutput && dreamMakerOutput != null) builder.AddField(new EmbedFieldBuilder { Name = "DreamMaker Output", diff --git a/tests/Tgstation.Server.Tests/Instance/ChatTest.cs b/tests/Tgstation.Server.Tests/Instance/ChatTest.cs index 57b9470e23..f41c1016d5 100644 --- a/tests/Tgstation.Server.Tests/Instance/ChatTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/ChatTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -93,7 +93,12 @@ namespace Tgstation.Server.Tests.Instance { var firstBot = new ChatBot { - ConnectionString = Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_TOKEN"), + ConnectionString = + new DiscordConnectionStringBuilder + { + BotToken = Environment.GetEnvironmentVariable("TGS4_TEST_DISCORD_TOKEN"), + DMOutputDisplay = DiscordDMOutputDisplayType.OnError + }.ToString(), Enabled = false, Name = "r4407", Provider = ChatProvider.Discord, From 2aac6ff4f8800c5222959e3939c8391feaf3bbb3 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Aug 2020 14:30:53 -0400 Subject: [PATCH 3/8] Properly bump TgsConfigVersion --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index d72259794c..5c2d3abf56 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ 4.5.0 - 2.0.0 + 2.1.0 7.3.0 8.2.0 5.2.3 From 64c34d4a8ab10fcad6afac253108b972c797907a Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Aug 2020 16:37:55 -0400 Subject: [PATCH 4/8] Add v to VersionString --- .../System/AssemblyInformationProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs index 3553623e84..81c17a464d 100644 --- a/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs +++ b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; using Tgstation.Server.Api; @@ -31,7 +31,7 @@ namespace Tgstation.Server.Host.System Path = assembly.Location; Name = assembly.GetName(); Version = Name.Version.Semver(); - VersionString = String.Concat(VersionPrefix, '-', Version); + VersionString = String.Concat(VersionPrefix, "-v", Version); } } } From 3e9457fb3e8c4cae414f2611c5bed5a8c5eccf08 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Aug 2020 16:56:46 -0400 Subject: [PATCH 5/8] Make TokenFactory constants configurable --- .../Configuration/SecurityConfiguration.cs | 48 +++++++++++++++++ src/Tgstation.Server.Host/Core/Application.cs | 1 + .../Security/CryptographySuite.cs | 12 ++--- .../Security/TokenFactory.cs | 51 ++++++++++--------- .../System/AssemblyInformationProvider.cs | 6 +-- .../System/IAssemblyInformationProvider.cs | 6 +-- src/Tgstation.Server.Host/appsettings.json | 6 +++ 7 files changed, 94 insertions(+), 36 deletions(-) create mode 100644 src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs diff --git a/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs b/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs new file mode 100644 index 0000000000..c0101eeca9 --- /dev/null +++ b/src/Tgstation.Server.Host/Configuration/SecurityConfiguration.cs @@ -0,0 +1,48 @@ +namespace Tgstation.Server.Host.Configuration +{ + /// + /// Configuration options pertaining to user security + /// + sealed class SecurityConfiguration + { + /// + /// The key for the the resides in + /// + public const string Section = "Security"; + + /// + /// Default value of . + /// + const uint DefaultTokenExpiryMinutes = 15; + + /// + /// Default value of . + /// + const uint DefaultTokenClockSkewMinutes = 1; + + /// + /// Default value of . + /// + const uint DefaultTokenSigningKeyByteAmount = 256; + + /// + /// Amount of minutes until generated s expire. + /// + public uint TokenExpiryMinutes { get; set; } = DefaultTokenExpiryMinutes; + + /// + /// Amount of minutes to skew the clock for validation. + /// + public uint TokenClockSkewMinutes { get; set; } = DefaultTokenClockSkewMinutes; + + /// + /// Amount of bytes to use in the . + /// + public uint TokenSigningKeyByteCount { get; set; } = DefaultTokenSigningKeyByteAmount; + + /// + /// A custom token signing key. Overrides . + /// + public string CustomTokenSigningKeyBase64 { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 52a837f345..0048f302d7 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -94,6 +94,7 @@ namespace Tgstation.Server.Host.Core // configure configuration services.UseStandardConfig(Configuration); services.UseStandardConfig(Configuration); + services.UseStandardConfig(Configuration); // enable options which give us config reloading services.AddOptions(); diff --git a/src/Tgstation.Server.Host/Security/CryptographySuite.cs b/src/Tgstation.Server.Host/Security/CryptographySuite.cs index 77ab55db94..979ca8b5df 100644 --- a/src/Tgstation.Server.Host/Security/CryptographySuite.cs +++ b/src/Tgstation.Server.Host/Security/CryptographySuite.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity; using System; using System.Globalization; using System.Security.Cryptography; @@ -31,12 +31,10 @@ namespace Tgstation.Server.Host.Security /// public byte[] GetSecureBytes(uint amount) { - using (var rng = new RNGCryptoServiceProvider()) - { - var byt = new byte[amount]; - rng.GetBytes(byt); - return byt; - } + using var rng = new RNGCryptoServiceProvider(); + var byt = new byte[amount]; + rng.GetBytes(byt); + return byt; } /// diff --git a/src/Tgstation.Server.Host/Security/TokenFactory.cs b/src/Tgstation.Server.Host/Security/TokenFactory.cs index b214df6d08..40a898abd5 100644 --- a/src/Tgstation.Server.Host/Security/TokenFactory.cs +++ b/src/Tgstation.Server.Host/Security/TokenFactory.cs @@ -1,4 +1,5 @@ -using Microsoft.IdentityModel.Tokens; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; using System; using System.Globalization; using System.IdentityModel.Tokens.Jwt; @@ -6,6 +7,7 @@ using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.System; @@ -14,24 +16,14 @@ namespace Tgstation.Server.Host.Security /// sealed class TokenFactory : ITokenFactory { - /// - /// Amount of minutes until generated s expire - /// - 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 SecurityConfiguration securityConfiguration; + /// /// The for the /// @@ -43,31 +35,44 @@ namespace Tgstation.Server.Host.Security /// The value of /// The used for generating the /// The used to generate the issuer name. + /// The containing the value of . public TokenFactory( IAsyncDelayer asyncDelayer, ICryptographySuite cryptographySuite, - IAssemblyInformationProvider assemblyInformationProvider) + IAssemblyInformationProvider assemblyInformationProvider, + IOptions securityConfigurationOptions) { + this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); + + if (cryptographySuite == null) + throw new ArgumentNullException(nameof(cryptographySuite)); + if (assemblyInformationProvider == null) + throw new ArgumentNullException(nameof(assemblyInformationProvider)); + + securityConfiguration = securityConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(securityConfigurationOptions)); + + var signingKeyBytes = String.IsNullOrWhiteSpace(securityConfiguration.CustomTokenSigningKeyBase64) + ? cryptographySuite.GetSecureBytes(securityConfiguration.TokenSigningKeyByteCount) + : Convert.FromBase64String(securityConfiguration.CustomTokenSigningKeyBase64); + ValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(cryptographySuite.GetSecureBytes(TokenSigningKeyByteAmount)), + IssuerSigningKey = new SymmetricSecurityKey(signingKeyBytes), ValidateIssuer = true, - ValidIssuer = assemblyInformationProvider.Name.Name, + ValidIssuer = assemblyInformationProvider.AssemblyName.Name, ValidateLifetime = true, ValidateAudience = true, ValidAudience = typeof(Token).Assembly.GetName().Name, - ClockSkew = TimeSpan.FromMinutes(TokenClockSkewMinutes), + ClockSkew = TimeSpan.FromMinutes(securityConfiguration.TokenClockSkewMinutes), RequireSignedTokens = true, RequireExpirationTime = true }; - - this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); } /// @@ -88,7 +93,7 @@ namespace Tgstation.Server.Host.Security if (nowUnix == lpuUnix) await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false); - var expiry = now.AddMinutes(TokenExpiryMinutes); + var expiry = now.AddMinutes(securityConfiguration.TokenExpiryMinutes); var claims = new Claim[] { new Claim(JwtRegisteredClaimNames.Sub, user.Id.Value.ToString(CultureInfo.InvariantCulture)), diff --git a/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs index 81c17a464d..94c1c00664 100644 --- a/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs +++ b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs @@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.System public Version Version { get; } /// - public AssemblyName Name { get; } + public AssemblyName AssemblyName { get; } /// public string Path { get; } @@ -29,8 +29,8 @@ namespace Tgstation.Server.Host.System { Assembly assembly = Assembly.GetExecutingAssembly(); Path = assembly.Location; - Name = assembly.GetName(); - Version = Name.Version.Semver(); + AssemblyName = assembly.GetName(); + Version = AssemblyName.Version.Semver(); VersionString = String.Concat(VersionPrefix, "-v", Version); } } diff --git a/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs b/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs index d4e8f66dca..30c97a618d 100644 --- a/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs +++ b/src/Tgstation.Server.Host/System/IAssemblyInformationProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Reflection; namespace Tgstation.Server.Host.System @@ -14,9 +14,9 @@ namespace Tgstation.Server.Host.System string Path { get; } /// - /// Gets the . + /// Gets the . /// - AssemblyName Name { get; } + AssemblyName AssemblyName { get; } /// /// Prefix to . diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 8bcf394cc2..bdc52afffc 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -49,5 +49,11 @@ "ResetAdminPassword": false, "ServerVersion": null, "ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True" + }, + "Security": { + "TokenExpiryMinutes": 15, + "TokenClockSkewMinutes": 1, + "TokenSigningKeyByteAmount": 256, + "CustomTokenSigningKeyBase64": null } } From bf313e9e285e1c5e267f324dd8f3e10a00c91833 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Aug 2020 21:42:09 -0400 Subject: [PATCH 6/8] Stop using TaskCreationOptions.LongRunning --- .../Components/Chat/Providers/IrcProvider.cs | 11 ++++--- .../Repository/LibGit2RepositoryFactory.cs | 5 +-- .../Components/Repository/Repository.cs | 26 ++++++++-------- .../Components/StaticFiles/Configuration.cs | 6 ++-- src/Tgstation.Server.Host/IO/Console.cs | 8 ++--- .../IO/DefaultIOManager.cs | 31 +++++++++++-------- .../IO/PosixSymlinkFactory.cs | 4 +-- .../IO/WindowsSymlinkFactory.cs | 4 +-- .../Security/WindowsSystemIdentity.cs | 7 +++-- .../Security/WindowsSystemIdentityFactory.cs | 5 +-- src/Tgstation.Server.Host/System/Process.cs | 3 +- .../System/WindowsProcessFeatures.cs | 3 +- 12 files changed, 62 insertions(+), 51 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index aa63d7b6fa..02e413701a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Extensions; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; using Tgstation.Server.Host.System; @@ -302,7 +303,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Logger.LogTrace("Exiting listening task..."); }, cancellationToken, - TaskCreationOptions.LongRunning, + DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); await nickCheckCompleteTcs.Task.ConfigureAwait(false); @@ -430,7 +431,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } }, cancellationToken, - TaskCreationOptions.LongRunning, + DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current) .ConfigureAwait(false); await HardDisconnect(cancellationToken).ConfigureAwait(false); @@ -472,7 +473,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } }, cancellationToken, - TaskCreationOptions.None, + DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); await Task.WhenAny( @@ -545,7 +546,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers }) .ToList(); } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public override Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -573,7 +574,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { Logger.LogWarning(e, "Unable to send to channel {0}!", channelName); } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public override async Task> SendUpdateMessage( diff --git a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs index 4e24002bb7..b09fbc9c45 100644 --- a/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs +++ b/src/Tgstation.Server.Host/Components/Repository/LibGit2RepositoryFactory.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.Components.Repository @@ -46,7 +47,7 @@ namespace Tgstation.Server.Host.Components.Repository return new LibGit2Sharp.Repository(path); }, cancellationToken, - TaskCreationOptions.LongRunning, + DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } @@ -63,7 +64,7 @@ namespace Tgstation.Server.Host.Components.Repository logger.LogTrace(ex, "Suppressing clone cancellation exception"); cancellationToken.ThrowIfCancellationRequested(); } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public CredentialsHandler GenerateCredentialsHandler(string username, string password) => (a, b, supportedCredentialTypes) => diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 7eb127fe83..53e7d5b50b 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -375,7 +375,7 @@ namespace Tgstation.Server.Host.Components.Repository } libGitRepo.RemoveUntrackedFiles(); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); if (result.Status == MergeStatus.Conflicts) { @@ -389,7 +389,7 @@ namespace Tgstation.Server.Host.Components.Repository await Task.Factory.StartNew(() => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions { PrettifyMessage = true - }), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + }), cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); } await eventConsumer.HandleEvent( @@ -420,7 +420,7 @@ namespace Tgstation.Server.Host.Components.Repository { libGitRepo.RemoveUntrackedFiles(); RawCheckout(committish, progressReporter, cancellationToken); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); } /// @@ -453,7 +453,7 @@ namespace Tgstation.Server.Host.Components.Repository { cancellationToken.ThrowIfCancellationRequested(); } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); } /// @@ -492,7 +492,7 @@ namespace Tgstation.Server.Host.Components.Repository { libGitRepo.Branches.Remove(branch); } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public async Task ResetToOrigin(Action progressReporter, CancellationToken cancellationToken) @@ -530,7 +530,7 @@ namespace Tgstation.Server.Host.Components.Repository { OnCheckoutProgress = CheckoutProgressHandler(progressReporter) }); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public async Task CopyTo(string path, CancellationToken cancellationToken) @@ -550,7 +550,7 @@ namespace Tgstation.Server.Host.Components.Repository cancellationToken.ThrowIfCancellationRequested(); return libGitRepo.Head.TrackedBranch.Tip.Sha; - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public async Task MergeOrigin(string committerName, string committerEmail, Action progressReporter, CancellationToken cancellationToken) @@ -601,7 +601,7 @@ namespace Tgstation.Server.Host.Components.Repository } libGitRepo.RemoveUntrackedFiles(); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); if (result.Status == MergeStatus.Conflicts) { @@ -643,7 +643,7 @@ namespace Tgstation.Server.Host.Components.Repository libGitRepo.Config.Set("user.name", committerName); cancellationToken.ThrowIfCancellationRequested(); libGitRepo.Config.Set("user.email", committerEmail); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); try @@ -668,7 +668,7 @@ namespace Tgstation.Server.Host.Components.Repository { OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10)) }); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); } void FinalReporter(int progress) => progressReporter((int)(((float)progress) / 100 * 90)); @@ -711,7 +711,7 @@ namespace Tgstation.Server.Host.Components.Repository logger.LogWarning(e, "Unable to make synchronization push!"); return false; } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); } /// @@ -732,7 +732,7 @@ namespace Tgstation.Server.Host.Components.Repository if (libGitRepo.Lookup(committish) != null) return true; return false; - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public Task ShaIsParent(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -770,6 +770,6 @@ namespace Tgstation.Server.Host.Components.Repository startSha); return false; - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index c18aee9d6a..de5bfb0174 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -287,7 +287,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) if (systemIdentity == null) - await Task.Factory.StartNew(ReadImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + await Task.Factory.StartNew(ReadImpl, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); else await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false); @@ -418,7 +418,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) if (systemIdentity == null) - await Task.Factory.StartNew(WriteImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + await Task.Factory.StartNew(WriteImpl, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); else await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false); @@ -436,7 +436,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false)) if (systemIdentity == null) - await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + await Task.Factory.StartNew(DoCreate, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current).ConfigureAwait(false); else await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/IO/Console.cs b/src/Tgstation.Server.Host/IO/Console.cs index f8c8162d29..66f1908e37 100644 --- a/src/Tgstation.Server.Host/IO/Console.cs +++ b/src/Tgstation.Server.Host/IO/Console.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -61,7 +61,7 @@ namespace Tgstation.Server.Host.IO { CheckAvailable(); global::System.Console.Read(); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public Task ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -97,7 +97,7 @@ namespace Tgstation.Server.Host.IO cancellationToken.ThrowIfCancellationRequested(); global::System.Console.WriteLine(); return passwordBuilder.ToString(); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -113,6 +113,6 @@ namespace Tgstation.Server.Host.IO global::System.Console.WriteLine(text); else global::System.Console.Write(text); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 92421074ba..d63c32753c 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -24,6 +24,11 @@ namespace Tgstation.Server.Host.IO /// public const int DefaultBufferSize = 4096; + /// + /// The used to spawn s for potentially long running, blocking operations. + /// + public const TaskCreationOptions BlockingTaskCreationOptions = TaskCreationOptions.None; + /// /// Recursively empty a directory /// @@ -149,7 +154,7 @@ namespace Tgstation.Server.Host.IO } /// - public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.CreateDirectory(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.CreateDirectory(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// public Task DeleteDirectory(string path, CancellationToken cancellationToken) @@ -162,18 +167,18 @@ namespace Tgstation.Server.Host.IO return Task.Factory.StartNew( () => NormalizeAndDelete(di, cancellationToken), cancellationToken, - TaskCreationOptions.LongRunning, + BlockingTaskCreationOptions, TaskScheduler.Current); } /// - public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Delete(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Delete(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// - public Task FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + public Task FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// - public Task DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + public Task DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// public string GetDirectoryName(string path) => Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path))); @@ -201,7 +206,7 @@ namespace Tgstation.Server.Host.IO } return results; - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// public Task MoveFile(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -211,7 +216,7 @@ namespace Tgstation.Server.Host.IO source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source))); destination = ResolvePath(destination); File.Move(source, destination); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// public Task MoveDirectory(string source, string destination, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -221,7 +226,7 @@ namespace Tgstation.Server.Host.IO source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source))); destination = ResolvePath(destination); Directory.Move(source, destination); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// public async Task ReadAllBytes(string path, CancellationToken cancellationToken) @@ -261,7 +266,7 @@ namespace Tgstation.Server.Host.IO } return (IReadOnlyList)results; - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// public Task> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -276,7 +281,7 @@ namespace Tgstation.Server.Host.IO } return (IReadOnlyList)results; - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// public async Task DownloadFile(Uri url, CancellationToken cancellationToken) @@ -312,7 +317,7 @@ namespace Tgstation.Server.Host.IO using var ms = new MemoryStream(zipFileBytes); using var archive = new ZipArchive(ms, ZipArchiveMode.Read); archive.ExtractToDirectory(path); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); /// public bool PathContainsParentAccess(string path) => path?.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Any(x => x == "..") ?? throw new ArgumentNullException(nameof(path)); @@ -323,6 +328,6 @@ namespace Tgstation.Server.Host.IO path = ResolvePath(path ?? throw new ArgumentNullException(nameof(path))); var fileInfo = new FileInfo(path); return new DateTimeOffset(fileInfo.LastWriteTimeUtc); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs index 4499d5ac13..997fa3d005 100644 --- a/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/PosixSymlinkFactory.cs @@ -1,4 +1,4 @@ -using Mono.Unix; +using Mono.Unix; using System; using System.IO; using System.Threading; @@ -28,6 +28,6 @@ namespace Tgstation.Server.Host.IO fsInfo = new UnixDirectoryInfo(targetPath); cancellationToken.ThrowIfCancellationRequested(); fsInfo.CreateSymbolicLink(linkPath); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs index a1426d7663..99f4221b6f 100644 --- a/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs +++ b/src/Tgstation.Server.Host/IO/WindowsSymlinkFactory.cs @@ -1,4 +1,4 @@ -using BetterWin32Errors; +using BetterWin32Errors; using System; using System.IO; using System.Threading; @@ -37,6 +37,6 @@ namespace Tgstation.Server.Host.IO cancellationToken.ThrowIfCancellationRequested(); if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags)) throw new Win32Exception(); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs index aad76f2360..a833dc4d14 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentity.cs @@ -1,8 +1,9 @@ -using System; +using System; using System.DirectoryServices.AccountManagement; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.Security { @@ -90,6 +91,6 @@ namespace Tgstation.Server.Host.Security if (identity == null) throw new InvalidOperationException("Impersonate using a UserPrincipal based WindowsSystemIdentity!"); WindowsIdentity.RunImpersonated(identity.AccessToken, action); - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs index 1b9e64e4e6..fa5eb5924b 100644 --- a/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs +++ b/src/Tgstation.Server.Host/Security/WindowsSystemIdentityFactory.cs @@ -5,6 +5,7 @@ using System.DirectoryServices.AccountManagement; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Security @@ -88,7 +89,7 @@ namespace Tgstation.Server.Host.Security return null; return (ISystemIdentity)new WindowsSystemIdentity(principal); }, - cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); /// public Task CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() => @@ -114,6 +115,6 @@ namespace Tgstation.Server.Host.Security using var handle = new SafeAccessTokenHandle(token); 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); + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/System/Process.cs b/src/Tgstation.Server.Host/System/Process.cs index 60734cf725..e034e1aedc 100644 --- a/src/Tgstation.Server.Host/System/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Host.Extensions; +using Tgstation.Server.Host.IO; namespace Tgstation.Server.Host.System { @@ -97,7 +98,7 @@ namespace Tgstation.Server.Host.System } }, default, // DCT: None available - TaskCreationOptions.LongRunning, + DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); logger.LogTrace("Created process ID: {0}", Id); diff --git a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs index a01d5e850f..f200be8192 100644 --- a/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs +++ b/src/Tgstation.Server.Host/System/WindowsProcessFeatures.cs @@ -7,6 +7,7 @@ using System.Management; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.IO; using Tgstation.Server.Host.Jobs; namespace Tgstation.Server.Host.System @@ -119,7 +120,7 @@ namespace Tgstation.Server.Host.System throw new Win32Exception(); }, cancellationToken, - TaskCreationOptions.LongRunning, + DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } } From 882e05ac6e40c768359e15b7fe0cfa647310d97f Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Aug 2020 23:08:13 -0400 Subject: [PATCH 7/8] Discord improvements - Channel 0 can be mapped to indicate all unmapped channels the bot has access to. - Discord bot now "types" when responding to a command --- .../Components/Chat/ChatManager.cs | 10 +- .../Components/Chat/Message.cs | 11 +- .../Chat/Providers/DiscordProvider.cs | 193 ++++++++++++------ 3 files changed, 150 insertions(+), 64 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index 49ffccb072..25807b4d2f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -21,7 +21,10 @@ namespace Tgstation.Server.Host.Components.Chat #pragma warning disable CA1506 sealed class ChatManager : IChatManager, IRestartHandler { - const string CommonMention = "!tgs"; + /// + /// The common bot mention. + /// + public const string CommonMention = "!tgs"; /// /// The for the @@ -415,7 +418,11 @@ namespace Tgstation.Server.Host.Components.Chat { // prune disconnected providers foreach (var I in messageTasks.Where(x => !x.Key.Disposed).ToList()) + { messageTasks.Remove(I.Key); + if (I.Value.IsCompleted) + (await I.Value.ConfigureAwait(false))?.Context?.Dispose(); + } // add new ones Task updatedTask; @@ -439,6 +446,7 @@ namespace Tgstation.Server.Host.Components.Chat foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) { var message = await I.Value.ConfigureAwait(false); + using var messageContext = message?.Context; var messageNumber = Interlocked.Increment(ref messagesProcessed); using (LogContext.PushProperty("ChatMessage", messageNumber)) await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); diff --git a/src/Tgstation.Server.Host/Components/Chat/Message.cs b/src/Tgstation.Server.Host/Components/Chat/Message.cs index 5f661fd4f7..ef87a84d34 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Message.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Message.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.Host.Components.Chat.Providers +using System; + +namespace Tgstation.Server.Host.Components.Chat.Providers { /// /// Represents a message recieved by a @@ -14,5 +16,10 @@ /// The who sent the /// public ChatUser User { get; set; } + + /// + /// The that should be d once the is processed. + /// + public IDisposable Context { get; set; } } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index 4488b7319a..d624c960d4 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -106,53 +106,76 @@ namespace Tgstation.Server.Host.Components.Chat.Providers if (e.Author.Id == client.CurrentUser.Id) return; - if (e.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase)) + IDisposable typingState = null; + void StartTyping() => typingState = e.Channel.EnterTypingState(); + try { - // DCT: None available - await SendMessage( - e.Channel.Id, - "https://youtu.be/LrNu-SuFF_o", - default) - .ConfigureAwait(false); - } - - var pm = e.Channel is IPrivateChannel; - - if (!pm && !mappedChannels.Contains(e.Channel.Id)) - { - var mentionedUs = e.MentionedUsers.Any(x => x.Id == client.CurrentUser.Id); - if (mentionedUs) + if (e.Content.Equals("Based on what?", StringComparison.OrdinalIgnoreCase)) { - Logger.LogTrace("Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!", e.Channel.Id, e.Channel.Name, e.Author.Id, e.Author.Username); + StartTyping(); // DCT: None available - await SendMessage(e.Channel.Id, "I do not respond to this channel!", default).ConfigureAwait(false); + await SendMessage( + e.Channel.Id, + "https://youtu.be/LrNu-SuFF_o", + default) + .ConfigureAwait(false); + return; } - return; - } + var pm = e.Channel is IPrivateChannel; + var shouldNotAnswer = !pm; + if (shouldNotAnswer) + lock (mappedChannels) + shouldNotAnswer = !mappedChannels.Contains(e.Channel.Id); - var result = new Message - { - Content = NormalizeMentions(e.Content), - User = new ChatUser + var content = NormalizeMentions(e.Content); + var mentionedUs = e.MentionedUsers.Any(x => x.Id == client.CurrentUser.Id) + || (!shouldNotAnswer && content.Split(' ').First().Equals(ChatManager.CommonMention, StringComparison.OrdinalIgnoreCase)); + if (mentionedUs) + StartTyping(); + + if (shouldNotAnswer) { - RealId = e.Author.Id, - Channel = new ChannelRepresentation + if (mentionedUs) { - RealId = e.Channel.Id, - IsPrivateChannel = pm, - ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", - FriendlyName = e.Channel.Name + Logger.LogTrace("Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!", e.Channel.Id, e.Channel.Name, e.Author.Id, e.Author.Username); - // isAdmin and Tag populated by manager - }, - FriendlyName = e.Author.Username, - Mention = NormalizeMentions(e.Author.Mention) + // DCT: None available + await SendMessage(e.Channel.Id, "I do not respond to this channel!", default).ConfigureAwait(false); + } + + return; } - }; - EnqueueMessage(result); + var result = new Message + { + Content = content, + User = new ChatUser + { + RealId = e.Author.Id, + Channel = new ChannelRepresentation + { + RealId = e.Channel.Id, + IsPrivateChannel = pm, + ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN", + FriendlyName = e.Channel.Name + + // isAdmin and Tag populated by manager + }, + FriendlyName = e.Author.Username, + Mention = NormalizeMentions(e.Author.Mention) + }, + Context = typingState + }; + + EnqueueMessage(result); + typingState = null; + } + finally + { + typingState?.Dispose(); + } } /// @@ -236,29 +259,47 @@ namespace Tgstation.Server.Host.Components.Chat.Providers throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); var channelId = channelFromDB.DiscordChannelId.Value; - var discordChannel = client.GetChannel(channelId); - if (discordChannel is ITextChannel textChannel) + ulong discordChannelId; + string connectionName; + string friendlyName; + if (channelId == 0) { - var channelModel = new ChannelRepresentation + connectionName = client.CurrentUser.Username; + friendlyName = "(Unmapped accessible channels)"; + discordChannelId = 0; + } + else + { + var discordChannel = client.GetChannel(channelId); + if (!(discordChannel is ITextChannel textChannel)) { - RealId = discordChannel.Id, - IsAdminChannel = channelFromDB.IsAdminChannel == true, - ConnectionName = textChannel.Guild.Name, - FriendlyName = textChannel.Name, - IsPrivateChannel = false, - Tag = channelFromDB.Tag - }; - Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName); - return channelModel; + Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType()); + return null; + } + + discordChannelId = textChannel.Id; + connectionName = textChannel.Guild.Name; + friendlyName = textChannel.Name; } - Logger.LogWarning("Cound not map channel {0}! Incorrect type: {1}", channelId, discordChannel?.GetType()); - return null; + var channelModel = new ChannelRepresentation + { + RealId = discordChannelId, + IsAdminChannel = channelFromDB.IsAdminChannel == true, + ConnectionName = connectionName, + FriendlyName = friendlyName, + IsPrivateChannel = false, + Tag = channelFromDB.Tag + }; + Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName); + return channelModel; } - var enumerator = channels.Select(x => GetModelChannelFromDBChannel(x)).Where(x => x != null).ToList(); + var enumerator = channels + .Select(x => GetModelChannelFromDBChannel(x)) + .Where(x => x != null).ToList(); - lock (client) + lock (mappedChannels) { mappedChannels.Clear(); mappedChannels.AddRange(enumerator.Select(x => x.RealId)); @@ -270,21 +311,51 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public override async Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) { + var requestOptions = new RequestOptions + { + CancelToken = cancellationToken, + Timeout = 10000 // prevent stupid long hold ups from this + }; + + Task SendToChannel(IMessageChannel channel) => channel.SendMessageAsync( + message, + false, + null, + requestOptions); + try { + if (channelId == 0) + { + var unmappedTextChannels = client + .Guilds + .SelectMany(x => x.TextChannels); + + lock (mappedChannels) + unmappedTextChannels = unmappedTextChannels.Where(x => !mappedChannels.Contains(x.Id)); + + // discord API confirmed weak boned: https://stackoverflow.com/a/52462336 + var channelCount = 0UL; + var tasks = unmappedTextChannels + .Select(x => + { + ++channelCount; + return SendToChannel(x); + }); + + if (channelCount > 0) + { + Logger.LogTrace("Dispatched to {0} unmapped channels...", channelCount); + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + return; + } + if (!(client.GetChannel(channelId) is IMessageChannel channel)) return; - await channel.SendMessageAsync( - message, - false, - null, - new RequestOptions - { - CancelToken = cancellationToken, - Timeout = 10000 // prevent stupid long hold ups from this - }) - .ConfigureAwait(false); + await SendToChannel(channel).ConfigureAwait(false); } catch (Exception e) { From 7e4721c37a8d75b93a5af0e026f55b732cc77a9b Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 17 Aug 2020 23:11:29 -0400 Subject: [PATCH 8/8] Bump client version to 8.3.0 --- build/Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Version.props b/build/Version.props index 5c2d3abf56..e96b43801a 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,7 +5,7 @@ 4.5.0 2.1.0 7.3.0 - 8.2.0 + 8.3.0 5.2.3 0.4.0 1.1.0