diff --git a/src/Tgstation.Server.Api/ModelAttribute.cs b/src/Tgstation.Server.Api/ModelAttribute.cs index 6145721634..df50284cf3 100644 --- a/src/Tgstation.Server.Api/ModelAttribute.cs +++ b/src/Tgstation.Server.Api/ModelAttribute.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api /// /// Indicates the rights for a model /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public sealed class ModelAttribute : Attribute { /// diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 43d3eea404..276eed69e3 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// Metadata about a server instance /// [Model(RightsType.InstanceManager, CanCrud = true, CanList = true)] - public sealed class Instance + public class Instance { /// /// The id of the . Not modifiable diff --git a/src/Tgstation.Server.Api/Models/User.cs b/src/Tgstation.Server.Api/Models/User.cs index fe50e0bc77..e53f42e419 100644 --- a/src/Tgstation.Server.Api/Models/User.cs +++ b/src/Tgstation.Server.Api/Models/User.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models /// Represents a server /// [Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers)] - public sealed class User + public class User { /// /// The ID of the diff --git a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs index 6c770faa5b..6418d3be78 100644 --- a/src/Tgstation.Server.Host/Core/AuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/AuthenticationContext.cs @@ -1,63 +1,113 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; using System; using System.Net; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Tgstation.Server.Api; -using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { + /// + /// Manages s for a scope + /// sealed class AuthenticationContext : IAuthenticationContext { + /// + /// Used to store the in + /// static readonly object contextKey = new object(); + /// + /// Get the current from an + /// + /// The containing the + /// The in public static IAuthenticationContext Current(HttpContext httpContext) => (IAuthenticationContext)httpContext.Items[contextKey]; - public static void AddToPipeline(IApplicationBuilder applicationBuilder) => applicationBuilder.Use(async (httpContext, next) => + /// + /// Adds handling to an pipeline + /// + /// The to add to + public static void AddToPipeline(IApplicationBuilder applicationBuilder) + { + applicationBuilder.Use(async (httpContext, next) => + { + Headers headers; + try + { + headers = new Headers(httpContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value.First())); + } + catch (InvalidOperationException) + { + httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; + return; + } + + ISystemIdentity systemIdentity; + + var systemIdentityFactory = applicationBuilder.ApplicationServices.GetRequiredService(); + var tokenManager = applicationBuilder.ApplicationServices.GetRequiredService(); + var databaseContext = applicationBuilder.ApplicationServices.GetRequiredService(); + try + { + if (headers.IsTokenAuthentication) + { + var user = await tokenManager.GetUser(headers.Token, httpContext.RequestAborted).ConfigureAwait(false); + systemIdentity = systemIdentityFactory.CreateSystemIdentity(user); + } + else + systemIdentity = systemIdentityFactory.CreateSystemIdentity(headers.Username, headers.Password); + } + catch (UnauthorizedAccessException) + { + httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + return; + } + + using (var authContext = new AuthenticationContext(systemIdentity, databaseContext)) + { + httpContext.Items[contextKey] = authContext; + await next().ConfigureAwait(false); + } + }); + } + + /// + public ISystemIdentity SystemIdentity { get; } + + /// + /// The for the + /// + readonly IDatabaseContext databaseContext; + + /// + /// Construct a + /// + /// The value of + /// The value of + public AuthenticationContext(ISystemIdentity systemIdentity, IDatabaseContext databaseContext) { - Headers headers; - try - { - headers = new Headers(httpContext.Request.Headers.ToDictionary(x => x.Key, x => x.Value.First())); - } - catch (InvalidOperationException) - { - httpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest; - return; - } + SystemIdentity = systemIdentity ?? throw new ArgumentNullException(nameof(systemIdentity)); + this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext)); + } - using (var authContext = new AuthenticationContext(headers)) - { - if (!authContext.Valid) - { - httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - return; - } - httpContext.Items[contextKey] = authContext; - await next().ConfigureAwait(false); - } - }); + /// + public void Dispose() => SystemIdentity.Dispose(); - public AuthenticationContext(Headers headers) + /// + public Task User(CancellationToken cancellationToken) { throw new NotImplementedException(); } - ~AuthenticationContext() => Dispose(); - - public void Dispose() - { - - GC.SuppressFinalize(this); - } - - public IAuthenticationContext Clone() => throw new NotImplementedException(); - - bool Valid => throw new NotImplementedException(); - - public User User => throw new NotImplementedException(); - - public InstanceUser InstanceUser => throw new NotImplementedException(); + /// + public Task InstanceUser(Instance instance, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } } diff --git a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs index dacb668c86..1d2466bbaf 100644 --- a/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs +++ b/src/Tgstation.Server.Host/Core/IAuthenticationContext.cs @@ -1,15 +1,33 @@ using System; -using System.Collections.Generic; -using Tgstation.Server.Api.Models; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Core { + /// + /// Represents the currently authenticated + /// interface IAuthenticationContext : IDisposable { - User User { get; } + /// + /// The of + /// + ISystemIdentity SystemIdentity { get; } - InstanceUser InstanceUser { get; } + /// + /// The represented by the + /// + /// The for the operation + /// A resulting in the represented by the + Task User(CancellationToken cancellationToken); - IAuthenticationContext Clone(); + /// + /// The represented by and a given + /// + /// The of the + /// The for the operation + /// A resulting in the represented by and a given + Task InstanceUser(Instance instance, CancellationToken cancellationToken); } } \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/ISystemIdentity.cs b/src/Tgstation.Server.Host/Core/ISystemIdentity.cs new file mode 100644 index 0000000000..6f60a6bc99 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ISystemIdentity.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace Tgstation.Server.Host.Core +{ + interface ISystemIdentity : IDisposable + { + string Uid { get; } + string Username { get; } + IEnumerable Groups { get; } + + ISystemIdentity Clone(); + } +} \ No newline at end of file diff --git a/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs b/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs new file mode 100644 index 0000000000..05e03ea829 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ISystemIdentityFactory.cs @@ -0,0 +1,10 @@ +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Core +{ + interface ISystemIdentityFactory + { + ISystemIdentity CreateSystemIdentity(User user); + ISystemIdentity CreateSystemIdentity(string username, string password); + } +} diff --git a/src/Tgstation.Server.Host/Core/ITokenManager.cs b/src/Tgstation.Server.Host/Core/ITokenManager.cs new file mode 100644 index 0000000000..e898760f51 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ITokenManager.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Core +{ + interface ITokenManager + { + Task GetUser(string token, CancellationToken cancellationToken); + Task CreateToken(User user, CancellationToken cancellationToken); + Task> UserTokens(User user, CancellationToken cancellationToken); + Task> AllTokens(CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Models/IDatabaseContext.cs b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs new file mode 100644 index 0000000000..7b44a65ca6 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/IDatabaseContext.cs @@ -0,0 +1,20 @@ +using Microsoft.EntityFrameworkCore; +using System.Threading; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represents the database + /// + interface IDatabaseContext + { + DbSet Users { get; } + + DbSet Instances { get; } + + Task Save(CancellationToken cancellationToken); + + Task Initialize(CancellationToken cancellationToken); + } +} diff --git a/src/Tgstation.Server.Host/Models/Instance.cs b/src/Tgstation.Server.Host/Models/Instance.cs new file mode 100644 index 0000000000..573aa46e45 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Instance.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represents an in the database + /// + sealed class Instance : Api.Models.Instance + { + /// + /// The s in the + /// + public List InstanceUsers { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs new file mode 100644 index 0000000000..7ecbf704cc --- /dev/null +++ b/src/Tgstation.Server.Host/Models/TestMerge.cs @@ -0,0 +1,41 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represent a merge of a live pull request + /// + sealed class TestMerge + { + long Id { get; set; } + + int Number { get; set; } + + [Required] + string OriginCommit { get; set; } + + [Required] + string PullRequestCommit { get; set; } + + string MergeCommit { get; set; } + + [Required] + DateTimeOffset MergedAt { get; set; } + + [Required] + DateTimeOffset RemovedAt { get; set; } + + [Required] + User MergedBy { get; set; } + + [Required] + string TitleAtMerge { get; set; } + + [Required] + string BodyAtMerge { get; set; } + + [Required] + string Author { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs new file mode 100644 index 0000000000..743ee5e8db --- /dev/null +++ b/src/Tgstation.Server.Host/Models/User.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Models +{ + /// + /// Represents a in the database + /// + sealed class User : Api.Models.User + { + /// + /// The s for the + /// + List InstanceUsers { get; set; } + + /// + /// The s for the + /// + List Tokens { get; set; } + } +} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 9f4dc2c1ea..40ab8fa5f7 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -19,7 +19,10 @@ + + +