diff --git a/build/Version.props b/build/Version.props
index d72259794c..e96b43801a 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -3,9 +3,9 @@
4.5.0
- 2.0.0
+ 2.1.0
7.3.0
- 8.2.0
+ 8.3.0
5.2.3
0.4.0
1.1.0
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/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 e4b6aef02f..d624c960d4 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();
@@ -96,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();
+ }
}
///
@@ -150,7 +183,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();
@@ -226,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));
@@ -260,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)
{
@@ -379,7 +460,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/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/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/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 a5104530bc..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();
@@ -394,7 +395,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/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/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/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/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/System/AssemblyInformationProvider.cs b/src/Tgstation.Server.Host/System/AssemblyInformationProvider.cs
index 3553623e84..94c1c00664 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;
@@ -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,9 +29,9 @@ namespace Tgstation.Server.Host.System
{
Assembly assembly = Assembly.GetExecutingAssembly();
Path = assembly.Location;
- Name = assembly.GetName();
- Version = Name.Version.Semver();
- VersionString = String.Concat(VersionPrefix, '-', Version);
+ 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/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);
}
}
diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json
index 49409225a3..bdc52afffc 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,
@@ -48,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
}
}
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,
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,