diff --git a/README.md b/README.md
index ea66abd2b8..f5dc9af2ad 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ The following dependencies are required to run tgstation-server on Linux alongsi
- libc6-i386
- libstdc++6:i386
+- libssl1.0.0
- gcc-multilib (Only on 64-bit systems)
Note that tgstation-server has only ever been tested on Linux via it's [docker environment](build/Dockerfile#L22). If you are having trouble with something in a native installation, or figure out a required workaround, please contact project maintainers so this documentation may be better updated.
diff --git a/build/Version.props b/build/Version.props
index b09d27300e..ab5b742297 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -3,8 +3,8 @@
4.2.5
- 6.4.0
- 6.3.0
+ 6.4.1
+ 7.0.05.2.10.4.01.1.0
diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs
index 2ee152dc56..69d6fa32e6 100644
--- a/src/Tgstation.Server.Api/ApiHeaders.cs
+++ b/src/Tgstation.Server.Api/ApiHeaders.cs
@@ -69,12 +69,12 @@ namespace Tgstation.Server.Api
///
/// The client's user agent as a if valid
///
- public ProductHeaderValue UserAgent => ProductInfoHeaderValue.TryParse(RawUserAgent, out var userAgent) ? userAgent.Product : null;
+ public ProductHeaderValue? UserAgent => ProductInfoHeaderValue.TryParse(RawUserAgent, out var userAgent) ? userAgent.Product : null;
///
/// The client's raw user agent
///
- public string RawUserAgent { get; }
+ public string? RawUserAgent { get; }
///
/// The client's API version
@@ -84,17 +84,17 @@ namespace Tgstation.Server.Api
///
/// The client's JWT
///
- public string Token { get; }
+ public string? Token { get; }
///
/// The client's username
///
- public string Username { get; }
+ public string? Username { get; }
///
/// The client's password
///
- public string Password { get; }
+ public string? Password { get; }
///
/// If the header uses password or JWT authentication
@@ -236,7 +236,7 @@ namespace Tgstation.Server.Api
/// The value of
/// The value of
/// The value of
- ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)
+ ApiHeaders(ProductHeaderValue userAgent, string? token, string? username, string? password)
{
RawUserAgent = userAgent?.ToString();
Token = token;
@@ -274,7 +274,7 @@ namespace Tgstation.Server.Api
headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
- instanceId = instanceId ?? InstanceId;
+ instanceId ??= InstanceId;
if (instanceId.HasValue)
headers.Add(InstanceIdHeader, instanceId.ToString());
}
diff --git a/src/Tgstation.Server.Api/Models/Administration.cs b/src/Tgstation.Server.Api/Models/Administration.cs
index f325862bbe..9febaa977a 100644
--- a/src/Tgstation.Server.Api/Models/Administration.cs
+++ b/src/Tgstation.Server.Api/Models/Administration.cs
@@ -15,16 +15,16 @@ namespace Tgstation.Server.Api.Models
///
/// The GitHub repository the server is built to recieve updates from
///
- public Uri TrackedRepositoryUrl { get; set; }
+ public Uri? TrackedRepositoryUrl { get; set; }
///
/// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If is higher than 's the update cannot be applied due to API changes
///
- public Version LatestVersion { get; set; }
+ public Version? LatestVersion { get; set; }
///
/// Changes the version of Tgstation.Server.Host to the given version from the upstream repository
///
- public Version NewVersion { get; set; }
+ public Version? NewVersion { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Byond.cs b/src/Tgstation.Server.Api/Models/Byond.cs
index 6ad465fbf6..d5322db8c0 100644
--- a/src/Tgstation.Server.Api/Models/Byond.cs
+++ b/src/Tgstation.Server.Api/Models/Byond.cs
@@ -10,11 +10,11 @@ namespace Tgstation.Server.Api.Models
///
/// The of the installation used for new compiles. Will be if the user does not have permission to view it or there is no BYOND version installed. Only considers the and numbers.
///
- public Version Version { get; set; }
+ public Version? Version { get; set; }
///
/// The being used to install a new
///
- public Job InstallJob { get; set; }
+ public Job? InstallJob { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/ChatBot.cs b/src/Tgstation.Server.Api/Models/ChatBot.cs
index afa8aab001..c0034d32bb 100644
--- a/src/Tgstation.Server.Api/Models/ChatBot.cs
+++ b/src/Tgstation.Server.Api/Models/ChatBot.cs
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Api.Models
///
/// Channels the Discord bot should listen/announce in
///
- public ICollection Channels { get; set; }
+ public ICollection? Channels { get; set; }
///
/// Validates are correct for the
@@ -20,15 +20,12 @@ namespace Tgstation.Server.Api.Models
{
if (!Provider.HasValue)
return true;
- switch (Provider.Value)
+ return Provider.Value switch
{
- case ChatProvider.Discord:
- return Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true;
- case ChatProvider.Irc:
- return Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true;
- default:
- throw new InvalidOperationException("Invalid provider type!");
- }
+ ChatProvider.Discord => Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true,
+ ChatProvider.Irc => Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true,
+ _ => throw new InvalidOperationException("Invalid provider type!"),
+ };
}
}
}
diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs
index 0039e9bc20..805fa1fbbb 100644
--- a/src/Tgstation.Server.Api/Models/ChatChannel.cs
+++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Api.Models
/// If multiple copies of the same channel with different keys are added to the server, the one that will be used is undefined.
///
[StringLength(Limits.MaximumIndexableStringLength)]
- public string IrcChannel { get; set; }
+ public string? IrcChannel { get; set; }
///
/// The Discord channel ID
@@ -41,6 +41,6 @@ namespace Tgstation.Server.Api.Models
/// A custom tag users can define to group channels together
///
[StringLength(Limits.MaximumStringLength)]
- public string Tag { get; set; }
+ public string? Tag { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/CompileJob.cs b/src/Tgstation.Server.Api/Models/CompileJob.cs
index 44a3b7baad..964bc9dad7 100644
--- a/src/Tgstation.Server.Api/Models/CompileJob.cs
+++ b/src/Tgstation.Server.Api/Models/CompileJob.cs
@@ -8,16 +8,16 @@ namespace Tgstation.Server.Api.Models
///
/// The relating to this job
///
- public Job Job { get; set; }
+ public Job? Job { get; set; }
///
/// Git revision the compiler ran on. Not modifiable
///
- public RevisionInformation RevisionInformation { get; set; }
+ public RevisionInformation? RevisionInformation { get; set; }
///
/// The the was made with
///
- public Version ByondVersion { get; set; }
+ public Version? ByondVersion { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs
index 01d5380881..5703586515 100644
--- a/src/Tgstation.Server.Api/Models/ConfigurationFile.cs
+++ b/src/Tgstation.Server.Api/Models/ConfigurationFile.cs
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Api.Models
/// The path to the file
///
[StringLength(Limits.MaximumStringLength)]
- public string Path { get; set; }
+ public string? Path { get; set; }
///
/// If access to the file was denied for the operation
@@ -26,13 +26,13 @@ namespace Tgstation.Server.Api.Models
///
/// The MD5 hash of the file when last read by the user. If this doesn't match during update actions, the write will be denied with
///
- public string LastReadHash { get; set; }
+ public string? LastReadHash { get; set; }
///
/// The content of the . Will be if is or during listing and write operations
///
-#pragma warning disable CA1819 // Properties should not return arrays
- public byte[] Content { get; set; }
-#pragma warning restore CA1819 // Properties should not return arrays
+#pragma warning disable CA1819, SA1011 // Properties should not return arrays, Closing square bracket should be followed by a space
+ public byte[]? Content { get; set; }
+#pragma warning restore CA1819, SA1011 // Properties should not return arrays, Closing square bracket should be followed by a space
}
}
diff --git a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs
index a919e18380..2fe677ea02 100644
--- a/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs
+++ b/src/Tgstation.Server.Api/Models/DiscordConnectionStringBuilder.cs
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Api.Models
/// The Discord bot token
///
/// See https://discordapp.com/developers/docs/topics/oauth2#bots
- public string BotToken { get; set; }
+ public string? BotToken { get; set; }
///
/// Construct a
@@ -32,6 +32,6 @@ namespace Tgstation.Server.Api.Models
}
///
- public override string ToString() => BotToken;
+ public override string ToString() => BotToken ?? "(null)";
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Api/Models/DreamDaemon.cs b/src/Tgstation.Server.Api/Models/DreamDaemon.cs
index 7360918bd6..38e29363e0 100644
--- a/src/Tgstation.Server.Api/Models/DreamDaemon.cs
+++ b/src/Tgstation.Server.Api/Models/DreamDaemon.cs
@@ -11,12 +11,12 @@ namespace Tgstation.Server.Api.Models
///
/// The live revision
///
- public CompileJob ActiveCompileJob { get; set; }
+ public CompileJob? ActiveCompileJob { get; set; }
///
/// The next revision to go live
///
- public CompileJob StagedCompileJob { get; set; }
+ public CompileJob? StagedCompileJob { get; set; }
///
/// The current status of
diff --git a/src/Tgstation.Server.Api/Models/DreamMaker.cs b/src/Tgstation.Server.Api/Models/DreamMaker.cs
index 5b07d0b33d..9055a56d31 100644
--- a/src/Tgstation.Server.Api/Models/DreamMaker.cs
+++ b/src/Tgstation.Server.Api/Models/DreamMaker.cs
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Api.Models
/// The .dme file tries to compile with without the extension
///
[StringLength(Limits.MaximumStringLength)]
- public string ProjectName { get; set; }
+ public string? ProjectName { get; set; }
///
/// The port used during compilation to validate the DMAPI
diff --git a/src/Tgstation.Server.Api/Models/ErrorCodeExtensions.cs b/src/Tgstation.Server.Api/Models/ErrorCodeExtensions.cs
index adabefbb7c..a69f18e483 100644
--- a/src/Tgstation.Server.Api/Models/ErrorCodeExtensions.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorCodeExtensions.cs
@@ -1,4 +1,5 @@
-using System.ComponentModel;
+using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
namespace Tgstation.Server.Api.Models
@@ -13,9 +14,9 @@ namespace Tgstation.Server.Api.Models
///
/// The to describe.
/// A description of the on success, on failure.
- public static string Describe(this ErrorCode errorCode)
+ public static string? Describe(this ErrorCode errorCode)
{
- var attributes = (DescriptionAttribute[])typeof(ErrorCode)
+ var attributes = (IEnumerable?)typeof(ErrorCode)
.GetField(errorCode.ToString())
?.GetCustomAttributes(typeof(DescriptionAttribute), false);
diff --git a/src/Tgstation.Server.Api/Models/ErrorMessage.cs b/src/Tgstation.Server.Api/Models/ErrorMessage.cs
index 37e190fc2f..099c49cdf7 100644
--- a/src/Tgstation.Server.Api/Models/ErrorMessage.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorMessage.cs
@@ -12,18 +12,18 @@ namespace Tgstation.Server.Api.Models
/// The version of the API the server is using
///
[Required]
- public Version ServerApiVersion { get; set; }
+ public Version? ServerApiVersion { get; set; }
///
/// A human readable description of the error
///
[Required]
- public string Message { get; set; }
+ public string? Message { get; set; }
///
/// Additional data associated with the error message.
///
- public string AdditionalData { get; set; }
+ public string? AdditionalData { get; set; }
///
/// The of the .
@@ -31,6 +31,11 @@ namespace Tgstation.Server.Api.Models
[EnumDataType(typeof(ErrorCode))]
public ErrorCode ErrorCode { get; set; }
+ ///
+ /// Initializes a new instance of the .
+ ///
+ public ErrorMessage() { }
+
///
/// Initializes a new instance of the .
///
diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs
index b8d5afb013..d3f1a78115 100644
--- a/src/Tgstation.Server.Api/Models/Instance.cs
+++ b/src/Tgstation.Server.Api/Models/Instance.cs
@@ -18,13 +18,13 @@ namespace Tgstation.Server.Api.Models
///
[Required]
[StringLength(Limits.MaximumStringLength)]
- public string Name { get; set; }
+ public string? Name { get; set; }
///
/// The path to where the is located. Can only be changed while the is offline. Must not exist when the instance is created
///
[Required]
- public string Path { get; set; }
+ public string? Path { get; set; }
///
/// If the is online
@@ -56,7 +56,7 @@ namespace Tgstation.Server.Api.Models
///
/// Due to how s are children of s but moving one requires the to be offline, interactions with this are performed in a non-standard fashion. The is read by querying the again (either via list or ID lookup) and cancelled by making any sort of update to the . Once the comes back it can be queried like a normal job
[NotMapped]
- public Job MoveJob { get; set; }
+ public Job? MoveJob { get; set; }
///
/// Create a clone of the essential metadata
diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs b/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs
index 44ae5e5c4d..5db8be2eda 100644
--- a/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
[Required]
[StringLength(Limits.MaximumIndexableStringLength)]
- public string Name { get; set; }
+ public string? Name { get; set; }
///
/// If the connection is enabled
@@ -50,25 +50,22 @@ namespace Tgstation.Server.Api.Models.Internal
///
[Required]
[StringLength(Limits.MaximumStringLength)]
- public string ConnectionString { get; set; }
+ public string? ConnectionString { get; set; }
///
/// Get the which maps to the .
///
/// A for the .
- public ChatConnectionStringBuilder CreateConnectionStringBuilder()
+ public ChatConnectionStringBuilder? CreateConnectionStringBuilder()
{
if (ConnectionString == null)
return null;
- switch (Provider)
+ return Provider switch
{
- case ChatProvider.Discord:
- return new DiscordConnectionStringBuilder(ConnectionString);
- case ChatProvider.Irc:
- return new IrcConnectionStringBuilder(ConnectionString);
- default:
- throw new InvalidOperationException("Invalid Provider!");
- }
+ ChatProvider.Discord => new DiscordConnectionStringBuilder(ConnectionString),
+ ChatProvider.Irc => new IrcConnectionStringBuilder(ConnectionString),
+ _ => throw new InvalidOperationException("Invalid Provider!"),
+ };
}
///
@@ -77,7 +74,7 @@ namespace Tgstation.Server.Api.Models.Internal
/// The optional .
public void SetConnectionStringBuilder(ChatConnectionStringBuilder stringBuilder)
{
- ConnectionString = stringBuilder?.ToString();
+ ConnectionString = stringBuilder?.ToString() ?? throw new ArgumentNullException(nameof(stringBuilder));
}
}
}
diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
index a39532bd3c..87f4bc13cd 100644
--- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
@@ -13,13 +13,13 @@ namespace Tgstation.Server.Api.Models.Internal
/// The .dme file used for compilation
///
[Required]
- public string DmeName { get; set; }
+ public string? DmeName { get; set; }
///
/// Textual output of DM
///
[Required]
- public string Output { get; set; }
+ public string? Output { get; set; }
///
/// The Game folder the results were compiled into
@@ -37,6 +37,6 @@ namespace Tgstation.Server.Api.Models.Internal
/// The DMAPI .
///
[NotMapped]
- public virtual Version DMApiVersion { get; set; }
+ public virtual Version? DMApiVersion { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Internal/Job.cs b/src/Tgstation.Server.Api/Models/Internal/Job.cs
index 3e5656e56e..ac3b00f1b8 100644
--- a/src/Tgstation.Server.Api/Models/Internal/Job.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/Job.cs
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Api.Models.Internal
/// English description of the
///
[Required]
- public string Description { get; set; }
+ public string? Description { get; set; }
///
/// The associated with the if any.
@@ -23,7 +23,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// Details of any exceptions caught during the
///
- public string ExceptionDetails { get; set; }
+ public string? ExceptionDetails { get; set; }
///
/// When the was started
diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs
index e392669537..b211010351 100644
--- a/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/RepositorySettings.cs
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
[Required]
[StringLength(Limits.MaximumStringLength)]
- public string CommitterName { get; set; }
+ public string? CommitterName { get; set; }
///
/// The e-mail of the committer
@@ -20,19 +20,19 @@ namespace Tgstation.Server.Api.Models.Internal
[Required]
[StringLength(Limits.MaximumStringLength)]
[EmailAddress]
- public string CommitterEmail { get; set; }
+ public string? CommitterEmail { get; set; }
///
/// The username to access the git repository with
///
[StringLength(Limits.MaximumStringLength)]
- public string AccessUser { get; set; }
+ public string? AccessUser { get; set; }
///
/// The token/password to access the git repository with
///
[StringLength(Limits.MaximumStringLength)]
- public string AccessToken { get; set; }
+ public string? AccessToken { get; set; }
///
/// If commits created from testmerges are pushed to the remote
diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs
index 312f5812d1..efdb763aa2 100644
--- a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs
@@ -12,13 +12,13 @@ namespace Tgstation.Server.Api.Models.Internal
///
[Required]
[StringLength(40)]
- public string CommitSha { get; set; }
+ public string? CommitSha { get; set; }
///
/// The sha of the most recent remote commit
///
[Required]
[StringLength(40)]
- public string OriginCommitSha { get; set; }
+ public string? OriginCommitSha { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Internal/ServerInformation.cs b/src/Tgstation.Server.Api/Models/Internal/ServerInformation.cs
index 101877c917..0c6768e53a 100644
--- a/src/Tgstation.Server.Api/Models/Internal/ServerInformation.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/ServerInformation.cs
@@ -25,6 +25,6 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// Limits the locations instances may be created or attached from.
///
- public ICollection ValidInstancePaths { get; set; }
+ public ICollection? ValidInstancePaths { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs b/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs
index 76dbe54ce2..c105e643f7 100644
--- a/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/TestMergeBase.cs
@@ -12,27 +12,27 @@ namespace Tgstation.Server.Api.Models.Internal
/// The title of the pull request
///
[Required]
- public string TitleAtMerge { get; set; }
+ public string? TitleAtMerge { get; set; }
///
/// The body of the pull request
///
[Required]
- public string BodyAtMerge { get; set; }
+ public string? BodyAtMerge { get; set; }
///
/// The URL of the pull request
///
[Required]
#pragma warning disable CA1056 // Uri properties should not be strings
- public string Url { get; set; }
+ public string? Url { get; set; }
#pragma warning restore CA1056 // Uri properties should not be strings
///
/// The author of the pull request
///
[Required]
- public string Author { get; set; }
+ public string? Author { get; set; }
///
/// Construct a
diff --git a/src/Tgstation.Server.Api/Models/Internal/User.cs b/src/Tgstation.Server.Api/Models/Internal/User.cs
index 35bd7c5e3d..e50b23afde 100644
--- a/src/Tgstation.Server.Api/Models/Internal/User.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/User.cs
@@ -31,14 +31,14 @@ namespace Tgstation.Server.Api.Models.Internal
/// The SID/UID of the on Windows/POSIX respectively
///
// No need for StringLength as the server MUST validate it.
- public string SystemIdentifier { get; set; }
+ public string? SystemIdentifier { get; set; }
///
/// The name of the
///
[Required]
[StringLength(Limits.MaximumStringLength)]
- public string Name { get; set; }
+ public string? Name { get; set; }
///
/// The for the
diff --git a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs
index 8f4534f0c5..4661742d12 100644
--- a/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs
+++ b/src/Tgstation.Server.Api/Models/IrcConnectionStringBuilder.cs
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Api.Models
///
/// The IP address or URL of the IRC server
///
- public string Address { get; set; }
+ public string? Address { get; set; }
///
/// The port the server runs on
@@ -26,7 +26,7 @@ namespace Tgstation.Server.Api.Models
///
/// The nickname for the bot to use
///
- public string Nickname { get; set; }
+ public string? Nickname { get; set; }
///
/// If the connection should be made using SSL
@@ -41,7 +41,7 @@ namespace Tgstation.Server.Api.Models
///
/// The optional password to use
///
- public string Password { get; set; }
+ public string? Password { get; set; }
///
/// Construct an
diff --git a/src/Tgstation.Server.Api/Models/Job.cs b/src/Tgstation.Server.Api/Models/Job.cs
index ad4e1372ab..9869be84ab 100644
--- a/src/Tgstation.Server.Api/Models/Job.cs
+++ b/src/Tgstation.Server.Api/Models/Job.cs
@@ -8,12 +8,12 @@
///
/// The that started the job
///
- public User StartedBy { get; set; }
+ public User? StartedBy { get; set; }
///
/// The that cancelled the job
///
- public User CancelledBy { get; set; }
+ public User? CancelledBy { get; set; }
///
/// Optional progress between 0 and 100 inclusive
diff --git a/src/Tgstation.Server.Api/Models/Repository.cs b/src/Tgstation.Server.Api/Models/Repository.cs
index ee50701502..26a3e1fa46 100644
--- a/src/Tgstation.Server.Api/Models/Repository.cs
+++ b/src/Tgstation.Server.Api/Models/Repository.cs
@@ -10,32 +10,32 @@ namespace Tgstation.Server.Api.Models
///
/// The origin URL. If , the does not exist
///
- public string Origin { get; set; }
+ public string? Origin { get; set; }
///
/// The commit HEAD should point to. Not populated in responses, use instead for retrieval
///
- public string CheckoutSha { get; set; }
+ public string? CheckoutSha { get; set; }
///
/// The current for the
///
- public RevisionInformation RevisionInformation { get; set; }
+ public RevisionInformation? RevisionInformation { get; set; }
///
/// If the repository was cloned from GitHub.com this will be set with the owner of the repository
///
- public string GitHubOwner { get; set; }
+ public string? GitHubOwner { get; set; }
///
/// If the repository was cloned from GitHub.com this will be set with the name of the repository
///
- public string GitHubName { get; set; }
+ public string? GitHubName { get; set; }
///
/// The started by the if any
///
- public Job ActiveJob { get; set; }
+ public Job? ActiveJob { get; set; }
///
/// Do the equivalent of a git pull. Will attempt to merge unless is also specified in which case a hard reset will be performed after checking out
@@ -45,11 +45,11 @@ namespace Tgstation.Server.Api.Models
///
/// The branch or tag HEAD points to
///
- public string Reference { get; set; }
+ public string? Reference { get; set; }
///
/// for new s. Note that merges that conflict will not be performed
///
- public List NewTestMerges { get; set; }
+ public ICollection? NewTestMerges { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Api/Models/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/RevisionInformation.cs
index 9fbe61578a..1d05a22755 100644
--- a/src/Tgstation.Server.Api/Models/RevisionInformation.cs
+++ b/src/Tgstation.Server.Api/Models/RevisionInformation.cs
@@ -8,16 +8,16 @@ namespace Tgstation.Server.Api.Models
///
/// The that was created with this
///
- public TestMerge PrimaryTestMerge { get; set; }
+ public TestMerge? PrimaryTestMerge { get; set; }
///
/// The s active in the
///
- public ICollection ActiveTestMerges { get; set; }
+ public ICollection? ActiveTestMerges { get; set; }
///
/// The s made from the
///
- public ICollection CompileJobs { get; set; }
+ public ICollection? CompileJobs { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/ServerInformation.cs b/src/Tgstation.Server.Api/Models/ServerInformation.cs
index 081671e850..26d9cbc0fd 100644
--- a/src/Tgstation.Server.Api/Models/ServerInformation.cs
+++ b/src/Tgstation.Server.Api/Models/ServerInformation.cs
@@ -10,16 +10,16 @@ namespace Tgstation.Server.Api.Models
///
/// The version of the host
///
- public Version Version { get; set; }
+ public Version? Version { get; set; }
///
/// The version of the host
///
- public Version ApiVersion { get; set; }
+ public Version? ApiVersion { get; set; }
///
/// The DMAPI version of the host.
///
- public Version DMApiVersion { get; set; }
+ public Version? DMApiVersion { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/TestMerge.cs b/src/Tgstation.Server.Api/Models/TestMerge.cs
index d448504e2d..f9cdbcea75 100644
--- a/src/Tgstation.Server.Api/Models/TestMerge.cs
+++ b/src/Tgstation.Server.Api/Models/TestMerge.cs
@@ -6,6 +6,6 @@
///
/// The who created the
///
- public User MergedBy { get; set; }
+ public User? MergedBy { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs
index 09fe947a57..ba68c72e7e 100644
--- a/src/Tgstation.Server.Api/Models/TestMergeParameters.cs
+++ b/src/Tgstation.Server.Api/Models/TestMergeParameters.cs
@@ -17,12 +17,12 @@ namespace Tgstation.Server.Api.Models
///
[Required]
[StringLength(40)]
- public string PullRequestRevision { get; set; }
+ public string? PullRequestRevision { get; set; }
///
/// Optional comment about the test
///
[StringLength(Limits.MaximumStringLength)]
- public string Comment { get; set; }
+ public string? Comment { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Api/Models/Token.cs b/src/Tgstation.Server.Api/Models/Token.cs
index bc0010be5d..4f73c1c28e 100644
--- a/src/Tgstation.Server.Api/Models/Token.cs
+++ b/src/Tgstation.Server.Api/Models/Token.cs
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Api.Models
///
/// The value of the JWT
///
- public string Bearer { get; set; }
+ public string? Bearer { get; set; }
///
/// When the expires
diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs
index 3382bba8f5..e6f50acb27 100644
--- a/src/Tgstation.Server.Api/Models/User.cs
+++ b/src/Tgstation.Server.Api/Models/User.cs
@@ -6,16 +6,16 @@
///
/// The name of the default admin user
///
- public const string AdminName = "Admin";
+ public static readonly string AdminName = "Admin";
///
/// The default admin password
///
- public const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
+ public static readonly string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
///
/// The who created this
///
- public User CreatedBy { get; set; }
+ public User? CreatedBy { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Api/Models/UserUpdate.cs b/src/Tgstation.Server.Api/Models/UserUpdate.cs
index 6e93926be8..dc35f3de24 100644
--- a/src/Tgstation.Server.Api/Models/UserUpdate.cs
+++ b/src/Tgstation.Server.Api/Models/UserUpdate.cs
@@ -11,6 +11,6 @@ namespace Tgstation.Server.Api.Models
/// Cleartext password of the
///
[Required]
- public string Password { get; set; }
+ public string? Password { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
index e8e39bc692..de34a2b556 100644
--- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
+++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
@@ -2,15 +2,16 @@
- netstandard2.0
+ netstandard2.1Full
+ $(TgsApiVersion)trueCyberboss
- /tg/station
+ /tg/station 13API definitions for tgstation-server
+ https://tgstation.github.io/tgstation-serverLICENSEtgs.png
- https://tgstation.github.io/tgstation-serverGithttps://github.com/tgstation/tgstation-server2018
@@ -18,21 +19,16 @@
See https://github.com/tgstation/tgstation-server/releases/tag/api-v$(TgsApiVersion)truesnupkg
- $(TgsApiVersion)../../build/analyzers.rulesetlatest
+ enable
+ bin\$(Configuration)\netstandard2.1\Tgstation.Server.Api.xml
+ CA1028
-
+ true
- bin\Release\netstandard2.1\Tgstation.Server.Api.xml
- 1701;1702;CA1028
-
-
-
- 1701;1702;CA1028
- bin\Debug\netstandard2.1\Tgstation.Server.Api.xml
diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs
index d15629ea32..4fcdc1e981 100644
--- a/src/Tgstation.Server.Client/ApiClient.cs
+++ b/src/Tgstation.Server.Client/ApiClient.cs
@@ -44,6 +44,16 @@ namespace Tgstation.Server.Client
///
readonly List requestLoggers;
+ ///
+ /// Backing field for
+ ///
+ readonly ApiHeaders? tokenRefreshHeaders;
+
+ ///
+ /// The for refreshes.
+ ///
+ readonly SemaphoreSlim semaphoreSlim;
+
///
/// Backing field for
///
@@ -57,7 +67,7 @@ namespace Tgstation.Server.Client
static void HandleBadResponse(HttpResponseMessage response, string json)
{
- ErrorMessage errorMessage = null;
+ ErrorMessage? errorMessage = null;
try
{
// check if json serializes to an error message
@@ -101,18 +111,25 @@ namespace Tgstation.Server.Client
///
/// The value of
/// The value of
- /// The value of
- public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders)
+ /// The value of
+ /// The value of
+ public ApiClient(IHttpClient httpClient, Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders)
{
this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
Url = url ?? throw new ArgumentNullException(nameof(url));
headers = apiHeaders ?? throw new ArgumentNullException(nameof(apiHeaders));
+ this.tokenRefreshHeaders = tokenRefreshHeaders;
requestLoggers = new List();
+ semaphoreSlim = new SemaphoreSlim(1);
}
///
- public void Dispose() => httpClient.Dispose();
+ public void Dispose()
+ {
+ httpClient.Dispose();
+ semaphoreSlim.Dispose();
+ }
///
/// Main request method
@@ -122,9 +139,10 @@ namespace Tgstation.Server.Client
/// The body of the request
/// The method of the request
/// The optional for the request
+ /// If this is a token refresh operation.
/// The for the operation
/// A resulting in the response on success
- async Task RunRequest(string route, object body, HttpMethod method, long? instanceId, CancellationToken cancellationToken)
+ async Task RunRequest(string route, object? body, HttpMethod method, long? instanceId, bool tokenRefresh, CancellationToken cancellationToken)
{
if (route == null)
throw new ArgumentNullException(nameof(route));
@@ -141,11 +159,25 @@ namespace Tgstation.Server.Client
if (body != null)
request.Content = new StringContent(JsonConvert.SerializeObject(body, serializerSettings), Encoding.UTF8, ApiHeaders.ApplicationJson);
- headers.SetRequestHeaders(request.Headers, instanceId);
+ var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
+ headersToUse.SetRequestHeaders(request.Headers, instanceId);
- await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
+ // This is meant to be a gate against token refresh operations
+ await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
+ if(!tokenRefresh)
+ semaphoreSlim.Release();
- response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ try
+ {
+ await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
+
+ response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ if (tokenRefresh)
+ semaphoreSlim.Release();
+ }
}
using (response)
@@ -155,14 +187,20 @@ namespace Tgstation.Server.Client
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
+ {
+ if (!tokenRefresh
+ && response.StatusCode == HttpStatusCode.Unauthorized
+ && await RefreshToken(cancellationToken).ConfigureAwait(false))
+ return await RunRequest(route, body, method, instanceId, false, cancellationToken).ConfigureAwait(false);
HandleBadResponse(response, json);
+ }
if (String.IsNullOrWhiteSpace(json))
json = JsonConvert.SerializeObject(new object());
try
{
- return JsonConvert.DeserializeObject(json, serializerSettings);
+ return JsonConvert.DeserializeObject(json, serializerSettings) !;
}
catch (JsonException)
{
@@ -171,50 +209,68 @@ namespace Tgstation.Server.Client
}
}
- ///
- public Task Create(string route, CancellationToken cancellationToken) => RunRequest(route, new object(), HttpMethod.Put, null, cancellationToken);
+ async Task RefreshToken(CancellationToken cancellationToken)
+ {
+ if (tokenRefreshHeaders == null)
+ return false;
+
+ try
+ {
+ var token = await RunRequest(Routes.Root, null, HttpMethod.Post, null, true, cancellationToken);
+ headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
+ }
+ catch (ClientException)
+ {
+ return false;
+ }
+
+ return true;
+ }
///
- public Task Read(string route, CancellationToken cancellationToken) => RunRequest(route, null, HttpMethod.Get, null, cancellationToken);
+ public Task Create(string route, CancellationToken cancellationToken) => RunRequest(route, new object(), HttpMethod.Put, null, false, cancellationToken);
///
- public Task Update(string route, CancellationToken cancellationToken) => RunRequest(route, new object(), HttpMethod.Post, null, cancellationToken);
+ public Task Read(string route, CancellationToken cancellationToken) => RunRequest(route, null, HttpMethod.Get, null, false, cancellationToken);
///
- public Task Update(string route, TBody body, CancellationToken cancellationToken) => RunRequest(route, body, HttpMethod.Post, null, cancellationToken);
+ public Task Update(string route, CancellationToken cancellationToken) => RunRequest(route, new object(), HttpMethod.Post, null, false, cancellationToken);
///
- public Task Update(string route, TBody body, CancellationToken cancellationToken) => RunRequest