mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-29 16:11:05 +01:00
Merge pull request #673 from Cyberboss/AntiPattern
Remove service locator anti-pattern where possible
This commit is contained in:
@@ -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 <see cref="Core.IJobManager"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The running <see cref="Job"/></param>
|
||||
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> for the operation</param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation</param>
|
||||
/// <param name="progressReporter">The <see cref="Action{T1}"/> to report compilation progress</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task CompileProcess(Job job, IServiceProvider serviceProvider, Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
Task CompileProcess(Job job, IDatabaseContext databaseContext, Action<int> progressReporter, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CompileProcess(Job job, IServiceProvider serviceProvider, Action<int> progressReporter, CancellationToken cancellationToken)
|
||||
public async Task CompileProcess(Job job, IDatabaseContext databaseContext, Action<int> 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<IDatabaseContext>();
|
||||
|
||||
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<IDatabaseContext>();
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
readonly bool requireInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Runs after a <see cref="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.SecurityToken.ValidFrom, 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)
|
||||
{
|
||||
//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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ApiController"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<IDatabaseContext>();
|
||||
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<IDatabaseContext>();
|
||||
|
||||
var attachedInstance = new Models.Instance
|
||||
{
|
||||
Id = Instance.Id
|
||||
|
||||
@@ -101,6 +101,8 @@ namespace Tgstation.Server.Host.Core
|
||||
|
||||
services.AddOptions();
|
||||
|
||||
services.AddScoped<IClaimsInjector, ClaimsInjector>();
|
||||
|
||||
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<IClaimsInjector>().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted)
|
||||
};
|
||||
});
|
||||
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); //fucking converts 'sub' to M$ bs
|
||||
|
||||
services.AddMvc().AddJsonOptions(options =>
|
||||
|
||||
@@ -9,20 +9,26 @@ namespace Tgstation.Server.Host.Core
|
||||
sealed class DatabaseContextFactory : IDatabaseContextFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IServiceProvider"/> for the <see cref="DatabaseContextFactory"/>
|
||||
/// The <see cref="IServiceScopeFactory"/> for the <see cref="DatabaseContextFactory"/>
|
||||
/// </summary>
|
||||
readonly IServiceProvider serviceProvider;
|
||||
readonly IServiceScopeFactory scopeFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DatabaseContextFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The value of <see cref="serviceProvider"/></param>
|
||||
public DatabaseContextFactory(IServiceProvider serviceProvider) => this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
/// <param name="scopeFactory">The value of <see cref="scopeFactory"/>. Created scopes must be able to provide instances of <see cref="IDatabaseContext"/></param>
|
||||
public DatabaseContextFactory(IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
this.scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
|
||||
|
||||
using (var scope = scopeFactory.CreateScope())
|
||||
scope.ServiceProvider.GetRequiredService<IDatabaseContext>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UseContext(Func<IDatabaseContext, Task> operation)
|
||||
{
|
||||
using (var scope = serviceProvider.CreateScope())
|
||||
using (var scope = scopeFactory.CreateScope())
|
||||
await operation(scope.ServiceProvider.GetRequiredService<IDatabaseContext>()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ namespace Tgstation.Server.Host.Core
|
||||
/// Registers a given <see cref="Job"/> and begins running it
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/></param>
|
||||
/// <param name="operation">The operation to run taking the started <see cref="Job"/>, a <see cref="IServiceProvider"/> progress reporter <see cref="Action{T1}"/> and a <see cref="CancellationToken"/></param>
|
||||
/// <param name="operation">The operation to run taking the started <see cref="Job"/>, a <see cref="IDatabaseContext"/>, progress reporter <see cref="Action{T1}"/> and a <see cref="CancellationToken"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing a running operation</returns>
|
||||
Task RegisterOperation(Job job, Func<Job, IServiceProvider, Action<int>, CancellationToken, Task> operation, CancellationToken cancellationToken);
|
||||
Task RegisterOperation(Job job, Func<Job, IDatabaseContext, Action<int>, CancellationToken, Task> operation, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Wait for a given <paramref name="job"/> to complete
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// The <see cref="IServiceProvider"/> for the <see cref="JobManager"/>
|
||||
/// </summary>
|
||||
readonly IServiceProvider serviceProvider;
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="JobManager"/>
|
||||
@@ -31,11 +30,11 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <summary>
|
||||
/// Construct a <see cref="JobManager"/>
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">The value of <see cref="serviceProvider"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public JobManager(IServiceProvider serviceProvider, ILogger<JobManager> logger)
|
||||
public JobManager(IDatabaseContextFactory databaseContextFactory, ILogger<JobManager> 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<long, JobHandler>();
|
||||
}
|
||||
@@ -69,11 +68,11 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <param name="operation">The operation for the <paramref name="job"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task RunJob(Job job, Func<Job, IServiceProvider, CancellationToken, Task> operation, CancellationToken cancellationToken)
|
||||
async Task RunJob(Job job, Func<Job, IDatabaseContext, CancellationToken, Task> 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<IDatabaseContext>();
|
||||
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
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RegisterOperation(Job job, Func<Job, IServiceProvider, Action<int>, CancellationToken, Task> operation, CancellationToken cancellationToken)
|
||||
public Task RegisterOperation(Job job, Func<Job, IDatabaseContext, Action<int>, 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<IDatabaseContext>();
|
||||
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);
|
||||
});
|
||||
|
||||
/// <inheritdoc />
|
||||
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<IDatabaseContext>();
|
||||
|
||||
//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<IDatabaseContext>();
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ClaimsInjector : IClaimsInjector
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContext"/> for the <see cref="ClaimsInjector"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContext databaseContext;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IAuthenticationContextFactory"/> for the <see cref="ClaimsInjector"/>
|
||||
/// </summary>
|
||||
readonly IAuthenticationContextFactory authenticationContextFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ClaimsInjector"/>
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The value of <see cref="databaseContext"/></param>
|
||||
/// <param name="authenticationContextFactory">The value of <see cref="authenticationContextFactory"/></param>
|
||||
public ClaimsInjector(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory)
|
||||
{
|
||||
this.databaseContext = databaseContext ?? throw new ArgumentNullException(nameof(databaseContext));
|
||||
this.authenticationContextFactory = authenticationContextFactory ?? throw new ArgumentNullException(nameof(authenticationContextFactory));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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<Claim>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
/// <summary>
|
||||
/// For injecting <see cref="System.Security.Claims.Claim"/>s that <see cref="Controllers.TgsAuthorizeAttribute"/> can look for
|
||||
/// </summary>
|
||||
interface IClaimsInjector
|
||||
{
|
||||
/// <summary>
|
||||
/// Setup the <see cref="System.Security.Claims.Claim"/>s for a given <paramref name="tokenValidatedContext"/>
|
||||
/// </summary>
|
||||
/// <param name="tokenValidatedContext">The <see cref="TokenValidatedContext"/> containing the <see cref="Microsoft.AspNetCore.Http.HttpContext"/> and <see cref="Microsoft.IdentityModel.Tokens.SecurityToken"/> of the request and the <see cref="System.Security.Claims.ClaimsPrincipal"/> to add <see cref="System.Security.Claims.Claim"/>s to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task InjectClaimsIntoContext(TokenValidatedContext tokenValidatedContext, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user