mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-28 07:27:18 +01:00
Commit early, commit often
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -6,7 +6,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
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// Represents a server <see cref="User"/>
|
||||
/// </summary>
|
||||
[Model(RightsType.Administration, WriteRight = AdministrationRights.EditUsers)]
|
||||
public sealed class User
|
||||
public class User
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="User"/>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages <see cref="Api.Models.User"/>s for a scope
|
||||
/// </summary>
|
||||
sealed class AuthenticationContext : IAuthenticationContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to store the <see cref="AuthenticationContext"/> in <see cref="HttpContext.Items"/>
|
||||
/// </summary>
|
||||
static readonly object contextKey = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Get the current <see cref="IAuthenticationContext"/> from an <paramref name="httpContext"/>
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> containing the <see cref="IAuthenticationContext"/></param>
|
||||
/// <returns>The <see cref="IAuthenticationContext"/> in <paramref name="httpContext"/></returns>
|
||||
public static IAuthenticationContext Current(HttpContext httpContext) => (IAuthenticationContext)httpContext.Items[contextKey];
|
||||
|
||||
public static void AddToPipeline(IApplicationBuilder applicationBuilder) => applicationBuilder.Use(async (httpContext, next) =>
|
||||
/// <summary>
|
||||
/// Adds <see cref="AuthenticationContext"/> handling to an <paramref name="applicationBuilder"/> pipeline
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/> to add to</param>
|
||||
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<ISystemIdentityFactory>();
|
||||
var tokenManager = applicationBuilder.ApplicationServices.GetRequiredService<ITokenManager>();
|
||||
var databaseContext = applicationBuilder.ApplicationServices.GetRequiredService<IDatabaseContext>();
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISystemIdentity SystemIdentity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContext"/> for the <see cref="AuthenticationContext"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContext databaseContext;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="IAuthenticationContext"/>
|
||||
/// </summary>
|
||||
/// <param name="systemIdentity">The value of <see cref="systemIdentity"/></param>
|
||||
/// <param name="databaseContext">The value of <see cref="databaseContext"/></param>
|
||||
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);
|
||||
}
|
||||
});
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => SystemIdentity.Dispose();
|
||||
|
||||
public AuthenticationContext(Headers headers)
|
||||
/// <inheritdoc />
|
||||
public Task<User> 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();
|
||||
/// <inheritdoc />
|
||||
public Task<Api.Models.InstanceUser> InstanceUser(Instance instance, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the currently authenticated <see cref="Api.Models.User"/>
|
||||
/// </summary>
|
||||
interface IAuthenticationContext : IDisposable
|
||||
{
|
||||
User User { get; }
|
||||
/// <summary>
|
||||
/// The <see cref="ISystemIdentity"/> of <see cref="User"/>
|
||||
/// </summary>
|
||||
ISystemIdentity SystemIdentity { get; }
|
||||
|
||||
InstanceUser InstanceUser { get; }
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.User"/> represented by the <see cref="IAuthenticationContext"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="User"/> represented by the <see cref="IAuthenticationContext"/></returns>
|
||||
Task<User> User(CancellationToken cancellationToken);
|
||||
|
||||
IAuthenticationContext Clone();
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.InstanceUser"/> represented by <see cref="User"/> and a given <paramref name="instance"/>
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="Instance"/> of the <see cref="Api.Models.InstanceUser"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Api.Models.InstanceUser"/> represented by <see cref="User"/> and a given <paramref name="instance"/></returns>
|
||||
Task<Api.Models.InstanceUser> InstanceUser(Instance instance, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -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<string> Groups { get; }
|
||||
|
||||
ISystemIdentity Clone();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<User> GetUser(string token, CancellationToken cancellationToken);
|
||||
Task<Token> CreateToken(User user, CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<Token>> UserTokens(User user, CancellationToken cancellationToken);
|
||||
Task<IDictionary<User, Token>> AllTokens(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the database
|
||||
/// </summary>
|
||||
interface IDatabaseContext
|
||||
{
|
||||
DbSet<User> Users { get; }
|
||||
|
||||
DbSet<Instance> Instances { get; }
|
||||
|
||||
Task Save(CancellationToken cancellationToken);
|
||||
|
||||
Task Initialize(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an <see cref="Api.Models.Instance"/> in the database
|
||||
/// </summary>
|
||||
sealed class Instance : Api.Models.Instance
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceUser"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public List<InstanceUser> InstanceUsers { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent a merge of a live pull request
|
||||
/// </summary>
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="Api.Models.User"/> in the database
|
||||
/// </summary>
|
||||
sealed class User : Api.Models.User
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceUser"/>s for the <see cref="User"/>
|
||||
/// </summary>
|
||||
List<InstanceUser> InstanceUsers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Token"/>s for the <see cref="User"/>
|
||||
/// </summary>
|
||||
List<Token> Tokens { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.2" />
|
||||
<PackageReference Include="Octokit" Version="0.29.0" />
|
||||
<PackageReference Include="System.Security.Principal.Windows" Version="4.4.1" />
|
||||
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="1.7.16" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user