mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-25 16:42:54 +01:00
Compare commits
5 Commits
a89a8738f9
...
4ac60f61eb
Author | SHA1 | Date | |
---|---|---|---|
4ac60f61eb | |||
489c68ad8e | |||
5b74038c9d | |||
24d41dc9f8 | |||
627e7436fd |
@ -1,9 +1,9 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Server + Agent x3" type="CompoundRunConfigurationType">
|
||||
<configuration default="false" name="Controller + Agent x3" type="CompoundRunConfigurationType">
|
||||
<toRun name="Agent 1" type="DotNetProject" />
|
||||
<toRun name="Agent 2" type="DotNetProject" />
|
||||
<toRun name="Agent 3" type="DotNetProject" />
|
||||
<toRun name="Server" type="DotNetProject" />
|
||||
<toRun name="Controller" type="DotNetProject" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
7
.run/Controller + Agent.run.xml
Normal file
7
.run/Controller + Agent.run.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Controller + Agent" type="CompoundRunConfigurationType">
|
||||
<toRun name="Agent 1" type="DotNetProject" />
|
||||
<toRun name="Controller" type="DotNetProject" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
@ -1,7 +0,0 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Server + Agent" type="CompoundRunConfigurationType">
|
||||
<toRun name="Agent 1" type="DotNetProject" />
|
||||
<toRun name="Server" type="DotNetProject" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
43
Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs
Normal file
43
Agent/Phantom.Agent.Rpc/RpcClientRuntime.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Messages.Agent;
|
||||
using Phantom.Common.Messages.Agent.ToController;
|
||||
using Phantom.Utils.Rpc;
|
||||
using Phantom.Utils.Tasks;
|
||||
|
||||
namespace Phantom.Agent.Rpc;
|
||||
|
||||
public sealed class RpcClientRuntime : RpcClientRuntime<IMessageToAgentListener, IMessageToControllerListener, RegisterAgentMessage> {
|
||||
public static Task Launch(RpcConfiguration config, AuthToken authToken, AgentInfo agentInfo, Func<RpcConnectionToServer<IMessageToControllerListener>, IMessageToAgentListener> listenerFactory, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) {
|
||||
return new RpcClientRuntime(config, agentInfo.Guid, listenerFactory, new RegisterAgentMessage(authToken, agentInfo), disconnectSemaphore, receiveCancellationToken).Launch();
|
||||
}
|
||||
|
||||
private readonly RpcConfiguration config;
|
||||
private readonly Guid agentGuid;
|
||||
|
||||
private RpcClientRuntime(RpcConfiguration config, Guid agentGuid, Func<RpcConnectionToServer<IMessageToControllerListener>, IMessageToAgentListener> messageListenerFactory, RegisterAgentMessage registerAgentMessage, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(config, AgentMessageRegistries.Definitions, messageListenerFactory, registerAgentMessage, disconnectSemaphore, receiveCancellationToken) {
|
||||
this.config = config;
|
||||
this.agentGuid = agentGuid;
|
||||
}
|
||||
|
||||
protected override void RunWithConnection(ClientSocket socket, RpcConnectionToServer<IMessageToControllerListener> connection, TaskManager taskManager) {
|
||||
ServerMessaging.SetCurrentConnection(connection);
|
||||
|
||||
var keepAliveLoop = new KeepAliveLoop(connection);
|
||||
try {
|
||||
base.RunWithConnection(socket, connection, taskManager);
|
||||
} finally {
|
||||
keepAliveLoop.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task Disconnect(ClientSocket socket) {
|
||||
var unregisterMessageBytes = AgentMessageRegistries.ToController.Write(new UnregisterAgentMessage(agentGuid)).ToArray();
|
||||
try {
|
||||
await socket.SendAsync(unregisterMessageBytes).AsTask().WaitAsync(TimeSpan.FromSeconds(5), CancellationToken.None);
|
||||
} catch (TimeoutException) {
|
||||
config.RuntimeLogger.Error("Timed out communicating agent shutdown with the controller.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Messages.Agent;
|
||||
using Phantom.Common.Messages.Agent.BiDirectional;
|
||||
using Phantom.Common.Messages.Agent.ToController;
|
||||
using Phantom.Utils.Rpc;
|
||||
using Phantom.Utils.Rpc.Message;
|
||||
using Phantom.Utils.Tasks;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Phantom.Agent.Rpc;
|
||||
|
||||
public sealed class RpcLauncher : RpcRuntime<ClientSocket> {
|
||||
public static Task Launch(RpcConfiguration config, AuthToken authToken, AgentInfo agentInfo, Func<RpcConnectionToServer<IMessageToControllerListener>, IMessageToAgentListener> listenerFactory, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) {
|
||||
var socket = new ClientSocket();
|
||||
var options = socket.Options;
|
||||
|
||||
options.CurveServerCertificate = config.ServerCertificate;
|
||||
options.CurveCertificate = new NetMQCertificate();
|
||||
options.HelloMessage = AgentMessageRegistries.ToController.Write(new RegisterAgentMessage(authToken, agentInfo)).ToArray();
|
||||
|
||||
return new RpcLauncher(config, socket, agentInfo.Guid, listenerFactory, disconnectSemaphore, receiveCancellationToken).Launch();
|
||||
}
|
||||
|
||||
private readonly RpcConfiguration config;
|
||||
private readonly Guid agentGuid;
|
||||
private readonly Func<RpcConnectionToServer<IMessageToControllerListener>, IMessageToAgentListener> messageListenerFactory;
|
||||
|
||||
private readonly SemaphoreSlim disconnectSemaphore;
|
||||
private readonly CancellationToken receiveCancellationToken;
|
||||
|
||||
private RpcLauncher(RpcConfiguration config, ClientSocket socket, Guid agentGuid, Func<RpcConnectionToServer<IMessageToControllerListener>, IMessageToAgentListener> messageListenerFactory, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(config, socket) {
|
||||
this.config = config;
|
||||
this.agentGuid = agentGuid;
|
||||
this.messageListenerFactory = messageListenerFactory;
|
||||
this.disconnectSemaphore = disconnectSemaphore;
|
||||
this.receiveCancellationToken = receiveCancellationToken;
|
||||
}
|
||||
|
||||
protected override void Connect(ClientSocket socket) {
|
||||
var logger = config.RuntimeLogger;
|
||||
var url = config.TcpUrl;
|
||||
|
||||
logger.Information("Starting ZeroMQ client and connecting to {Url}...", url);
|
||||
socket.Connect(url);
|
||||
logger.Information("ZeroMQ client ready.");
|
||||
}
|
||||
|
||||
protected override void Run(ClientSocket socket, MessageReplyTracker replyTracker, TaskManager taskManager) {
|
||||
var connection = new RpcConnectionToServer<IMessageToControllerListener>(socket, AgentMessageRegistries.ToController, replyTracker);
|
||||
ServerMessaging.SetCurrentConnection(connection);
|
||||
|
||||
var logger = config.RuntimeLogger;
|
||||
var handler = new MessageToAgentHandler(messageListenerFactory(connection), logger, taskManager, receiveCancellationToken);
|
||||
var keepAliveLoop = new KeepAliveLoop(connection);
|
||||
|
||||
try {
|
||||
while (!receiveCancellationToken.IsCancellationRequested) {
|
||||
var data = socket.Receive(receiveCancellationToken);
|
||||
|
||||
LogMessageType(logger, data);
|
||||
|
||||
if (data.Length > 0) {
|
||||
AgentMessageRegistries.ToAgent.Handle(data, handler);
|
||||
}
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
// Ignore.
|
||||
} finally {
|
||||
logger.Debug("ZeroMQ client stopped receiving messages.");
|
||||
|
||||
disconnectSemaphore.Wait(CancellationToken.None);
|
||||
keepAliveLoop.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private static void LogMessageType(ILogger logger, ReadOnlyMemory<byte> data) {
|
||||
if (!logger.IsEnabled(LogEventLevel.Verbose)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.Length > 0 && AgentMessageRegistries.ToAgent.TryGetType(data, out var type)) {
|
||||
logger.Verbose("Received {MessageType} ({Bytes} B) from controller.", type.Name, data.Length);
|
||||
}
|
||||
else {
|
||||
logger.Verbose("Received {Bytes} B message from controller.", data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task Disconnect() {
|
||||
var unregisterTimeoutTask = Task.Delay(TimeSpan.FromSeconds(5), CancellationToken.None);
|
||||
var finishedTask = await Task.WhenAny(ServerMessaging.Send(new UnregisterAgentMessage(agentGuid)), unregisterTimeoutTask);
|
||||
if (finishedTask == unregisterTimeoutTask) {
|
||||
config.RuntimeLogger.Error("Timed out communicating agent shutdown with the controller.");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MessageToAgentHandler : MessageHandler<IMessageToAgentListener> {
|
||||
public MessageToAgentHandler(IMessageToAgentListener listener, ILogger logger, TaskManager taskManager, CancellationToken cancellationToken) : base(listener, logger, taskManager, cancellationToken) {}
|
||||
|
||||
protected override Task SendReply(uint sequenceId, byte[] serializedReply) {
|
||||
return ServerMessaging.Send(new ReplyMessage(sequenceId, serializedReply));
|
||||
}
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@ public sealed class MessageListener : IMessageToAgentListener {
|
||||
}
|
||||
}
|
||||
|
||||
await ServerMessaging.Send(new AdvertiseJavaRuntimesMessage(agent.JavaRuntimeRepository.All));
|
||||
await connection.Send(new AdvertiseJavaRuntimesMessage(agent.JavaRuntimeRepository.All));
|
||||
await agent.InstanceSessionManager.RefreshAgentStatus();
|
||||
|
||||
return NoReply.Instance;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using NetMQ;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Cryptography;
|
||||
|
@ -58,7 +58,7 @@ try {
|
||||
|
||||
var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1);
|
||||
var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), controllerHost, controllerPort, controllerCertificate);
|
||||
var rpcTask = RpcLauncher.Launch(rpcConfiguration, agentToken, agentInfo, MessageListenerFactory, rpcDisconnectSemaphore, shutdownCancellationToken);
|
||||
var rpcTask = RpcClientRuntime.Launch(rpcConfiguration, agentToken, agentInfo, MessageListenerFactory, rpcDisconnectSemaphore, shutdownCancellationToken);
|
||||
try {
|
||||
await rpcTask.WaitAsync(shutdownCancellationToken);
|
||||
} finally {
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Controller.Database.Enums;
|
||||
namespace Phantom.Common.Data.Web.AuditLog;
|
||||
|
||||
public enum AuditLogEventType {
|
||||
AdministratorUserCreated,
|
||||
@ -16,7 +16,7 @@ public enum AuditLogEventType {
|
||||
InstanceCommandExecuted
|
||||
}
|
||||
|
||||
static class AuditLogEventTypeExtensions {
|
||||
public static class AuditLogEventTypeExtensions {
|
||||
private static readonly Dictionary<AuditLogEventType, AuditLogSubjectType> SubjectTypes = new () {
|
||||
{ AuditLogEventType.AdministratorUserCreated, AuditLogSubjectType.User },
|
||||
{ AuditLogEventType.AdministratorUserModified, AuditLogSubjectType.User },
|
||||
@ -41,7 +41,7 @@ static class AuditLogEventTypeExtensions {
|
||||
}
|
||||
}
|
||||
|
||||
internal static AuditLogSubjectType GetSubjectType(this AuditLogEventType type) {
|
||||
public static AuditLogSubjectType GetSubjectType(this AuditLogEventType type) {
|
||||
return SubjectTypes[type];
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
|
||||
namespace Phantom.Controller.Services.Audit;
|
||||
namespace Phantom.Common.Data.Web.AuditLog;
|
||||
|
||||
public sealed record AuditLogItem(DateTime UtcTime, Guid? UserGuid, string? UserName, AuditLogEventType EventType, AuditLogSubjectType SubjectType, string? SubjectId, JsonDocument? Data);
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Controller.Database.Enums;
|
||||
namespace Phantom.Common.Data.Web.AuditLog;
|
||||
|
||||
public enum AuditLogSubjectType {
|
||||
User,
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Controller.Database.Enums;
|
||||
namespace Phantom.Common.Data.Web.EventLog;
|
||||
|
||||
public enum EventLogEventType {
|
||||
InstanceLaunchSucceded,
|
||||
@ -10,7 +10,7 @@ public enum EventLogEventType {
|
||||
InstanceBackupFailed,
|
||||
}
|
||||
|
||||
static class EventLogEventTypeExtensions {
|
||||
public static class EventLogEventTypeExtensions {
|
||||
private static readonly Dictionary<EventLogEventType, EventLogSubjectType> SubjectTypes = new () {
|
||||
{ EventLogEventType.InstanceLaunchSucceded, EventLogSubjectType.Instance },
|
||||
{ EventLogEventType.InstanceLaunchFailed, EventLogSubjectType.Instance },
|
@ -1,6 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
|
||||
namespace Phantom.Controller.Services.Events;
|
||||
namespace Phantom.Common.Data.Web.EventLog;
|
||||
|
||||
public sealed record EventLogItem(DateTime UtcTime, Guid? AgentGuid, EventLogEventType EventType, EventLogSubjectType SubjectType, string SubjectId, JsonDocument? Data);
|
@ -0,0 +1,5 @@
|
||||
namespace Phantom.Common.Data.Web.EventLog;
|
||||
|
||||
public enum EventLogSubjectType {
|
||||
Instance
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Controller.Services.Users.Roles;
|
||||
namespace Phantom.Common.Data.Web.Users;
|
||||
|
||||
public enum AddRoleError : byte {
|
||||
NameIsEmpty,
|
||||
|
@ -1,25 +1,28 @@
|
||||
using System.Collections.Immutable;
|
||||
using MemoryPack;
|
||||
using Phantom.Common.Data.Web.Users.AddUserErrors;
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users;
|
||||
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(NameIsInvalid))]
|
||||
[MemoryPackUnion(1, typeof(PasswordIsInvalid))]
|
||||
[MemoryPackUnion(2, typeof(NameAlreadyExists))]
|
||||
[MemoryPackUnion(3, typeof(UnknownError))]
|
||||
public abstract record AddUserError {
|
||||
private AddUserError() {}
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record NameIsInvalid([property: MemoryPackOrder(0)] UsernameRequirementViolation Violation) : AddUserError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record PasswordIsInvalid([property: MemoryPackOrder(0)] ImmutableArray<PasswordRequirementViolation> Violations) : AddUserError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record NameAlreadyExists : AddUserError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record UnknownError : AddUserError;
|
||||
namespace Phantom.Common.Data.Web.Users {
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(NameIsInvalid))]
|
||||
[MemoryPackUnion(1, typeof(PasswordIsInvalid))]
|
||||
[MemoryPackUnion(2, typeof(NameAlreadyExists))]
|
||||
[MemoryPackUnion(3, typeof(UnknownError))]
|
||||
public abstract partial record AddUserError {
|
||||
internal AddUserError() {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users.AddUserErrors {
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record NameIsInvalid([property: MemoryPackOrder(0)] UsernameRequirementViolation Violation) : AddUserError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record PasswordIsInvalid([property: MemoryPackOrder(0)] ImmutableArray<PasswordRequirementViolation> Violations) : AddUserError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record NameAlreadyExists : AddUserError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record UnknownError : AddUserError;
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
using MemoryPack;
|
||||
using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults;
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users;
|
||||
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(Success))]
|
||||
[MemoryPackUnion(1, typeof(CreationFailed))]
|
||||
[MemoryPackUnion(2, typeof(UpdatingFailed))]
|
||||
[MemoryPackUnion(3, typeof(AddingToRoleFailed))]
|
||||
[MemoryPackUnion(4, typeof(UnknownError))]
|
||||
public abstract partial record CreateOrUpdateAdministratorUserResult {
|
||||
private CreateOrUpdateAdministratorUserResult() {}
|
||||
namespace Phantom.Common.Data.Web.Users {
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(Success))]
|
||||
[MemoryPackUnion(1, typeof(CreationFailed))]
|
||||
[MemoryPackUnion(2, typeof(UpdatingFailed))]
|
||||
[MemoryPackUnion(3, typeof(AddingToRoleFailed))]
|
||||
[MemoryPackUnion(4, typeof(UnknownError))]
|
||||
public abstract partial record CreateOrUpdateAdministratorUserResult {
|
||||
internal CreateOrUpdateAdministratorUserResult() {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults {
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record Success([property: MemoryPackOrder(0)] UserInfo User) : CreateOrUpdateAdministratorUserResult;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users.Permissions;
|
||||
namespace Phantom.Common.Data.Web.Users;
|
||||
|
||||
public sealed class IdentityPermissions {
|
||||
public static IdentityPermissions None { get; } = new (ImmutableHashSet<string>.Empty);
|
||||
|
@ -1,24 +1,27 @@
|
||||
using MemoryPack;
|
||||
using Phantom.Common.Data.Web.Users.PasswordRequirementViolations;
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users;
|
||||
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(TooShort))]
|
||||
[MemoryPackUnion(1, typeof(LowercaseLetterRequired))]
|
||||
[MemoryPackUnion(2, typeof(UppercaseLetterRequired))]
|
||||
[MemoryPackUnion(3, typeof(DigitRequired))]
|
||||
public abstract record PasswordRequirementViolation {
|
||||
private PasswordRequirementViolation() {}
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record TooShort([property: MemoryPackOrder(0)] int MinimumLength) : PasswordRequirementViolation;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record LowercaseLetterRequired : PasswordRequirementViolation;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record UppercaseLetterRequired : PasswordRequirementViolation;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record DigitRequired : PasswordRequirementViolation;
|
||||
namespace Phantom.Common.Data.Web.Users {
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(TooShort))]
|
||||
[MemoryPackUnion(1, typeof(MustContainLowercaseLetter))]
|
||||
[MemoryPackUnion(2, typeof(MustContainUppercaseLetter))]
|
||||
[MemoryPackUnion(3, typeof(MustContainDigit))]
|
||||
public abstract partial record PasswordRequirementViolation {
|
||||
internal PasswordRequirementViolation() {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users.PasswordRequirementViolations {
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record TooShort([property: MemoryPackOrder(0)] int MinimumLength) : PasswordRequirementViolation;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record MustContainLowercaseLetter : PasswordRequirementViolation;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record MustContainUppercaseLetter : PasswordRequirementViolation;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record MustContainDigit : PasswordRequirementViolation;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Common.Data.Web.Users.Permissions;
|
||||
namespace Phantom.Common.Data.Web.Users;
|
||||
|
||||
public sealed record Permission(string Id, Permission? Parent) {
|
||||
private static readonly List<Permission> AllPermissions = new ();
|
||||
|
@ -1,21 +1,24 @@
|
||||
using System.Collections.Immutable;
|
||||
using MemoryPack;
|
||||
using Phantom.Common.Data.Web.Users.SetUserPasswordErrors;
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users;
|
||||
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(UserNotFound))]
|
||||
[MemoryPackUnion(1, typeof(PasswordIsInvalid))]
|
||||
[MemoryPackUnion(2, typeof(UnknownError))]
|
||||
public abstract record SetUserPasswordError {
|
||||
private SetUserPasswordError() {}
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record UserNotFound : SetUserPasswordError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record PasswordIsInvalid(ImmutableArray<PasswordRequirementViolation> Violations) : SetUserPasswordError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record UnknownError : SetUserPasswordError;
|
||||
namespace Phantom.Common.Data.Web.Users {
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(UserNotFound))]
|
||||
[MemoryPackUnion(1, typeof(PasswordIsInvalid))]
|
||||
[MemoryPackUnion(2, typeof(UnknownError))]
|
||||
public abstract partial record SetUserPasswordError {
|
||||
internal SetUserPasswordError() {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users.SetUserPasswordErrors {
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record UserNotFound : SetUserPasswordError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record PasswordIsInvalid([property: MemoryPackOrder(0)] ImmutableArray<PasswordRequirementViolation> Violations) : SetUserPasswordError;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record UnknownError : SetUserPasswordError;
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
using MemoryPack;
|
||||
using Phantom.Common.Data.Web.Users.UsernameRequirementViolations;
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users;
|
||||
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(IsEmpty))]
|
||||
[MemoryPackUnion(1, typeof(TooLong))]
|
||||
public abstract record UsernameRequirementViolation {
|
||||
private UsernameRequirementViolation() {}
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record IsEmpty : UsernameRequirementViolation;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed record TooLong(int MaxLength) : UsernameRequirementViolation;
|
||||
namespace Phantom.Common.Data.Web.Users {
|
||||
[MemoryPackable]
|
||||
[MemoryPackUnion(0, typeof(IsEmpty))]
|
||||
[MemoryPackUnion(1, typeof(TooLong))]
|
||||
public abstract partial record UsernameRequirementViolation {
|
||||
internal UsernameRequirementViolation() {}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Phantom.Common.Data.Web.Users.UsernameRequirementViolations {
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record IsEmpty : UsernameRequirementViolation;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record TooLong([property: MemoryPackOrder(0)] int MaxLength) : UsernameRequirementViolation;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
using System.Security.Cryptography;
|
||||
using MemoryPack;
|
||||
|
||||
namespace Phantom.Common.Data.Agent;
|
||||
namespace Phantom.Common.Data;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
@ -1,4 +1,4 @@
|
||||
namespace Phantom.Common.Data.Agent;
|
||||
namespace Phantom.Common.Data;
|
||||
|
||||
public readonly record struct ConnectionCommonKey(byte[] CertificatePublicKey, AuthToken AuthToken) {
|
||||
private const byte TokenLength = AuthToken.Length;
|
@ -34,14 +34,18 @@ public static class AgentMessageRegistries {
|
||||
}
|
||||
|
||||
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToAgentListener, IMessageToControllerListener> {
|
||||
public MessageRegistry<IMessageToAgentListener> Outgoing => ToAgent;
|
||||
public MessageRegistry<IMessageToControllerListener> Incoming => ToController;
|
||||
public MessageRegistry<IMessageToAgentListener> ToClient => ToAgent;
|
||||
public MessageRegistry<IMessageToControllerListener> ToServer => ToController;
|
||||
|
||||
public bool IsRegistrationMessage(Type messageType) {
|
||||
return messageType == typeof(RegisterAgentMessage);
|
||||
}
|
||||
|
||||
public IMessage<IMessageToAgentListener, NoReply> CreateReplyMessage( uint sequenceId, byte[] serializedReply) {
|
||||
public IMessage<IMessageToAgentListener, NoReply> CreateReplyToServerMessage( uint sequenceId, byte[] serializedReply) {
|
||||
return new ReplyMessage(sequenceId, serializedReply);
|
||||
}
|
||||
|
||||
public IMessage<IMessageToControllerListener, NoReply> CreateReplyToClientMessage(uint sequenceId, byte[] serializedReply) {
|
||||
return new ReplyMessage(sequenceId, serializedReply);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using MemoryPack;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Utils.Rpc.Message;
|
||||
|
||||
|
@ -6,6 +6,7 @@ using Phantom.Utils.Rpc.Message;
|
||||
namespace Phantom.Common.Messages.Web;
|
||||
|
||||
public interface IMessageToControllerListener {
|
||||
Task<NoReply> HandleRegisterWeb(RegisterWebMessage message);
|
||||
Task<CreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUser message);
|
||||
Task<NoReply> HandleReply(ReplyMessage message);
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
using MemoryPack;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Utils.Rpc.Message;
|
||||
|
||||
namespace Phantom.Common.Messages.Web.ToController;
|
||||
|
||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
||||
public sealed partial record RegisterWebMessage(
|
||||
[property: MemoryPackOrder(0)] AuthToken AuthToken
|
||||
) : IMessageToController {
|
||||
public Task<NoReply> Accept(IMessageToControllerListener listener) {
|
||||
return listener.HandleRegisterWeb(this);
|
||||
}
|
||||
}
|
@ -20,14 +20,18 @@ public static class WebMessageRegistries {
|
||||
}
|
||||
|
||||
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener> {
|
||||
public MessageRegistry<IMessageToWebListener> Outgoing => ToWeb;
|
||||
public MessageRegistry<IMessageToControllerListener> Incoming => ToController;
|
||||
public MessageRegistry<IMessageToWebListener> ToClient => ToWeb;
|
||||
public MessageRegistry<IMessageToControllerListener> ToServer => ToController;
|
||||
|
||||
public bool IsRegistrationMessage(Type messageType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public IMessage<IMessageToWebListener, NoReply> CreateReplyMessage( uint sequenceId, byte[] serializedReply) {
|
||||
public IMessage<IMessageToWebListener, NoReply> CreateReplyToServerMessage( uint sequenceId, byte[] serializedReply) {
|
||||
return new ReplyMessage(sequenceId, serializedReply);
|
||||
}
|
||||
|
||||
public IMessage<IMessageToControllerListener, NoReply> CreateReplyToClientMessage(uint sequenceId, byte[] serializedReply) {
|
||||
return new ReplyMessage(sequenceId, serializedReply);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,10 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Common.Data.Minecraft;
|
||||
using Phantom.Common.Data.Web.AuditLog;
|
||||
using Phantom.Common.Data.Web.EventLog;
|
||||
using Phantom.Controller.Database.Converters;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
using Phantom.Controller.Database.Factories;
|
||||
|
||||
namespace Phantom.Controller.Database;
|
||||
|
@ -2,7 +2,7 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
using Phantom.Common.Data.Web.AuditLog;
|
||||
|
||||
namespace Phantom.Controller.Database.Entities;
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
using Phantom.Common.Data.Web.EventLog;
|
||||
|
||||
namespace Phantom.Controller.Database.Entities;
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
namespace Phantom.Controller.Database.Enums;
|
||||
|
||||
public enum EventLogSubjectType {
|
||||
Instance
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
using Phantom.Common.Data.Web.AuditLog;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
|
||||
namespace Phantom.Controller.Database.Repositories;
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Data.Web.AuditLog;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
using Phantom.Controller.Services.Audit;
|
||||
|
||||
namespace Phantom.Controller.Database.Repositories;
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Controller.Services.Users.Roles;
|
||||
using Phantom.Utils.Collections;
|
||||
using Phantom.Utils.Tasks;
|
||||
|
||||
namespace Phantom.Controller.Services.Users;
|
||||
namespace Phantom.Controller.Database.Repositories;
|
||||
|
||||
public sealed class RoleRepository {
|
||||
private const int MaxRoleNameLength = 40;
|
||||
|
@ -1,6 +1,9 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
using Phantom.Common.Data.Web.Users.AddUserErrors;
|
||||
using Phantom.Common.Data.Web.Users.PasswordRequirementViolations;
|
||||
using Phantom.Common.Data.Web.Users.UsernameRequirementViolations;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Utils.Collections;
|
||||
using Phantom.Utils.Tasks;
|
||||
@ -13,10 +16,10 @@ public sealed class UserRepository {
|
||||
|
||||
private static UsernameRequirementViolation? CheckUsernameRequirements(string username) {
|
||||
if (string.IsNullOrWhiteSpace(username)) {
|
||||
return new UsernameRequirementViolation.IsEmpty();
|
||||
return new IsEmpty();
|
||||
}
|
||||
else if (username.Length > MaxUserNameLength) {
|
||||
return new UsernameRequirementViolation.TooLong(MaxUserNameLength);
|
||||
return new TooLong(MaxUserNameLength);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
@ -27,19 +30,19 @@ public sealed class UserRepository {
|
||||
var violations = ImmutableArray.CreateBuilder<PasswordRequirementViolation>();
|
||||
|
||||
if (password.Length < MinimumPasswordLength) {
|
||||
violations.Add(new PasswordRequirementViolation.TooShort(MinimumPasswordLength));
|
||||
violations.Add(new TooShort(MinimumPasswordLength));
|
||||
}
|
||||
|
||||
if (!password.Any(char.IsLower)) {
|
||||
violations.Add(new PasswordRequirementViolation.LowercaseLetterRequired());
|
||||
violations.Add(new MustContainLowercaseLetter());
|
||||
}
|
||||
|
||||
if (!password.Any(char.IsUpper)) {
|
||||
violations.Add(new PasswordRequirementViolation.UppercaseLetterRequired());
|
||||
violations.Add(new MustContainUppercaseLetter());
|
||||
}
|
||||
|
||||
if (!password.Any(char.IsDigit)) {
|
||||
violations.Add(new PasswordRequirementViolation.DigitRequired());
|
||||
violations.Add(new MustContainDigit());
|
||||
}
|
||||
|
||||
return violations.ToImmutable();
|
||||
@ -73,16 +76,16 @@ public sealed class UserRepository {
|
||||
public async Task<Result<UserEntity, AddUserError>> CreateUser(string username, string password) {
|
||||
var usernameRequirementViolation = CheckUsernameRequirements(username);
|
||||
if (usernameRequirementViolation != null) {
|
||||
return new AddUserError.NameIsInvalid(usernameRequirementViolation);
|
||||
return new NameIsInvalid(usernameRequirementViolation);
|
||||
}
|
||||
|
||||
var passwordRequirementViolations = CheckPasswordRequirements(password);
|
||||
if (!passwordRequirementViolations.IsEmpty) {
|
||||
return new AddUserError.PasswordIsInvalid(passwordRequirementViolations);
|
||||
return new PasswordIsInvalid(passwordRequirementViolations);
|
||||
}
|
||||
|
||||
if (await db.Ctx.Users.AnyAsync(user => user.Name == username)) {
|
||||
return new AddUserError.NameAlreadyExists();
|
||||
return new NameAlreadyExists();
|
||||
}
|
||||
|
||||
var user = new UserEntity(Guid.NewGuid(), username, UserPasswords.Hash(password));
|
||||
@ -96,7 +99,7 @@ public sealed class UserRepository {
|
||||
public Result<SetUserPasswordError> SetUserPassword(UserEntity user, string password) {
|
||||
var requirementViolations = CheckPasswordRequirements(password);
|
||||
if (!requirementViolations.IsEmpty) {
|
||||
return new SetUserPasswordError.PasswordIsInvalid(requirementViolations);
|
||||
return new Common.Data.Web.Users.SetUserPasswordErrors.PasswordIsInvalid(requirementViolations);
|
||||
}
|
||||
|
||||
user.PasswordHash = UserPasswords.Hash(password);
|
||||
|
@ -1,11 +1,10 @@
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
using Phantom.Common.Messages.Agent.BiDirectional;
|
||||
using Phantom.Utils.Rpc.Message;
|
||||
|
||||
namespace Phantom.Controller.Rpc;
|
||||
|
||||
public sealed class RpcClientConnection<TListener> {
|
||||
public sealed class RpcConnectionToClient<TListener> {
|
||||
private readonly ServerSocket socket;
|
||||
private readonly uint routingId;
|
||||
|
||||
@ -15,14 +14,14 @@ public sealed class RpcClientConnection<TListener> {
|
||||
internal event EventHandler<RpcClientConnectionClosedEventArgs>? Closed;
|
||||
private bool isClosed;
|
||||
|
||||
internal RpcClientConnection(ServerSocket socket, uint routingId, MessageRegistry<TListener> messageRegistry, MessageReplyTracker messageReplyTracker) {
|
||||
internal RpcConnectionToClient(ServerSocket socket, uint routingId, MessageRegistry<TListener> messageRegistry, MessageReplyTracker messageReplyTracker) {
|
||||
this.socket = socket;
|
||||
this.routingId = routingId;
|
||||
this.messageRegistry = messageRegistry;
|
||||
this.messageReplyTracker = messageReplyTracker;
|
||||
}
|
||||
|
||||
public bool IsSame(RpcClientConnection<TListener> other) {
|
||||
public bool IsSame(RpcConnectionToClient<TListener> other) {
|
||||
return this.routingId == other.routingId && this.socket == other.socket;
|
||||
}
|
||||
|
||||
@ -63,7 +62,7 @@ public sealed class RpcClientConnection<TListener> {
|
||||
return await messageReplyTracker.WaitForReply<TReply>(sequenceId, waitForReplyTime, waitForReplyCancellationToken);
|
||||
}
|
||||
|
||||
public void Receive(ReplyMessage message) {
|
||||
public void Receive(IReply message) {
|
||||
messageReplyTracker.ReceiveReply(message.SequenceId, message.SerializedReply);
|
||||
}
|
||||
}
|
@ -8,14 +8,14 @@ using Serilog.Events;
|
||||
namespace Phantom.Controller.Rpc;
|
||||
|
||||
public static class RpcRuntime {
|
||||
public static Task Launch<TOutgoingListener, TIncomingListener>(RpcConfiguration config, IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions, Func<RpcClientConnection<TOutgoingListener>, TIncomingListener> listenerFactory, CancellationToken cancellationToken) {
|
||||
return RpcRuntime<TOutgoingListener, TIncomingListener>.Launch(config, messageDefinitions, listenerFactory, cancellationToken);
|
||||
public static Task Launch<TClientListener, TServerListener>(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) {
|
||||
return RpcRuntime<TClientListener, TServerListener>.Launch(config, messageDefinitions, listenerFactory, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class RpcRuntime<TOutgoingListener, TIncomingListener> : RpcRuntime<ServerSocket> {
|
||||
internal static Task Launch(RpcConfiguration config, IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions, Func<RpcClientConnection<TOutgoingListener>, TIncomingListener> listenerFactory, CancellationToken cancellationToken) {
|
||||
return new RpcRuntime<TOutgoingListener, TIncomingListener>(config, messageDefinitions, listenerFactory, cancellationToken).Launch();
|
||||
internal sealed class RpcRuntime<TClientListener, TServerListener> : RpcRuntime<ServerSocket> {
|
||||
internal static Task Launch(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) {
|
||||
return new RpcRuntime<TClientListener, TServerListener>(config, messageDefinitions, listenerFactory, cancellationToken).Launch();
|
||||
}
|
||||
|
||||
private static ServerSocket CreateSocket(RpcConfiguration config) {
|
||||
@ -29,11 +29,11 @@ internal sealed class RpcRuntime<TOutgoingListener, TIncomingListener> : RpcRunt
|
||||
}
|
||||
|
||||
private readonly RpcConfiguration config;
|
||||
private readonly IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions;
|
||||
private readonly Func<RpcClientConnection<TOutgoingListener>, TIncomingListener> listenerFactory;
|
||||
private readonly IMessageDefinitions<TClientListener, TServerListener> messageDefinitions;
|
||||
private readonly Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
private RpcRuntime(RpcConfiguration config, IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions, Func<RpcClientConnection<TOutgoingListener>, TIncomingListener> listenerFactory, CancellationToken cancellationToken) : base(config, CreateSocket(config)) {
|
||||
private RpcRuntime(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) : base(config, CreateSocket(config)) {
|
||||
this.config = config;
|
||||
this.messageDefinitions = messageDefinitions;
|
||||
this.listenerFactory = listenerFactory;
|
||||
@ -71,7 +71,7 @@ internal sealed class RpcRuntime<TOutgoingListener, TIncomingListener> : RpcRunt
|
||||
continue;
|
||||
}
|
||||
|
||||
var connection = new RpcClientConnection<TOutgoingListener>(socket, routingId, messageDefinitions.Outgoing, replyTracker);
|
||||
var connection = new RpcConnectionToClient<TClientListener>(socket, routingId, messageDefinitions.ToClient, replyTracker);
|
||||
connection.Closed += OnConnectionClosed;
|
||||
|
||||
client = new Client(connection, messageDefinitions, listenerFactory(connection), logger, taskManager, cancellationToken);
|
||||
@ -79,7 +79,7 @@ internal sealed class RpcRuntime<TOutgoingListener, TIncomingListener> : RpcRunt
|
||||
}
|
||||
|
||||
LogMessageType(logger, routingId, data);
|
||||
messageDefinitions.Incoming.Handle(data, client);
|
||||
messageDefinitions.ToServer.Handle(data, client);
|
||||
}
|
||||
|
||||
foreach (var client in clients.Values) {
|
||||
@ -92,7 +92,7 @@ internal sealed class RpcRuntime<TOutgoingListener, TIncomingListener> : RpcRunt
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.Length > 0 && messageDefinitions.Incoming.TryGetType(data, out var type)) {
|
||||
if (data.Length > 0 && messageDefinitions.ToServer.TryGetType(data, out var type)) {
|
||||
logger.Verbose("Received {MessageType} ({Bytes} B) from {RoutingId}.", type.Name, data.Length, routingId);
|
||||
}
|
||||
else {
|
||||
@ -101,7 +101,7 @@ internal sealed class RpcRuntime<TOutgoingListener, TIncomingListener> : RpcRunt
|
||||
}
|
||||
|
||||
private bool CheckIsRegistrationMessage(ReadOnlyMemory<byte> data, ILogger logger, uint routingId) {
|
||||
if (messageDefinitions.Incoming.TryGetType(data, out var type) && messageDefinitions.IsRegistrationMessage(type)) {
|
||||
if (messageDefinitions.ToServer.TryGetType(data, out var type) && messageDefinitions.IsRegistrationMessage(type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -109,18 +109,18 @@ internal sealed class RpcRuntime<TOutgoingListener, TIncomingListener> : RpcRunt
|
||||
return false;
|
||||
}
|
||||
|
||||
private sealed class Client : MessageHandler<TIncomingListener> {
|
||||
public RpcClientConnection<TOutgoingListener> Connection { get; }
|
||||
private sealed class Client : MessageHandler<TServerListener> {
|
||||
public RpcConnectionToClient<TClientListener> Connection { get; }
|
||||
|
||||
private readonly IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions;
|
||||
private readonly IMessageDefinitions<TClientListener, TServerListener> messageDefinitions;
|
||||
|
||||
public Client(RpcClientConnection<TOutgoingListener> connection, IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions, TIncomingListener listener, ILogger logger, TaskManager taskManager, CancellationToken cancellationToken) : base(listener, logger, taskManager, cancellationToken) {
|
||||
public Client(RpcConnectionToClient<TClientListener> connection, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, TServerListener listener, ILogger logger, TaskManager taskManager, CancellationToken cancellationToken) : base(listener, logger, taskManager, cancellationToken) {
|
||||
this.Connection = connection;
|
||||
this.messageDefinitions = messageDefinitions;
|
||||
}
|
||||
|
||||
protected override Task SendReply(uint sequenceId, byte[] serializedReply) {
|
||||
return Connection.Send(messageDefinitions.CreateReplyMessage(sequenceId, serializedReply));
|
||||
return Connection.Send(messageDefinitions.CreateReplyToServerMessage(sequenceId, serializedReply));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ using Phantom.Controller.Rpc;
|
||||
namespace Phantom.Controller.Services.Agents;
|
||||
|
||||
sealed class AgentConnection {
|
||||
private readonly RpcClientConnection<IMessageToAgentListener> connection;
|
||||
private readonly RpcConnectionToClient<IMessageToAgentListener> connection;
|
||||
|
||||
internal AgentConnection(RpcClientConnection<IMessageToAgentListener> connection) {
|
||||
internal AgentConnection(RpcConnectionToClient<IMessageToAgentListener> connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public bool IsSame(RpcClientConnection<IMessageToAgentListener> connection) {
|
||||
public bool IsSame(RpcConnectionToClient<IMessageToAgentListener> connection) {
|
||||
return this.connection.IsSame(connection);
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ public sealed class AgentManager {
|
||||
return agents.ByGuid.ToImmutable();
|
||||
}
|
||||
|
||||
internal async Task<bool> RegisterAgent(AuthToken authToken, AgentInfo agentInfo, InstanceManager instanceManager, RpcClientConnection<IMessageToAgentListener> connection) {
|
||||
internal async Task<bool> RegisterAgent(AuthToken authToken, AgentInfo agentInfo, InstanceManager instanceManager, RpcConnectionToClient<IMessageToAgentListener> connection) {
|
||||
if (!this.authToken.FixedTimeEquals(authToken)) {
|
||||
await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.InvalidToken));
|
||||
return false;
|
||||
@ -88,7 +88,7 @@ public sealed class AgentManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool UnregisterAgent(Guid agentGuid, RpcClientConnection<IMessageToAgentListener> connection) {
|
||||
internal bool UnregisterAgent(Guid agentGuid, RpcConnectionToClient<IMessageToAgentListener> connection) {
|
||||
if (agents.ByGuid.TryReplaceIf(agentGuid, static oldAgent => oldAgent.AsOffline(), oldAgent => oldAgent.Connection?.IsSame(connection) == true)) {
|
||||
Logger.Information("Unregistered agent with GUID {Guid}.", agentGuid);
|
||||
return true;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Common.Messages.Agent;
|
||||
using Phantom.Common.Messages.Web;
|
||||
@ -10,7 +10,6 @@ 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.Utils.Tasks;
|
||||
|
||||
namespace Phantom.Controller.Services;
|
||||
@ -50,11 +49,11 @@ public sealed class ControllerServices {
|
||||
this.cancellationToken = shutdownCancellationToken;
|
||||
}
|
||||
|
||||
public AgentMessageListener CreateAgentMessageListener(RpcClientConnection<IMessageToAgentListener> connection) {
|
||||
public AgentMessageListener CreateAgentMessageListener(RpcConnectionToClient<IMessageToAgentListener> connection) {
|
||||
return new AgentMessageListener(connection, AgentManager, AgentJavaRuntimesManager, InstanceManager, InstanceLogManager, EventLog, cancellationToken);
|
||||
}
|
||||
|
||||
public WebMessageListener CreateWebMessageListener(RpcClientConnection<IMessageToWebListener> connection) {
|
||||
public WebMessageListener CreateWebMessageListener(RpcConnectionToClient<IMessageToWebListener> connection) {
|
||||
return new WebMessageListener(connection, UserManager, RoleManager);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
using Phantom.Common.Data.Backups;
|
||||
using Phantom.Common.Data.Instance;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
using Phantom.Common.Data.Web.EventLog;
|
||||
|
||||
namespace Phantom.Controller.Services.Events;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Data.Web.EventLog;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Controller.Database.Enums;
|
||||
using Phantom.Utils.Collections;
|
||||
using Phantom.Utils.Tasks;
|
||||
|
||||
|
@ -3,7 +3,7 @@ using System.Collections.Immutable;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Collections;
|
||||
using Phantom.Utils.Events;
|
||||
using ILogger = Serilog.ILogger;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Controller.Services.Instances;
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next.StrongName" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Common\Phantom.Common.Data\Phantom.Common.Data.csproj" />
|
||||
@ -18,8 +18,4 @@
|
||||
<ProjectReference Include="..\Phantom.Controller.Rpc\Phantom.Controller.Rpc.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Audit\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -14,7 +14,7 @@ using Phantom.Utils.Tasks;
|
||||
namespace Phantom.Controller.Services.Rpc;
|
||||
|
||||
public sealed class AgentMessageListener : IMessageToControllerListener {
|
||||
private readonly RpcClientConnection<IMessageToAgentListener> connection;
|
||||
private readonly RpcConnectionToClient<IMessageToAgentListener> connection;
|
||||
private readonly AgentManager agentManager;
|
||||
private readonly AgentJavaRuntimesManager agentJavaRuntimesManager;
|
||||
private readonly InstanceManager instanceManager;
|
||||
@ -24,7 +24,7 @@ public sealed class AgentMessageListener : IMessageToControllerListener {
|
||||
|
||||
private readonly TaskCompletionSource<Guid> agentGuidWaiter = AsyncTasks.CreateCompletionSource<Guid>();
|
||||
|
||||
internal AgentMessageListener(RpcClientConnection<IMessageToAgentListener> connection, AgentManager agentManager, AgentJavaRuntimesManager agentJavaRuntimesManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager, EventLog eventLog, CancellationToken cancellationToken) {
|
||||
internal AgentMessageListener(RpcConnectionToClient<IMessageToAgentListener> connection, AgentManager agentManager, AgentJavaRuntimesManager agentJavaRuntimesManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager, EventLog eventLog, CancellationToken cancellationToken) {
|
||||
this.connection = connection;
|
||||
this.agentManager = agentManager;
|
||||
this.agentJavaRuntimesManager = agentJavaRuntimesManager;
|
||||
|
@ -9,21 +9,26 @@ using Phantom.Utils.Rpc.Message;
|
||||
namespace Phantom.Controller.Services.Rpc;
|
||||
|
||||
public sealed class WebMessageListener : IMessageToControllerListener {
|
||||
private readonly RpcClientConnection<IMessageToWebListener> connection;
|
||||
private readonly RpcConnectionToClient<IMessageToWebListener> connection;
|
||||
private readonly UserManager userManager;
|
||||
private readonly RoleManager roleManager;
|
||||
|
||||
internal WebMessageListener(RpcClientConnection<IMessageToWebListener> connection, UserManager userManager, RoleManager roleManager) {
|
||||
internal WebMessageListener(RpcConnectionToClient<IMessageToWebListener> connection, UserManager userManager, RoleManager roleManager) {
|
||||
this.connection = connection;
|
||||
this.userManager = userManager;
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
|
||||
public async Task<NoReply> HandleRegisterWeb(RegisterWebMessage message) {
|
||||
return default;
|
||||
}
|
||||
|
||||
public Task<CreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUser message) {
|
||||
return userManager.CreateOrUpdateAdministrator(message.Username, message.Password);
|
||||
}
|
||||
|
||||
public Task<NoReply> HandleReply(ReplyMessage message) {
|
||||
connection.Receive(message);
|
||||
return Task.FromResult(NoReply.Instance);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Data.Web.Users.Permissions;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Utils.Collections;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Controller.Services.Users.Permissions;
|
||||
namespace Phantom.Controller.Services.Users;
|
||||
|
||||
public sealed class PermissionManager {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<PermissionManager>();
|
||||
@ -42,28 +41,28 @@ public sealed class PermissionManager {
|
||||
return allPermissions.Select(static permission => permission.Id).Except(existingPermissionIds).Order().ToImmutableArray();
|
||||
}
|
||||
|
||||
private IdentityPermissions FetchPermissionsForUserId(Guid userId) {
|
||||
using var ctx = dbProvider.Lazy();
|
||||
private async Task<IdentityPermissions> FetchPermissionsForUserId(Guid userId) {
|
||||
await using var ctx = dbProvider.Eager();
|
||||
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).ToImmutableHashSet());
|
||||
return new IdentityPermissions(await userPermissions.Union(rolePermissions).AsAsyncEnumerable().ToImmutableSetAsync());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
// 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,7 +1,7 @@
|
||||
using System.Collections.Immutable;
|
||||
using Phantom.Common.Data.Web.Users.Permissions;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
|
||||
namespace Phantom.Controller.Services.Users.Roles;
|
||||
namespace Phantom.Controller.Services.Users;
|
||||
|
||||
public sealed record Role(Guid Guid, string Name, ImmutableArray<Permission> Permissions) {
|
||||
private static readonly List<Role> AllRoles = new ();
|
||||
|
@ -3,8 +3,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Entities;
|
||||
using Phantom.Controller.Services.Users.Permissions;
|
||||
using Phantom.Controller.Services.Users.Roles;
|
||||
using Phantom.Utils.Collections;
|
||||
using Serilog;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Controller.Database;
|
||||
using Phantom.Controller.Database.Repositories;
|
||||
using Phantom.Controller.Services.Users.Roles;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Controller.Services.Users;
|
||||
@ -69,28 +69,28 @@ sealed class UserManager {
|
||||
user = result.Value;
|
||||
}
|
||||
else {
|
||||
return new CreateOrUpdateAdministratorUserResult.CreationFailed(result.Error);
|
||||
return new CreationFailed(result.Error);
|
||||
}
|
||||
}
|
||||
else {
|
||||
var result = repository.SetUserPassword(user, password);
|
||||
if (!result) {
|
||||
return new CreateOrUpdateAdministratorUserResult.UpdatingFailed(result.Error);
|
||||
return new UpdatingFailed(result.Error);
|
||||
}
|
||||
}
|
||||
|
||||
var role = await new RoleRepository(db).GetByGuid(Role.Administrator.Guid);
|
||||
if (role == null) {
|
||||
return new CreateOrUpdateAdministratorUserResult.AddingToRoleFailed();
|
||||
return new AddingToRoleFailed();
|
||||
}
|
||||
|
||||
await new UserRoleRepository(db).Add(user, role);
|
||||
|
||||
Logger.Information("Created administrator user \"{Username}\" (GUID {Guid}).", username, user.UserGuid);
|
||||
return new CreateOrUpdateAdministratorUserResult.Success(user.ToUserInfo());
|
||||
return new Success(user.ToUserInfo());
|
||||
} catch (Exception e) {
|
||||
Logger.Error(e, "Could not create or update administrator user \"{Username}\".", username);
|
||||
return new CreateOrUpdateAdministratorUserResult.UnknownError();
|
||||
return new UnknownError();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using NetMQ;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Common.Data.Agent;
|
||||
|
||||
namespace Phantom.Controller;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using NetMQ;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Cryptography;
|
||||
|
@ -149,8 +149,8 @@ The repository includes a [Rider](https://www.jetbrains.com/rider/) projects wit
|
||||
- `Controller` starts the Controller.
|
||||
- `Web` starts the Web server.
|
||||
- `Agent 1`, `Agent 2`, `Agent 3` start one of the Agents.
|
||||
- `Server + Agent` starts the Server and Agent 1.
|
||||
- `Server + Agent x3` starts the Server and Agent 1, 2, and 3.
|
||||
- `Controller + Agent` starts the Controller and Agent 1.
|
||||
- `Controller + Agent x3` starts the Controller and Agent 1, 2, and 3.
|
||||
|
||||
## Bootstrap
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
namespace Phantom.Utils.Rpc.Message;
|
||||
|
||||
public interface IMessageDefinitions<TOutgoingListener, TIncomingListener> {
|
||||
MessageRegistry<TOutgoingListener> Outgoing { get; }
|
||||
MessageRegistry<TIncomingListener> Incoming { get; }
|
||||
public interface IMessageDefinitions<TClientListener, TServerListener> {
|
||||
MessageRegistry<TClientListener> ToClient { get; }
|
||||
MessageRegistry<TServerListener> ToServer { get; }
|
||||
|
||||
bool IsRegistrationMessage(Type messageType);
|
||||
IMessage<TOutgoingListener, NoReply> CreateReplyMessage(uint sequenceId, byte[] serializedReply);
|
||||
IMessage<TClientListener, NoReply> CreateReplyToServerMessage(uint sequenceId, byte[] serializedReply);
|
||||
IMessage<TServerListener, NoReply> CreateReplyToClientMessage(uint sequenceId, byte[] serializedReply);
|
||||
}
|
||||
|
99
Utils/Phantom.Utils.Rpc/RpcClientRuntime.cs
Normal file
99
Utils/Phantom.Utils.Rpc/RpcClientRuntime.cs
Normal file
@ -0,0 +1,99 @@
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
using Phantom.Utils.Rpc.Message;
|
||||
using Phantom.Utils.Tasks;
|
||||
using Serilog.Events;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Utils.Rpc;
|
||||
|
||||
public abstract class RpcClientRuntime<TClientListener, TServerListener, THelloMessage> : RpcRuntime<ClientSocket> where THelloMessage : IMessage<TServerListener, NoReply> {
|
||||
private static ClientSocket CreateSocket(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, THelloMessage helloMessage) {
|
||||
var socket = new ClientSocket();
|
||||
var options = socket.Options;
|
||||
|
||||
options.CurveServerCertificate = config.ServerCertificate;
|
||||
options.CurveCertificate = new NetMQCertificate();
|
||||
options.HelloMessage = messageDefinitions.ToServer.Write(helloMessage).ToArray();
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
private readonly RpcConfiguration config;
|
||||
private readonly IMessageDefinitions<TClientListener, TServerListener> messageDefinitions;
|
||||
private readonly Func<RpcConnectionToServer<TServerListener>, TClientListener> messageListenerFactory;
|
||||
|
||||
private readonly SemaphoreSlim disconnectSemaphore;
|
||||
private readonly CancellationToken receiveCancellationToken;
|
||||
|
||||
protected RpcClientRuntime(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, Func<RpcConnectionToServer<TServerListener>, TClientListener> messageListenerFactory, THelloMessage helloMessage, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(config, CreateSocket(config, messageDefinitions, helloMessage)) {
|
||||
this.config = config;
|
||||
this.messageDefinitions = messageDefinitions;
|
||||
this.messageListenerFactory = messageListenerFactory;
|
||||
this.disconnectSemaphore = disconnectSemaphore;
|
||||
this.receiveCancellationToken = receiveCancellationToken;
|
||||
}
|
||||
|
||||
protected sealed override void Connect(ClientSocket socket) {
|
||||
var logger = config.RuntimeLogger;
|
||||
var url = config.TcpUrl;
|
||||
|
||||
logger.Information("Starting ZeroMQ client and connecting to {Url}...", url);
|
||||
socket.Connect(url);
|
||||
logger.Information("ZeroMQ client ready.");
|
||||
}
|
||||
|
||||
protected sealed override void Run(ClientSocket socket, MessageReplyTracker replyTracker, TaskManager taskManager) {
|
||||
var connection = new RpcConnectionToServer<TServerListener>(socket, messageDefinitions.ToServer, replyTracker);
|
||||
RunWithConnection(socket, connection, taskManager);
|
||||
}
|
||||
|
||||
protected virtual void RunWithConnection(ClientSocket socket, RpcConnectionToServer<TServerListener> connection, TaskManager taskManager) {
|
||||
var logger = config.RuntimeLogger;
|
||||
var handler = new Handler(connection, messageDefinitions, messageListenerFactory(connection), logger, taskManager, receiveCancellationToken);
|
||||
|
||||
try {
|
||||
while (!receiveCancellationToken.IsCancellationRequested) {
|
||||
var data = socket.Receive(receiveCancellationToken);
|
||||
|
||||
LogMessageType(logger, data);
|
||||
|
||||
if (data.Length > 0) {
|
||||
messageDefinitions.ToClient.Handle(data, handler);
|
||||
}
|
||||
}
|
||||
} catch (OperationCanceledException) {
|
||||
// Ignore.
|
||||
} finally {
|
||||
logger.Debug("ZeroMQ client stopped receiving messages.");
|
||||
disconnectSemaphore.Wait(CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogMessageType(ILogger logger, ReadOnlyMemory<byte> data) {
|
||||
if (!logger.IsEnabled(LogEventLevel.Verbose)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.Length > 0 && messageDefinitions.ToClient.TryGetType(data, out var type)) {
|
||||
logger.Verbose("Received {MessageType} ({Bytes} B).", type.Name, data.Length);
|
||||
}
|
||||
else {
|
||||
logger.Verbose("Received {Bytes} B message.", data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Handler : MessageHandler<TClientListener> {
|
||||
private readonly RpcConnectionToServer<TServerListener> connection;
|
||||
private readonly IMessageDefinitions<TClientListener, TServerListener> messageDefinitions;
|
||||
|
||||
public Handler(RpcConnectionToServer<TServerListener> connection, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, TClientListener listener, ILogger logger, TaskManager taskManager, CancellationToken cancellationToken) : base(listener, logger, taskManager, cancellationToken) {
|
||||
this.connection = connection;
|
||||
this.messageDefinitions = messageDefinitions;
|
||||
}
|
||||
|
||||
protected override Task SendReply(uint sequenceId, byte[] serializedReply) {
|
||||
return connection.Send(messageDefinitions.CreateReplyToClientMessage(sequenceId, serializedReply));
|
||||
}
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ public abstract class RpcRuntime<TSocket> where TSocket : ThreadSafeSocket {
|
||||
// ignore
|
||||
} finally {
|
||||
await taskManager.Stop();
|
||||
await Disconnect();
|
||||
await Disconnect(socket);
|
||||
|
||||
socket.Dispose();
|
||||
NetMQConfig.Cleanup();
|
||||
@ -56,7 +56,7 @@ public abstract class RpcRuntime<TSocket> where TSocket : ThreadSafeSocket {
|
||||
protected abstract void Connect(TSocket socket);
|
||||
protected abstract void Run(TSocket socket, MessageReplyTracker replyTracker, TaskManager taskManager);
|
||||
|
||||
protected virtual Task Disconnect() {
|
||||
protected virtual Task Disconnect(TSocket socket) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Phantom.Web.Identity.Authentication;
|
||||
using Phantom.Web.Services;
|
||||
|
||||
namespace Phantom.Web.Identity;
|
||||
namespace Phantom.Web.Services.Authentication;
|
||||
|
||||
sealed class PhantomIdentityMiddleware {
|
||||
public const string LoginPath = "/login";
|
||||
|
@ -3,11 +3,9 @@ using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Cryptography;
|
||||
using Phantom.Web.Services;
|
||||
using Phantom.Web.Services.Authentication;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Web.Identity.Authentication;
|
||||
namespace Phantom.Web.Services.Authentication;
|
||||
|
||||
public sealed class PhantomLoginManager {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<PhantomLoginManager>();
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Phantom.Web.Services.Authorization;
|
||||
|
||||
namespace Phantom.Web.Identity.Authorization;
|
||||
namespace Phantom.Web.Services.Authorization;
|
||||
|
||||
sealed class PermissionBasedPolicyHandler : AuthorizationHandler<PermissionBasedPolicyRequirement> {
|
||||
private readonly PermissionManager permissionManager;
|
||||
|
@ -1,5 +1,5 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Phantom.Common.Data.Web.Users.Permissions;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
|
||||
namespace Phantom.Web.Services.Authorization;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
using System.Security.Claims;
|
||||
using Phantom.Common.Data.Web.Users.Permissions;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
|
||||
namespace Phantom.Web.Services.Authorization;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Phantom.Common.Data.Web.Users.Permissions
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@inject PermissionManager PermissionManager
|
||||
|
||||
<AuthorizeView>
|
||||
|
@ -5,6 +5,10 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Common\Phantom.Common.Data\Phantom.Common.Data.csproj" />
|
||||
<ProjectReference Include="..\..\Common\Phantom.Common.Data.Web\Phantom.Common.Data.Web.csproj" />
|
||||
|
@ -2,10 +2,7 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Server;
|
||||
using Phantom.Common.Data.Web.Users.Permissions;
|
||||
using Phantom.Web.Identity;
|
||||
using Phantom.Web.Identity.Authentication;
|
||||
using Phantom.Web.Identity.Authorization;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
using Phantom.Web.Services.Authentication;
|
||||
using Phantom.Web.Services.Authorization;
|
||||
|
||||
|
22
Web/Phantom.Web.Services/Rpc/RpcClientRuntime.cs
Normal file
22
Web/Phantom.Web.Services/Rpc/RpcClientRuntime.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using NetMQ;
|
||||
using NetMQ.Sockets;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Messages.Web;
|
||||
using Phantom.Common.Messages.Web.BiDirectional;
|
||||
using Phantom.Common.Messages.Web.ToController;
|
||||
using Phantom.Utils.Rpc;
|
||||
using Phantom.Utils.Rpc.Message;
|
||||
using Phantom.Utils.Tasks;
|
||||
using Serilog.Events;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Web.Services.Rpc;
|
||||
|
||||
public sealed class RpcClientRuntime : RpcClientRuntime<IMessageToWebListener, IMessageToControllerListener> {
|
||||
public static Task Launch(RpcConfiguration config, AuthToken authToken, Func<RpcConnectionToServer<IMessageToControllerListener>, IMessageToWebListener> listenerFactory, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) {
|
||||
return new RpcClientRuntime(config, listenerFactory, new RegisterWebMessage(authToken), disconnectSemaphore, receiveCancellationToken).Launch();
|
||||
}
|
||||
|
||||
private RpcClientRuntime(RpcConfiguration config, Func<RpcConnectionToServer<IMessageToControllerListener>, IMessageToWebListener> messageListenerFactory, RegisterWebMessage registerWebMessage, SemaphoreSlim disconnectSemaphore, CancellationToken receiveCancellationToken) : base(config, WebMessageRegistries.Definitions, messageListenerFactory, registerWebMessage, disconnectSemaphore, receiveCancellationToken) {}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
@using Phantom.Web.Services
|
||||
@using Phantom.Web.Identity.Authentication
|
||||
@using Phantom.Web.Services.Authentication
|
||||
@inject INavigation Nav
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Phantom.Common.Data.Web.Users.Permissions;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Web.Services.Authorization;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
@ -2,6 +2,6 @@
|
||||
|
||||
namespace Phantom.Web;
|
||||
|
||||
public sealed record Configuration(ILogger Logger, string Host, ushort Port, string BasePath, string KeyFolderPath, CancellationToken CancellationToken) {
|
||||
public sealed record Configuration(ILogger Logger, string Host, ushort Port, string BasePath, string DataProtectionKeyFolderPath, CancellationToken CancellationToken) {
|
||||
public string HttpUrl => "http://" + Host + ":" + Port;
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ static class Launcher {
|
||||
builder.Services.AddSingleton<IHostLifetime>(new NullLifetime());
|
||||
builder.Services.AddScoped<INavigation>(Navigation.Create(config.BasePath));
|
||||
|
||||
builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(config.KeyFolderPath));
|
||||
builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(config.DataProtectionKeyFolderPath));
|
||||
|
||||
builder.Services.AddRazorPages(static options => options.RootDirectory = "/Layout");
|
||||
builder.Services.AddServerSideBlazor();
|
||||
|
@ -1,5 +1,5 @@
|
||||
@using Phantom.Web.Services.Authorization
|
||||
@using Phantom.Common.Data.Web.Users.Permissions
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@inject ServiceConfiguration Configuration
|
||||
@inject PermissionManager PermissionManager
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
@page "/audit"
|
||||
@attribute [Authorize(Permission.ViewAuditPolicy)]
|
||||
@using Phantom.Common.Data.Web.AuditLog
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using System.Collections.Immutable
|
||||
@using Phantom.Web.Services.Instances
|
||||
@implements IDisposable
|
||||
@inject AuditLog AuditLog
|
||||
@inject InstanceManager InstanceManager
|
||||
|
@ -1,6 +1,10 @@
|
||||
@page "/events"
|
||||
@attribute [Authorize(Permission.ViewEventsPolicy)]
|
||||
@using Phantom.Common.Data.Web.EventLog
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using System.Collections.Immutable
|
||||
@using System.Diagnostics
|
||||
@using Phantom.Web.Services.Instances
|
||||
@implements IDisposable
|
||||
@inject AgentManager AgentManager
|
||||
@inject EventLog EventLog
|
||||
|
@ -1,4 +1,5 @@
|
||||
@page "/instances/create"
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@attribute [Authorize(Permission.CreateInstancesPolicy)]
|
||||
|
||||
<h1>New Instance</h1>
|
||||
|
@ -1,6 +1,9 @@
|
||||
@page "/instances/{InstanceGuid:guid}"
|
||||
@attribute [Authorize(Permission.ViewInstancesPolicy)]
|
||||
@inherits PhantomComponent
|
||||
@using Phantom.Web.Services.Instances
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Common.Data.Replies
|
||||
@implements IDisposable
|
||||
@inject InstanceManager InstanceManager
|
||||
@inject AuditLog AuditLog
|
||||
|
@ -1,5 +1,8 @@
|
||||
@page "/instances/{InstanceGuid:guid}/edit"
|
||||
@attribute [Authorize(Permission.CreateInstancesPolicy)]
|
||||
@using Phantom.Common.Data.Instance
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Web.Services.Instances
|
||||
@inherits PhantomComponent
|
||||
@inject InstanceManager InstanceManager
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
@page "/login"
|
||||
@using Phantom.Web.Services
|
||||
@using Phantom.Web.Identity.Authentication
|
||||
@using Phantom.Web.Services.Authentication
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@attribute [AllowAnonymous]
|
||||
@inject INavigation Navigation
|
||||
|
@ -1,6 +1,6 @@
|
||||
@page "/setup"
|
||||
@using Phantom.Utils.Tasks
|
||||
@using Phantom.Web.Identity.Authentication
|
||||
@using Phantom.Web.Services.Authentication
|
||||
@using Phantom.Web.Services
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using Phantom.Utils.Cryptography
|
||||
|
@ -1,5 +1,7 @@
|
||||
@page "/users"
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using System.Collections.Immutable
|
||||
@using Phantom.Web.Services.Authorization
|
||||
@attribute [Authorize(Permission.ViewUsersPolicy)]
|
||||
@inject UserManager UserManager
|
||||
@inject UserRoleManager UserRoleManager
|
||||
@ -28,10 +30,10 @@
|
||||
<tbody>
|
||||
@{ var myUserId = UserManager.GetAuthenticatedUserId(context.User); }
|
||||
@foreach (var user in allUsers) {
|
||||
var isMe = myUserId == user.UserGuid;
|
||||
var isMe = myUserId == user.Guid;
|
||||
<tr>
|
||||
<td>
|
||||
<code class="text-uppercase">@user.UserGuid</code>
|
||||
<code class="text-uppercase">@user.Guid</code>
|
||||
</td>
|
||||
@if (isMe) {
|
||||
<td class="fw-semibold">@user.Name</td>
|
||||
@ -39,7 +41,7 @@
|
||||
else {
|
||||
<td>@user.Name</td>
|
||||
}
|
||||
<td>@(userGuidToRoleDescription.TryGetValue(user.UserGuid, out var roles) ? roles : "?")</td>
|
||||
<td>@(userGuidToRoleDescription.TryGetValue(user.Guid, out var roles) ? roles : "?")</td>
|
||||
@if (canEdit) {
|
||||
<td>
|
||||
@if (!isMe) {
|
||||
@ -63,7 +65,7 @@
|
||||
|
||||
@code {
|
||||
|
||||
private ImmutableArray<UserEntity> allUsers = ImmutableArray<UserEntity>.Empty;
|
||||
private ImmutableArray<UserInfo> allUsers = ImmutableArray<UserInfo>.Empty;
|
||||
private readonly Dictionary<Guid, string> userGuidToRoleDescription = new();
|
||||
|
||||
private UserRolesDialog userRolesDialog = null!;
|
||||
@ -82,27 +84,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshUserRoles(UserEntity user) {
|
||||
private async Task RefreshUserRoles(UserInfo user) {
|
||||
var roles = await UserRoleManager.GetUserRoles(user);
|
||||
userGuidToRoleDescription[user.UserGuid] = StringifyRoles(roles);
|
||||
userGuidToRoleDescription[user.Guid] = StringifyRoles(roles);
|
||||
}
|
||||
|
||||
private static string StringifyRoles(ImmutableArray<RoleEntity> roles) {
|
||||
private static string StringifyRoles(ImmutableArray<RoleInfo> roles) {
|
||||
return roles.IsEmpty ? "-" : string.Join(", ", roles.Select(static role => role.Name));
|
||||
}
|
||||
|
||||
private Task OnUserAdded(UserEntity user) {
|
||||
private Task OnUserAdded(UserInfo user) {
|
||||
allUsers = allUsers.Add(user);
|
||||
return RefreshUserRoles(user);
|
||||
}
|
||||
|
||||
private Task OnUserRolesChanged(UserEntity user) {
|
||||
private Task OnUserRolesChanged(UserInfo user) {
|
||||
return RefreshUserRoles(user);
|
||||
}
|
||||
|
||||
private void OnUserDeleted(UserEntity user) {
|
||||
private void OnUserDeleted(UserInfo user) {
|
||||
allUsers = allUsers.Remove(user);
|
||||
userGuidToRoleDescription.Remove(user.UserGuid);
|
||||
userGuidToRoleDescription.Remove(user.Guid);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,13 +2,16 @@
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Cryptography;
|
||||
using Phantom.Utils.IO;
|
||||
using Phantom.Utils.Rpc;
|
||||
using Phantom.Utils.Runtime;
|
||||
using Phantom.Utils.Tasks;
|
||||
using Phantom.Web;
|
||||
using Phantom.Web.Services.Rpc;
|
||||
|
||||
var cancellationTokenSource = new CancellationTokenSource();
|
||||
var shutdownCancellationTokenSource = new CancellationTokenSource();
|
||||
var shutdownCancellationToken = shutdownCancellationTokenSource.Token;
|
||||
|
||||
PosixSignals.RegisterCancellation(cancellationTokenSource, static () => {
|
||||
PosixSignals.RegisterCancellation(shutdownCancellationTokenSource, static () => {
|
||||
PhantomLogger.Root.InformationHeading("Stopping Phantom Panel web...");
|
||||
});
|
||||
|
||||
@ -31,26 +34,41 @@ try {
|
||||
|
||||
var (controllerHost, controllerPort, webKeyToken, webKeyFilePath, webServerHost, webServerPort, webBasePath) = Variables.LoadOrStop();
|
||||
|
||||
string webKeysPath = Path.GetFullPath("./keys");
|
||||
CreateFolderOrStop(webKeysPath, Chmod.URWX);
|
||||
var webKey = await WebKey.Load(webKeyToken, webKeyFilePath);
|
||||
if (webKey == null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
string dataProtectionKeysPath = Path.GetFullPath("./keys");
|
||||
CreateFolderOrStop(dataProtectionKeysPath, Chmod.URWX);
|
||||
|
||||
var (controllerCertificate, webToken) = webKey.Value;
|
||||
|
||||
PhantomLogger.Root.InformationHeading("Launching Phantom Panel web...");
|
||||
|
||||
var taskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Web"));
|
||||
|
||||
var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1);
|
||||
var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), controllerHost, controllerPort, controllerCertificate);
|
||||
var rpcTask = RpcClientRuntime.Launch(rpcConfiguration, webToken, MessageListenerFactory, rpcDisconnectSemaphore, shutdownCancellationToken);
|
||||
try {
|
||||
var configuration = new Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, webKeysPath, cancellationTokenSource.Token);
|
||||
var configuration = new Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, dataProtectionKeysPath, shutdownCancellationToken);
|
||||
|
||||
var administratorToken = TokenGenerator.Create(60);
|
||||
PhantomLogger.Root.Information("Your administrator token is: {AdministratorToken}", administratorToken);
|
||||
PhantomLogger.Root.Information("For administrator setup, visit: {HttpUrl}{SetupPath}", configuration.HttpUrl, configuration.BasePath + "setup");
|
||||
|
||||
var serviceConfiguration = new ServiceConfiguration(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken), cancellationTokenSource.Token);
|
||||
var serviceConfiguration = new ServiceConfiguration(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken), shutdownCancellationToken);
|
||||
var webApplication = Launcher.CreateApplication(configuration, serviceConfiguration, taskManager);
|
||||
|
||||
await Launcher.Launch(configuration, webApplication);
|
||||
} finally {
|
||||
cancellationTokenSource.Cancel();
|
||||
shutdownCancellationTokenSource.Cancel();
|
||||
await taskManager.Stop();
|
||||
|
||||
rpcDisconnectSemaphore.Release();
|
||||
await rpcTask;
|
||||
rpcDisconnectSemaphore.Dispose();
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -62,7 +80,8 @@ try {
|
||||
PhantomLogger.Root.Fatal(e, "Caught exception in entry point.");
|
||||
return 1;
|
||||
} finally {
|
||||
cancellationTokenSource.Dispose();
|
||||
shutdownCancellationTokenSource.Dispose();
|
||||
|
||||
PhantomLogger.Root.Information("Bye!");
|
||||
PhantomLogger.Dispose();
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using System.Diagnostics.CodeAnalysis
|
||||
@using Phantom.Common.Data
|
||||
@using Phantom.Web.Services
|
||||
@using Phantom.Web.Services.Instances
|
||||
@inject INavigation Nav
|
||||
@inject MinecraftVersions MinecraftVersions
|
||||
@inject AgentManager AgentManager
|
||||
|
@ -1,5 +1,5 @@
|
||||
@using Phantom.Web.Services.Instances
|
||||
@using Phantom.Common.Data.Web.Users.Permissions
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Common.Data.Replies
|
||||
@inherits PhantomComponent
|
||||
@inject InstanceManager InstanceManager
|
||||
|
@ -1,7 +1,7 @@
|
||||
@inherits PhantomComponent
|
||||
@using Phantom.Utils.Collections
|
||||
@using System.Diagnostics
|
||||
@using Phantom.Common.Data.Web.Users.Permissions
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@implements IDisposable
|
||||
@inject IJSRuntime Js;
|
||||
@inject InstanceLogManager InstanceLogManager
|
||||
|
@ -1,6 +1,6 @@
|
||||
@using Phantom.Controller.Services.Instances
|
||||
@using Phantom.Controller.Services.Audit
|
||||
@using Phantom.Web.Services.Instances
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Common.Data.Minecraft
|
||||
@using Phantom.Common.Data.Replies
|
||||
@inherits PhantomComponent
|
||||
|
@ -3,8 +3,6 @@
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@inherits PhantomComponent
|
||||
@inject IJSRuntime Js;
|
||||
@inject UserManager UserManager
|
||||
@inject AuditLog AuditLog
|
||||
|
||||
<Form Model="form" OnSubmit="AddUser">
|
||||
<Modal Id="@ModalId" TitleText="Add User">
|
||||
@ -37,7 +35,7 @@
|
||||
public string ModalId { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<UserEntity> UserAdded { get; set; }
|
||||
public EventCallback<UserInfo> UserAdded { get; set; }
|
||||
|
||||
private readonly AddUserFormModel form = new();
|
||||
|
||||
@ -58,14 +56,14 @@
|
||||
}
|
||||
|
||||
switch (await UserManager.CreateUser(form.Username, form.Password)) {
|
||||
case Result<UserEntity, AddUserError>.Ok ok:
|
||||
case Result<UserInfo, AddUserError>.Ok ok:
|
||||
await AuditLog.AddUserCreatedEvent(ok.Value);
|
||||
await UserAdded.InvokeAsync(ok.Value);
|
||||
await Js.InvokeVoidAsync("closeModal", ModalId);
|
||||
form.SubmitModel.StopSubmitting();
|
||||
break;
|
||||
|
||||
case Result<UserEntity, AddUserError>.Fail fail:
|
||||
case Result<UserInfo, AddUserError>.Fail fail:
|
||||
form.SubmitModel.StopSubmitting(fail.Error.ToSentences("\n"));
|
||||
break;
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
using Phantom.Common.Data.Web.Users.Permissions;
|
||||
using Phantom.Web.Base;
|
||||
using Phantom.Web.Components.Forms;
|
||||
|
||||
|
61
Web/Phantom.Web/WebKey.cs
Normal file
61
Web/Phantom.Web/WebKey.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using NetMQ;
|
||||
using Phantom.Common.Data;
|
||||
using Phantom.Common.Data.Agent;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Utils.Cryptography;
|
||||
using Phantom.Utils.IO;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Web;
|
||||
|
||||
static class WebKey {
|
||||
private static ILogger Logger { get; } = PhantomLogger.Create(nameof(WebKey));
|
||||
|
||||
public static Task<(NetMQCertificate, AuthToken)?> Load(string? webKeyToken, string? webKeyFilePath) {
|
||||
if (webKeyFilePath != null) {
|
||||
return LoadFromFile(webKeyFilePath);
|
||||
}
|
||||
else if (webKeyToken != null) {
|
||||
return Task.FromResult(LoadFromToken(webKeyToken));
|
||||
}
|
||||
else {
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<(NetMQCertificate, AuthToken)?> LoadFromFile(string webKeyFilePath) {
|
||||
if (!File.Exists(webKeyFilePath)) {
|
||||
Logger.Fatal("Missing web key file: {WebKeyFilePath}", webKeyFilePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Files.RequireMaximumFileSize(webKeyFilePath, 64);
|
||||
return LoadFromBytes(await File.ReadAllBytesAsync(webKeyFilePath));
|
||||
} catch (IOException e) {
|
||||
Logger.Fatal("Error loading web key from file: {WebKeyFilePath}", webKeyFilePath);
|
||||
Logger.Fatal(e.Message);
|
||||
return null;
|
||||
} catch (Exception) {
|
||||
Logger.Fatal("File does not contain a valid web key: {WebKeyFilePath}", webKeyFilePath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static (NetMQCertificate, AuthToken)? LoadFromToken(string webKey) {
|
||||
try {
|
||||
return LoadFromBytes(TokenGenerator.DecodeBytes(webKey));
|
||||
} catch (Exception) {
|
||||
Logger.Fatal("Invalid web key: {WebKey}", webKey);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static (NetMQCertificate, AuthToken)? LoadFromBytes(byte[] webKey) {
|
||||
var (publicKey, webToken) = ConnectionCommonKey.FromBytes(webKey);
|
||||
var controllerCertificate = NetMQCertificate.FromPublicKey(publicKey);
|
||||
|
||||
Logger.Information("Loaded web key.");
|
||||
return (controllerCertificate, webToken);
|
||||
}
|
||||
}
|
@ -12,8 +12,6 @@
|
||||
@using Phantom.Web.Components.Forms
|
||||
@using Phantom.Web.Components.Graphics
|
||||
@using Phantom.Web.Components.Tables
|
||||
@using Phantom.Web.Identity
|
||||
@using Phantom.Web.Identity.Authorization
|
||||
@using Phantom.Web.Layout
|
||||
@using Phantom.Web.Shared
|
||||
@using Phantom.Web.Utils
|
||||
|
Loading…
Reference in New Issue
Block a user