Merge pull request #1105 from tgstation/1103-SwaggerUI [APIDeploy][NugetDeploy]

Adds configuration option for swagger UI. Add DM Output display options to Discord embed. Add TokenFactory config settings. Plus other Discord buffs
This commit is contained in:
Jordan Brown
2020-08-17 23:50:10 -04:00
committed by GitHub
29 changed files with 402 additions and 176 deletions
+2 -2
View File
@@ -3,9 +3,9 @@
<!-- This is the authorative version list -->
<!-- Integration tests will ensure they match across the board -->
<TgsCoreVersion>4.5.0</TgsCoreVersion>
<TgsConfigVersion>2.0.0</TgsConfigVersion>
<TgsConfigVersion>2.1.0</TgsConfigVersion>
<TgsApiVersion>7.3.0</TgsApiVersion>
<TgsClientVersion>8.2.0</TgsClientVersion>
<TgsClientVersion>8.3.0</TgsClientVersion>
<TgsDmapiVersion>5.2.3</TgsDmapiVersion>
<TgsControlPanelVersion>0.4.0</TgsControlPanelVersion>
<TgsHostWatchdogVersion>1.1.0</TgsHostWatchdogVersion>
@@ -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);
/// <summary>
/// The Discord bot token
/// The Discord bot token.
/// </summary>
/// <remarks>See https://discordapp.com/developers/docs/topics/oauth2#bots</remarks>
public string? BotToken { get; set; }
/// <summary>
/// The <see cref="DiscordDMOutputDisplayType"/>.
/// </summary>
public DiscordDMOutputDisplayType DMOutputDisplay { get; set; }
/// <summary>
/// Construct a <see cref="DiscordConnectionStringBuilder"/>
/// </summary>
@@ -28,10 +34,17 @@ namespace Tgstation.Server.Api.Models
/// <param name="connectionString">The connection string</param>
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<DiscordDMOutputDisplayType>(splits[1], out var dMOutputDisplayType))
dMOutputDisplayType = DiscordDMOutputDisplayType.Always;
DMOutputDisplay = dMOutputDisplayType;
}
/// <inheritdoc />
public override string ToString() => BotToken ?? "(null)";
public override string ToString() => $"{BotToken};{(int)DMOutputDisplay}";
}
}
}
@@ -0,0 +1,23 @@
namespace Tgstation.Server.Api.Models
{
/// <summary>
/// When the DM output section of Discord deployment embeds should be shown.
/// </summary>
public enum DiscordDMOutputDisplayType
{
/// <summary>
/// Always show.
/// </summary>
Always,
/// <summary>
/// Only show if DM failed.
/// </summary>
OnError,
/// <summary>
/// Never show.
/// </summary>
Never
}
}
@@ -21,7 +21,10 @@ namespace Tgstation.Server.Host.Components.Chat
#pragma warning disable CA1506
sealed class ChatManager : IChatManager, IRestartHandler
{
const string CommonMention = "!tgs";
/// <summary>
/// The common bot mention.
/// </summary>
public const string CommonMention = "!tgs";
/// <summary>
/// The <see cref="IProviderFactory"/> for the <see cref="ChatManager"/>
@@ -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);
@@ -1,4 +1,6 @@
namespace Tgstation.Server.Host.Components.Chat.Providers
using System;
namespace Tgstation.Server.Host.Components.Chat.Providers
{
/// <summary>
/// Represents a message recieved by a <see cref="IProvider"/>
@@ -14,5 +16,10 @@
/// The <see cref="ChatUser"/> who sent the <see cref="Message"/>
/// </summary>
public ChatUser User { get; set; }
/// <summary>
/// The <see cref="IDisposable"/> that should be <see cref="IDisposable.Dispose"/>d once the <see cref="Message"/> is processed.
/// </summary>
public IDisposable Context { get; set; }
}
}
}
@@ -32,11 +32,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
}
/// <summary>
/// Gets the Discord bot token.
/// </summary>
string BotToken => ChatBot.ConnectionString;
/// <summary>
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="DiscordProvider"/>.
/// </summary>
@@ -52,6 +47,16 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// </summary>
readonly List<ulong> mappedChannels;
/// <summary>
/// The Discord bot token.
/// </summary>
readonly string botToken;
/// <summary>
/// The <see cref="DiscordDMOutputDisplayType"/>.
/// </summary>
readonly DiscordDMOutputDisplayType outputDisplayType;
/// <summary>
/// Normalize a discord mention string
/// </summary>
@@ -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<ulong>();
@@ -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();
}
}
/// <inheritdoc />
@@ -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
/// <inheritdoc />
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",
@@ -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);
/// <inheritdoc />
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);
/// <inheritdoc />
public override async Task<Func<string, string, Task>> SendUpdateMessage(
@@ -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);
/// <inheritdoc />
public CredentialsHandler GenerateCredentialsHandler(string username, string password) => (a, b, supportedCredentialTypes) =>
@@ -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);
}
/// <inheritdoc />
@@ -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);
}
/// <summary>
@@ -492,7 +492,7 @@ namespace Tgstation.Server.Host.Components.Repository
{
libGitRepo.Branches.Remove(branch);
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current);
/// <inheritdoc />
public async Task ResetToOrigin(Action<int> 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);
/// <inheritdoc />
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);
/// <inheritdoc />
public async Task<bool?> MergeOrigin(string committerName, string committerEmail, Action<int> 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);
}
/// <inheritdoc />
@@ -732,7 +732,7 @@ namespace Tgstation.Server.Host.Components.Repository
if (libGitRepo.Lookup<Commit>(committish) != null)
return true;
return false;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current);
/// <inheritdoc />
public Task<bool> 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);
}
}
@@ -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);
@@ -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
/// <summary>
/// The current <see cref="ConfigVersion"/>.
/// </summary>
public static readonly Version CurrentConfigVersion = new Version(2, 0, 0);
public static readonly Version CurrentConfigVersion = new Version(2, 1, 0);
/// <summary>
/// The default value for <see cref="ServerInformation.MinimumPasswordLength"/>.
@@ -87,6 +87,11 @@ namespace Tgstation.Server.Host.Configuration
/// </summary>
public bool UseBasicWatchdog { get; set; }
/// <summary>
/// If the swagger UI should be made avaiable.
/// </summary>
public bool HostApiDocumemtation { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="GeneralConfiguration"/> <see langword="class"/>.
/// </summary>
@@ -0,0 +1,48 @@
namespace Tgstation.Server.Host.Configuration
{
/// <summary>
/// Configuration options pertaining to user security
/// </summary>
sealed class SecurityConfiguration
{
/// <summary>
/// The key for the <see cref="Microsoft.Extensions.Configuration.IConfigurationSection"/> the <see cref="SecurityConfiguration"/> resides in
/// </summary>
public const string Section = "Security";
/// <summary>
/// Default value of <see cref="TokenExpiryMinutes"/>.
/// </summary>
const uint DefaultTokenExpiryMinutes = 15;
/// <summary>
/// Default value of <see cref="TokenClockSkewMinutes"/>.
/// </summary>
const uint DefaultTokenClockSkewMinutes = 1;
/// <summary>
/// Default value of <see cref="TokenSigningKeyByteCount"/>.
/// </summary>
const uint DefaultTokenSigningKeyByteAmount = 256;
/// <summary>
/// Amount of minutes until generated <see cref="Api.Models.Token"/>s expire.
/// </summary>
public uint TokenExpiryMinutes { get; set; } = DefaultTokenExpiryMinutes;
/// <summary>
/// Amount of minutes to skew the clock for <see cref="Api.Models.Token"/> validation.
/// </summary>
public uint TokenClockSkewMinutes { get; set; } = DefaultTokenClockSkewMinutes;
/// <summary>
/// Amount of bytes to use in the <see cref="Microsoft.IdentityModel.Tokens.TokenValidationParameters.IssuerSigningKey"/>.
/// </summary>
public uint TokenSigningKeyByteCount { get; set; } = DefaultTokenSigningKeyByteAmount;
/// <summary>
/// A custom token signing key. Overrides <see cref="TokenSigningKeyByteCount"/>.
/// </summary>
public string CustomTokenSigningKeyBase64 { get; set; }
}
}
@@ -94,6 +94,7 @@ namespace Tgstation.Server.Host.Core
// configure configuration
services.UseStandardConfig<UpdatesConfiguration>(Configuration);
services.UseStandardConfig<ControlPanelConfiguration>(Configuration);
services.UseStandardConfig<SecurityConfiguration>(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"));
+4 -4
View File
@@ -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);
/// <inheritdoc />
public Task<string> 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);
/// <inheritdoc />
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);
}
}
@@ -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
/// </summary>
public const int DefaultBufferSize = 4096;
/// <summary>
/// The <see cref="TaskCreationOptions"/> used to spawn <see cref="Task"/>s for potentially long running, blocking operations.
/// </summary>
public const TaskCreationOptions BlockingTaskCreationOptions = TaskCreationOptions.None;
/// <summary>
/// Recursively empty a directory
/// </summary>
@@ -149,7 +154,7 @@ namespace Tgstation.Server.Host.IO
}
/// <inheritdoc />
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);
/// <inheritdoc />
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);
}
/// <inheritdoc />
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);
/// <inheritdoc />
public Task<bool> FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
public Task<bool> FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
/// <inheritdoc />
public Task<bool> DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
public Task<bool> DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
/// <inheritdoc />
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);
/// <inheritdoc />
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);
/// <inheritdoc />
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);
/// <inheritdoc />
public async Task<byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
@@ -261,7 +266,7 @@ namespace Tgstation.Server.Host.IO
}
return (IReadOnlyList<string>)results;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
/// <inheritdoc />
public Task<IReadOnlyList<string>> GetFiles(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
@@ -276,7 +281,7 @@ namespace Tgstation.Server.Host.IO
}
return (IReadOnlyList<string>)results;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}, cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
/// <inheritdoc />
public async Task<byte[]> 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);
/// <inheritdoc />
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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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
/// <inheritdoc />
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;
}
/// <inheritdoc />
@@ -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
/// <inheritdoc />
sealed class TokenFactory : ITokenFactory
{
/// <summary>
/// Amount of minutes until generated <see cref="Token"/>s expire
/// </summary>
const uint TokenExpiryMinutes = 15;
/// <summary>
/// Amount of minutes to skew the clock for <see cref="Token"/> validation
/// </summary>
const uint TokenClockSkewMinutes = 1;
/// <summary>
/// Amount of bytes to use in the <see cref="TokenValidationParameters.IssuerSigningKey"/>
/// </summary>
const uint TokenSigningKeyByteAmount = 256;
/// <inheritdoc />
public TokenValidationParameters ValidationParameters { get; }
/// <summary>
/// The <see cref="SecurityConfiguration"/> for the <see cref="TokenFactory"/>.
/// </summary>
readonly SecurityConfiguration securityConfiguration;
/// <summary>
/// The <see cref="IAsyncDelayer"/> for the <see cref="TokenFactory"/>
/// </summary>
@@ -43,31 +35,44 @@ namespace Tgstation.Server.Host.Security
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
/// <param name="cryptographySuite">The <see cref="ICryptographySuite"/> used for generating the <see cref="ValidationParameters"/></param>
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> used to generate the issuer name.</param>
/// <param name="securityConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="securityConfiguration"/>.</param>
public TokenFactory(
IAsyncDelayer asyncDelayer,
ICryptographySuite cryptographySuite,
IAssemblyInformationProvider assemblyInformationProvider)
IAssemblyInformationProvider assemblyInformationProvider,
IOptions<SecurityConfiguration> 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));
}
/// <inheritdoc />
@@ -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)),
@@ -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);
}
}
}
@@ -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);
/// <inheritdoc />
public Task<ISystemIdentity> 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);
}
}
@@ -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;
}
@@ -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; }
/// <inheritdoc />
public AssemblyName Name { get; }
public AssemblyName AssemblyName { get; }
/// <inheritdoc />
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);
}
}
}
@@ -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; }
/// <summary>
/// Gets the <see cref="AssemblyName"/>.
/// Gets the <see cref="global::System.Reflection.AssemblyName"/>.
/// </summary>
AssemblyName Name { get; }
AssemblyName AssemblyName { get; }
/// <summary>
/// Prefix to <see cref="VersionString"/>.
+2 -1
View File
@@ -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);
@@ -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);
}
}
+9 -2
View File
@@ -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
}
}
@@ -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,
@@ -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,