WIP Stylecopping Host

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