diff --git a/src/Tgstation.Server.Host/Components/IInstance.cs b/src/Tgstation.Server.Host/Components/IInstance.cs
index 921a408266..d90826f9b4 100644
--- a/src/Tgstation.Server.Host/Components/IInstance.cs
+++ b/src/Tgstation.Server.Host/Components/IInstance.cs
@@ -4,7 +4,6 @@ using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Components.Byond;
using Tgstation.Server.Host.Components.Chat;
-using Tgstation.Server.Host.Components.Compiler;
using Tgstation.Server.Host.Components.Repository;
using Tgstation.Server.Host.Components.StaticFiles;
using Tgstation.Server.Host.Components.Watchdog;
@@ -65,10 +64,10 @@ namespace Tgstation.Server.Host.Components
/// Run the compile job and insert it into the database. Meant to be called by a
///
/// The running
- /// The for the operation
+ /// The for the operation
/// The to report compilation progress
/// The for the operation
/// A representing the running operation
- Task CompileProcess(Job job, IServiceProvider serviceProvider, Action progressReporter, CancellationToken cancellationToken);
+ Task CompileProcess(Job job, IDatabaseContext databaseContext, Action progressReporter, CancellationToken cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs
index 79faad2b45..708725c349 100644
--- a/src/Tgstation.Server.Host/Components/Instance.cs
+++ b/src/Tgstation.Server.Host/Components/Instance.cs
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
@@ -117,18 +116,16 @@ namespace Tgstation.Server.Host.Components
}
///
- public async Task CompileProcess(Job job, IServiceProvider serviceProvider, Action progressReporter, CancellationToken cancellationToken)
+ public async Task CompileProcess(Job job, IDatabaseContext databaseContext, Action progressReporter, CancellationToken cancellationToken)
{
//DO NOT FOLLOW THE SUGGESTION FOR A THROW EXPRESSION HERE
if (job == null)
throw new ArgumentNullException(nameof(job));
- if (serviceProvider == null)
- throw new ArgumentNullException(nameof(serviceProvider));
+ if (databaseContext == null)
+ throw new ArgumentNullException(nameof(databaseContext));
if (progressReporter == null)
throw new ArgumentNullException(nameof(progressReporter));
- var databaseContext = serviceProvider.GetRequiredService();
-
var ddSettingsTask = databaseContext.DreamDaemonSettings.Where(x => x.InstanceId == metadata.Id).Select(x => new DreamDaemonSettings
{
StartupTimeout = x.StartupTimeout,
@@ -205,10 +202,9 @@ namespace Tgstation.Server.Host.Components
};
var noRepo = false;
- await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, serviceProvider, progressReporter, jobCancellationToken) =>
+ await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) =>
{
- var db = serviceProvider.GetRequiredService();
- var repositorySettingsTask = db.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
+ var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
//assume 5 steps with synchronize
const int ProgressSections = 5;
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs
index 28a1eeb60c..412612e49c 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs
@@ -836,7 +836,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
CancelRight = (ulong)DreamDaemonRights.Shutdown,
CancelRightsType = RightsType.DreamDaemon
};
- await jobManager.RegisterOperation(job, (j, serviceProvider, progressFunction, ct) => Launch(ct), cancellationToken).ConfigureAwait(false);
+ await jobManager.RegisterOperation(job, (j, databaseContext, progressFunction, ct) => Launch(ct), cancellationToken).ConfigureAwait(false);
}
///
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index 16fa1704dd..0fc660be6c 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -1,20 +1,14 @@
-using Microsoft.AspNetCore.Authentication.JwtBearer;
-using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
-using System.Collections.Generic;
using System.Globalization;
-using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net;
-using System.Security.Claims;
using System.Threading.Tasks;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
-using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
@@ -57,64 +51,6 @@ namespace Tgstation.Server.Host.Controllers
///
readonly bool requireInstance;
- ///
- /// Runs after a has been validated. Creates the for the
- ///
- /// The for the operation
- /// A representing the running operation
- public static async Task OnTokenValidated(TokenValidatedContext context)
- {
- var databaseContext = context.HttpContext.RequestServices.GetRequiredService();
- var authenticationContextFactory = context.HttpContext.RequestServices.GetRequiredService();
-
- 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.SecurityToken.ValidFrom, context.HttpContext.RequestAborted).ConfigureAwait(false);
-
- var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
-
- var enumerator = Enum.GetValues(typeof(RightsType));
- var claims = new List();
- foreach (RightsType I in enumerator)
- {
- //if there's no instance user, do a weird thing and add all the instance roles
- //we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid
- //if user is null that means they got the token with an expired password
- var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I);
- var rightEnum = RightsHelper.RightToType(I);
- var right = (Enum)Enum.ToObject(rightEnum, rightInt);
- 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));
- }
-
///
/// Construct an
///
diff --git a/src/Tgstation.Server.Host/Controllers/ByondController.cs b/src/Tgstation.Server.Host/Controllers/ByondController.cs
index 566aaae2b0..f8be5a0174 100644
--- a/src/Tgstation.Server.Host/Controllers/ByondController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ByondController.cs
@@ -95,7 +95,7 @@ namespace Tgstation.Server.Host.Controllers
CancelRight = (ulong)ByondRights.CancelInstall,
Instance = Instance
};
- await jobManager.RegisterOperation(job, (paramJob, serviceProvicer, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false);
+ await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false);
result.InstallJob = job.ToApi();
}
result.Version = byondManager.ActiveVersion;
diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
index 85d7841d8c..c13ec7e820 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
@@ -67,7 +67,7 @@ namespace Tgstation.Server.Host.Controllers
StartedBy = AuthenticationContext.User
};
await jobManager.RegisterOperation(job,
- async (paramJob, serviceProvider, progressHandler, innerCt) =>
+ async (paramJob, databaseContext, progressHandler, innerCt) =>
{
var result = await instance.Watchdog.Launch(innerCt).ConfigureAwait(false);
if (result == null)
@@ -231,7 +231,7 @@ namespace Tgstation.Server.Host.Controllers
var watchdog = instanceManager.GetInstance(Instance).Watchdog;
- await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => watchdog.Restart(false, ct), cancellationToken).ConfigureAwait(false);
+ await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressReporter, ct) => watchdog.Restart(false, ct), cancellationToken).ConfigureAwait(false);
return Accepted(job.ToApi());
}
}
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index aafd0da61e..0c6801cbf2 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -352,7 +352,7 @@ namespace Tgstation.Server.Host.Controllers
StartedBy = AuthenticationContext.User
};
- await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressHandler, ct) => instanceManager.MoveInstance(originalModel, rawPath, ct), cancellationToken).ConfigureAwait(false);
+ await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => instanceManager.MoveInstance(originalModel, rawPath, ct), cancellationToken).ConfigureAwait(false);
api.MoveJob = job.ToApi();
}
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index 20f87f6cd1..240227aaaf 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -1,6 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
@@ -186,20 +185,19 @@ namespace Tgstation.Server.Host.Controllers
Instance = Instance
};
var api = currentModel.ToApi();
- await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) =>
+ await jobManager.RegisterOperation(job, async (paramJob, databaseContext, progressReporter, ct) =>
{
using (var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, ct).ConfigureAwait(false))
{
if (repos == null)
throw new JobException("Filesystem conflict while cloning repository!");
- var db = serviceProvider.GetRequiredService();
var instance = new Models.Instance
{
Id = Instance.Id
};
- db.Instances.Attach(instance);
- if (await PopulateApi(api, repos, db, instance, ct).ConfigureAwait(false))
- await db.Save(ct).ConfigureAwait(false);
+ databaseContext.Instances.Attach(instance);
+ if (await PopulateApi(api, repos, databaseContext, instance, ct).ConfigureAwait(false))
+ await databaseContext.Save(ct).ConfigureAwait(false);
}
}, cancellationToken).ConfigureAwait(false);
@@ -238,7 +236,7 @@ namespace Tgstation.Server.Host.Controllers
Instance = Instance
};
var api = currentModel.ToApi();
- await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false);
+ await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false);
api.ActiveJob = job.ToApi();
return Accepted(api);
}
@@ -419,7 +417,7 @@ namespace Tgstation.Server.Host.Controllers
CancelRight = (ulong)RepositoryRights.CancelPendingChanges,
};
- await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressReporter, ct) =>
+ await jobManager.RegisterOperation(job, async (paramJob, databaseContext, progressReporter, ct) =>
{
using (var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false))
{
@@ -443,8 +441,7 @@ namespace Tgstation.Server.Host.Controllers
//get a base line for where we are
Models.RevisionInformation lastRevisionInfo = null;
-
- var databaseContext = serviceProvider.GetRequiredService();
+
var attachedInstance = new Models.Instance
{
Id = Instance.Id
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index 86475ae07c..6233129ca1 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -101,6 +101,8 @@ namespace Tgstation.Server.Host.Core
services.AddOptions();
+ services.AddScoped();
+
const string scheme = "JwtBearer";
services.AddAuthentication((options) =>
{
@@ -128,9 +130,11 @@ namespace Tgstation.Server.Host.Core
};
jwtBearerOptions.Events = new JwtBearerEvents
{
- OnTokenValidated = ApiController.OnTokenValidated
+ //Application is our composition root so this monstrosity of a line is okay
+ OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted)
};
});
+
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs
services.AddMvc().AddJsonOptions(options =>
diff --git a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs
index f3d64f355c..69c5287aa9 100644
--- a/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs
+++ b/src/Tgstation.Server.Host/Core/DatabaseContextFactory.cs
@@ -9,20 +9,26 @@ namespace Tgstation.Server.Host.Core
sealed class DatabaseContextFactory : IDatabaseContextFactory
{
///
- /// The for the
+ /// The for the
///
- readonly IServiceProvider serviceProvider;
+ readonly IServiceScopeFactory scopeFactory;
///
/// Construct a
///
- /// The value of
- public DatabaseContextFactory(IServiceProvider serviceProvider) => this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
+ /// The value of . Created scopes must be able to provide instances of
+ public DatabaseContextFactory(IServiceScopeFactory scopeFactory)
+ {
+ this.scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
+
+ using (var scope = scopeFactory.CreateScope())
+ scope.ServiceProvider.GetRequiredService();
+ }
///
public async Task UseContext(Func operation)
{
- using (var scope = serviceProvider.CreateScope())
+ using (var scope = scopeFactory.CreateScope())
await operation(scope.ServiceProvider.GetRequiredService()).ConfigureAwait(false);
}
}
diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs
index 7389ce78d5..12ecf7666f 100644
--- a/src/Tgstation.Server.Host/Core/IJobManager.cs
+++ b/src/Tgstation.Server.Host/Core/IJobManager.cs
@@ -20,10 +20,10 @@ namespace Tgstation.Server.Host.Core
/// Registers a given and begins running it
///
/// The
- /// The operation to run taking the started , a progress reporter and a
+ /// The operation to run taking the started , a , progress reporter and a
/// The for the operation
/// A representing a running operation
- Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken);
+ Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken);
///
/// Wait for a given to complete
diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs
index 304f2d0acd..2900dc28f4 100644
--- a/src/Tgstation.Server.Host/Core/JobManager.cs
+++ b/src/Tgstation.Server.Host/Core/JobManager.cs
@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
@@ -16,7 +15,7 @@ namespace Tgstation.Server.Host.Core
///
/// The for the
///
- readonly IServiceProvider serviceProvider;
+ readonly IDatabaseContextFactory databaseContextFactory;
///
/// The for the
@@ -31,11 +30,11 @@ namespace Tgstation.Server.Host.Core
///
/// Construct a
///
- /// The value of
+ /// The value of
/// The value of
- public JobManager(IServiceProvider serviceProvider, ILogger logger)
+ public JobManager(IDatabaseContextFactory databaseContextFactory, ILogger logger)
{
- this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
+ this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
jobs = new Dictionary();
}
@@ -69,11 +68,11 @@ namespace Tgstation.Server.Host.Core
/// The operation for the
/// The for the operation
/// A representing the running operation
- async Task RunJob(Job job, Func operation, CancellationToken cancellationToken)
+ async Task RunJob(Job job, Func operation, CancellationToken cancellationToken)
{
try
{
- using (var scope = serviceProvider.CreateScope())
+ await databaseContextFactory.UseContext(async databaseContext =>
{
async Task HandleExceptions(Task task)
{
@@ -97,15 +96,13 @@ namespace Tgstation.Server.Host.Core
}
}
- IDatabaseContext databaseContext = null;
async Task RunJobInternal()
{
var oldJob = job;
job = new Job { Id = oldJob.Id };
- databaseContext = scope.ServiceProvider.GetRequiredService();
databaseContext.Jobs.Attach(job);
- await operation(job, scope.ServiceProvider, cancellationToken).ConfigureAwait(false);
+ await operation(job, databaseContext, cancellationToken).ConfigureAwait(false);
logger.LogDebug("Job {0} completed!", job.Id);
};
@@ -123,7 +120,7 @@ namespace Tgstation.Server.Host.Core
if (JobErroredOrCancelled())
await databaseContext.Save(default).ConfigureAwait(false);
}
- }
+ }).ConfigureAwait(false);
}
finally
{
@@ -137,50 +134,44 @@ namespace Tgstation.Server.Host.Core
}
///
- public async Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken)
+ public Task RegisterOperation(Job job, Func, CancellationToken, Task> operation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
{
- using (var scope = serviceProvider.CreateScope())
+ job.StartedAt = DateTimeOffset.Now;
+ job.Cancelled = false;
+ job.Instance = new Instance
{
- var databaseContext = scope.ServiceProvider.GetRequiredService();
- job.StartedAt = DateTimeOffset.Now;
- job.Cancelled = false;
- job.Instance = new Instance
+ Id = job.Instance.Id
+ };
+ databaseContext.Instances.Attach(job.Instance);
+ if (job.StartedBy != null)
+ {
+ job.StartedBy = new User
{
- Id = job.Instance.Id
+ Id = job.StartedBy.Id
};
- databaseContext.Instances.Attach(job.Instance);
- if (job.StartedBy != null)
- {
- job.StartedBy = new User
- {
- Id = job.StartedBy.Id
- };
- databaseContext.Users.Attach(job.StartedBy);
- }
- databaseContext.Jobs.Add(job);
- await databaseContext.Save(cancellationToken).ConfigureAwait(false);
- logger.LogDebug("Starting job {0}: {1}...", job.Id, job.Description);
- var jobHandler = JobHandler.Create(x => RunJob(job, (jobParam, serviceProvider, ct) =>
- operation(jobParam, serviceProvider, y =>
- {
- lock (this)
- if (jobs.TryGetValue(job.Id, out var handler))
- handler.Progress = y;
- }, ct),
- x));
- lock (this)
- jobs.Add(job.Id, jobHandler);
+ databaseContext.Users.Attach(job.StartedBy);
}
- }
+ databaseContext.Jobs.Add(job);
+ await databaseContext.Save(cancellationToken).ConfigureAwait(false);
+ logger.LogDebug("Starting job {0}: {1}...", job.Id, job.Description);
+ var jobHandler = JobHandler.Create(x => RunJob(job, (jobParam, serviceProvider, ct) =>
+ operation(jobParam, serviceProvider, y =>
+ {
+ lock (this)
+ if (jobs.TryGetValue(job.Id, out var handler))
+ handler.Progress = y;
+ }, ct),
+ x));
+ lock (this)
+ jobs.Add(job.Id, jobHandler);
+ });
///
public async Task StartAsync(CancellationToken cancellationToken)
{
logger.LogTrace("Starting job manager...");
- using (var scope = serviceProvider.CreateScope())
+ await databaseContextFactory.UseContext(async databaseContext =>
{
- var databaseContext = scope.ServiceProvider.GetRequiredService();
-
//mark all jobs as cancelled
var badJobs = await databaseContext.Jobs.Where(y => !y.StoppedAt.HasValue).Select(y => y.Id).ToListAsync(cancellationToken).ConfigureAwait(false);
if (badJobs.Count > 0)
@@ -195,7 +186,7 @@ namespace Tgstation.Server.Host.Core
}
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
}
- }
+ }).ConfigureAwait(false);
logger.LogDebug("Job manager started!");
}
@@ -228,9 +219,8 @@ namespace Tgstation.Server.Host.Core
return false;
}
handler.Cancel(); //this will ensure the db update is only done once
- using (var scope = serviceProvider.CreateScope())
+ await databaseContextFactory.UseContext(async databaseContext =>
{
- var databaseContext = scope.ServiceProvider.GetRequiredService();
job = new Job { Id = job.Id };
databaseContext.Jobs.Attach(job);
user = new User { Id = user.Id };
@@ -238,7 +228,7 @@ namespace Tgstation.Server.Host.Core
job.CancelledBy = user;
//let either startup or cancellation set job.cancelled
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
- }
+ }).ConfigureAwait(false);
if (blocking)
await handler.Wait(cancellationToken).ConfigureAwait(false);
return true;
diff --git a/src/Tgstation.Server.Host/Security/ClaimsInjector.cs b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs
new file mode 100644
index 0000000000..3e7f202b2e
--- /dev/null
+++ b/src/Tgstation.Server.Host/Security/ClaimsInjector.cs
@@ -0,0 +1,95 @@
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Http;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Threading;
+using System.Threading.Tasks;
+using Tgstation.Server.Api;
+using Tgstation.Server.Api.Rights;
+using Tgstation.Server.Host.Models;
+
+namespace Tgstation.Server.Host.Security
+{
+ ///
+ sealed class ClaimsInjector : IClaimsInjector
+ {
+ ///
+ /// The for the
+ ///
+ readonly IDatabaseContext databaseContext;
+
+ ///
+ /// The for the
+ ///
+ readonly IAuthenticationContextFactory authenticationContextFactory;
+
+ ///
+ /// Construct a
+ ///
+ /// The value of
+ /// The value of
+ public ClaimsInjector(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory)
+ {
+ this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
+ this.authenticationContextFactory = authenticationContextFactory ?? throw new ArgumentNullException(nameof(authenticationContextFactory));
+ }
+
+ ///
+ public async Task InjectClaimsIntoContext(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken)
+ {
+ if (tokenValidatedContext == null)
+ throw new ArgumentNullException(nameof(tokenValidatedContext));
+
+ //Find the user id in the token
+ var userIdClaim = tokenValidatedContext.Principal.FindFirst(JwtRegisteredClaimNames.Sub);
+ if (userIdClaim == default)
+ 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(tokenValidatedContext.HttpContext.Request.GetTypedHeaders());
+ }
+ catch (InvalidOperationException)
+ {
+ //we are not responsible for handling header validation issues
+ return;
+ }
+
+ //This populates the CurrentAuthenticationContext field for use by us and subsequent controllers
+ await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, tokenValidatedContext.SecurityToken.ValidFrom, cancellationToken).ConfigureAwait(false);
+
+ var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
+
+ var enumerator = Enum.GetValues(typeof(RightsType));
+ var claims = new List();
+ foreach (RightsType I in enumerator)
+ {
+ //if there's no instance user, do a weird thing and add all the instance roles
+ //we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid
+ //if user is null that means they got the token with an expired password
+ var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I);
+ var rightEnum = RightsHelper.RightToType(I);
+ var right = (Enum)Enum.ToObject(rightEnum, rightInt);
+ foreach (Enum J in Enum.GetValues(rightEnum))
+ if (right.HasFlag(J))
+ claims.Add(new Claim(ClaimTypes.Role, RightsHelper.RoleName(I, J)));
+ }
+
+ tokenValidatedContext.Principal.AddIdentity(new ClaimsIdentity(claims));
+ }
+ }
+}
diff --git a/src/Tgstation.Server.Host/Security/IClaimsInjector.cs b/src/Tgstation.Server.Host/Security/IClaimsInjector.cs
new file mode 100644
index 0000000000..216b03b0f0
--- /dev/null
+++ b/src/Tgstation.Server.Host/Security/IClaimsInjector.cs
@@ -0,0 +1,20 @@
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Security
+{
+ ///
+ /// For injecting s that can look for
+ ///
+ interface IClaimsInjector
+ {
+ ///
+ /// Setup the s for a given
+ ///
+ /// The containing the and of the request and the to add s to
+ /// The for the operation
+ /// A representing the running operation
+ Task InjectClaimsIntoContext(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken);
+ }
+}
\ No newline at end of file