mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-31 09:02:41 +01:00
Merge pull request #496 from Cyberboss/OkayButWhenAreYouActuallyGoingToWriteTheServer
Authentication Framework
This commit is contained in:
@@ -5,5 +5,7 @@ docker-compose.yml
|
||||
.vs
|
||||
.vscode
|
||||
legacy
|
||||
packages
|
||||
build
|
||||
*/bin
|
||||
*/obj
|
||||
|
||||
@@ -11,3 +11,4 @@ artifacts/
|
||||
*DS_Store
|
||||
*.sln.ide
|
||||
/TestResults
|
||||
/src/Tgstation.Server.Host/appsettings.Development.json
|
||||
|
||||
+8
-2
@@ -1,4 +1,4 @@
|
||||
FROM microsoft/dotnet:2.1-sdk AS build
|
||||
FROM microsoft/dotnet:2.0-sdk AS build
|
||||
WORKDIR /src
|
||||
|
||||
COPY tgstation-server.sln ./
|
||||
@@ -36,8 +36,14 @@ RUN dotnet test -c Release
|
||||
FROM build AS publish
|
||||
RUN dotnet publish -c Release -o /app
|
||||
|
||||
FROM microsoft/dotnet:2.1-runtime
|
||||
FROM microsoft/dotnet:2.0-runtime
|
||||
EXPOSE 80
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=publish /app .
|
||||
|
||||
RUN mkdir /config_data && mv appsettings.Docker.json /config_data/appsettings.Production.json
|
||||
VOLUME ["/config_data"]
|
||||
|
||||
ENTRYPOINT ["dotnet", "Tgstation.Server.Host.Console.dll"]
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Http.Headers;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the header that must be present for every server request
|
||||
/// </summary>
|
||||
public sealed class ApiHeaders
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO: Remove this when https://github.com/dotnet/corefx/pull/26701 makes it into the sdk
|
||||
/// </summary>
|
||||
public const string ApplicationJson = "application/json";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Username"/> header key
|
||||
/// </summary>
|
||||
const string usernameHeader = "Username";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceId"/> header key
|
||||
/// </summary>
|
||||
const string instanceIdHeader = "InstanceId";
|
||||
|
||||
/// <summary>
|
||||
/// The JWT authentication header scheme
|
||||
/// </summary>
|
||||
const string jwtAuthenticationScheme = "Bearer";
|
||||
|
||||
/// <summary>
|
||||
/// The password authentication header scheme
|
||||
/// </summary>
|
||||
const string passwordAuthenticationScheme = "Password";
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="AssemblyName"/>
|
||||
/// </summary>
|
||||
static readonly AssemblyName assemblyName = Assembly.GetExecutingAssembly().GetName();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance.Id"/> being accessed
|
||||
/// </summary>
|
||||
public long? InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's user agent
|
||||
/// </summary>
|
||||
public ProductHeaderValue UserAgent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's API version
|
||||
/// </summary>
|
||||
public Version ApiVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's JWT
|
||||
/// </summary>
|
||||
public string Token { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's username
|
||||
/// </summary>
|
||||
public string Username { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The client's password
|
||||
/// </summary>
|
||||
public string Password { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the header uses password or JWT authentication
|
||||
/// </summary>
|
||||
public bool IsTokenAuthentication => Token != null;
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="ApiHeaders"/> for JWT authentication
|
||||
/// </summary>
|
||||
/// <param name="userAgent">The value of <see cref="UserAgent"/></param>
|
||||
/// <param name="token">The value of <see cref="Token"/></param>
|
||||
public ApiHeaders(ProductHeaderValue userAgent, string token) : this(userAgent, token, null, null)
|
||||
{
|
||||
if (userAgent == null)
|
||||
throw new ArgumentNullException(nameof(userAgent));
|
||||
if (token == null)
|
||||
throw new ArgumentNullException(nameof(token));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="ApiHeaders"/> for password authentication
|
||||
/// </summary>
|
||||
/// <param name="userAgent">The value of <see cref="UserAgent"/></param>
|
||||
/// <param name="username">The value of <see cref="Username"/></param>
|
||||
/// <param name="password">The value of <see cref="Password"/></param>
|
||||
public ApiHeaders(ProductHeaderValue userAgent, string username, string password) : this(userAgent, null, username, password)
|
||||
{
|
||||
if (userAgent == null)
|
||||
throw new ArgumentNullException(nameof(userAgent));
|
||||
if (username == null)
|
||||
throw new ArgumentNullException(nameof(username));
|
||||
if (password == null)
|
||||
throw new ArgumentNullException(nameof(password));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct and validates <see cref="ApiHeaders"/> from a set of <paramref name="requestHeaders"/>
|
||||
/// </summary>
|
||||
/// <param name="requestHeaders">The <see cref="RequestHeaders"/> containing the <see cref="ApiHeaders"/></param>
|
||||
public ApiHeaders(RequestHeaders requestHeaders)
|
||||
{
|
||||
var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(ApplicationJson);
|
||||
if (!requestHeaders.Accept.Any(x => x.MediaType == jsonAccept.MediaType))
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", ApplicationJson));
|
||||
|
||||
if (!requestHeaders.Headers.TryGetValue(HeaderNames.UserAgent, out StringValues userAgentValues) || !ProductInfoHeaderValue.TryParse(userAgentValues.FirstOrDefault(), out ProductInfoHeaderValue clientUserAgent))
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} headers!", HeaderNames.UserAgent));
|
||||
|
||||
//assure the client user agent has a name and version
|
||||
if (String.IsNullOrWhiteSpace(clientUserAgent.Product.Name) || !Version.TryParse(clientUserAgent.Product.Version, out Version clientVersion))
|
||||
throw new InvalidOperationException("Malformed client user agent!");
|
||||
|
||||
var apiUserAgentHeader = userAgentValues[1];
|
||||
//make sure the api header matches ours
|
||||
if (!ProductInfoHeaderValue.TryParse(apiUserAgentHeader, out ProductInfoHeaderValue apiUserAgent) || apiUserAgent.Product.Name != assemblyName.Name)
|
||||
throw new InvalidOperationException("Missing API user agent!");
|
||||
|
||||
if (!Version.TryParse(apiUserAgent.Product.Version, out Version ApiVersion))
|
||||
throw new InvalidOperationException("Malformed API version!");
|
||||
|
||||
//check api version compatibility
|
||||
var ourVersion = assemblyName.Version;
|
||||
if (ourVersion.Major != ApiVersion.Major || ourVersion.Minor != ApiVersion.Minor || ourVersion.Build > ApiVersion.Build)
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Given API version is incompatible with version {0}!", ourVersion));
|
||||
|
||||
if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization))
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Missing {0} header!", HeaderNames.Authorization));
|
||||
var auth = authorization.First();
|
||||
var splits = new List<string>(auth.Split(' '));
|
||||
var scheme = splits.First();
|
||||
if (String.IsNullOrWhiteSpace(scheme))
|
||||
throw new InvalidOperationException("Missing authentication scheme!");
|
||||
|
||||
splits.RemoveAt(0);
|
||||
var parameter = String.Concat(splits);
|
||||
if (String.IsNullOrEmpty(parameter))
|
||||
throw new InvalidOperationException("Missing authentication parameter!");
|
||||
|
||||
if(requestHeaders.Headers.TryGetValue(instanceIdHeader, out StringValues instanceIdValues))
|
||||
{
|
||||
var instanceIdString = instanceIdValues.FirstOrDefault();
|
||||
if (instanceIdString != default && Int64.TryParse(instanceIdString, out long instanceId))
|
||||
InstanceId = instanceId;
|
||||
}
|
||||
|
||||
switch (scheme)
|
||||
{
|
||||
case jwtAuthenticationScheme:
|
||||
Token = parameter;
|
||||
break;
|
||||
case passwordAuthenticationScheme:
|
||||
Password = parameter;
|
||||
var fail = !requestHeaders.Headers.TryGetValue(usernameHeader, out StringValues values);
|
||||
if (!fail)
|
||||
{
|
||||
Username = values.FirstOrDefault();
|
||||
fail = String.IsNullOrWhiteSpace(Username);
|
||||
}
|
||||
if (fail)
|
||||
throw new InvalidOperationException("Missing Username header!");
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid authentication scheme!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="ApiHeaders"/>
|
||||
/// </summary>
|
||||
/// <param name="userAgent">The value of <see cref="UserAgent"/></param>
|
||||
/// <param name="token">The value of <see cref="Token"/></param>
|
||||
/// <param name="username">The value of <see cref="Username"/></param>
|
||||
/// <param name="password">The value of <see cref="Password"/></param>
|
||||
ApiHeaders(ProductHeaderValue userAgent, string token, string username, string password)
|
||||
{
|
||||
UserAgent = userAgent;
|
||||
Token = token;
|
||||
Username = username;
|
||||
Password = password;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set <see cref="HttpRequestHeaders"/> using the <see cref="ApiHeaders"/>. This initially clears <paramref name="headers"/>
|
||||
/// </summary>
|
||||
/// <param name="headers">The <see cref="HttpRequestHeaders"/> to set</param>
|
||||
public void SetRequestHeaders(HttpRequestHeaders headers)
|
||||
{
|
||||
if (headers == null)
|
||||
throw new ArgumentNullException(nameof(headers));
|
||||
|
||||
headers.Clear();
|
||||
headers.Accept.Add(new MediaTypeWithQualityHeaderValue(ApplicationJson));
|
||||
if (IsTokenAuthentication)
|
||||
headers.Authorization = new AuthenticationHeaderValue(jwtAuthenticationScheme, Token);
|
||||
else
|
||||
{
|
||||
headers.Authorization = new AuthenticationHeaderValue(passwordAuthenticationScheme, Password);
|
||||
headers.Add(usernameHeader, Username);
|
||||
}
|
||||
headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
|
||||
headers.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue(assemblyName.Name, assemblyName.Version.ToString())));
|
||||
if(InstanceId.HasValue)
|
||||
headers.Add(instanceIdHeader, InstanceId.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api
|
||||
/// <summary>
|
||||
/// Indicates the rights <see cref="Enum"/> for a model
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ModelAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -4,36 +4,15 @@ using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata about an installation
|
||||
/// </summary>
|
||||
[Model(RightsType.Administration)]
|
||||
public sealed class Administration
|
||||
/// <inheritdoc />
|
||||
public sealed class Administration : Internal.ServerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the specified Windows/UNIX authentication to authorize users. Setting this to <see langword="null"/> enables full administrative anonymous access
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)]
|
||||
public string SystemAuthenticationGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information.
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.ChangeTelemetry, WriteRight = AdministrationRights.ChangeTelemetry)]
|
||||
public bool EnableTelemetry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="DreamDaemon"/> instances will not be stopped when the server exits. Resets to <see langword="false"/> when the server restarts
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.SoftStop, WriteRight = AdministrationRights.SoftStop)]
|
||||
public bool SoftStop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The git repository to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to <see langword="null"/> upstream pulls will be disabled entirely
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)]
|
||||
public string UpstreamRepository { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If <see cref="Version.Minor"/> is higher than <see cref="CurrentVersion"/>'s the update cannot be applied due to API changes
|
||||
/// </summary>
|
||||
@@ -47,7 +26,7 @@ namespace Tgstation.Server.Api.Models
|
||||
public Version CurrentVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Users in the <see cref="SystemAuthenticationGroup"/>
|
||||
/// Users in the <see cref="Internal.ServerSettings.SystemAuthenticationGroup"/>
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public IReadOnlyList<User> Users { get; set; }
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Manage the server chat bots
|
||||
/// </summary>
|
||||
[Model(RightsType.Chat, RequiresInstance = true)]
|
||||
public sealed class Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// If the IRC client is enabled
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatRights.SetIrcEnabled)]
|
||||
public bool IrcEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IRC server name
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)]
|
||||
public string IrcHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IRC server port
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)]
|
||||
public ushort IrcPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IRC server NickServ password
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatRights.SetIrcSettings, WriteRight = ChatRights.SetIrcSettings)]
|
||||
public string IrcNickServPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Channels the IRC client should join/listen/announce in and allow admin commands
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatRights.SetIrcChannels)]
|
||||
public List<string> IrcAdminChannels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Channels the IRC client should join/listen/announce in
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatRights.SetIrcChannels)]
|
||||
public List<string> IrcGeneralChannels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the Discord bot is enabled
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatRights.SetDiscordEnabled)]
|
||||
public bool DiscordEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Discord bot token
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatRights.SetDiscordSettings, WriteRight = ChatRights.SetDiscordSettings)]
|
||||
public string DiscordBotToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Channels the Discord bot should listen/announce in and allow admin commands
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatRights.SetDiscordChannels)]
|
||||
public List<long> DiscordAdminChannels { get; set; }
|
||||
/// <summary>
|
||||
/// Channels the Discord bot should listen/announce in
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatRights.SetDiscordChannels)]
|
||||
public List<long> DiscordGeneralChannels { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates a chat channel
|
||||
/// </summary>
|
||||
public class ChatChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// The IRC channel name
|
||||
/// </summary>
|
||||
public string IrcChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Discord channel ID
|
||||
/// </summary>
|
||||
public long DiscordChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="ChatChannel"/> is an admin channel
|
||||
/// </summary>
|
||||
public bool IsAdminChannel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class ChatSettings : Internal.ChatSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Channels the Discord bot should listen/announce in
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatSettingsRights.SetChannels)]
|
||||
public List<ChatChannel> Channels { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class CompileJob : Internal.CompileJob
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> that triggered the job
|
||||
/// </summary>
|
||||
public User TriggeredBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Git revision the compiler ran on. Not modifiable
|
||||
/// </summary>
|
||||
public RevisionInformation RevisionInformation { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The content of the <see cref="Configuration"/> file. Will be <see langword="null"/> if <see cref="ReadDenied"/> is <see langword="true"/> or during listing operations
|
||||
/// </summary>
|
||||
#pragma warning disable CA1819 // Properties should not return arrays
|
||||
public byte[] Content { get; set; }
|
||||
#pragma warning restore CA1819 // Properties should not return arrays
|
||||
|
||||
/// <summary>
|
||||
/// The MD5 hash of the file when last read by the user. Will be <see langword="null"/> if <see cref="ReadDenied"/> is <see langword="true"/>. If this doesn't match during update actions, the write will be denied with error code 409
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
@@ -7,38 +6,13 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Represents an instance of BYOND's DreamDaemon game server. Create action starts the server. Delete action shuts down the server
|
||||
/// </summary>
|
||||
[Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)]
|
||||
public sealed class DreamDaemon
|
||||
public sealed class DreamDaemon : DreamDaemonSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The pull requests merged on the live game version
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)]
|
||||
public IReadOnlyDictionary<int, string> PullRequests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The git sha the live game version was compiled at
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)]
|
||||
public string Sha { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The git sha of the origin branch the live game version was compiled at
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)]
|
||||
public string OriginSha { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the live revision was compiled
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadRevision)]
|
||||
public DateTimeOffset CompiledAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="DreamDaemon"/> starts when it's <see cref="Instance"/> starts
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)]
|
||||
public bool? AutoStart { get; set; }
|
||||
public CompileJob CompileJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current status of <see cref="DreamDaemon"/>
|
||||
@@ -47,45 +21,15 @@ namespace Tgstation.Server.Api.Models
|
||||
public DreamDaemonStatus? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the BYOND web client can be used to connect to the game server
|
||||
/// The current <see cref="DreamDaemonSecurity"/> of <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)]
|
||||
public bool? AllowWebClient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the server is undergoing a soft reset. This may be automatically set by changes to other fields
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)]
|
||||
public bool? SoftRestart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the server is undergoing a soft shutdown
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)]
|
||||
public bool? SoftShutdown { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DreamDaemonSecurity"/> level of <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)]
|
||||
public DreamDaemonSecurity? SecurirtyLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The first port <see cref="DreamDaemon"/> uses. This should be the publically advertised port
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)]
|
||||
public ushort? PrimaryPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The second port <see cref="DreamDaemon"/> uses
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)]
|
||||
public ushort? SecondaryPort { get; set; }
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)]
|
||||
public DreamDaemonSecurity? CurrentSecurity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The port the running <see cref="DreamDaemon"/> instance is set to
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true, ReadRight = DreamDaemonRights.ReadMetadata)]
|
||||
public bool CurrentPort { get; set; }
|
||||
public ushort? CurrentPort { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
/// <summary>
|
||||
/// Server is unrestricted in terms of file access and shell commands
|
||||
/// </summary>
|
||||
Trusted = 0,
|
||||
Trusted,
|
||||
/// <summary>
|
||||
/// Server will not be able to run shell commands or access files outside it's working directory
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
@@ -6,60 +6,18 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Represents the state of the DreamMaker compiler. Create action starts a new compile. Delete action cancels the current compile
|
||||
/// </summary>
|
||||
[Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)]
|
||||
public sealed class DreamMaker
|
||||
public sealed class DreamMaker : DreamMakerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// When the compilation started
|
||||
/// The last <see cref="CompileJob"/> ran
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public DateTimeOffset StartedAt { get; set; }
|
||||
/// <summary>
|
||||
/// When the compilation finished
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public DateTimeOffset FinishedAt { get; set; }
|
||||
public CompileJob LastJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CompilerStatus"/> of the compiler
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public CompilerStatus Status { get; set; }
|
||||
/// <summary>
|
||||
/// If the compiler targeted the primary directory
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public bool? TargetedPrimaryDirectory { get; set; }
|
||||
/// <summary>
|
||||
/// Textual output of DM
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public string Output { get; set; }
|
||||
/// <summary>
|
||||
/// Exit code of DM
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public int? ExitCode { get; set; }
|
||||
/// <summary>
|
||||
/// Git revision the compiler ran on. Not modifiable
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public string Revision { get; set; }
|
||||
/// <summary>
|
||||
/// Git revision of the origin branch the compiler ran on. Not modifiable
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public string OriginRevision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How often the <see cref="DreamMaker"/> automatically compiles in minutes
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = DreamMakerRights.SetAutoCompile)]
|
||||
public int? AutoCompileInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The .dme file <see cref="DreamMaker"/> tries to compile with
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = DreamMakerRights.SetDme)]
|
||||
public string TargetDme { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
@@ -6,7 +7,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// Metadata about a server instance
|
||||
/// </summary>
|
||||
[Model(RightsType.InstanceManager, CanCrud = true, CanList = true)]
|
||||
public sealed class Instance
|
||||
public class Instance
|
||||
{
|
||||
/// <summary>
|
||||
/// The id of the <see cref="Instance"/>. Not modifiable
|
||||
@@ -18,12 +19,14 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The name of the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = InstanceManagerRights.Rename)]
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path to where the <see cref="Instance"/> is located
|
||||
/// The path to where the <see cref="Instance"/> is located. Changing this will temporarily offline the <see cref="Instance"/> while it moves
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = InstanceManagerRights.Relocate)]
|
||||
[Required]
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -31,5 +34,11 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = InstanceManagerRights.SetOnline)]
|
||||
public bool Online { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="Configuration"/> can be used on the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = InstanceManagerRights.SetConfiguration)]
|
||||
public bool ConfigurationAllowed { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,8 @@ namespace Tgstation.Server.Api.Models
|
||||
/// Represents a <see cref="User"/>s permissions in an <see cref="Instance"/>
|
||||
/// </summary>
|
||||
[Model(RightsType.InstanceUser, WriteRight = InstanceUserRights.WriteUsers, CanList = true, RequiresInstance = true)]
|
||||
public sealed class InstanceUser
|
||||
public class InstanceUser
|
||||
{
|
||||
/// <summary>
|
||||
/// See definition in <see cref="User.Id"/>
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ByondRights"/> of the <see cref="InstanceUser"/>
|
||||
/// </summary>
|
||||
@@ -35,9 +29,9 @@ namespace Tgstation.Server.Api.Models
|
||||
public RepositoryRights RepositoryRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ChatRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.ChatSettingsRights"/> of the <see cref="InstanceUser"/>
|
||||
/// </summary>
|
||||
public ChatRights ChatRights { get; set; }
|
||||
public ChatSettingsRights ChatSettingsRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ConfigurationRights"/> of the <see cref="InstanceUser"/>
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Manage the server chat bots
|
||||
/// </summary>
|
||||
[Model(RightsType.ChatSettings, RequiresInstance = true)]
|
||||
public class ChatSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// If the IRC client is enabled
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatSettingsRights.SetIrcEnabled)]
|
||||
public bool IrcEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IRC server name
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)]
|
||||
[Required]
|
||||
public string IrcHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IRC server port
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)]
|
||||
public ushort IrcPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The IRC server NickServ password
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatSettingsRights.SetIrcSettings, WriteRight = ChatSettingsRights.SetIrcSettings)]
|
||||
public string IrcNickServPassword { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the Discord bot is enabled
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = ChatSettingsRights.SetDiscordEnabled)]
|
||||
public bool DiscordEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Discord bot token
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = ChatSettingsRights.SetDiscordSettings, WriteRight = ChatSettingsRights.SetDiscordSettings)]
|
||||
public string DiscordBotToken { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a run of <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
public class CompileJob
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the job
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the compilation started
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DateTimeOffset StartedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the compilation finished
|
||||
/// </summary>
|
||||
public DateTimeOffset FinishedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the compiler targeted the primary directory
|
||||
/// </summary>
|
||||
public bool? TargetedPrimaryDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Textual output of DM
|
||||
/// </summary>
|
||||
public string Output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Exit code of DM. If <see langword="null"/>, the job was cancelled
|
||||
/// </summary>
|
||||
public int? ExitCode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Configurable settings for <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
[Model(RightsType.DreamDaemon, CanCrud = true, RequiresInstance = true)]
|
||||
public class DreamDaemonSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// If <see cref="DreamDaemon"/> starts when it's <see cref="Instance"/> starts
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetAutoStart)]
|
||||
public bool AutoStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the BYOND web client can be used to connect to the game server
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetWebClient)]
|
||||
public bool AllowWebClient { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the server is undergoing a soft reset. This may be automatically set by changes to other fields
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftRestart)]
|
||||
public bool SoftRestart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the server is undergoing a soft shutdown
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SoftShutdown)]
|
||||
public bool SoftShutdown { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DreamDaemonSecurity"/> level of <see cref="DreamDaemon"/>
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetSecurity)]
|
||||
public DreamDaemonSecurity SecurityLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The first port <see cref="DreamDaemon"/> uses. This should be the publically advertised port
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)]
|
||||
public ushort PrimaryPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The second port <see cref="DreamDaemon"/> uses
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = DreamDaemonRights.ReadMetadata, WriteRight = DreamDaemonRights.SetPorts)]
|
||||
public ushort SecondaryPort { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Configurable settings for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
[Model(RightsType.DreamMaker, ReadRight = DreamMakerRights.Read, CanCrud = true, RequiresInstance = true)]
|
||||
public class DreamMakerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// How often the <see cref="DreamMakerSettings"/> automatically compiles in minutes
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = DreamMakerRights.SetAutoCompile)]
|
||||
public int? AutoCompileInterval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The .dme file <see cref="DreamMakerSettings"/> tries to compile with
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = DreamMakerRights.SetDme)]
|
||||
public string TargetDme { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a long running job
|
||||
/// </summary>
|
||||
[Model(RequiresInstance = true)]
|
||||
public class Job
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/> ID
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// English description of the <see cref="Job"/>
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="Job"/> was started
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
[Required]
|
||||
public DateTimeOffset StartedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="Job"/> stopped
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public DateTimeOffset StoppedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="Job"/> was cancelled
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public bool Cancelled { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents configurable settings for a <see cref="Repository"/>
|
||||
/// </summary>
|
||||
[Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)]
|
||||
public class RepositorySettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the committer
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeCommitter)]
|
||||
[Required]
|
||||
public string CommitterName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The e-mail of the committer
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeCommitter)]
|
||||
[Required]
|
||||
public string CommitterEmail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The username to access the git repository with
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)]
|
||||
public string AccessUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token/password to access the git repository with
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If commits created from testmerges are pushed to the remote
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)]
|
||||
public bool PushTestMergeCommits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How often the <see cref="Repository"/> automatically updates in minutes
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeAutoUpdate)]
|
||||
public int? AutoUpdateInterval { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information about a current git revison
|
||||
/// </summary>
|
||||
public class RevisionInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// The revision sha
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
[Required, StringLength(40)]
|
||||
public string Revision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sha of the most recent remote commit
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
[Required, StringLength(40)]
|
||||
public string OriginRevision { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata about an installation
|
||||
/// </summary>
|
||||
[Model(RightsType.Administration)]
|
||||
public class ServerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Use the specified Windows/POSIX authentication group to authorize users. Changing this may enable or disable <see cref="User"/>s depending on how they were configured. Setting this to <see langword="null"/> changes the authentication mode to database.
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.ChangeAuthenticationGroup, WriteRight = AdministrationRights.ChangeAuthenticationGroup)]
|
||||
public string SystemAuthenticationGroup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information.
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.ChangeTelemetry, WriteRight = AdministrationRights.ChangeTelemetry)]
|
||||
public bool EnableTelemetry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The git repository to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to <see langword="null"/> upstream pulls will be disabled entirely
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)]
|
||||
public string UpstreamRepository { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a merge of a GitHub pull request
|
||||
/// </summary>
|
||||
public class TestMerge : TestMergeParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="TestMerge"/>
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="TestMerge"/> was created
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DateTimeOffset MergedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The title of the pull request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string TitleAtMerge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body of the pull request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string BodyAtMerge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The author of the pull request
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string Author { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a server <see cref="User"/>
|
||||
/// </summary>
|
||||
[Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers, CanCrud = true)]
|
||||
public class User
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="User"/> is enabled since users cannot be deleted. System users cannot be disabled
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = AdministrationRights.EditUsers)]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="User"/> was created
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
[Required]
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The SID/UID of the <see cref="User"/> on Windows/POSIX respectively
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public string SystemIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = AdministrationRights.EditUsers)]
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.AdministrationRights"/> for the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = AdministrationRights.EditUsers)]
|
||||
public AdministrationRights AdministrationRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.InstanceManagerRights"/> for the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = AdministrationRights.EditUsers)]
|
||||
public InstanceManagerRights InstanceManagerRights { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,26 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a long running job on the server. Model is read-only, updates attempt to cancel the job
|
||||
/// </summary>
|
||||
[Model]
|
||||
public sealed class Job
|
||||
public sealed class Job : Internal.Job
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/> ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Human readable description of the <see cref="Job"/>
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="Job"/> was started
|
||||
/// </summary>
|
||||
public DateTimeOffset StartedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="Job"/> stopped
|
||||
/// </summary>
|
||||
public DateTimeOffset StoppedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="Job"/> was cancelled
|
||||
/// </summary>
|
||||
public bool Cancelled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="Job"/> has incremental progress, this will range from 1 - 100. 0 otherwise
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public int Progress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the current user has permission to cancel the job. Will be <see langword="null"/> if <see cref="StoppedAt"/> isn't
|
||||
/// If the current user has permission to cancel the job
|
||||
/// </summary>
|
||||
public bool? UserCanCancel { get; set; }
|
||||
[Permissions(DenyWrite = true)]
|
||||
public bool UserCanCancel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> that started the job
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public User StartedBy { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Represents a git repository
|
||||
/// </summary>
|
||||
[Model(RightsType.Repository, ReadRight = RepositoryRights.Read, RequiresInstance = true)]
|
||||
public sealed class Repository
|
||||
public sealed class Repository : Internal.RepositorySettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The origin URL. If <see langword="null"/>, the <see cref="Repository"/> does not exist
|
||||
@@ -19,7 +18,13 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The commit HEAD points to
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.SetSha)]
|
||||
public string Sha { get; set; }
|
||||
public string NewRevision { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="Models.RevisionInformation"/> for the <see cref="Repository"/>
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public RevisionInformation RevisionInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The branch or tag HEAD points to
|
||||
@@ -28,51 +33,9 @@ namespace Tgstation.Server.Api.Models
|
||||
public string Reference { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the committer
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeCommitter)]
|
||||
public string CommitterName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The e-mail of the committer
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeCommitter)]
|
||||
public string CommitterEmail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Associated list of tag name -> sha for repository compiles. Not modifiable
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public IReadOnlyDictionary<string, string> Backups { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Associated list of GitHub pull request number -> sha for merged pull requests. Adding a <see langword="null"/> value to this list will merge the latest commit of the pull request numbered by the key
|
||||
/// <see cref="TestMergeParameters"/> for new <see cref="TestMerge"/>s
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.MergePullRequest)]
|
||||
public Dictionary<int, string> PullRequests { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The username to access the git repository with
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)]
|
||||
public string AccessUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token/password to access the git repository with
|
||||
/// </summary>
|
||||
[Permissions(ReadRight = RepositoryRights.ChangeCredentials, WriteRight = RepositoryRights.ChangeCredentials)]
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If commits created from testmerges are pushed to the remote
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeTestMergeCommits)]
|
||||
public bool PushTestMergeCommits { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How often the <see cref="Repository"/> automatically updates in minutes
|
||||
/// </summary>
|
||||
[Permissions(WriteRight = RepositoryRights.ChangeAutoUpdate)]
|
||||
public int? AutoUpdateInterval { get; set; }
|
||||
public List<TestMergeParameters> NewTestMerges { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class RevisionInformation : Internal.RevisionInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="TestMerge"/>s active in the <see cref="RevisionInformation"/>
|
||||
/// </summary>
|
||||
public List<TestMerge> TestMerges { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class TestMerge : Internal.TestMerge
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> who created the <see cref="TestMerge"/>
|
||||
/// </summary>
|
||||
public User MergedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters for creating a <see cref="TestMerge"/>
|
||||
/// </summary>
|
||||
public class TestMergeParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of the pull request
|
||||
/// </summary>
|
||||
public int Number { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sha of the pull request revision to merge
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string PullRequestRevision { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,13 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an access token for the server. Read action generates a new one. Update action expires all for user
|
||||
/// Represents a JWT returned by the API
|
||||
/// </summary>
|
||||
[Model(RightsType.Token, CanList = true)]
|
||||
public sealed class Token
|
||||
{
|
||||
/// <summary>
|
||||
/// The id of the <see cref="Token"/>. Not modifiable
|
||||
/// The value of the JWT
|
||||
/// </summary>
|
||||
public long Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user agent that created the <see cref="Token"/>
|
||||
/// </summary>
|
||||
public string ClientUserAgent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPAddress"/> the <see cref="Token"/> was originally issued to
|
||||
/// </summary>
|
||||
public IPAddress IssuedTo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When the <see cref="Token"/> was originally issued
|
||||
/// </summary>
|
||||
public DateTimeOffset IssuedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The token <see cref="string"/>. Not modifiable, only appears once
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
public string Bearer { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,12 @@
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a server <see cref="User"/>
|
||||
/// </summary>
|
||||
[Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers)]
|
||||
public sealed class User
|
||||
/// <inheritdoc />
|
||||
public sealed class User : Internal.User
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="User"/>
|
||||
/// The <see cref="User"/> who created this <see cref="User"/>
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The SID/UID of the <see cref="User"/> on Windows/POSIX respectively
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public string SystemId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Permissions(DenyWrite = true)]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.AdministrationRights"/> for the <see cref="User"/>
|
||||
/// </summary>
|
||||
public AdministrationRights AdministrationRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.InstanceManagerRights"/> for the <see cref="User"/>
|
||||
/// </summary>
|
||||
public InstanceManagerRights InstanceManagerRights { get; set; }
|
||||
public User CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,11 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Administration.SystemAuthenticationGroup"/>
|
||||
/// User can change <see cref="Models.Internal.ServerSettings.SystemAuthenticationGroup"/>
|
||||
/// </summary>
|
||||
ChangeAuthenticationGroup = 1,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Administration.EnableTelemetry"/>
|
||||
/// User can change <see cref="Models.Internal.ServerSettings.EnableTelemetry"/>
|
||||
/// </summary>
|
||||
ChangeTelemetry = 2,
|
||||
/// <summary>
|
||||
@@ -33,9 +33,8 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
ChangeVersion = 16,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Administration.UpstreamRepository"/>
|
||||
/// User can change <see cref="Models.Internal.ServerSettings.UpstreamRepository"/>
|
||||
/// </summary>
|
||||
SetUpstreamRepository
|
||||
|
||||
SetUpstreamRepository = 32
|
||||
}
|
||||
}
|
||||
|
||||
+4
-8
@@ -3,10 +3,10 @@
|
||||
namespace Tgstation.Server.Api.Rights
|
||||
{
|
||||
/// <summary>
|
||||
/// Rights for <see cref="Models.Chat"/>
|
||||
/// Rights for <see cref="Models.ChatSettings"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ChatRights
|
||||
public enum ChatSettingsRights
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
@@ -21,9 +21,9 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
SetIrcSettings = 2,
|
||||
/// <summary>
|
||||
/// User can change the IRC channels
|
||||
/// User can change the chat channels
|
||||
/// </summary>
|
||||
SetIrcChannels = 4,
|
||||
SetChannels = 4,
|
||||
/// <summary>
|
||||
/// User can enable/disable the Discord bot
|
||||
/// </summary>
|
||||
@@ -32,9 +32,5 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User can change the Discord settings
|
||||
/// </summary>
|
||||
SetDiscordSettings = 16,
|
||||
/// <summary>
|
||||
/// User can change the Discord channels
|
||||
/// </summary>
|
||||
SetDiscordChannels = 32,
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// User can read <see cref="Models.DreamDaemon.PullRequests"/>, <see cref="Models.DreamDaemon.Sha"/>, and <see cref="Models.DreamDaemon.OriginSha"/>
|
||||
/// User can read <see cref="Models.DreamDaemon.CompileJob"/>
|
||||
/// </summary>
|
||||
ReadRevision = 1,
|
||||
/// <summary>
|
||||
@@ -21,27 +21,27 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
SetPorts = 2,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.DreamDaemon.AutoStart"/>
|
||||
/// User can change <see cref="Models.Internal.DreamDaemonSettings.AutoStart"/>
|
||||
/// </summary>
|
||||
SetAutoStart = 4,
|
||||
/// <summary>
|
||||
/// User set <see cref="Models.DreamDaemon.SecurirtyLevel"/>
|
||||
/// User set <see cref="Models.Internal.DreamDaemonSettings.SecurityLevel"/>
|
||||
/// </summary>
|
||||
SetSecurity = 8,
|
||||
/// <summary>
|
||||
/// User can read all ports, <see cref="Models.DreamDaemon.SoftRestart"/>, <see cref="Models.DreamDaemon.SoftShutdown"/>, <see cref="Models.DreamDaemon.Status"/>, <see cref="Models.DreamDaemon.AllowWebClient"/>, and <see cref="Models.DreamDaemon.AutoStart"/>
|
||||
/// User can read all ports, <see cref="Models.Internal.DreamDaemonSettings.SoftRestart"/>, <see cref="Models.Internal.DreamDaemonSettings.SoftShutdown"/>, <see cref="Models.DreamDaemon.Status"/>, <see cref="Models.Internal.DreamDaemonSettings.AllowWebClient"/>, and <see cref="Models.Internal.DreamDaemonSettings.AutoStart"/>
|
||||
/// </summary>
|
||||
ReadMetadata = 16,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.DreamDaemon.AllowWebClient"/>
|
||||
/// User can change <see cref="Models.Internal.DreamDaemonSettings.AllowWebClient"/>
|
||||
/// </summary>
|
||||
SetWebClient = 32,
|
||||
/// <summary>
|
||||
/// User can enable <see cref="Models.DreamDaemon.SoftRestart"/>
|
||||
/// User can enable <see cref="Models.Internal.DreamDaemonSettings.SoftRestart"/>
|
||||
/// </summary>
|
||||
SoftRestart = 64,
|
||||
/// <summary>
|
||||
/// User can enable <see cref="Models.DreamDaemon.SoftShutdown"/>
|
||||
/// User can enable <see cref="Models.Internal.DreamDaemonSettings.SoftShutdown"/>
|
||||
/// </summary>
|
||||
SoftShutdown = 128,
|
||||
/// <summary>
|
||||
@@ -53,7 +53,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
Shutdown = 512,
|
||||
/// <summary>
|
||||
/// User can start <see cref="Models.DreamDaemon"/> and disable <see cref="Models.DreamDaemon.SoftRestart"/> and <see cref="Models.DreamDaemon.SoftShutdown"/>
|
||||
/// User can start <see cref="Models.DreamDaemon"/> and disable <see cref="Models.Internal.DreamDaemonSettings.SoftRestart"/> and <see cref="Models.Internal.DreamDaemonSettings.SoftShutdown"/>
|
||||
/// </summary>
|
||||
Start = 1024
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
CancelCompile = 4,
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.DreamMaker.AutoCompileInterval"/>
|
||||
/// User may modify <see cref="Models.Internal.DreamMakerSettings.AutoCompileInterval"/>
|
||||
/// </summary>
|
||||
SetAutoCompile = 8,
|
||||
/// <summary>
|
||||
/// User may modify <see cref="Models.DreamMaker.TargetDme"/>
|
||||
/// User may modify <see cref="Models.Internal.DreamMakerSettings.TargetDme"/>
|
||||
/// </summary>
|
||||
SetDme = 16
|
||||
}
|
||||
|
||||
@@ -39,6 +39,10 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// <summary>
|
||||
/// User can view all <see cref="Models.Instance"/>s
|
||||
/// </summary>
|
||||
List = 64
|
||||
List = 64,
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Instance.ConfigurationAllowed"/>
|
||||
/// </summary>
|
||||
SetConfiguration = 128
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,19 +29,19 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
UpdateBranch = 16,
|
||||
/// <summary>
|
||||
/// User may change <see cref="Models.Repository.CommitterName"/> and <see cref="Models.Repository.CommitterEmail"/>
|
||||
/// User may change <see cref="Models.Internal.RepositorySettings.CommitterName"/> and <see cref="Models.Internal.RepositorySettings.CommitterEmail"/>
|
||||
/// </summary>
|
||||
ChangeCommitter = 32,
|
||||
/// <summary>
|
||||
/// User may change <see cref="Models.Repository.PushTestMergeCommits"/>
|
||||
/// User may change <see cref="Models.Internal.RepositorySettings.PushTestMergeCommits"/>
|
||||
/// </summary>
|
||||
ChangeTestMergeCommits = 64,
|
||||
/// <summary>
|
||||
/// User may change <see cref="Models.Repository.AutoUpdateInterval"/>
|
||||
/// User may change <see cref="Models.Internal.RepositorySettings.AutoUpdateInterval"/>
|
||||
/// </summary>
|
||||
ChangeAutoUpdate = 128,
|
||||
/// <summary>
|
||||
/// User may read and change <see cref="Models.Repository.AccessUser"/> and <see cref="Models.Repository.AccessToken"/>
|
||||
/// User may read and change <see cref="Models.Internal.RepositorySettings.AccessUser"/> and <see cref="Models.Internal.RepositorySettings.AccessToken"/>
|
||||
/// </summary>
|
||||
ChangeCredentials = 256,
|
||||
/// <summary>
|
||||
@@ -49,7 +49,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
SetReference = 512,
|
||||
/// <summary>
|
||||
/// User may read all fields in the <see cref="Models.Repository"/> with the exception of <see cref="Models.Repository.AccessToken"/>
|
||||
/// User may read all fields in the <see cref="Models.Repository"/> with the exception of <see cref="Models.Internal.RepositorySettings.AccessToken"/>
|
||||
/// </summary>
|
||||
Read = 1024,
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@ namespace Tgstation.Server.Api.Rights
|
||||
{
|
||||
{ RightsType.Administration, typeof(AdministrationRights) },
|
||||
{ RightsType.InstanceManager, typeof(InstanceManagerRights) },
|
||||
{ RightsType.Token, typeof(TokenRights) },
|
||||
|
||||
{ RightsType.Repository, typeof(RepositoryRights) },
|
||||
{ RightsType.Byond, typeof(ByondRights) },
|
||||
{ RightsType.DreamMaker, typeof(DreamMakerRights) },
|
||||
{ RightsType.DreamDaemon, typeof(DreamDaemonRights) },
|
||||
{ RightsType.Chat, typeof(ChatRights) },
|
||||
{ RightsType.ChatSettings, typeof(ChatSettingsRights) },
|
||||
{ RightsType.Configuration, typeof(ConfigurationRights) },
|
||||
{ RightsType.InstanceUser, typeof(InstanceUserRights) }
|
||||
};
|
||||
@@ -31,6 +30,33 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// </summary>
|
||||
/// <param name="rightsType">The <see cref="RightsType"/> to lookup</param>
|
||||
/// <returns>The <see cref="Enum"/> <see cref="Type"/> of the given <paramref name="rightsType"/></returns>
|
||||
public static Type TypeToRight(RightsType rightsType) => typeMap[rightsType];
|
||||
public static Type RightToType(RightsType rightsType) => typeMap[rightsType];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role claim name used for a given <paramref name="right"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TRight">The <see cref="RightsType"/></typeparam>
|
||||
/// <param name="right">The <typeparamref name="TRight"/></param>
|
||||
/// <returns>A <see cref="string"/> representing the claim role name</returns>
|
||||
public static string RoleName<TRight>(TRight right) => String.Concat(typeof(TRight).Name, '.', right.ToString());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role claim name used for a given <paramref name="rightsType"/> and <paramref name="right"/>
|
||||
/// </summary>
|
||||
/// <param name="rightsType">The <see cref="RightsType"/></param>
|
||||
/// <param name="right">The right value</param>
|
||||
/// <returns>A <see cref="string"/> representing the claim role name</returns>
|
||||
public static string RoleName(RightsType rightsType, Enum right)
|
||||
{
|
||||
var enumType = typeMap[rightsType];
|
||||
return String.Concat(enumType.Name, '.', Enum.GetName(enumType, right));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given <paramref name="rightsType"/> is meant for an <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="rightsType">The <see cref="RightsType"/> to check</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="rightsType"/> is an instance right, <see langword="false"/> otherwise</returns>
|
||||
public static bool IsInstanceRight(RightsType rightsType) => !(rightsType == RightsType.Administration || rightsType == RightsType.InstanceManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,6 @@
|
||||
/// <see cref="InstanceManagerRights"/>
|
||||
/// </summary>
|
||||
InstanceManager,
|
||||
/// <summary>
|
||||
/// <see cref="TokenRights"/>
|
||||
/// </summary>
|
||||
Token,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="RepositoryRights"/>
|
||||
/// </summary>
|
||||
@@ -35,9 +30,9 @@
|
||||
/// </summary>
|
||||
DreamDaemon,
|
||||
/// <summary>
|
||||
/// <see cref="ChatRights"/>
|
||||
/// <see cref="ChatSettingsRights"/>
|
||||
/// </summary>
|
||||
Chat,
|
||||
ChatSettings,
|
||||
/// <summary>
|
||||
/// <see cref="ConfigurationRights"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Rights
|
||||
{
|
||||
/// <summary>
|
||||
/// Rights for <see cref="Models.Token"/>s
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum TokenRights
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
/// </summary>
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// User can create <see cref="Models.Token"/>s
|
||||
/// </summary>
|
||||
Create = 1,
|
||||
/// <summary>
|
||||
/// User can delete <see cref="Models.Token"/>s
|
||||
/// </summary>
|
||||
Delete = 2,
|
||||
/// <summary>
|
||||
/// User can list all their <see cref="Models.Token"/>s
|
||||
/// </summary>
|
||||
List = 4,
|
||||
/// <summary>
|
||||
/// User can perform all the actions they can for themselves on behalf of other users except <see cref="Create"/>
|
||||
/// </summary>
|
||||
Admin = 8,
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,18 @@
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>bin\Release\netstandard2.0\Tgstation.Server.Api.xml</DocumentationFile>
|
||||
<NoWarn>1701;1702;1705;CA2227</NoWarn>
|
||||
<NoWarn>1701;1702;1705;CA2227;CA1819</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>1701;1702;1705;CA2227</NoWarn>
|
||||
<NoWarn>1701;1702;1705;CA2227;CA1819</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.1.0-preview2-final" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.0" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+7
-7
@@ -8,21 +8,21 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// For managing the chat bots
|
||||
/// </summary>
|
||||
public interface IChatClient : IRightsClient<ChatRights>
|
||||
public interface IChatSettingsClient : IRightsClient<ChatSettingsRights>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="Chat"/> represented by the <see cref="IChatClient"/>
|
||||
/// Get the <see cref="ChatSettings"/> represented by the <see cref="IChatSettingsClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Chat"/> represented by the <see cref="IChatClient"/></returns>
|
||||
Task<Chat> Read(CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="ChatSettings"/> represented by the <see cref="IChatSettingsClient"/></returns>
|
||||
Task<ChatSettings> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="Chat"/> setttings
|
||||
/// Updates the <see cref="ChatSettings"/> setttings
|
||||
/// </summary>
|
||||
/// <param name="chat">The <see cref="Chat"/> to update</param>
|
||||
/// <param name="chat">The <see cref="ChatSettings"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update(Chat chat, CancellationToken cancellationToken);
|
||||
Task Update(ChatSettings chat, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace Tgstation.Server.Client.Components
|
||||
Task Shutdown(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Update <see cref="DreamDaemon"/>. This may trigger <see cref="DreamDaemon.SoftRestart"/>
|
||||
/// Update <see cref="DreamDaemon"/>. This may trigger <see cref="Api.Models.Internal.DreamDaemonSettings.SoftRestart"/>
|
||||
/// </summary>
|
||||
/// <param name="dreamDaemon">The <see cref="DreamDaemon"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Client.Components
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="DreamMaker"/> represented by the <see cref="IDreamMakerClient"/></returns>
|
||||
Task<Chat> Read(CancellationToken cancellationToken);
|
||||
Task<ChatSettings> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="DreamMaker"/> setttings
|
||||
|
||||
@@ -35,9 +35,9 @@ namespace Tgstation.Server.Client.Components
|
||||
IInstanceUserClient Users { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IChatClient"/>
|
||||
/// Access the <see cref="IChatSettingsClient"/>
|
||||
/// </summary>
|
||||
IChatClient Chat { get; }
|
||||
IChatSettingsClient Chat { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IDreamMakerClient"/>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IServerClient"/> for managing <see cref="Token"/>s
|
||||
/// </summary>
|
||||
public interface ITokenClient: IRightsClient<TokenRights>
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a new <see cref="Token"/> for the connected user/ipaddress, deleting the current one if applicable
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="Token"/></returns>
|
||||
Task<Token> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all active <see cref="Token"/>s for the <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting a <see cref="IReadOnlyList{T}"/> of active <see cref="Token"/>s for the <see cref="IServerClient"/></returns>
|
||||
Task<IReadOnlyList<Token>> GetClientInfos(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all active <see cref="Token"/>s for all users
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting a <see cref="IReadOnlyDictionary{TKey, TValue}"/> of active <see cref="Token"/>s for the <see cref="IServerClient"/> keyed by username</returns>
|
||||
Task<IReadOnlyDictionary<string, Token>> GetAllInfos(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidate the specified active <paramref name="token"/>
|
||||
/// </summary>
|
||||
/// <param name="token">The <see cref="Token"/> to invalidate</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Invalidate(Token token, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidate all active <see cref="Token"/>s for a <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task InvalidateAllClient(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Invalidate all active <see cref="Token"/>s
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task InvalidateAll(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -30,11 +30,6 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
int RequeryRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="ITokenClient"/>
|
||||
/// </summary>
|
||||
ITokenClient Tokens { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IInstanceManagerClient"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Tgstation.Server.Client
|
||||
/// Create a <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="hostname">The URL to access tgstation-server at</param>
|
||||
/// <param name="token">The <see cref="Api.Models.Token.Value"/> to access the API with</param>
|
||||
/// <param name="token">The <see cref="Api.Models.Token.Bearer"/> to access the API with</param>
|
||||
/// <param name="timeout">The initial timeout for the connection</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="IServerClient"/></returns>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:51882/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"Tgstation.Server.Host.Console": {
|
||||
"commandName": "Project",
|
||||
"workingDirectory": "bin\\Debug\\netcoreapp2.0",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:51885/"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,18 @@ namespace Tgstation.Server.Host.Startup
|
||||
/// <summary>
|
||||
/// Represents the host
|
||||
/// </summary>
|
||||
public interface IServer : IDisposable
|
||||
public interface IServer
|
||||
{
|
||||
/// <summary>
|
||||
/// The path to the updated assembly to run if any. Populated once <see cref="RunAsync(string[], CancellationToken)"/> returns
|
||||
/// The path to the updated assembly to run if any. Populated once <see cref="RunAsync(CancellationToken)"/> returns
|
||||
/// </summary>
|
||||
string UpdatePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Runs the <see cref="IServer"/>
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments for the <see cref="IServer"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task RunAsync(string[] args, CancellationToken cancellationToken);
|
||||
Task RunAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
/// </summary>
|
||||
public interface IServerFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a <see cref="IServer"/>
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="IServer"/></returns>
|
||||
IServer CreateServer();
|
||||
/// <summary>
|
||||
/// Create a <see cref="IServer"/>
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments for the <see cref="IServer"/></param>
|
||||
/// <returns>A new <see cref="IServer"/></returns>
|
||||
IServer CreateServer(string[] args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
/// <summary>
|
||||
/// Loads the <see cref="Assembly"/> at <see cref="assemblyPath"/> and creates an <see cref="IServer"/> from it
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments for the <see cref="IServer"/></param>
|
||||
/// <returns>A new <see cref="IServer"/></returns>
|
||||
public IServer CreateServer()
|
||||
public IServer CreateServer(string[] args)
|
||||
{
|
||||
var assembly = LoadFromAssemblyPath(assemblyPath);
|
||||
//find the IServerFactory implementation
|
||||
@@ -35,7 +36,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
var serverFactoryImplementationType = assembly.GetTypes().Where(x => serverFactoryInterfaceType.IsAssignableFrom(x)).First();
|
||||
|
||||
var serverFactory = (IServerFactory)Activator.CreateInstance(serverFactoryImplementationType);
|
||||
return serverFactory.CreateServer();
|
||||
return serverFactory.CreateServer(args);
|
||||
}
|
||||
|
||||
//honestly have no idea what this is for, but the examples i see just return null and it seems to work just fine
|
||||
|
||||
@@ -43,24 +43,19 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
//first run the host we started with
|
||||
var serverFactory = initialServerFactory;
|
||||
var assemblyPath = serverFactory.GetType().Assembly.Location;
|
||||
do
|
||||
{
|
||||
string updatePath;
|
||||
using (var server = serverFactory.CreateServer())
|
||||
{
|
||||
serverFactory = null;
|
||||
await server.RunAsync(args, cancellationToken).ConfigureAwait(false);
|
||||
updatePath = server.UpdatePath;
|
||||
}
|
||||
do
|
||||
{
|
||||
var server = serverFactory.CreateServer(args);
|
||||
await server.RunAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (updatePath == null)
|
||||
break;
|
||||
|
||||
activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath);
|
||||
File.Move(updatePath, assemblyPath);
|
||||
serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath);
|
||||
}
|
||||
while (!cancellationToken.IsCancellationRequested);
|
||||
if (server.UpdatePath == null)
|
||||
break;
|
||||
|
||||
activeAssemblyDeleter.DeleteActiveAssembly(assemblyPath);
|
||||
File.Move(server.UpdatePath, assemblyPath);
|
||||
serverFactory = isolatedAssemblyLoader.CreateIsolatedServerFactory(assemblyPath);
|
||||
}
|
||||
while (!cancellationToken.IsCancellationRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Tgstation.Server.Host.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration options for the <see cref="Models.DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
sealed class DatabaseConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The key for the <see cref="Microsoft.Extensions.Configuration.IConfigurationSection"/> the <see cref="DatabaseConfiguration"/> resides in
|
||||
/// </summary>
|
||||
public const string Section = "Database";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Configuration.DatabaseType"/> to create
|
||||
/// </summary>
|
||||
public DatabaseType DatabaseType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection string for the database
|
||||
/// </summary>
|
||||
public string ConnectionString { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Tgstation.Server.Host.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of database to user
|
||||
/// </summary>
|
||||
enum DatabaseType
|
||||
{
|
||||
/// <summary>
|
||||
/// Use Microsoft SQL Server
|
||||
/// </summary>
|
||||
SqlServer,
|
||||
/// <summary>
|
||||
/// Use MySQL/MariaDB
|
||||
/// </summary>
|
||||
MySql,
|
||||
/// <summary>
|
||||
/// Use SQLite 3
|
||||
/// </summary>
|
||||
Sqlite
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Controller"/> for API functions
|
||||
/// </summary>
|
||||
[Produces(ApiHeaders.ApplicationJson)]
|
||||
[Consumes(ApiHeaders.ApplicationJson)]
|
||||
public abstract class ApiController : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ApiHeaders"/> for the operation
|
||||
/// </summary>
|
||||
protected ApiHeaders ApiHeaders { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContext"/> for the operation
|
||||
/// </summary>
|
||||
protected IDatabaseContext DatabaseContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IAuthenticationContext"/> for the operation
|
||||
/// </summary>
|
||||
protected IAuthenticationContext AuthenticationContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the operation
|
||||
/// </summary>
|
||||
protected Instance Instance { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Runs after a <see cref="Api.Models.Token"/> has been validated. Creates the <see cref="IAuthenticationContext"/> for the <see cref="ControllerBase.Request"/>
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="TokenValidatedContext"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
public static async Task OnTokenValidated(TokenValidatedContext context)
|
||||
{
|
||||
var databaseContext = context.HttpContext.RequestServices.GetRequiredService<IDatabaseContext>();
|
||||
var authenticationContextFactory = context.HttpContext.RequestServices.GetRequiredService<IAuthenticationContextFactory>();
|
||||
|
||||
var userIdClaim = context.Principal.FindFirst(JwtRegisteredClaimNames.Sub);
|
||||
|
||||
if (userIdClaim == default(Claim))
|
||||
throw new InvalidOperationException("Missing required claim!");
|
||||
|
||||
long userId;
|
||||
try
|
||||
{
|
||||
userId = Int64.Parse(userIdClaim.Value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to parse user ID!", e);
|
||||
}
|
||||
|
||||
ApiHeaders apiHeaders;
|
||||
try
|
||||
{
|
||||
apiHeaders = new ApiHeaders(context.HttpContext.Request.GetTypedHeaders());
|
||||
}
|
||||
catch
|
||||
{
|
||||
//let OnActionExecutionAsync handle the reponse
|
||||
return;
|
||||
}
|
||||
|
||||
await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, context.HttpContext.RequestAborted).ConfigureAwait(false);
|
||||
|
||||
var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
|
||||
|
||||
var enumerator = Enum.GetValues(typeof(RightsType));
|
||||
var claims = new List<Claim>();
|
||||
foreach (RightsType I in enumerator)
|
||||
{
|
||||
var rightInt = authenticationContext.GetRight(I);
|
||||
var rightEnum = RightsHelper.RightToType(I);
|
||||
var right = (Enum)Enum.ToObject(rightEnum, authenticationContext.GetRight(I));
|
||||
foreach(Enum J in Enum.GetValues(rightEnum))
|
||||
if(right.HasFlag(J))
|
||||
claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J)));
|
||||
}
|
||||
|
||||
context.Principal.AddIdentity(new ClaimsIdentity(claims));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiController"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The value of <see cref="DatabaseContext"/></param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
|
||||
public ApiController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory)
|
||||
{
|
||||
DatabaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
|
||||
if (authenticationContextFactory == null)
|
||||
throw new ArgumentNullException(nameof(authenticationContextFactory));
|
||||
AuthenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
|
||||
Instance = AuthenticationContext?.InstanceUser?.Instance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
//validate the headers
|
||||
try
|
||||
{
|
||||
ApiHeaders = new ApiHeaders(Request.GetTypedHeaders());
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
await BadRequest(new { message = e.Message }).ExecuteResultAsync(context).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await base.OnActionExecutionAsync(context, next).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Main <see cref="ApiController"/> for the <see cref="Application"/>
|
||||
/// </summary>
|
||||
[Route("/")]
|
||||
public sealed class HomeController : ApiController
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ITokenFactory"/> for the <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
readonly ITokenFactory tokenFactory;
|
||||
/// <summary>
|
||||
/// The <see cref="ISystemIdentityFactory"/> for the <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
readonly ISystemIdentityFactory systemIdentityFactory;
|
||||
/// <summary>
|
||||
/// The <see cref="ICryptographySuite"/> for the <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
readonly ICryptographySuite cryptographySuite;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="HomeController"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="tokenFactory">The value of <see cref="tokenFactory"/></param>
|
||||
/// <param name="systemIdentityFactory">The value of <see cref="systemIdentityFactory"/></param>
|
||||
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
|
||||
public HomeController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, ITokenFactory tokenFactory, ISystemIdentityFactory systemIdentityFactory, ICryptographySuite cryptographySuite) : base(databaseContext, authenticationContextFactory)
|
||||
{
|
||||
this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory));
|
||||
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
|
||||
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the version of the <see cref="Application"/>
|
||||
/// </summary>
|
||||
/// <returns><see cref="Application.Version"/></returns>
|
||||
[TgsAuthorize]
|
||||
[HttpGet]
|
||||
public JsonResult Home() => Json(Application.Version);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to authenticate a <see cref="User"/> using <see cref="ApiController.ApiHeaders"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateToken(CancellationToken cancellationToken)
|
||||
{
|
||||
if (ApiHeaders.IsTokenAuthentication)
|
||||
return BadRequest(new { message = "Cannot create a token using another token!" });
|
||||
|
||||
var user = await DatabaseContext.Users.Where(x => x.Name == ApiHeaders.Username).Select(x => new User{
|
||||
Id = x.Id,
|
||||
PasswordHash = x.PasswordHash,
|
||||
SystemIdentifier = x.SystemIdentifier,
|
||||
Enabled = x.Enabled
|
||||
}).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
if (user.PasswordHash != null)
|
||||
{
|
||||
var originalHash = user.PasswordHash;
|
||||
if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
|
||||
return Unauthorized();
|
||||
if (user.PasswordHash != originalHash)
|
||||
{
|
||||
var updatedUser = new User
|
||||
{
|
||||
Id = user.Id,
|
||||
PasswordHash = originalHash
|
||||
};
|
||||
DatabaseContext.Users.Attach(updatedUser);
|
||||
updatedUser.PasswordHash = user.PasswordHash;
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
try
|
||||
{
|
||||
var systemIdentity = systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!user.Enabled)
|
||||
return Forbid();
|
||||
|
||||
return Json(tokenFactory.CreateToken(user));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="ApiController"/> representing a <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The model being represented</typeparam>
|
||||
public abstract class ModelController<TModel> : ApiController where TModel : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ModelAttribute"/> of the <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
protected static readonly ModelAttribute ModelAttribute = (ModelAttribute)typeof(TModel).GetCustomAttributes(typeof(ModelAttribute), true).First();
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ModelController{TModel}"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
|
||||
public ModelController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory) : base(databaseContext, authenticationContextFactory) { }
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to create a <paramref name="model"/>
|
||||
/// </summary>
|
||||
/// <param name="model">The <typeparamref name="TModel"/> being created</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
[HttpPut]
|
||||
public virtual Task<IActionResult> Create([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to read a <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
[HttpGet]
|
||||
public virtual Task<IActionResult> Read(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to update a <paramref name="model"/>
|
||||
/// </summary>
|
||||
/// <param name="model">The <typeparamref name="TModel"/> being updated</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
[HttpPost]
|
||||
public virtual Task<IActionResult> Update([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to delete a <paramref name="model"/>
|
||||
/// </summary>
|
||||
/// <param name="model">The <typeparamref name="TModel"/> being deleted</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
[HttpDelete]
|
||||
public virtual Task<IActionResult> Delete([FromBody]TModel model, CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to list entries of the <typeparamref name="TModel"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
[HttpGet("/List")]
|
||||
public virtual Task<IActionResult> List(CancellationToken cancellationToken) => Task.FromResult((IActionResult)NotFound());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using Cyberboss.AspNetCore.AsyncInitializer;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Reflection;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Controllers;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures the ASP.NET Core web application
|
||||
/// </summary>
|
||||
sealed class Application
|
||||
{
|
||||
/// <summary>
|
||||
/// The version of the <see cref="Application"/>
|
||||
/// </summary>
|
||||
public static readonly Version Version = Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IConfiguration"/> for the <see cref="Application"/>
|
||||
/// </summary>
|
||||
readonly IConfiguration configuration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IHostingEnvironment"/> for the <see cref="Application"/>
|
||||
/// </summary>
|
||||
readonly IHostingEnvironment hostingEnvironment;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="Application"/>
|
||||
/// </summary>
|
||||
/// <param name="configuration">The value of <see cref="configuration"/></param>
|
||||
/// <param name="hostingEnvironment">The value of <see cref="hostingEnvironment"/></param>
|
||||
public Application(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure dependency injected services
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to configure</param>
|
||||
#pragma warning disable CA1822 // Mark members as static
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
#pragma warning restore CA1822 // Mark members as static
|
||||
{
|
||||
if (services == null)
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
var workingDir = Environment.CurrentDirectory;
|
||||
var databaseConfigurationSection = configuration.GetSection(DatabaseConfiguration.Section);
|
||||
services.Configure<DatabaseConfiguration>(databaseConfigurationSection);
|
||||
|
||||
services.AddOptions();
|
||||
|
||||
const string scheme = "JwtBearer";
|
||||
services.AddAuthentication((options) =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = scheme;
|
||||
options.DefaultChallengeScheme = scheme;
|
||||
}).AddJwtBearer(scheme, jwtBearerOptions =>
|
||||
{
|
||||
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(TokenFactory.TokenSigningKey),
|
||||
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = TokenFactory.TokenIssuer,
|
||||
|
||||
ValidateLifetime = true,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = TokenFactory.TokenAudience,
|
||||
|
||||
ClockSkew = TimeSpan.FromMinutes(5),
|
||||
|
||||
RequireSignedTokens = true,
|
||||
|
||||
RequireExpirationTime = true
|
||||
};
|
||||
jwtBearerOptions.Events = new JwtBearerEvents
|
||||
{
|
||||
OnTokenValidated = ApiController.OnTokenValidated
|
||||
};
|
||||
});
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs
|
||||
|
||||
services.AddMvc();
|
||||
|
||||
var databaseConfiguration = databaseConfigurationSection.Get<DatabaseConfiguration>();
|
||||
void ConfigureDatabase(DbContextOptionsBuilder builder)
|
||||
{
|
||||
if (hostingEnvironment.IsDevelopment())
|
||||
builder.EnableSensitiveDataLogging();
|
||||
};
|
||||
|
||||
switch (databaseConfiguration.DatabaseType)
|
||||
{
|
||||
case DatabaseType.MySql:
|
||||
services.AddDbContext<MySqlDatabaseContext>(ConfigureDatabase);
|
||||
services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<MySqlDatabaseContext>());
|
||||
break;
|
||||
case DatabaseType.Sqlite:
|
||||
services.AddDbContext<SqliteDatabaseContext>(ConfigureDatabase);
|
||||
services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<SqliteDatabaseContext>());
|
||||
break;
|
||||
case DatabaseType.SqlServer:
|
||||
services.AddDbContext<SqlServerDatabaseContext>(ConfigureDatabase);
|
||||
services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<SqlServerDatabaseContext>());
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}!", nameof(DatabaseType)));
|
||||
}
|
||||
|
||||
services.AddScoped<IAuthenticationContextFactory, AuthenticationContextFactory>();
|
||||
|
||||
services.AddSingleton<ICryptographySuite, CryptographySuite>();
|
||||
services.AddSingleton<IDatabaseSeeder, DatabaseSeeder>();
|
||||
services.AddSingleton<IPasswordHasher<User>, PasswordHasher<User>>();
|
||||
services.AddSingleton<ITokenFactory, TokenFactory>();
|
||||
services.AddSingleton<ISystemIdentityFactory, SystemIdentityFactory>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the <see cref="Application"/>
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/> to configure</param>
|
||||
public void Configure(IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
if (applicationBuilder == null)
|
||||
throw new ArgumentNullException(nameof(applicationBuilder));
|
||||
|
||||
if (hostingEnvironment.IsDevelopment())
|
||||
applicationBuilder.UseDeveloperExceptionPage();
|
||||
|
||||
applicationBuilder.UseAsyncInitialization(async (cancellationToken) =>
|
||||
{
|
||||
using (var scope = applicationBuilder.ApplicationServices.CreateScope())
|
||||
await scope.ServiceProvider.GetRequiredService<IDatabaseContext>().Initialize(cancellationToken).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
applicationBuilder.UseAuthentication();
|
||||
applicationBuilder.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a service that may take an updated <see cref="Host"/> assembly and run it, stopping the current assembly in the process
|
||||
/// </summary>
|
||||
interface IServerUpdateConsumer
|
||||
{
|
||||
/// <summary>
|
||||
/// Run a new <see cref="Host"/> assembly and stop the current one. This will likely trigger all active <see cref="System.Threading.CancellationToken"/>s
|
||||
/// </summary>
|
||||
/// <param name="updatePath">The path to the new <see cref="Host"/> assembly</param>
|
||||
void ApplyUpdate(string updatePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class ChatChannel : Api.Models.ChatChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ChatSettings.Id"/>
|
||||
/// </summary>
|
||||
public long ChatSettingsId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ChatSettings"/>
|
||||
/// </summary>
|
||||
public ChatSettings ChatSettings { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class ChatSettings : Api.Models.Internal.ChatSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.ChatSettings.Channels"/>
|
||||
/// </summary>
|
||||
public List<ChatChannel> Channels { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class CompileJob : Api.Models.Internal.CompileJob
|
||||
{
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.CompileJob.TriggeredBy"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public User TriggeredBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.CompileJob.RevisionInformation"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public RevisionInformation RevisionInformation { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using ZNetCS.AspNetCore.Logging.EntityFrameworkCore;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
abstract class DatabaseContext<TParentContext> : DbContext, IDatabaseContext where TParentContext : DbContext
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public DbSet<ServerSettings> ServerSettings { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public DbSet<Instance> Instances { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DbSet{TEntity}"/> for <see cref="Log"/>s
|
||||
/// </summary>
|
||||
public DbSet<Log> Logs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceUser"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<InstanceUser> InstanceUsers { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="ChatChannel"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<ChatChannel> ChatChannels { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="ChatSettings"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<ChatSettings> ChatSettings { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamDaemonSettings"/> in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<DreamDaemonSettings> DreamDaemonSettings { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamMakerSettings"/> in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<DreamMakerSettings> DreamMakerSettings { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<CompileJob> CompileJobs { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<Job> Jobs { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="TestMerge"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<TestMerge> TestMerges { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="RevisionInformation"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<RevisionInformation> RevisionInformations { get; set; }
|
||||
/// <summary>
|
||||
/// The <see cref="Models.RepositorySettings"/> in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
public DbSet<RepositorySettings> RepositorySettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The connection string for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
protected string ConnectionString => databaseConfiguration.ConnectionString;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
readonly DatabaseConfiguration databaseConfiguration;
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
readonly ILoggerFactory loggerFactory;
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseSeeder databaseSeeder;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TParentContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="databaseConfiguration"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
/// <param name="databaseSeeder">The value of <see cref="databaseSeeder"/></param>
|
||||
public DatabaseContext(DbContextOptions<TParentContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfigurationOptions, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions)
|
||||
{
|
||||
databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// build default model.
|
||||
LogModelBuilderHelper.Build(modelBuilder.Entity<Log>());
|
||||
modelBuilder.Entity<Log>().ToTable(nameof(Logs));
|
||||
|
||||
modelBuilder.Entity<RevisionInformation>().HasIndex(x => x.Revision).IsUnique();
|
||||
var user = modelBuilder.Entity<User>();
|
||||
user.HasIndex(x => new { x.Name, x.SystemIdentifier }).IsUnique();
|
||||
user.HasOne(x => x.CreatedBy).WithMany(x => x.CreatedUsers).OnDelete(DeleteBehavior.Restrict);
|
||||
var chatChannel = modelBuilder.Entity<ChatChannel>();
|
||||
chatChannel.HasIndex(x => new { x.ChatSettingsId, x.IrcChannel }).IsUnique();
|
||||
chatChannel.HasIndex(x => new { x.ChatSettingsId, x.DiscordChannelId }).IsUnique();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
|
||||
optionsBuilder.UseLoggerFactory(loggerFactory);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ServerSettings> GetServerSettings(CancellationToken cancellationToken)
|
||||
{
|
||||
var settings = await ServerSettings.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (settings == default(ServerSettings))
|
||||
{
|
||||
settings = new ServerSettings();
|
||||
ServerSettings.Add(settings);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Initialize(CancellationToken cancellationToken)
|
||||
{
|
||||
#if DEBUG
|
||||
await Database.EnsureCreatedAsync().ConfigureAwait(false);
|
||||
var wasEmpty = (await Users.CountAsync().ConfigureAwait(false)) == 0;
|
||||
#else
|
||||
var migrations = await Database.GetAppliedMigrationsAsync().ConfigureAwait(false);
|
||||
var wasEmpty = !migrations.Any();
|
||||
await Database.MigrateAsync(cancellationToken).ConfigureAwait(false);
|
||||
#endif
|
||||
if (wasEmpty)
|
||||
await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DatabaseSeeder : IDatabaseSeeder
|
||||
{
|
||||
/// <summary>
|
||||
/// The default password mode admin password
|
||||
/// </summary>
|
||||
const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
|
||||
|
||||
/// <summary>
|
||||
/// The default git repository to pull server updates from
|
||||
/// </summary>
|
||||
const string DefaultUpstreamRepository = "https://github.com/tgstation/tgstation-server";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICryptographySuite"/> for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
readonly ICryptographySuite cryptographySuite;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DatabaseSeeder"/>
|
||||
/// </summary>
|
||||
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
|
||||
public DatabaseSeeder(ICryptographySuite cryptographySuite) => this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var admin = new User
|
||||
{
|
||||
AdministrationRights = (AdministrationRights)~0,
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
InstanceManagerRights = (InstanceManagerRights)~0,
|
||||
Name = "Admin",
|
||||
Enabled = true,
|
||||
};
|
||||
cryptographySuite.SetUserPassword(admin, DefaultAdminPassword);
|
||||
databaseContext.Users.Add(admin);
|
||||
|
||||
var serverSettings = await databaseContext.GetServerSettings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
serverSettings.EnableTelemetry = true;
|
||||
serverSettings.UpstreamRepository = DefaultUpstreamRepository;
|
||||
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class DreamDaemonSettings : Api.Models.Internal.DreamDaemonSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The PID of a currently running DD instance
|
||||
/// </summary>
|
||||
public int? ProcessId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The access token used for communication with DD
|
||||
/// </summary>
|
||||
public string AccessToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.DreamDaemon.CompileJob"/>
|
||||
/// </summary>
|
||||
public CompileJob CompileJob { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class DreamMakerSettings : Api.Models.Internal.DreamMakerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Api.Models.DreamMaker.LastJob"/>
|
||||
/// </summary>
|
||||
public CompileJob LastJob { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the database
|
||||
/// </summary>
|
||||
public interface IDatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="User"/>s in the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
DbSet<User> Users { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instances"/>s in the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
DbSet<Instance> Instances { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="ServerSettings"/> in the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="ServerSettings"/> in the <see cref="IDatabaseContext"/></returns>
|
||||
Task<ServerSettings> GetServerSettings(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Saves changes made to the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Save(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Creates and migrates the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Initialize(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// For initially seeding a database
|
||||
/// </summary>
|
||||
interface IDatabaseSeeder
|
||||
{
|
||||
/// <summary>
|
||||
/// Initially seed a given <paramref name="databaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> to seed</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an <see cref="Api.Models.Instance"/> in the database
|
||||
/// </summary>
|
||||
public sealed class Instance : Api.Models.Instance
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ChatSettings"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public ChatSettings ChatSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamMakerSettings"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public DreamMakerSettings DreamMakerSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamDaemonSettings"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public DreamDaemonSettings DreamDaemonSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.RepositorySettings"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public RepositorySettings RepositorySettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceUser"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public List<InstanceUser> InstanceUsers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TestMerge"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public List<TestMerge> TestMerges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public List<CompileJob> CompileJobs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Jobs"/> in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public List<Job> Jobs { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class InstanceUser : Api.Models.InstanceUser
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance"/> the <see cref="InstanceUser"/> belongs to
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Instance Instance { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class Job : Api.Models.Internal.Job
|
||||
{
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.Job.StartedBy"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public User StartedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains helpers for creating design time <see cref="Models.DatabaseContext{TParentContext}"/>s
|
||||
/// </summary>
|
||||
static class DesignTimeDbContextFactoryHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Path to the json file to use for migrations configuration
|
||||
/// </summary>
|
||||
const string MigrationsJson = "appsettings.Development.json";
|
||||
|
||||
/// <inheritdoc />
|
||||
public static IOptions<DatabaseConfiguration> GetDbContextOptions()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
|
||||
builder.AddJsonFile(MigrationsJson);
|
||||
var configuration = builder.Build();
|
||||
return Options.Create(configuration.GetSection(DatabaseConfiguration.Section).Get<DatabaseConfiguration>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class MySqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory<MySqlDatabaseContext>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public MySqlDatabaseContext CreateDbContext(string[] args) => new MySqlDatabaseContext(new DbContextOptions<MySqlDatabaseContext>(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher<User>())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class SqlServerDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SqlServerDatabaseContext>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public SqlServerDatabaseContext CreateDbContext(string[] args) => new SqlServerDatabaseContext(new DbContextOptions<SqlServerDatabaseContext>(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher<User>())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class SqliteDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SqliteDatabaseContext>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions<SqliteDatabaseContext>(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new LoggerFactory(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher<User>())));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext{TParentContext}"/> for MySQL
|
||||
/// </summary>
|
||||
sealed class MySqlDatabaseContext : DatabaseContext<MySqlDatabaseContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a <see cref="MySqlDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
public MySqlDatabaseContext(DbContextOptions<MySqlDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
options.UseMySQL(ConnectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class RepositorySettings : Api.Models.Internal.RepositorySettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.Instance.Id"/>
|
||||
/// </summary>
|
||||
public long InstanceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The parent <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Instance Instance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.Repository.RevisionInformation"/>
|
||||
/// </summary>
|
||||
public RevisionInformation RevisionInformation { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class RevisionInformation : Api.Models.Internal.RevisionInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.RevisionInformation.TestMerges"/>
|
||||
/// </summary>
|
||||
public List<TestMerge> TestMerges { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class ServerSettings : Api.Models.Internal.ServerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The row Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext{TParentContext}"/> for Sqlserver
|
||||
/// </summary>
|
||||
sealed class SqlServerDatabaseContext : DatabaseContext<SqlServerDatabaseContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a <see cref="SqlServerDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
public SqlServerDatabaseContext(DbContextOptions<SqlServerDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
options.UseSqlServer(ConnectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext{TParentContext}"/> for Sqlite
|
||||
/// </summary>
|
||||
sealed class SqliteDatabaseContext : DatabaseContext<SqliteDatabaseContext>
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a <see cref="SqliteDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
public SqliteDatabaseContext(DbContextOptions<SqliteDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, ILoggerFactory loggerFactory, IDatabaseSeeder databaseSeeder) : base(dbContextOptions, databaseConfiguration, loggerFactory, databaseSeeder)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
options.UseSqlite(ConnectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class TestMerge : Api.Models.Internal.TestMerge
|
||||
{
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.TestMerge.MergedBy"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public User MergedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class User : Api.Models.Internal.User
|
||||
{
|
||||
/// <summary>
|
||||
/// The hash of the user's password
|
||||
/// </summary>
|
||||
public string PasswordHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// See <see cref="Api.Models.User"/>
|
||||
/// </summary>
|
||||
public User CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="User"/>s created by this <see cref="User"/>
|
||||
/// </summary>
|
||||
public List<User> CreatedUsers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceUser"/>s for the <see cref="User"/>
|
||||
/// </summary>
|
||||
public List<InstanceUser> InstanceUsers { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages <see cref="Api.Models.User"/>s for a scope
|
||||
/// </summary>
|
||||
sealed class AuthenticationContext : IAuthenticationContext
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public User User { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public InstanceUser InstanceUser { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity SystemIdentity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="IAuthenticationContext"/>
|
||||
/// </summary>
|
||||
/// <param name="systemIdentity">The value of <see cref="SystemIdentity"/></param>
|
||||
/// <param name="user">The value of <see cref="User"/></param>
|
||||
/// <param name="instanceUser">The value of <see cref="InstanceUser"/></param>
|
||||
public AuthenticationContext(ISystemIdentity systemIdentity, User user, InstanceUser instanceUser)
|
||||
{
|
||||
User = user ?? throw new ArgumentNullException(nameof(user));
|
||||
if (systemIdentity == null && User.SystemIdentifier != null)
|
||||
throw new ArgumentNullException(nameof(systemIdentity));
|
||||
InstanceUser = instanceUser;
|
||||
SystemIdentity = systemIdentity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => SystemIdentity.Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAuthenticationContext Clone() => new AuthenticationContext(SystemIdentity.Clone(), User, InstanceUser);
|
||||
|
||||
/// <inheritdoc />
|
||||
public int GetRight(RightsType rightsType)
|
||||
{
|
||||
var isInstance = RightsHelper.IsInstanceRight(rightsType);
|
||||
|
||||
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);
|
||||
|
||||
var prop = typeToCheck.GetProperties().Where(x => x.PropertyType == rightsEnum).First();
|
||||
|
||||
var right = prop.GetMethod.Invoke(isInstance ? (object)InstanceUser : User, Array.Empty<object>());
|
||||
|
||||
return (int)right;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class AuthenticationContextFactory : IAuthenticationContextFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IAuthenticationContext CurrentAuthenticationContext { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISystemIdentityFactory"/> for the <see cref="AuthenticationContextFactory"/>
|
||||
/// </summary>
|
||||
readonly ISystemIdentityFactory systemIdentityFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContext"/> for the <see cref="AuthenticationContextFactory"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContext databaseContext;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="AuthenticationContextFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="systemIdentityFactory">The value of <see cref="systemIdentityFactory"/></param>
|
||||
/// <param name="databaseContext">The value of <see cref="databaseContext"/></param>
|
||||
public AuthenticationContextFactory(ISystemIdentityFactory systemIdentityFactory, IDatabaseContext databaseContext)
|
||||
{
|
||||
this.systemIdentityFactory = systemIdentityFactory ?? throw new ArgumentNullException(nameof(systemIdentityFactory));
|
||||
this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CreateAuthenticationContext(long userId, long? instanceId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (CurrentAuthenticationContext != null)
|
||||
throw new InvalidOperationException("Authentication context has already been loaded");
|
||||
|
||||
var userQuery = databaseContext.Users.Where(x => x.Id == userId);
|
||||
|
||||
if (instanceId.HasValue)
|
||||
userQuery = userQuery.Include(x => x.InstanceUsers.Where(y => y.Id == instanceId));
|
||||
|
||||
var user = await userQuery.FirstAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
InstanceUser instanceUser = null;
|
||||
if (instanceId.HasValue)
|
||||
instanceUser = user.InstanceUsers.First();
|
||||
|
||||
CurrentAuthenticationContext = new AuthenticationContext(user.SystemIdentifier != null ? systemIdentityFactory.CreateSystemIdentity(user) : null, user, instanceUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class CryptographySuite : ICryptographySuite
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a secure set of <see cref="byte"/>s
|
||||
/// </summary>
|
||||
/// <returns>A secure set of <see cref="byte"/>s</returns>
|
||||
public static byte[] GetSecureBytes(int amount)
|
||||
{
|
||||
using (var rng = new RNGCryptoServiceProvider())
|
||||
{
|
||||
var byt = new byte[amount];
|
||||
rng.GetBytes(byt);
|
||||
return byt;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPasswordHasher{TUser}"/> for the <see cref="CryptographySuite"/>
|
||||
/// </summary>
|
||||
readonly IPasswordHasher<User> passwordHasher;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="CryptographySuite"/>
|
||||
/// </summary>
|
||||
/// <param name="passwordHasher">The value of <see cref="passwordHasher"/></param>
|
||||
public CryptographySuite(IPasswordHasher<User> passwordHasher) => this.passwordHasher = passwordHasher ?? throw new ArgumentNullException(nameof(passwordHasher));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetUserPassword(User user, string newPassword)
|
||||
{
|
||||
if (user == null)
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
if (String.IsNullOrEmpty(newPassword))
|
||||
throw new ArgumentNullException(nameof(newPassword));
|
||||
user.PasswordHash = passwordHasher.HashPassword(user, newPassword);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CheckUserPassword(User user, string password)
|
||||
{
|
||||
switch(passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password))
|
||||
{
|
||||
case PasswordVerificationResult.Failed:
|
||||
return false;
|
||||
case PasswordVerificationResult.SuccessRehashNeeded:
|
||||
user.PasswordHash = passwordHasher.HashPassword(user, password);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the currently authenticated <see cref="Api.Models.User"/>
|
||||
/// </summary>
|
||||
public interface IAuthenticationContext : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The authenticated user
|
||||
/// </summary>
|
||||
User User { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceUser"/> of <see cref="User"/> if applicable
|
||||
/// </summary>
|
||||
InstanceUser InstanceUser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of a given <paramref name="rightsType"/>
|
||||
/// </summary>
|
||||
/// <param name="rightsType">The <see cref="RightsType"/> of the right to get</param>
|
||||
/// <returns>The value of <paramref name="rightsType"/>. Note that if <see cref="InstanceUser"/> is <see langword="null"/> all <see cref="Instance"/> based rights will return 0</returns>
|
||||
int GetRight(RightsType rightsType);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISystemIdentity"/> of <see cref="User"/> if applicable
|
||||
/// </summary>
|
||||
ISystemIdentity SystemIdentity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of the <see cref="IAuthenticationContext"/>
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="IAuthenticationContext"/></returns>
|
||||
IAuthenticationContext Clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// For creating and accessing authentication contexts
|
||||
/// </summary>
|
||||
public interface IAuthenticationContextFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IAuthenticationContext"/> the <see cref="IAuthenticationContextFactory"/> created
|
||||
/// </summary>
|
||||
IAuthenticationContext CurrentAuthenticationContext { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an <see cref="IAuthenticationContext"/> to populate <see cref="CurrentAuthenticationContext"/>
|
||||
/// </summary>
|
||||
/// <param name="userId">The <see cref="Api.Models.Internal.User.Id"/> of the <see cref="IAuthenticationContext.User"/></param>
|
||||
/// <param name="instanceId">The <see cref="Api.Models.Instance.Id"/> of the operation</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task CreateAuthenticationContext(long userId, long? instanceId, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains various cryptographic functions
|
||||
/// </summary>
|
||||
public interface ICryptographySuite
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets a <see cref="User.PasswordHash"/> for a given <paramref name="user"/>
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="User"/> whos <see cref="User.PasswordHash"/> is to be set</param>
|
||||
/// <param name="newPassword">The new password for the <see cref="User"/></param>
|
||||
void SetUserPassword(User user, string newPassword);
|
||||
|
||||
/// <summary>
|
||||
/// Checks a given <paramref name="password"/> matches a given <paramref name="user"/>'s <see cref="User.PasswordHash"/>. This may result in <see cref="User.PasswordHash"/> being modified and this should be persisted
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="User"/> to check</param>
|
||||
/// <param name="password">The password to check</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="password"/> matches the hash, <see langword="false"/> otherwise</returns>
|
||||
bool CheckUserPassword(User user, string password);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a user on the current <see cref="System.Runtime.InteropServices.OSPlatform"/>
|
||||
/// </summary>
|
||||
public interface ISystemIdentity : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// A unique identifier for the user
|
||||
/// </summary>
|
||||
string Uid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The user's name
|
||||
/// </summary>
|
||||
string Username { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Groups the user is in
|
||||
/// </summary>
|
||||
IEnumerable<string> Groups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clone the <see cref="ISystemIdentity"/> creating another copy that must have <see cref="IDisposable.Dispose"/> called on it
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="ISystemIdentity"/> mirroring the current one</returns>
|
||||
ISystemIdentity Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Runs a given <paramref name="action"/> in the context of the <see cref="ISystemIdentity"/>
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to perform, should be simple and not use any <see cref="Task"/>s or threading</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task RunImpersonated(Action action, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for <see cref="ISystemIdentity"/>s
|
||||
/// </summary>
|
||||
public interface ISystemIdentityFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a <see cref="ISystemIdentity"/> for a given <paramref name="user"/>
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create a <see cref="ISystemIdentity"/> for</param>
|
||||
/// <returns>A new <see cref="ISystemIdentity"/> or <see langword="null"/> if the <paramref name="user"/> has no <see cref="ISystemIdentity"/></returns>
|
||||
ISystemIdentity CreateSystemIdentity(User user);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ISystemIdentity"/> for a given username and password
|
||||
/// </summary>
|
||||
/// <param name="username">The username of the user</param>
|
||||
/// <param name="password">The password of the user</param>
|
||||
/// <returns>A new <see cref="ISystemIdentity"/></returns>
|
||||
ISystemIdentity CreateSystemIdentity(string username, string password);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// For creating <see cref="Token"/>s
|
||||
/// </summary>
|
||||
public interface ITokenFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a <see cref="Token"/> for a given <paramref name="user"/>
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="Models.User"/> to create the token for. Must have the <see cref="Api.Models.Internal.User.Id"/> field available</param>
|
||||
/// <returns>A new <see cref="Token"/></returns>
|
||||
Token CreateToken(Models.User user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class SystemIdentityFactory : ISystemIdentityFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity CreateSystemIdentity(User user)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity CreateSystemIdentity(string username, string password)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class TokenFactory : ITokenFactory
|
||||
{
|
||||
public static readonly string TokenAudience = typeof(Token).Assembly.GetName().Name;
|
||||
public static readonly string TokenIssuer = Assembly.GetExecutingAssembly().GetName().Name;
|
||||
public static readonly byte[] TokenSigningKey = CryptographySuite.GetSecureBytes(256);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Token CreateToken(Models.User user)
|
||||
{
|
||||
if (user == null)
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
|
||||
var claims = new Claim[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString(CultureInfo.InvariantCulture)),
|
||||
new Claim(JwtRegisteredClaimNames.Exp, $"{DateTimeOffset.Now.AddHours(1).ToUnixTimeSeconds()}"),
|
||||
new Claim(JwtRegisteredClaimNames.Nbf, $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"),
|
||||
new Claim(JwtRegisteredClaimNames.Iss, TokenIssuer),
|
||||
new Claim(JwtRegisteredClaimNames.Aud, TokenAudience)
|
||||
};
|
||||
|
||||
var key = new SymmetricSecurityKey(TokenSigningKey);
|
||||
|
||||
var token = new JwtSecurityToken(new JwtHeader(new SigningCredentials(key, SecurityAlgorithms.HmacSha256)), new JwtPayload(claims));
|
||||
return new Token { Bearer = new JwtSecurityTokenHandler().WriteToken(token) };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,60 @@
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Startup;
|
||||
|
||||
namespace Tgstation.Server.Host
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class Server : IServer
|
||||
sealed class Server : IServer, IServerUpdateConsumer
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string UpdatePath => null;
|
||||
public string UpdatePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWebHostBuilder"/> for the <see cref="Server"/>
|
||||
/// </summary>
|
||||
readonly IWebHostBuilder webHostBuilder;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="cancellationTokenSource"/> for the <see cref="Server"/>
|
||||
/// </summary>
|
||||
CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="Server"/>
|
||||
/// </summary>
|
||||
/// <param name="webHostBuilder">The value of <see cref="webHostBuilder"/></param>
|
||||
public Server(IWebHostBuilder webHostBuilder) => this.webHostBuilder = webHostBuilder ?? throw new ArgumentNullException(nameof(webHostBuilder));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
[ExcludeFromCodeCoverage]
|
||||
public async Task RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
using (cancellationTokenSource)
|
||||
using (var webHost = webHostBuilder
|
||||
.UseStartup<Application>()
|
||||
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerUpdateConsumer>(this))
|
||||
.Build()
|
||||
)
|
||||
await webHost.RunAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RunAsync(string[] args, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
public void ApplyUpdate(string updatePath)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (updatePath != null)
|
||||
throw new InvalidOperationException("ApplyUpdate has already been called!");
|
||||
UpdatePath = updatePath;
|
||||
}
|
||||
cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Tgstation.Server.Host.Startup;
|
||||
using Microsoft.AspNetCore;
|
||||
using Tgstation.Server.Host.Startup;
|
||||
|
||||
namespace Tgstation.Server.Host
|
||||
{
|
||||
@@ -6,6 +7,6 @@ namespace Tgstation.Server.Host
|
||||
public sealed class ServerFactory : IServerFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IServer CreateServer() => new Server();
|
||||
public IServer CreateServer(string[] args) => new Server(WebHost.CreateDefaultBuilder(args));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Host
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper for using the <see cref="AuthorizeAttribute"/> with the <see cref="Api.Rights"/> system
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
sealed class TgsAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/>
|
||||
/// </summary>
|
||||
public TgsAuthorizeAttribute() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="AdministrationRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(AdministrationRights requiredRights) => Roles = RightsHelper.RoleName(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.RoleName(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.RoleName(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.RoleName(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.RoleName(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.RoleName(requiredRights);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="ChatSettingsRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(ChatSettingsRights requiredRights) => Roles = RightsHelper.RoleName(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.RoleName(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.RoleName(requiredRights);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user