Commit early, commit often

This commit is contained in:
Cyberboss
2018-04-07 10:13:55 -04:00
parent 21ff43c1db
commit 34d610a336
13 changed files with 255 additions and 47 deletions
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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; }
}
}
+21
View File
@@ -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>