mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-25 16:42:54 +01:00
Compare commits
1 Commits
340b236282
...
55d8c6bcfc
Author | SHA1 | Date | |
---|---|---|---|
55d8c6bcfc |
@ -1,15 +1,15 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Server" type="DotNetProject" factoryName=".NET Project">
|
||||
<configuration default="false" name="Controller" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/.artifacts/bin/Phantom.Controller/debug/Phantom.Controller.exe" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Server" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/.workdir/Controller" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<envs>
|
||||
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
|
||||
<env name="PG_DATABASE" value="postgres" />
|
||||
<env name="PG_HOST" value="localhost" />
|
||||
<env name="PG_PASS" value="development" />
|
||||
<env name="PG_PORT" value="9402" />
|
||||
<env name="PG_PORT" value="9403" />
|
||||
<env name="PG_USER" value="postgres" />
|
||||
<env name="RPC_SERVER_HOST" value="localhost" />
|
||||
<env name="WEB_SERVER_HOST" value="localhost" />
|
1
.workdir/Controller/.gitignore
vendored
Normal file
1
.workdir/Controller/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
|
@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
|
||||
|
||||
namespace Phantom.Controller.Database.Postgres;
|
||||
|
||||
public sealed class ApplicationDbContextFactory : IDatabaseProvider {
|
||||
private readonly PooledDbContextFactory<ApplicationDbContext> factory;
|
||||
|
||||
public ApplicationDbContextFactory(string connectionString) {
|
||||
this.factory = new PooledDbContextFactory<ApplicationDbContext>(CreateOptions(connectionString), poolSize: 32);
|
||||
}
|
||||
|
||||
public ApplicationDbContext Provide() {
|
||||
return factory.CreateDbContext();
|
||||
}
|
||||
|
||||
private static DbContextOptions<ApplicationDbContext> CreateOptions(string connectionString) {
|
||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
builder.UseNpgsql(connectionString, ConfigureOptions);
|
||||
return builder.Options;
|
||||
}
|
||||
|
||||
private static void ConfigureOptions(NpgsqlDbContextOptionsBuilder options) {
|
||||
options.CommandTimeout(10);
|
||||
options.MigrationsAssembly(typeof(ApplicationDbContextDesignFactory).Assembly.FullName);
|
||||
}
|
||||
}
|
25
Controller/Phantom.Controller.Database/DatabaseMigrator.cs
Normal file
25
Controller/Phantom.Controller.Database/DatabaseMigrator.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Controller.Database;
|
||||
|
||||
public static class DatabaseMigrator {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create(nameof(DatabaseMigrator));
|
||||
|
||||
public static async Task Run(IDatabaseProvider databaseProvider, CancellationToken cancellationToken) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
|
||||
Logger.Information("Connecting to database...");
|
||||
|
||||
var retryConnection = new Throttler(TimeSpan.FromSeconds(10));
|
||||
while (!await ctx.Database.CanConnectAsync(cancellationToken)) {
|
||||
Logger.Warning("Cannot connect to database, retrying...");
|
||||
await retryConnection.Wait();
|
||||
}
|
||||
|
||||
Logger.Information("Running migrations...");
|
||||
await ctx.Database.MigrateAsync(CancellationToken.None);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Phantom.Controller.Database;
|
||||
|
||||
public sealed class DatabaseProvider {
|
||||
private readonly IServiceScopeFactory serviceScopeFactory;
|
||||
|
||||
public DatabaseProvider(IServiceScopeFactory serviceScopeFactory) {
|
||||
this.serviceScopeFactory = serviceScopeFactory;
|
||||
}
|
||||
|
||||
public Scope CreateScope() {
|
||||
return new Scope(serviceScopeFactory.CreateScope());
|
||||
}
|
||||
|
||||
public readonly struct Scope : IDisposable {
|
||||
private readonly IServiceScope scope;
|
||||
|
||||
public ApplicationDbContext Ctx { get; }
|
||||
|
||||
internal Scope(IServiceScope scope) {
|
||||
this.scope = scope;
|
||||
this.Ctx = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
scope.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
namespace Phantom.Controller.Database;
|
||||
|
||||
public interface IDatabaseProvider {
|
||||
ApplicationDbContext Provide();
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Common\Phantom.Common.Data\Phantom.Common.Data.csproj" />
|
||||
<ProjectReference Include="..\..\Common\Phantom.Common.Logging\Phantom.Common.Logging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -27,19 +27,19 @@ public sealed class AgentManager {
|
||||
|
||||
private readonly CancellationToken cancellationToken;
|
||||
private readonly AgentAuthToken authToken;
|
||||
private readonly DatabaseProvider databaseProvider;
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
|
||||
public AgentManager(ServiceConfiguration configuration, AgentAuthToken authToken, DatabaseProvider databaseProvider, TaskManager taskManager) {
|
||||
this.cancellationToken = configuration.CancellationToken;
|
||||
public AgentManager(AgentAuthToken authToken, IDatabaseProvider databaseProvider, TaskManager taskManager, CancellationToken cancellationToken) {
|
||||
this.authToken = authToken;
|
||||
this.databaseProvider = databaseProvider;
|
||||
this.cancellationToken = cancellationToken;
|
||||
taskManager.Run("Refresh agent status loop", RefreshAgentStatus);
|
||||
}
|
||||
|
||||
public async Task Initialize() {
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
internal async Task Initialize() {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
|
||||
await foreach (var entity in scope.Ctx.Agents.AsAsyncEnumerable().WithCancellation(cancellationToken)) {
|
||||
await foreach (var entity in ctx.Agents.AsAsyncEnumerable().WithCancellation(cancellationToken)) {
|
||||
var agent = new Agent(entity.AgentGuid, entity.Name, entity.ProtocolVersion, entity.BuildVersion, entity.MaxInstances, entity.MaxMemory);
|
||||
if (!agents.ByGuid.AddOrReplaceIf(agent.Guid, agent, static oldAgent => oldAgent.IsOffline)) {
|
||||
// TODO
|
||||
@ -68,8 +68,8 @@ public sealed class AgentManager {
|
||||
oldAgent.Connection?.Close();
|
||||
}
|
||||
|
||||
using (var scope = databaseProvider.CreateScope()) {
|
||||
var entity = scope.Ctx.AgentUpsert.Fetch(agent.Guid);
|
||||
await using (var ctx = databaseProvider.Provide()) {
|
||||
var entity = ctx.AgentUpsert.Fetch(agent.Guid);
|
||||
|
||||
entity.Name = agent.Name;
|
||||
entity.ProtocolVersion = agent.ProtocolVersion;
|
||||
@ -77,7 +77,7 @@ public sealed class AgentManager {
|
||||
entity.MaxInstances = agent.MaxInstances;
|
||||
entity.MaxMemory = agent.MaxMemory;
|
||||
|
||||
await scope.Ctx.SaveChangesAsync(cancellationToken);
|
||||
await ctx.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
Logger.Information("Registered agent \"{Name}\" (GUID {Guid}).", agent.Name, agent.Guid);
|
||||
|
@ -9,16 +9,16 @@ using Phantom.Utils.Tasks;
|
||||
namespace Phantom.Controller.Services.Audit;
|
||||
|
||||
public sealed partial class AuditLog {
|
||||
private readonly CancellationToken cancellationToken;
|
||||
private readonly DatabaseProvider databaseProvider;
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
private readonly AuthenticationStateProvider authenticationStateProvider;
|
||||
private readonly TaskManager taskManager;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
public AuditLog(ServiceConfiguration serviceConfiguration, DatabaseProvider databaseProvider, AuthenticationStateProvider authenticationStateProvider, TaskManager taskManager) {
|
||||
this.cancellationToken = serviceConfiguration.CancellationToken;
|
||||
public AuditLog(IDatabaseProvider databaseProvider, AuthenticationStateProvider authenticationStateProvider, TaskManager taskManager, CancellationToken cancellationToken) {
|
||||
this.databaseProvider = databaseProvider;
|
||||
this.authenticationStateProvider = authenticationStateProvider;
|
||||
this.taskManager = taskManager;
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
private async Task<Guid?> GetCurrentAuthenticatedUserId() {
|
||||
@ -27,9 +27,9 @@ public sealed partial class AuditLog {
|
||||
}
|
||||
|
||||
private async Task AddEntityToDatabase(AuditLogEntity logEntity) {
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
scope.Ctx.AuditLog.Add(logEntity);
|
||||
await scope.Ctx.SaveChangesAsync(cancellationToken);
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
ctx.AuditLog.Add(logEntity);
|
||||
await ctx.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private void AddItem(Guid? userGuid, AuditLogEventType eventType, string subjectId, Dictionary<string, object?>? extra = null) {
|
||||
@ -42,13 +42,13 @@ public sealed partial class AuditLog {
|
||||
}
|
||||
|
||||
public async Task<AuditLogItem[]> GetItems(int count, CancellationToken cancellationToken) {
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
return await scope.Ctx.AuditLog
|
||||
.Include(static entity => entity.User)
|
||||
.AsQueryable()
|
||||
.OrderByDescending(static entity => entity.UtcTime)
|
||||
.Take(count)
|
||||
.Select(static entity => new AuditLogItem(entity.UtcTime, entity.UserGuid, entity.User == null ? null : entity.User.Name, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.AuditLog
|
||||
.Include(static entity => entity.User)
|
||||
.AsQueryable()
|
||||
.OrderByDescending(static entity => entity.UtcTime)
|
||||
.Take(count)
|
||||
.Select(static entity => new AuditLogItem(entity.UtcTime, entity.UserGuid, entity.User == null ? null : entity.User.Name, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
65
Controller/Phantom.Controller.Services/ControllerServices.cs
Normal file
65
Controller/Phantom.Controller.Services/ControllerServices.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Minecraft;
|
||||
using Phantom.Controller.Rpc;
|
||||
using Phantom.Controller.Services.Agents;
|
||||
using Phantom.Controller.Services.Events;
|
||||
using Phantom.Controller.Services.Instances;
|
||||
using Phantom.Controller.Services.Rpc;
|
||||
using Phantom.Controller.Services.Users;
|
||||
using Phantom.Controller.Services.Users.Permissions;
|
||||
using Phantom.Controller.Services.Users.Roles;
|
||||
using Phantom.Utils.Tasks;
|
||||
|
||||
namespace Phantom.Controller.Services;
|
||||
|
||||
public sealed class ControllerServices {
|
||||
private TaskManager TaskManager { get; }
|
||||
private MinecraftVersions MinecraftVersions { get; }
|
||||
|
||||
private AgentManager AgentManager { get; }
|
||||
private AgentJavaRuntimesManager AgentJavaRuntimesManager { get; }
|
||||
private EventLog EventLog { get; }
|
||||
private InstanceManager InstanceManager { get; }
|
||||
private InstanceLogManager InstanceLogManager { get; }
|
||||
|
||||
private UserManager UserManager { get; }
|
||||
private RoleManager RoleManager { get; }
|
||||
private UserRoleManager UserRoleManager { get; }
|
||||
private PermissionManager PermissionManager { get; }
|
||||
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
public ControllerServices(IDatabaseProvider databaseProvider, AgentAuthToken agentAuthToken, CancellationToken shutdownCancellationToken) {
|
||||
this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, ControllerServices>());
|
||||
this.MinecraftVersions = new MinecraftVersions();
|
||||
|
||||
this.AgentManager = new AgentManager(agentAuthToken, databaseProvider, TaskManager, shutdownCancellationToken);
|
||||
this.AgentJavaRuntimesManager = new AgentJavaRuntimesManager();
|
||||
this.EventLog = new EventLog(databaseProvider, TaskManager, shutdownCancellationToken);
|
||||
this.InstanceManager = new InstanceManager(AgentManager, MinecraftVersions, databaseProvider, shutdownCancellationToken);
|
||||
this.InstanceLogManager = new InstanceLogManager();
|
||||
|
||||
this.UserManager = new UserManager(databaseProvider);
|
||||
this.RoleManager = new RoleManager(databaseProvider);
|
||||
this.UserRoleManager = new UserRoleManager(databaseProvider);
|
||||
this.PermissionManager = new PermissionManager(databaseProvider);
|
||||
|
||||
this.databaseProvider = databaseProvider;
|
||||
this.cancellationToken = shutdownCancellationToken;
|
||||
}
|
||||
|
||||
public MessageToServerListener CreateMessageToServerListener(RpcClientConnection connection) {
|
||||
return new MessageToServerListener(connection, AgentManager, AgentJavaRuntimesManager, InstanceManager, InstanceLogManager, EventLog, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task Initialize() {
|
||||
await DatabaseMigrator.Run(databaseProvider, cancellationToken);
|
||||
await PermissionManager.Initialize();
|
||||
await RoleManager.Initialize();
|
||||
await AgentManager.Initialize();
|
||||
await InstanceManager.Initialize();
|
||||
}
|
||||
}
|
@ -1,26 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
using Phantom.Utils.Collections;
|
||||
using Phantom.Utils.Tasks;
|
||||
|
||||
namespace Phantom.Controller.Services.Events;
|
||||
|
||||
public sealed partial class EventLog {
|
||||
private readonly CancellationToken cancellationToken;
|
||||
private readonly DatabaseProvider databaseProvider;
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
private readonly TaskManager taskManager;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
public EventLog(ServiceConfiguration serviceConfiguration, DatabaseProvider databaseProvider, TaskManager taskManager) {
|
||||
this.cancellationToken = serviceConfiguration.CancellationToken;
|
||||
public EventLog(IDatabaseProvider databaseProvider, TaskManager taskManager, CancellationToken cancellationToken) {
|
||||
this.databaseProvider = databaseProvider;
|
||||
this.taskManager = taskManager;
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
private async Task AddEntityToDatabase(EventLogEntity logEntity) {
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
scope.Ctx.EventLog.Add(logEntity);
|
||||
await scope.Ctx.SaveChangesAsync(cancellationToken);
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
ctx.EventLog.Add(logEntity);
|
||||
await ctx.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private void AddItem(Guid eventGuid, DateTime utcTime, Guid? agentGuid, EventLogEventType eventType, string subjectId, Dictionary<string, object?>? extra = null) {
|
||||
@ -28,13 +30,14 @@ public sealed partial class EventLog {
|
||||
taskManager.Run("Store event log item to database", () => AddEntityToDatabase(logEntity));
|
||||
}
|
||||
|
||||
public async Task<EventLogItem[]> GetItems(int count, CancellationToken cancellationToken) {
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
return await scope.Ctx.EventLog
|
||||
.AsQueryable()
|
||||
.OrderByDescending(static entity => entity.UtcTime)
|
||||
.Take(count)
|
||||
.Select(static entity => new EventLogItem(entity.UtcTime, entity.AgentGuid, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data))
|
||||
.ToArrayAsync(cancellationToken);
|
||||
public async Task<ImmutableArray<EventLogItem>> GetItems(int count, CancellationToken cancellationToken) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.EventLog
|
||||
.AsQueryable()
|
||||
.OrderByDescending(static entity => entity.UtcTime)
|
||||
.Take(count)
|
||||
.Select(static entity => new EventLogItem(entity.UtcTime, entity.AgentGuid, entity.EventType, entity.SubjectType, entity.SubjectId, entity.Data))
|
||||
.AsAsyncEnumerable()
|
||||
.ToImmutableArrayAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -24,23 +24,23 @@ public sealed class InstanceManager {
|
||||
|
||||
public EventSubscribers<ImmutableDictionary<Guid, Instance>> InstancesChanged => instances.Subs;
|
||||
|
||||
private readonly CancellationToken cancellationToken;
|
||||
private readonly AgentManager agentManager;
|
||||
private readonly MinecraftVersions minecraftVersions;
|
||||
private readonly DatabaseProvider databaseProvider;
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
private readonly SemaphoreSlim modifyInstancesSemaphore = new (1, 1);
|
||||
|
||||
public InstanceManager(ServiceConfiguration configuration, AgentManager agentManager, MinecraftVersions minecraftVersions, DatabaseProvider databaseProvider) {
|
||||
this.cancellationToken = configuration.CancellationToken;
|
||||
public InstanceManager(AgentManager agentManager, MinecraftVersions minecraftVersions, IDatabaseProvider databaseProvider, CancellationToken cancellationToken) {
|
||||
this.agentManager = agentManager;
|
||||
this.minecraftVersions = minecraftVersions;
|
||||
this.databaseProvider = databaseProvider;
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public async Task Initialize() {
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
|
||||
await foreach (var entity in scope.Ctx.Instances.AsAsyncEnumerable().WithCancellation(cancellationToken)) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
await foreach (var entity in ctx.Instances.AsAsyncEnumerable().WithCancellation(cancellationToken)) {
|
||||
var configuration = new InstanceConfiguration(
|
||||
entity.AgentGuid,
|
||||
entity.InstanceGuid,
|
||||
@ -98,8 +98,8 @@ public sealed class InstanceManager {
|
||||
});
|
||||
|
||||
if (result.Is(AddOrEditInstanceResult.Success)) {
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
InstanceEntity entity = scope.Ctx.InstanceUpsert.Fetch(configuration.InstanceGuid);
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
InstanceEntity entity = ctx.InstanceUpsert.Fetch(configuration.InstanceGuid);
|
||||
|
||||
entity.AgentGuid = configuration.AgentGuid;
|
||||
entity.InstanceName = configuration.InstanceName;
|
||||
@ -111,7 +111,7 @@ public sealed class InstanceManager {
|
||||
entity.JavaRuntimeGuid = configuration.JavaRuntimeGuid;
|
||||
entity.JvmArguments = JvmArgumentsHelper.Join(configuration.JvmArguments);
|
||||
|
||||
await scope.Ctx.SaveChangesAsync(cancellationToken);
|
||||
await ctx.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
else if (isNewInstance) {
|
||||
instances.ByGuid.Remove(configuration.InstanceGuid);
|
||||
@ -188,11 +188,11 @@ public sealed class InstanceManager {
|
||||
try {
|
||||
instances.ByGuid.TryReplace(instanceGuid, instance => instance with { LaunchAutomatically = shouldLaunchAutomatically });
|
||||
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
var entity = await scope.Ctx.Instances.FindAsync(instanceGuid, cancellationToken);
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
var entity = await ctx.Instances.FindAsync(instanceGuid, cancellationToken);
|
||||
if (entity != null) {
|
||||
entity.LaunchAutomatically = shouldLaunchAutomatically;
|
||||
await scope.Ctx.SaveChangesAsync(cancellationToken);
|
||||
await ctx.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
} finally {
|
||||
modifyInstancesSemaphore.Release();
|
||||
|
@ -15,25 +15,25 @@ namespace Phantom.Controller.Services.Rpc;
|
||||
|
||||
public sealed class MessageToServerListener : IMessageToServerListener {
|
||||
private readonly RpcClientConnection connection;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
private readonly AgentManager agentManager;
|
||||
private readonly AgentJavaRuntimesManager agentJavaRuntimesManager;
|
||||
private readonly InstanceManager instanceManager;
|
||||
private readonly InstanceLogManager instanceLogManager;
|
||||
private readonly EventLog eventLog;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
private readonly TaskCompletionSource<Guid> agentGuidWaiter = AsyncTasks.CreateCompletionSource<Guid>();
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
internal MessageToServerListener(RpcClientConnection connection, ServiceConfiguration configuration, AgentManager agentManager, AgentJavaRuntimesManager agentJavaRuntimesManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager, EventLog eventLog) {
|
||||
internal MessageToServerListener(RpcClientConnection connection, AgentManager agentManager, AgentJavaRuntimesManager agentJavaRuntimesManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager, EventLog eventLog, CancellationToken cancellationToken) {
|
||||
this.connection = connection;
|
||||
this.cancellationToken = configuration.CancellationToken;
|
||||
this.agentManager = agentManager;
|
||||
this.agentJavaRuntimesManager = agentJavaRuntimesManager;
|
||||
this.instanceManager = instanceManager;
|
||||
this.instanceLogManager = instanceLogManager;
|
||||
this.eventLog = eventLog;
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public async Task<NoReply> HandleRegisterAgent(RegisterAgentMessage message) {
|
||||
|
@ -1,28 +0,0 @@
|
||||
using Phantom.Controller.Rpc;
|
||||
using Phantom.Controller.Services.Agents;
|
||||
using Phantom.Controller.Services.Events;
|
||||
using Phantom.Controller.Services.Instances;
|
||||
|
||||
namespace Phantom.Controller.Services.Rpc;
|
||||
|
||||
public sealed class MessageToServerListenerFactory {
|
||||
private readonly ServiceConfiguration configuration;
|
||||
private readonly AgentManager agentManager;
|
||||
private readonly AgentJavaRuntimesManager agentJavaRuntimesManager;
|
||||
private readonly InstanceManager instanceManager;
|
||||
private readonly InstanceLogManager instanceLogManager;
|
||||
private readonly EventLog eventLog;
|
||||
|
||||
public MessageToServerListenerFactory(ServiceConfiguration configuration, AgentManager agentManager, AgentJavaRuntimesManager agentJavaRuntimesManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager, EventLog eventLog) {
|
||||
this.configuration = configuration;
|
||||
this.agentManager = agentManager;
|
||||
this.agentJavaRuntimesManager = agentJavaRuntimesManager;
|
||||
this.instanceManager = instanceManager;
|
||||
this.instanceLogManager = instanceLogManager;
|
||||
this.eventLog = eventLog;
|
||||
}
|
||||
|
||||
public MessageToServerListener CreateListener(RpcClientConnection connection) {
|
||||
return new MessageToServerListener(connection, configuration, agentManager, agentJavaRuntimesManager, instanceManager, instanceLogManager, eventLog);
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
using System.Collections.Immutable;
|
||||
using Phantom.Web.Identity.Data;
|
||||
|
||||
namespace Phantom.Web.Identity.Authorization;
|
||||
namespace Phantom.Controller.Services.Users.Permissions;
|
||||
|
||||
public sealed class IdentityPermissions {
|
||||
internal static IdentityPermissions None { get; } = new ();
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Web.Identity.Data;
|
||||
namespace Phantom.Controller.Services.Users.Permissions;
|
||||
|
||||
public sealed record Permission(string Id, Permission? Parent) {
|
||||
private static readonly List<Permission> AllPermissions = new ();
|
@ -0,0 +1,68 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Utils.Collections;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Controller.Services.Users.Permissions;
|
||||
|
||||
public sealed class PermissionManager {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<PermissionManager>();
|
||||
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
private readonly Dictionary<Guid, IdentityPermissions> userIdsToPermissionIds = new ();
|
||||
|
||||
public PermissionManager(IDatabaseProvider databaseProvider) {
|
||||
this.databaseProvider = databaseProvider;
|
||||
}
|
||||
|
||||
internal async Task Initialize() {
|
||||
Logger.Information("Adding default permissions to database.");
|
||||
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
|
||||
var existingPermissionIds = await ctx.Permissions.Select(static p => p.Id).AsAsyncEnumerable().ToImmutableSetAsync();
|
||||
var missingPermissionIds = GetMissingPermissionsOrdered(Permission.All, existingPermissionIds);
|
||||
if (!missingPermissionIds.IsEmpty) {
|
||||
Logger.Information("Adding default permissions: {Permissions}", string.Join(", ", missingPermissionIds));
|
||||
|
||||
foreach (var permissionId in missingPermissionIds) {
|
||||
ctx.Permissions.Add(new PermissionEntity(permissionId));
|
||||
}
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
internal static ImmutableArray<string> GetMissingPermissionsOrdered(IEnumerable<Permission> allPermissions, ImmutableHashSet<string> existingPermissionIds) {
|
||||
return allPermissions.Select(static permission => permission.Id).Except(existingPermissionIds).Order().ToImmutableArray();
|
||||
}
|
||||
|
||||
private IdentityPermissions FetchPermissionsForUserId(Guid userId) {
|
||||
using var ctx = databaseProvider.Provide();
|
||||
var userPermissions = ctx.UserPermissions.Where(up => up.UserGuid == userId).Select(static up => up.PermissionId);
|
||||
var rolePermissions = ctx.UserRoles.Where(ur => ur.UserGuid == userId).Join(ctx.RolePermissions, static ur => ur.RoleGuid, static rp => rp.RoleGuid, static (ur, rp) => rp.PermissionId);
|
||||
return new IdentityPermissions(userPermissions.Union(rolePermissions));
|
||||
}
|
||||
|
||||
private IdentityPermissions GetPermissionsForUserId(Guid userId, bool refreshCache) {
|
||||
if (!refreshCache && userIdsToPermissionIds.TryGetValue(userId, out var userPermissions)) {
|
||||
return userPermissions;
|
||||
}
|
||||
else {
|
||||
return userIdsToPermissionIds[userId] = FetchPermissionsForUserId(userId);
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityPermissions GetPermissions(ClaimsPrincipal user, bool refreshCache = false) {
|
||||
Guid? userId = UserManager.GetAuthenticatedUserId(user);
|
||||
return userId == null ? IdentityPermissions.None : GetPermissionsForUserId(userId.Value, refreshCache);
|
||||
}
|
||||
|
||||
public bool CheckPermission(ClaimsPrincipal user, Permission permission, bool refreshCache = false) {
|
||||
return GetPermissions(user, refreshCache).Check(permission);
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Utils.Collections;
|
||||
using Phantom.Utils.Tasks;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Controller.Services.Users;
|
||||
|
||||
public sealed class RoleManager {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<RoleManager>();
|
||||
|
||||
private const int MaxRoleNameLength = 40;
|
||||
|
||||
private readonly ApplicationDbContext db;
|
||||
|
||||
public RoleManager(ApplicationDbContext db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public Task<List<RoleEntity>> GetAll() {
|
||||
return db.Roles.ToListAsync();
|
||||
}
|
||||
|
||||
public Task<ImmutableHashSet<string>> GetAllNames() {
|
||||
return db.Roles.Select(static role => role.Name).AsAsyncEnumerable().ToImmutableSetAsync();
|
||||
}
|
||||
|
||||
public ValueTask<RoleEntity?> GetByGuid(Guid guid) {
|
||||
return db.Roles.FindAsync(guid);
|
||||
}
|
||||
|
||||
public async Task<Result<RoleEntity, AddRoleError>> Create(Guid guid, string name) {
|
||||
if (string.IsNullOrWhiteSpace(name)) {
|
||||
return Result.Fail<RoleEntity, AddRoleError>(AddRoleError.NameIsEmpty);
|
||||
}
|
||||
else if (name.Length > MaxRoleNameLength) {
|
||||
return Result.Fail<RoleEntity, AddRoleError>(AddRoleError.NameIsTooLong);
|
||||
}
|
||||
|
||||
try {
|
||||
if (await db.Roles.AnyAsync(role => role.Name == name)) {
|
||||
return Result.Fail<RoleEntity, AddRoleError>(AddRoleError.NameAlreadyExists);
|
||||
}
|
||||
|
||||
var role = new RoleEntity(guid, name);
|
||||
|
||||
db.Roles.Add(role);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
Logger.Information("Created role \"{Name}\" (GUID {Guid}).", name, guid);
|
||||
return Result.Ok<RoleEntity, AddRoleError>(role);
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not create role \"{Name}\" (GUID {Guid}).", name, guid);
|
||||
return Result.Fail<RoleEntity, AddRoleError>(AddRoleError.UnknownError);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Controller.Services.Users;
|
||||
namespace Phantom.Controller.Services.Users.Roles;
|
||||
|
||||
public enum AddRoleError : byte {
|
||||
NameIsEmpty,
|
@ -1,6 +1,7 @@
|
||||
using System.Collections.Immutable;
|
||||
using Phantom.Controller.Services.Users.Permissions;
|
||||
|
||||
namespace Phantom.Web.Identity.Data;
|
||||
namespace Phantom.Controller.Services.Users.Roles;
|
||||
|
||||
public sealed record Role(Guid Guid, string Name, ImmutableArray<Permission> Permissions) {
|
||||
private static readonly List<Role> AllRoles = new ();
|
@ -0,0 +1,99 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Controller.Services.Users.Permissions;
|
||||
using Phantom.Utils.Collections;
|
||||
using Phantom.Utils.Tasks;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Controller.Services.Users.Roles;
|
||||
|
||||
public sealed class RoleManager {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<RoleManager>();
|
||||
|
||||
private const int MaxRoleNameLength = 40;
|
||||
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
|
||||
public RoleManager(IDatabaseProvider databaseProvider) {
|
||||
this.databaseProvider = databaseProvider;
|
||||
}
|
||||
|
||||
internal async Task Initialize() {
|
||||
Logger.Information("Adding default roles to database.");
|
||||
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
|
||||
var existingRoleNames = await ctx.Roles
|
||||
.Select(static role => role.Name)
|
||||
.AsAsyncEnumerable()
|
||||
.ToImmutableSetAsync();
|
||||
|
||||
var existingPermissionIdsByRoleGuid = await ctx.RolePermissions
|
||||
.GroupBy(static rp => rp.RoleGuid, static rp => rp.PermissionId)
|
||||
.ToDictionaryAsync(static g => g.Key, static g => g.ToImmutableHashSet());
|
||||
|
||||
foreach (var role in Role.All) {
|
||||
if (!existingRoleNames.Contains(role.Name)) {
|
||||
Logger.Information("Adding default role \"{Name}\".", role.Name);
|
||||
ctx.Roles.Add(new RoleEntity(role.Guid, role.Name));
|
||||
}
|
||||
|
||||
var existingPermissionIds = existingPermissionIdsByRoleGuid.TryGetValue(role.Guid, out var ids) ? ids : ImmutableHashSet<string>.Empty;
|
||||
var missingPermissionIds = PermissionManager.GetMissingPermissionsOrdered(role.Permissions, existingPermissionIds);
|
||||
if (!missingPermissionIds.IsEmpty) {
|
||||
Logger.Information("Assigning default permission to role \"{Name}\": {Permissions}", role.Name, string.Join(", ", missingPermissionIds));
|
||||
foreach (var permissionId in missingPermissionIds) {
|
||||
ctx.RolePermissions.Add(new RolePermissionEntity(role.Guid, permissionId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<List<RoleEntity>> GetAll() {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.Roles.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<ImmutableHashSet<string>> GetAllNames() {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.Roles.Select(static role => role.Name).AsAsyncEnumerable().ToImmutableSetAsync();
|
||||
}
|
||||
|
||||
public async ValueTask<RoleEntity?> GetByGuid(Guid guid) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.Roles.FindAsync(guid);
|
||||
}
|
||||
|
||||
public async Task<Result<RoleEntity, AddRoleError>> Create(string name) {
|
||||
if (string.IsNullOrWhiteSpace(name)) {
|
||||
return Result.Fail<RoleEntity, AddRoleError>(AddRoleError.NameIsEmpty);
|
||||
}
|
||||
else if (name.Length > MaxRoleNameLength) {
|
||||
return Result.Fail<RoleEntity, AddRoleError>(AddRoleError.NameIsTooLong);
|
||||
}
|
||||
|
||||
RoleEntity newRole;
|
||||
try {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
|
||||
if (await ctx.Roles.AnyAsync(role => role.Name == name)) {
|
||||
return Result.Fail<RoleEntity, AddRoleError>(AddRoleError.NameAlreadyExists);
|
||||
}
|
||||
|
||||
newRole = new RoleEntity(Guid.NewGuid(), name);
|
||||
ctx.Roles.Add(newRole);
|
||||
await ctx.SaveChangesAsync();
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not create role \"{Name}\".", name);
|
||||
return Result.Fail<RoleEntity, AddRoleError>(AddRoleError.UnknownError);
|
||||
}
|
||||
|
||||
Logger.Information("Created role \"{Name}\" (GUID {Guid}).", name, newRole.RoleGuid);
|
||||
return Result.Ok<RoleEntity, AddRoleError>(newRole);
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Utils.Collections;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Controller.Services.Users.Roles;
|
||||
|
||||
public sealed class UserRoleManager {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<UserRoleManager>();
|
||||
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
|
||||
public UserRoleManager(IDatabaseProvider databaseProvider) {
|
||||
this.databaseProvider = databaseProvider;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, ImmutableArray<RoleEntity>>> GetAllByUserGuid() {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.UserRoles
|
||||
.Include(static ur => ur.Role)
|
||||
.GroupBy(static ur => ur.UserGuid, static ur => ur.Role)
|
||||
.ToDictionaryAsync(static group => group.Key, static group => group.ToImmutableArray());
|
||||
}
|
||||
|
||||
public async Task<ImmutableArray<RoleEntity>> GetUserRoles(UserEntity user) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.UserRoles
|
||||
.Include(static ur => ur.Role)
|
||||
.Where(ur => ur.UserGuid == user.UserGuid)
|
||||
.Select(static ur => ur.Role)
|
||||
.AsAsyncEnumerable()
|
||||
.ToImmutableArrayAsync();
|
||||
}
|
||||
|
||||
public async Task<ImmutableHashSet<Guid>> GetUserRoleGuids(UserEntity user) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.UserRoles
|
||||
.Where(ur => ur.UserGuid == user.UserGuid)
|
||||
.Select(static ur => ur.RoleGuid)
|
||||
.AsAsyncEnumerable()
|
||||
.ToImmutableSetAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> Add(UserEntity user, RoleEntity role) {
|
||||
try {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
|
||||
var userRole = await ctx.UserRoles.FindAsync(user.UserGuid, role.RoleGuid);
|
||||
if (userRole == null) {
|
||||
userRole = new UserRoleEntity(user.UserGuid, role.RoleGuid);
|
||||
ctx.UserRoles.Add(userRole);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not add user \"{UserName}\" (GUID {UserGuid}) to role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.Information("Added user \"{UserName}\" (GUID {UserGuid}) to role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> Remove(UserEntity user, RoleEntity role) {
|
||||
try {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
|
||||
var userRole = await ctx.UserRoles.FindAsync(user.UserGuid, role.RoleGuid);
|
||||
if (userRole != null) {
|
||||
ctx.UserRoles.Remove(userRole);
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not remove user \"{UserName}\" (GUID {UserGuid}) from role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.Information("Removed user \"{UserName}\" (GUID {UserGuid}) from role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -16,10 +16,10 @@ public sealed class UserManager {
|
||||
|
||||
private const int MaxUserNameLength = 40;
|
||||
|
||||
private readonly ApplicationDbContext db;
|
||||
private readonly IDatabaseProvider databaseProvider;
|
||||
|
||||
public UserManager(ApplicationDbContext db) {
|
||||
this.db = db;
|
||||
public UserManager(IDatabaseProvider databaseProvider) {
|
||||
this.databaseProvider = databaseProvider;
|
||||
}
|
||||
|
||||
public static Guid? GetAuthenticatedUserId(ClaimsPrincipal user) {
|
||||
@ -35,20 +35,24 @@ public sealed class UserManager {
|
||||
return Guid.TryParse(claim.Value, out var guid) ? guid : null;
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<UserEntity>> GetAll() {
|
||||
return db.Users.AsAsyncEnumerable().ToImmutableArrayAsync();
|
||||
public async Task<ImmutableArray<UserEntity>> GetAll() {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.Users.AsAsyncEnumerable().ToImmutableArrayAsync();
|
||||
}
|
||||
|
||||
public Task<Dictionary<Guid, T>> GetAllByGuid<T>(Func<UserEntity, T> valueSelector, CancellationToken cancellationToken = default) {
|
||||
return db.Users.ToDictionaryAsync(static user => user.UserGuid, valueSelector, cancellationToken);
|
||||
public async Task<Dictionary<Guid, T>> GetAllByGuid<T>(Func<UserEntity, T> valueSelector, CancellationToken cancellationToken = default) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.Users.ToDictionaryAsync(static user => user.UserGuid, valueSelector, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<UserEntity?> GetByName(string username) {
|
||||
return db.Users.FirstOrDefaultAsync(user => user.Name == username);
|
||||
public async Task<UserEntity?> GetByName(string username) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
return await ctx.Users.FirstOrDefaultAsync(user => user.Name == username);
|
||||
}
|
||||
|
||||
public async Task<UserEntity?> GetAuthenticated(string username, string password) {
|
||||
var user = await db.Users.FirstOrDefaultAsync(user => user.Name == username);
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
var user = await ctx.Users.FirstOrDefaultAsync(user => user.Name == username);
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
@ -57,7 +61,7 @@ public sealed class UserManager {
|
||||
case PasswordVerificationResult.SuccessRehashNeeded:
|
||||
try {
|
||||
UserPasswords.Set(user, password);
|
||||
await db.SaveChangesAsync();
|
||||
await ctx.SaveChangesAsync();
|
||||
} catch (Exception e) {
|
||||
Logger.Warning(e, "Could not rehash password for \"{Username}\".", user.Name);
|
||||
}
|
||||
@ -87,58 +91,66 @@ public sealed class UserManager {
|
||||
return Result.Fail<UserEntity, AddUserError>(new AddUserError.PasswordIsInvalid(requirementViolations));
|
||||
}
|
||||
|
||||
UserEntity newUser;
|
||||
try {
|
||||
if (await db.Users.AnyAsync(user => user.Name == username)) {
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
|
||||
if (await ctx.Users.AnyAsync(user => user.Name == username)) {
|
||||
return Result.Fail<UserEntity, AddUserError>(new AddUserError.NameAlreadyExists());
|
||||
}
|
||||
|
||||
var guid = Guid.NewGuid();
|
||||
var user = new UserEntity(guid, username);
|
||||
UserPasswords.Set(user, password);
|
||||
newUser = new UserEntity(Guid.NewGuid(), username);
|
||||
UserPasswords.Set(newUser, password);
|
||||
|
||||
db.Users.Add(user);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
Logger.Information("Created user \"{Name}\" (GUID {Guid}).", username, guid);
|
||||
return Result.Ok<UserEntity, AddUserError>(user);
|
||||
ctx.Users.Add(newUser);
|
||||
await ctx.SaveChangesAsync();
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not create user \"{Name}\".", username);
|
||||
return Result.Fail<UserEntity, AddUserError>(new AddUserError.UnknownError());
|
||||
}
|
||||
|
||||
Logger.Information("Created user \"{Name}\" (GUID {Guid}).", username, newUser.UserGuid);
|
||||
return Result.Ok<UserEntity, AddUserError>(newUser);
|
||||
}
|
||||
|
||||
public async Task<Result<SetUserPasswordError>> SetUserPassword(Guid guid, string password) {
|
||||
var user = await db.Users.FindAsync(guid);
|
||||
if (user == null) {
|
||||
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.UserNotFound());
|
||||
}
|
||||
UserEntity foundUser;
|
||||
|
||||
try {
|
||||
var requirementViolations = UserPasswords.CheckRequirements(password);
|
||||
if (!requirementViolations.IsEmpty) {
|
||||
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.PasswordIsInvalid(requirementViolations));
|
||||
await using (var ctx = databaseProvider.Provide()) {
|
||||
var user = await ctx.Users.FindAsync(guid);
|
||||
if (user == null) {
|
||||
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.UserNotFound());
|
||||
}
|
||||
|
||||
UserPasswords.Set(user, password);
|
||||
await db.SaveChangesAsync();
|
||||
foundUser = user;
|
||||
try {
|
||||
var requirementViolations = UserPasswords.CheckRequirements(password);
|
||||
if (!requirementViolations.IsEmpty) {
|
||||
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.PasswordIsInvalid(requirementViolations));
|
||||
}
|
||||
|
||||
Logger.Information("Changed password for user \"{Name}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
||||
return Result.Ok<SetUserPasswordError>();
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not change password for user \"{Name}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
||||
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.UnknownError());
|
||||
UserPasswords.Set(user, password);
|
||||
await ctx.SaveChangesAsync();
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not change password for user \"{Name}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
||||
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.UnknownError());
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Information("Changed password for user \"{Name}\" (GUID {Guid}).", foundUser.Name, foundUser.UserGuid);
|
||||
return Result.Ok<SetUserPasswordError>();
|
||||
}
|
||||
|
||||
public async Task<DeleteUserResult> DeleteByGuid(Guid guid) {
|
||||
var user = await db.Users.FindAsync(guid);
|
||||
await using var ctx = databaseProvider.Provide();
|
||||
var user = await ctx.Users.FindAsync(guid);
|
||||
if (user == null) {
|
||||
return DeleteUserResult.NotFound;
|
||||
}
|
||||
|
||||
try {
|
||||
db.Users.Remove(user);
|
||||
await db.SaveChangesAsync();
|
||||
ctx.Users.Remove(user);
|
||||
await ctx.SaveChangesAsync();
|
||||
return DeleteUserResult.Deleted;
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not delete user \"{Name}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
||||
|
@ -1,76 +0,0 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Utils.Collections;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Controller.Services.Users;
|
||||
|
||||
public sealed class UserRoleManager {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<UserRoleManager>();
|
||||
|
||||
private readonly ApplicationDbContext db;
|
||||
|
||||
public UserRoleManager(ApplicationDbContext db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public Task<Dictionary<Guid, ImmutableArray<RoleEntity>>> GetAllByUserGuid() {
|
||||
return db.UserRoles
|
||||
.Include(static ur => ur.Role)
|
||||
.GroupBy(static ur => ur.UserGuid, static ur => ur.Role)
|
||||
.ToDictionaryAsync(static group => group.Key, static group => group.ToImmutableArray());
|
||||
}
|
||||
|
||||
public Task<ImmutableArray<RoleEntity>> GetUserRoles(UserEntity user) {
|
||||
return db.UserRoles
|
||||
.Include(static ur => ur.Role)
|
||||
.Where(ur => ur.UserGuid == user.UserGuid)
|
||||
.Select(static ur => ur.Role)
|
||||
.AsAsyncEnumerable()
|
||||
.ToImmutableArrayAsync();
|
||||
}
|
||||
|
||||
public Task<ImmutableHashSet<Guid>> GetUserRoleGuids(UserEntity user) {
|
||||
return db.UserRoles
|
||||
.Where(ur => ur.UserGuid == user.UserGuid)
|
||||
.Select(static ur => ur.RoleGuid)
|
||||
.AsAsyncEnumerable()
|
||||
.ToImmutableSetAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> Add(UserEntity user, RoleEntity role) {
|
||||
try {
|
||||
var userRole = await db.UserRoles.FindAsync(user.UserGuid, role.RoleGuid);
|
||||
if (userRole == null) {
|
||||
userRole = new UserRoleEntity(user.UserGuid, role.RoleGuid);
|
||||
db.UserRoles.Add(userRole);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
Logger.Information("Added user \"{UserName}\" (GUID {UserGuid}) to role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not add user \"{UserName}\" (GUID {UserGuid}) to role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Remove(UserEntity user, RoleEntity role) {
|
||||
try {
|
||||
var userRole = await db.UserRoles.FindAsync(user.UserGuid, role.RoleGuid);
|
||||
if (userRole != null) {
|
||||
db.UserRoles.Remove(userRole);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
Logger.Information("Removed user \"{UserName}\" (GUID {UserGuid}) from role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not remove user \"{UserName}\" (GUID {UserGuid}) from role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@
|
||||
<ProjectReference Include="..\Phantom.Controller.Minecraft\Phantom.Controller.Minecraft.csproj" />
|
||||
<ProjectReference Include="..\Phantom.Controller.Rpc\Phantom.Controller.Rpc.csproj" />
|
||||
<ProjectReference Include="..\Phantom.Controller.Services\Phantom.Controller.Services.csproj" />
|
||||
<ProjectReference Include="..\..\Web\Phantom.Web\Phantom.Web.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,24 +1,19 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller;
|
||||
using Phantom.Controller.Database.Postgres;
|
||||
using Phantom.Controller.Rpc;
|
||||
using Phantom.Controller.Services;
|
||||
using Phantom.Controller.Services.Rpc;
|
||||
using Phantom.Utils.Cryptography;
|
||||
using Phantom.Utils.IO;
|
||||
using Phantom.Utils.Rpc;
|
||||
using Phantom.Utils.Runtime;
|
||||
using Phantom.Utils.Tasks;
|
||||
using WebConfiguration = Phantom.Web.Configuration;
|
||||
using WebLauncher = Phantom.Web.Launcher;
|
||||
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
var shutdownCancellationTokenSource = new CancellationTokenSource();
|
||||
var shutdownCancellationToken = shutdownCancellationTokenSource.Token;
|
||||
|
||||
PosixSignals.RegisterCancellation(cancellationTokenSource, static () => {
|
||||
PhantomLogger.Root.InformationHeading("Stopping Phantom Panel server...");
|
||||
PosixSignals.RegisterCancellation(shutdownCancellationTokenSource, static () => {
|
||||
PhantomLogger.Root.InformationHeading("Stopping Phantom Panel controller...");
|
||||
});
|
||||
|
||||
static void CreateFolderOrStop(string path, UnixFileMode chmod) {
|
||||
@ -35,48 +30,33 @@ static void CreateFolderOrStop(string path, UnixFileMode chmod) {
|
||||
try {
|
||||
var fullVersion = AssemblyAttributes.GetFullVersion(Assembly.GetExecutingAssembly());
|
||||
|
||||
PhantomLogger.Root.InformationHeading("Initializing Phantom Panel server...");
|
||||
PhantomLogger.Root.Information("Server version: {Version}", fullVersion);
|
||||
PhantomLogger.Root.InformationHeading("Initializing Phantom Panel controller...");
|
||||
PhantomLogger.Root.Information("Controller version: {Version}", fullVersion);
|
||||
|
||||
var (webServerHost, webServerPort, webBasePath, rpcServerHost, rpcServerPort, sqlConnectionString) = Variables.LoadOrStop();
|
||||
var (rpcServerHost, rpcServerPort, sqlConnectionString) = Variables.LoadOrStop();
|
||||
|
||||
string secretsPath = Path.GetFullPath("./secrets");
|
||||
CreateFolderOrStop(secretsPath, Chmod.URWX_GRX);
|
||||
|
||||
string webKeysPath = Path.GetFullPath("./keys");
|
||||
CreateFolderOrStop(webKeysPath, Chmod.URWX);
|
||||
|
||||
var certificateData = await CertificateFiles.CreateOrLoad(secretsPath);
|
||||
if (certificateData == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
var (certificate, agentToken) = certificateData.Value;
|
||||
var (certificate, agentAuthToken) = certificateData.Value;
|
||||
var dbContextFactory = new ApplicationDbContextFactory(sqlConnectionString);
|
||||
var controllerServices = new ControllerServices(dbContextFactory, agentAuthToken, shutdownCancellationToken);
|
||||
|
||||
PhantomLogger.Root.InformationHeading("Launching Phantom Panel server...");
|
||||
|
||||
var taskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Server"));
|
||||
await controllerServices.Initialize();
|
||||
|
||||
var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), rpcServerHost, rpcServerPort, certificate);
|
||||
var rpcTask = RpcLauncher.Launch(rpcConfiguration, controllerServices.CreateMessageToServerListener, shutdownCancellationToken);
|
||||
try {
|
||||
var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), rpcServerHost, rpcServerPort, certificate);
|
||||
var webConfiguration = new WebConfiguration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, webKeysPath, cancellationTokenSource.Token);
|
||||
|
||||
var administratorToken = TokenGenerator.Create(60);
|
||||
PhantomLogger.Root.Information("Your administrator token is: {AdministratorToken}", administratorToken);
|
||||
PhantomLogger.Root.Information("For administrator setup, visit: {HttpUrl}{SetupPath}", webConfiguration.HttpUrl, webConfiguration.BasePath + "setup");
|
||||
|
||||
var serviceConfiguration = new ServiceConfiguration(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken), cancellationTokenSource.Token);
|
||||
var webConfigurator = new WebConfigurator(serviceConfiguration, taskManager, agentToken);
|
||||
var webApplication = await WebLauncher.CreateApplication(webConfiguration, webConfigurator, options => options.UseNpgsql(sqlConnectionString, static options => {
|
||||
options.CommandTimeout(10).MigrationsAssembly(typeof(ApplicationDbContextDesignFactory).Assembly.FullName);
|
||||
}));
|
||||
|
||||
await Task.WhenAll(
|
||||
RpcLauncher.Launch(rpcConfiguration, webApplication.Services.GetRequiredService<MessageToServerListenerFactory>().CreateListener, cancellationTokenSource.Token),
|
||||
WebLauncher.Launch(webConfiguration, webApplication)
|
||||
);
|
||||
await rpcTask.WaitAsync(shutdownCancellationToken);
|
||||
} finally {
|
||||
cancellationTokenSource.Cancel();
|
||||
await taskManager.Stop();
|
||||
await rpcTask;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -88,7 +68,8 @@ try {
|
||||
PhantomLogger.Root.Fatal(e, "Caught exception in entry point.");
|
||||
return 1;
|
||||
} finally {
|
||||
cancellationTokenSource.Dispose();
|
||||
shutdownCancellationTokenSource.Dispose();
|
||||
|
||||
PhantomLogger.Root.Information("Bye!");
|
||||
PhantomLogger.Dispose();
|
||||
}
|
||||
|
@ -5,9 +5,6 @@ using Phantom.Utils.Runtime;
|
||||
namespace Phantom.Controller;
|
||||
|
||||
sealed record Variables(
|
||||
string WebServerHost,
|
||||
ushort WebServerPort,
|
||||
string WebBasePath,
|
||||
string RpcServerHost,
|
||||
ushort RpcServerPort,
|
||||
string SqlConnectionString
|
||||
@ -22,9 +19,6 @@ sealed record Variables(
|
||||
};
|
||||
|
||||
return new Variables(
|
||||
EnvironmentVariables.GetString("WEB_SERVER_HOST").WithDefault("0.0.0.0"),
|
||||
EnvironmentVariables.GetPortNumber("WEB_SERVER_PORT").WithDefault(9400),
|
||||
EnvironmentVariables.GetString("WEB_BASE_PATH").Validate(static value => value.StartsWith('/') && value.EndsWith('/'), "Environment variable must begin and end with '/'").WithDefault("/"),
|
||||
EnvironmentVariables.GetString("RPC_SERVER_HOST").WithDefault("0.0.0.0"),
|
||||
EnvironmentVariables.GetPortNumber("RPC_SERVER_PORT").WithDefault(9401),
|
||||
connectionStringBuilder.ToString()
|
||||
|
@ -1,45 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Controller.Minecraft;
|
||||
using Phantom.Controller.Services;
|
||||
using Phantom.Controller.Services.Agents;
|
||||
using Phantom.Controller.Services.Audit;
|
||||
using Phantom.Controller.Services.Events;
|
||||
using Phantom.Controller.Services.Instances;
|
||||
using Phantom.Controller.Services.Rpc;
|
||||
using Phantom.Utils.Tasks;
|
||||
using WebLauncher = Phantom.Web.Launcher;
|
||||
|
||||
namespace Phantom.Controller;
|
||||
|
||||
sealed class WebConfigurator : WebLauncher.IConfigurator {
|
||||
private readonly ServiceConfiguration serviceConfiguration;
|
||||
private readonly TaskManager taskManager;
|
||||
private readonly AgentAuthToken agentToken;
|
||||
|
||||
public WebConfigurator(ServiceConfiguration serviceConfiguration, TaskManager taskManager, AgentAuthToken agentToken) {
|
||||
this.serviceConfiguration = serviceConfiguration;
|
||||
this.taskManager = taskManager;
|
||||
this.agentToken = agentToken;
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services) {
|
||||
services.AddSingleton(serviceConfiguration);
|
||||
services.AddSingleton(taskManager);
|
||||
services.AddSingleton(agentToken);
|
||||
services.AddSingleton<AgentManager>();
|
||||
services.AddSingleton<AgentJavaRuntimesManager>();
|
||||
services.AddSingleton<EventLog>();
|
||||
services.AddSingleton<InstanceManager>();
|
||||
services.AddSingleton<InstanceLogManager>();
|
||||
services.AddSingleton<MinecraftVersions>();
|
||||
services.AddSingleton<MessageToServerListenerFactory>();
|
||||
|
||||
services.AddScoped<AuditLog>();
|
||||
}
|
||||
|
||||
public async Task LoadFromDatabase(IServiceProvider serviceProvider) {
|
||||
await serviceProvider.GetRequiredService<AgentManager>().Initialize();
|
||||
await serviceProvider.GetRequiredService<InstanceManager>().Initialize();
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ services:
|
||||
image: postgres:14
|
||||
container_name: "phantom-panel-postgres"
|
||||
ports:
|
||||
- "127.0.0.1:9402:5432"
|
||||
- "127.0.0.1:9403:5432"
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
environment:
|
||||
|
65
Dockerfile
65
Dockerfile
@ -7,6 +7,7 @@ ARG TARGETARCH
|
||||
ADD . /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN mkdir /data && chmod 777 /data
|
||||
RUN dotnet restore --arch "$TARGETARCH"
|
||||
|
||||
|
||||
@ -24,25 +25,31 @@ RUN dotnet publish Agent/Phantom.Agent/Phantom.Agent.csproj \
|
||||
--output /app/out
|
||||
|
||||
|
||||
# +----------------------+
|
||||
# | Build Phantom Server |
|
||||
# +----------------------+
|
||||
FROM phantom-base-builder AS phantom-server-builder
|
||||
|
||||
RUN dotnet publish Web/Phantom.Web/Phantom.Web.csproj \
|
||||
/p:DebugType=None \
|
||||
/p:DebugSymbols=false \
|
||||
--no-restore \
|
||||
--arch "$TARGETARCH" \
|
||||
--configuration Release \
|
||||
--output /app/out
|
||||
# +--------------------------+
|
||||
# | Build Phantom Controller |
|
||||
# +--------------------------+
|
||||
FROM phantom-base-builder AS phantom-controller-builder
|
||||
|
||||
RUN dotnet publish Controller/Phantom.Controller/Phantom.Controller.csproj \
|
||||
/p:DebugType=None \
|
||||
/p:DebugSymbols=false \
|
||||
--no-restore \
|
||||
--arch "$TARGETARCH" \
|
||||
--configuration Release \
|
||||
/p:DebugType=None \
|
||||
/p:DebugSymbols=false \
|
||||
--no-restore \
|
||||
--arch "$TARGETARCH" \
|
||||
--configuration Release \
|
||||
--output /app/out
|
||||
|
||||
|
||||
# +-------------------+
|
||||
# | Build Phantom Web |
|
||||
# +-------------------+
|
||||
FROM phantom-base-builder AS phantom-controller-builder
|
||||
|
||||
RUN dotnet publish Web/Phantom.Web/Phantom.Web.csproj \
|
||||
/p:DebugType=None \
|
||||
/p:DebugSymbols=false \
|
||||
--no-restore \
|
||||
--arch "$TARGETARCH" \
|
||||
--configuration Release \
|
||||
--output /app/out
|
||||
|
||||
|
||||
@ -51,7 +58,6 @@ RUN dotnet publish Controller/Phantom.Controller/Phantom.Controller.csproj \
|
||||
# +------------------------------+
|
||||
FROM mcr.microsoft.com/dotnet/nightly/runtime:8.0-preview AS phantom-agent
|
||||
|
||||
RUN mkdir /data && chmod 777 /data
|
||||
WORKDIR /data
|
||||
|
||||
COPY --from=eclipse-temurin:8-jre /opt/java/openjdk /opt/java/8
|
||||
@ -73,14 +79,25 @@ COPY --from=phantom-agent-builder --chmod=755 /app/out /app
|
||||
ENTRYPOINT ["dotnet", "/app/Phantom.Agent.dll"]
|
||||
|
||||
|
||||
# +-------------------------------+
|
||||
# | Finalize Phantom Server image |
|
||||
# +-------------------------------+
|
||||
FROM mcr.microsoft.com/dotnet/nightly/aspnet:8.0-preview AS phantom-server
|
||||
# +-----------------------------------+
|
||||
# | Finalize Phantom Controller image |
|
||||
# +-----------------------------------+
|
||||
FROM mcr.microsoft.com/dotnet/nightly/runtime:8.0-preview AS phantom-controller
|
||||
|
||||
RUN mkdir /data && chmod 777 /data
|
||||
WORKDIR /data
|
||||
|
||||
COPY --from=phantom-server-builder --chmod=755 /app/out /app
|
||||
COPY --from=phantom-controller-builder --chmod=755 /app/out /app
|
||||
|
||||
ENTRYPOINT ["dotnet", "/app/Phantom.Controller.dll"]
|
||||
|
||||
|
||||
# +----------------------------+
|
||||
# | Finalize Phantom Web image |
|
||||
# +----------------------------+
|
||||
FROM mcr.microsoft.com/dotnet/nightly/aspnet:8.0-preview AS phantom-web
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
COPY --from=phantom-web-builder --chmod=755 /app/out /app
|
||||
|
||||
ENTRYPOINT ["dotnet", "/app/Phantom.Web.dll"]
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Web.Components.Utils;
|
||||
namespace Phantom.Utils.Tasks;
|
||||
|
||||
public sealed class Throttler {
|
||||
private readonly TimeSpan interval;
|
@ -1,40 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Services.Users;
|
||||
using Phantom.Web.Identity.Data;
|
||||
|
||||
namespace Phantom.Web.Identity.Authorization;
|
||||
|
||||
public sealed class PermissionManager {
|
||||
private readonly DatabaseProvider databaseProvider;
|
||||
private readonly Dictionary<Guid, IdentityPermissions> userIdsToPermissionIds = new ();
|
||||
|
||||
public PermissionManager(DatabaseProvider databaseProvider) {
|
||||
this.databaseProvider = databaseProvider;
|
||||
}
|
||||
|
||||
private IdentityPermissions FetchPermissionsForUserId(Guid userId) {
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
var userPermissions = scope.Ctx.UserPermissions.Where(up => up.UserGuid == userId).Select(static up => up.PermissionId);
|
||||
var rolePermissions = scope.Ctx.UserRoles.Where(ur => ur.UserGuid == userId).Join(scope.Ctx.RolePermissions, static ur => ur.RoleGuid, static rp => rp.RoleGuid, static (ur, rp) => rp.PermissionId);
|
||||
return new IdentityPermissions(userPermissions.Union(rolePermissions));
|
||||
}
|
||||
|
||||
private IdentityPermissions GetPermissionsForUserId(Guid userId, bool refreshCache) {
|
||||
if (!refreshCache && userIdsToPermissionIds.TryGetValue(userId, out var userPermissions)) {
|
||||
return userPermissions;
|
||||
}
|
||||
else {
|
||||
return userIdsToPermissionIds[userId] = FetchPermissionsForUserId(userId);
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityPermissions GetPermissions(ClaimsPrincipal user, bool refreshCache = false) {
|
||||
Guid? userId = UserManager.GetAuthenticatedUserId(user);
|
||||
return userId == null ? IdentityPermissions.None : GetPermissionsForUserId(userId.Value, refreshCache);
|
||||
}
|
||||
|
||||
public bool CheckPermission(ClaimsPrincipal user, Permission permission, bool refreshCache = false) {
|
||||
return GetPermissions(user, refreshCache).Check(permission);
|
||||
}
|
||||
}
|
@ -20,8 +20,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Common\Phantom.Common.Logging\Phantom.Common.Logging.csproj" />
|
||||
<ProjectReference Include="..\..\Controller\Phantom.Controller.Database\Phantom.Controller.Database.csproj" />
|
||||
<ProjectReference Include="..\..\Controller\Phantom.Controller.Services\Phantom.Controller.Services.csproj" />
|
||||
<ProjectReference Include="..\..\Utils\Phantom.Utils\Phantom.Utils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,8 +1,5 @@
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Web.Base;
|
||||
using Phantom.Web.Components.Utils;
|
||||
using Phantom.Web.Identity;
|
||||
using Phantom.Web.Identity.Interfaces;
|
||||
using Serilog;
|
||||
@ -83,24 +80,6 @@ public static class Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task MigrateDatabase(Configuration config, DatabaseProvider databaseProvider) {
|
||||
var logger = config.Logger;
|
||||
|
||||
using var scope = databaseProvider.CreateScope();
|
||||
var database = scope.Ctx.Database;
|
||||
|
||||
logger.Information("Connecting to database...");
|
||||
|
||||
var retryConnection = new Throttler(TimeSpan.FromSeconds(10));
|
||||
while (!await database.CanConnectAsync(config.CancellationToken)) {
|
||||
logger.Warning("Cannot connect to database, retrying...");
|
||||
await retryConnection.Wait();
|
||||
}
|
||||
|
||||
logger.Information("Running database migrations...");
|
||||
await database.MigrateAsync(); // Do not allow cancellation.
|
||||
}
|
||||
|
||||
public interface IConfigurator {
|
||||
void ConfigureServices(IServiceCollection services);
|
||||
Task LoadFromDatabase(IServiceProvider serviceProvider);
|
||||
|
Loading…
Reference in New Issue
Block a user