1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2024-10-18 15:42:50 +02:00

Compare commits

..

7 Commits

Author SHA1 Message Date
a89a8738f9
WIP 2023-10-22 07:41:46 +02:00
0d7f10d7ee
WIP 2023-10-22 07:41:46 +02:00
3afa724672
WIP 2023-10-22 07:41:46 +02:00
de040036e7
WIP 2023-10-22 07:41:46 +02:00
53edd80486
WIP 2023-10-22 07:41:46 +02:00
7b728a30ff
Extract classes for client to server RPC connection 2023-10-22 07:41:46 +02:00
e20d2232ed
Fully separate Controller and Web into their own services - Controller compiling and setup 2023-10-21 17:55:54 +02:00
179 changed files with 491 additions and 676 deletions

View File

@ -1,7 +0,0 @@
<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>

View File

@ -1,9 +1,9 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Controller + Agent x3" type="CompoundRunConfigurationType"> <configuration default="false" name="Server + Agent x3" type="CompoundRunConfigurationType">
<toRun name="Agent 1" type="DotNetProject" /> <toRun name="Agent 1" type="DotNetProject" />
<toRun name="Agent 2" type="DotNetProject" /> <toRun name="Agent 2" type="DotNetProject" />
<toRun name="Agent 3" type="DotNetProject" /> <toRun name="Agent 3" type="DotNetProject" />
<toRun name="Controller" type="DotNetProject" /> <toRun name="Server" type="DotNetProject" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>

View File

@ -0,0 +1,7 @@
<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>

View File

@ -1,43 +0,0 @@
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.");
}
}
}

View File

@ -0,0 +1,107 @@
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));
}
}
}

View File

@ -41,7 +41,7 @@ public sealed class MessageListener : IMessageToAgentListener {
} }
} }
await connection.Send(new AdvertiseJavaRuntimesMessage(agent.JavaRuntimeRepository.All)); await ServerMessaging.Send(new AdvertiseJavaRuntimesMessage(agent.JavaRuntimeRepository.All));
await agent.InstanceSessionManager.RefreshAgentStatus(); await agent.InstanceSessionManager.RefreshAgentStatus();
return NoReply.Instance; return NoReply.Instance;

View File

@ -1,5 +1,4 @@
using NetMQ; using NetMQ;
using Phantom.Common.Data;
using Phantom.Common.Data.Agent; using Phantom.Common.Data.Agent;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Utils.Cryptography; using Phantom.Utils.Cryptography;

View File

@ -58,7 +58,7 @@ try {
var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1); var rpcDisconnectSemaphore = new SemaphoreSlim(0, 1);
var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), controllerHost, controllerPort, controllerCertificate); var rpcConfiguration = new RpcConfiguration(PhantomLogger.Create("Rpc"), PhantomLogger.Create<TaskManager>("Rpc"), controllerHost, controllerPort, controllerCertificate);
var rpcTask = RpcClientRuntime.Launch(rpcConfiguration, agentToken, agentInfo, MessageListenerFactory, rpcDisconnectSemaphore, shutdownCancellationToken); var rpcTask = RpcLauncher.Launch(rpcConfiguration, agentToken, agentInfo, MessageListenerFactory, rpcDisconnectSemaphore, shutdownCancellationToken);
try { try {
await rpcTask.WaitAsync(shutdownCancellationToken); await rpcTask.WaitAsync(shutdownCancellationToken);
} finally { } finally {

View File

@ -1,4 +1,4 @@
namespace Phantom.Common.Data.Web.AuditLog; namespace Phantom.Controller.Database.Enums;
public enum AuditLogEventType { public enum AuditLogEventType {
AdministratorUserCreated, AdministratorUserCreated,
@ -16,7 +16,7 @@ public enum AuditLogEventType {
InstanceCommandExecuted InstanceCommandExecuted
} }
public static class AuditLogEventTypeExtensions { static class AuditLogEventTypeExtensions {
private static readonly Dictionary<AuditLogEventType, AuditLogSubjectType> SubjectTypes = new () { private static readonly Dictionary<AuditLogEventType, AuditLogSubjectType> SubjectTypes = new () {
{ AuditLogEventType.AdministratorUserCreated, AuditLogSubjectType.User }, { AuditLogEventType.AdministratorUserCreated, AuditLogSubjectType.User },
{ AuditLogEventType.AdministratorUserModified, AuditLogSubjectType.User }, { AuditLogEventType.AdministratorUserModified, AuditLogSubjectType.User },
@ -41,7 +41,7 @@ public static class AuditLogEventTypeExtensions {
} }
} }
public static AuditLogSubjectType GetSubjectType(this AuditLogEventType type) { internal static AuditLogSubjectType GetSubjectType(this AuditLogEventType type) {
return SubjectTypes[type]; return SubjectTypes[type];
} }
} }

View File

@ -1,5 +1,6 @@
using System.Text.Json; using System.Text.Json;
using Phantom.Controller.Database.Enums;
namespace Phantom.Common.Data.Web.AuditLog; namespace Phantom.Controller.Services.Audit;
public sealed record AuditLogItem(DateTime UtcTime, Guid? UserGuid, string? UserName, AuditLogEventType EventType, AuditLogSubjectType SubjectType, string? SubjectId, JsonDocument? Data); public sealed record AuditLogItem(DateTime UtcTime, Guid? UserGuid, string? UserName, AuditLogEventType EventType, AuditLogSubjectType SubjectType, string? SubjectId, JsonDocument? Data);

View File

@ -1,4 +1,4 @@
namespace Phantom.Common.Data.Web.AuditLog; namespace Phantom.Controller.Database.Enums;
public enum AuditLogSubjectType { public enum AuditLogSubjectType {
User, User,

View File

@ -1,5 +0,0 @@
namespace Phantom.Common.Data.Web.EventLog;
public enum EventLogSubjectType {
Instance
}

View File

@ -1,4 +1,4 @@
namespace Phantom.Common.Data.Web.Users; namespace Phantom.Controller.Services.Users.Roles;
public enum AddRoleError : byte { public enum AddRoleError : byte {
NameIsEmpty, NameIsEmpty,

View File

@ -1,28 +1,25 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using MemoryPack; using MemoryPack;
using Phantom.Common.Data.Web.Users.AddUserErrors;
namespace Phantom.Common.Data.Web.Users { namespace Phantom.Common.Data.Web.Users;
[MemoryPackable] [MemoryPackable]
[MemoryPackUnion(0, typeof(NameIsInvalid))] [MemoryPackUnion(0, typeof(NameIsInvalid))]
[MemoryPackUnion(1, typeof(PasswordIsInvalid))] [MemoryPackUnion(1, typeof(PasswordIsInvalid))]
[MemoryPackUnion(2, typeof(NameAlreadyExists))] [MemoryPackUnion(2, typeof(NameAlreadyExists))]
[MemoryPackUnion(3, typeof(UnknownError))] [MemoryPackUnion(3, typeof(UnknownError))]
public abstract partial record AddUserError { public abstract record AddUserError {
internal AddUserError() {} private AddUserError() {}
}
} [MemoryPackable(GenerateType.VersionTolerant)]
public sealed record NameIsInvalid([property: MemoryPackOrder(0)] UsernameRequirementViolation Violation) : AddUserError;
namespace Phantom.Common.Data.Web.Users.AddUserErrors {
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record NameIsInvalid([property: MemoryPackOrder(0)] UsernameRequirementViolation Violation) : AddUserError; public sealed record PasswordIsInvalid([property: MemoryPackOrder(0)] ImmutableArray<PasswordRequirementViolation> Violations) : AddUserError;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record PasswordIsInvalid([property: MemoryPackOrder(0)] ImmutableArray<PasswordRequirementViolation> Violations) : AddUserError; public sealed record NameAlreadyExists : AddUserError;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record NameAlreadyExists : AddUserError; public sealed record UnknownError : AddUserError;
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record UnknownError : AddUserError;
} }

View File

@ -1,7 +1,7 @@
using MemoryPack; using MemoryPack;
using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults;
namespace Phantom.Common.Data.Web.Users { namespace Phantom.Common.Data.Web.Users;
[MemoryPackable] [MemoryPackable]
[MemoryPackUnion(0, typeof(Success))] [MemoryPackUnion(0, typeof(Success))]
[MemoryPackUnion(1, typeof(CreationFailed))] [MemoryPackUnion(1, typeof(CreationFailed))]
@ -9,11 +9,8 @@ namespace Phantom.Common.Data.Web.Users {
[MemoryPackUnion(3, typeof(AddingToRoleFailed))] [MemoryPackUnion(3, typeof(AddingToRoleFailed))]
[MemoryPackUnion(4, typeof(UnknownError))] [MemoryPackUnion(4, typeof(UnknownError))]
public abstract partial record CreateOrUpdateAdministratorUserResult { public abstract partial record CreateOrUpdateAdministratorUserResult {
internal CreateOrUpdateAdministratorUserResult() {} private CreateOrUpdateAdministratorUserResult() {}
}
}
namespace Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults {
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record Success([property: MemoryPackOrder(0)] UserInfo User) : CreateOrUpdateAdministratorUserResult; public sealed partial record Success([property: MemoryPackOrder(0)] UserInfo User) : CreateOrUpdateAdministratorUserResult;

View File

@ -1,6 +1,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
namespace Phantom.Common.Data.Web.Users; namespace Phantom.Common.Data.Web.Users.Permissions;
public sealed class IdentityPermissions { public sealed class IdentityPermissions {
public static IdentityPermissions None { get; } = new (ImmutableHashSet<string>.Empty); public static IdentityPermissions None { get; } = new (ImmutableHashSet<string>.Empty);

View File

@ -1,27 +1,24 @@
using MemoryPack; using MemoryPack;
using Phantom.Common.Data.Web.Users.PasswordRequirementViolations;
namespace Phantom.Common.Data.Web.Users { namespace Phantom.Common.Data.Web.Users;
[MemoryPackable] [MemoryPackable]
[MemoryPackUnion(0, typeof(TooShort))] [MemoryPackUnion(0, typeof(TooShort))]
[MemoryPackUnion(1, typeof(MustContainLowercaseLetter))] [MemoryPackUnion(1, typeof(LowercaseLetterRequired))]
[MemoryPackUnion(2, typeof(MustContainUppercaseLetter))] [MemoryPackUnion(2, typeof(UppercaseLetterRequired))]
[MemoryPackUnion(3, typeof(MustContainDigit))] [MemoryPackUnion(3, typeof(DigitRequired))]
public abstract partial record PasswordRequirementViolation { public abstract record PasswordRequirementViolation {
internal PasswordRequirementViolation() {} private PasswordRequirementViolation() {}
}
} [MemoryPackable(GenerateType.VersionTolerant)]
public sealed record TooShort([property: MemoryPackOrder(0)] int MinimumLength) : PasswordRequirementViolation;
namespace Phantom.Common.Data.Web.Users.PasswordRequirementViolations {
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record TooShort([property: MemoryPackOrder(0)] int MinimumLength) : PasswordRequirementViolation; public sealed record LowercaseLetterRequired : PasswordRequirementViolation;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record MustContainLowercaseLetter : PasswordRequirementViolation; public sealed record UppercaseLetterRequired : PasswordRequirementViolation;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record MustContainUppercaseLetter : PasswordRequirementViolation; public sealed record DigitRequired : PasswordRequirementViolation;
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record MustContainDigit : PasswordRequirementViolation;
} }

View File

@ -1,4 +1,4 @@
namespace Phantom.Common.Data.Web.Users; namespace Phantom.Common.Data.Web.Users.Permissions;
public sealed record Permission(string Id, Permission? Parent) { public sealed record Permission(string Id, Permission? Parent) {
private static readonly List<Permission> AllPermissions = new (); private static readonly List<Permission> AllPermissions = new ();

View File

@ -1,24 +1,21 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using MemoryPack; using MemoryPack;
using Phantom.Common.Data.Web.Users.SetUserPasswordErrors;
namespace Phantom.Common.Data.Web.Users { namespace Phantom.Common.Data.Web.Users;
[MemoryPackable] [MemoryPackable]
[MemoryPackUnion(0, typeof(UserNotFound))] [MemoryPackUnion(0, typeof(UserNotFound))]
[MemoryPackUnion(1, typeof(PasswordIsInvalid))] [MemoryPackUnion(1, typeof(PasswordIsInvalid))]
[MemoryPackUnion(2, typeof(UnknownError))] [MemoryPackUnion(2, typeof(UnknownError))]
public abstract partial record SetUserPasswordError { public abstract record SetUserPasswordError {
internal SetUserPasswordError() {} private SetUserPasswordError() {}
}
}
namespace Phantom.Common.Data.Web.Users.SetUserPasswordErrors {
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record UserNotFound : SetUserPasswordError;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record PasswordIsInvalid([property: MemoryPackOrder(0)] ImmutableArray<PasswordRequirementViolation> Violations) : SetUserPasswordError; public sealed record UserNotFound : SetUserPasswordError;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record UnknownError : SetUserPasswordError; public sealed record PasswordIsInvalid(ImmutableArray<PasswordRequirementViolation> Violations) : SetUserPasswordError;
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed record UnknownError : SetUserPasswordError;
} }

View File

@ -1,19 +1,16 @@
using MemoryPack; using MemoryPack;
using Phantom.Common.Data.Web.Users.UsernameRequirementViolations;
namespace Phantom.Common.Data.Web.Users { namespace Phantom.Common.Data.Web.Users;
[MemoryPackable] [MemoryPackable]
[MemoryPackUnion(0, typeof(IsEmpty))] [MemoryPackUnion(0, typeof(IsEmpty))]
[MemoryPackUnion(1, typeof(TooLong))] [MemoryPackUnion(1, typeof(TooLong))]
public abstract partial record UsernameRequirementViolation { public abstract record UsernameRequirementViolation {
internal UsernameRequirementViolation() {} private UsernameRequirementViolation() {}
}
}
namespace Phantom.Common.Data.Web.Users.UsernameRequirementViolations {
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record IsEmpty : UsernameRequirementViolation;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
public sealed partial record TooLong([property: MemoryPackOrder(0)] int MaxLength) : UsernameRequirementViolation; public sealed record IsEmpty : UsernameRequirementViolation;
[MemoryPackable(GenerateType.VersionTolerant)]
public sealed record TooLong(int MaxLength) : UsernameRequirementViolation;
} }

View File

@ -2,7 +2,7 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using MemoryPack; using MemoryPack;
namespace Phantom.Common.Data; namespace Phantom.Common.Data.Agent;
[MemoryPackable(GenerateType.VersionTolerant)] [MemoryPackable(GenerateType.VersionTolerant)]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]

View File

@ -1,4 +1,4 @@
namespace Phantom.Common.Data; namespace Phantom.Common.Data.Agent;
public readonly record struct ConnectionCommonKey(byte[] CertificatePublicKey, AuthToken AuthToken) { public readonly record struct ConnectionCommonKey(byte[] CertificatePublicKey, AuthToken AuthToken) {
private const byte TokenLength = AuthToken.Length; private const byte TokenLength = AuthToken.Length;

View File

@ -34,18 +34,14 @@ public static class AgentMessageRegistries {
} }
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToAgentListener, IMessageToControllerListener> { private sealed class MessageDefinitions : IMessageDefinitions<IMessageToAgentListener, IMessageToControllerListener> {
public MessageRegistry<IMessageToAgentListener> ToClient => ToAgent; public MessageRegistry<IMessageToAgentListener> Outgoing => ToAgent;
public MessageRegistry<IMessageToControllerListener> ToServer => ToController; public MessageRegistry<IMessageToControllerListener> Incoming => ToController;
public bool IsRegistrationMessage(Type messageType) { public bool IsRegistrationMessage(Type messageType) {
return messageType == typeof(RegisterAgentMessage); return messageType == typeof(RegisterAgentMessage);
} }
public IMessage<IMessageToAgentListener, NoReply> CreateReplyToServerMessage( uint sequenceId, byte[] serializedReply) { public IMessage<IMessageToAgentListener, NoReply> CreateReplyMessage( uint sequenceId, byte[] serializedReply) {
return new ReplyMessage(sequenceId, serializedReply);
}
public IMessage<IMessageToControllerListener, NoReply> CreateReplyToClientMessage(uint sequenceId, byte[] serializedReply) {
return new ReplyMessage(sequenceId, serializedReply); return new ReplyMessage(sequenceId, serializedReply);
} }
} }

View File

@ -1,5 +1,4 @@
using MemoryPack; using MemoryPack;
using Phantom.Common.Data;
using Phantom.Common.Data.Agent; using Phantom.Common.Data.Agent;
using Phantom.Utils.Rpc.Message; using Phantom.Utils.Rpc.Message;

View File

@ -6,7 +6,6 @@ using Phantom.Utils.Rpc.Message;
namespace Phantom.Common.Messages.Web; namespace Phantom.Common.Messages.Web;
public interface IMessageToControllerListener { public interface IMessageToControllerListener {
Task<NoReply> HandleRegisterWeb(RegisterWebMessage message);
Task<CreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUser message); Task<CreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUser message);
Task<NoReply> HandleReply(ReplyMessage message); Task<NoReply> HandleReply(ReplyMessage message);
} }

View File

@ -1,14 +0,0 @@
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);
}
}

View File

@ -20,18 +20,14 @@ public static class WebMessageRegistries {
} }
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener> { private sealed class MessageDefinitions : IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener> {
public MessageRegistry<IMessageToWebListener> ToClient => ToWeb; public MessageRegistry<IMessageToWebListener> Outgoing => ToWeb;
public MessageRegistry<IMessageToControllerListener> ToServer => ToController; public MessageRegistry<IMessageToControllerListener> Incoming => ToController;
public bool IsRegistrationMessage(Type messageType) { public bool IsRegistrationMessage(Type messageType) {
return false; return false;
} }
public IMessage<IMessageToWebListener, NoReply> CreateReplyToServerMessage( uint sequenceId, byte[] serializedReply) { public IMessage<IMessageToWebListener, NoReply> CreateReplyMessage( uint sequenceId, byte[] serializedReply) {
return new ReplyMessage(sequenceId, serializedReply);
}
public IMessage<IMessageToControllerListener, NoReply> CreateReplyToClientMessage(uint sequenceId, byte[] serializedReply) {
return new ReplyMessage(sequenceId, serializedReply); return new ReplyMessage(sequenceId, serializedReply);
} }
} }

View File

@ -3,10 +3,9 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Phantom.Common.Data; using Phantom.Common.Data;
using Phantom.Common.Data.Minecraft; 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.Converters;
using Phantom.Controller.Database.Entities; using Phantom.Controller.Database.Entities;
using Phantom.Controller.Database.Enums;
using Phantom.Controller.Database.Factories; using Phantom.Controller.Database.Factories;
namespace Phantom.Controller.Database; namespace Phantom.Controller.Database;

View File

@ -2,7 +2,7 @@
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using Phantom.Common.Data.Web.AuditLog; using Phantom.Controller.Database.Enums;
namespace Phantom.Controller.Database.Entities; namespace Phantom.Controller.Database.Entities;

View File

@ -2,7 +2,7 @@
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using Phantom.Common.Data.Web.EventLog; using Phantom.Controller.Database.Enums;
namespace Phantom.Controller.Database.Entities; namespace Phantom.Controller.Database.Entities;

View File

@ -1,4 +1,4 @@
namespace Phantom.Common.Data.Web.EventLog; namespace Phantom.Controller.Database.Enums;
public enum EventLogEventType { public enum EventLogEventType {
InstanceLaunchSucceded, InstanceLaunchSucceded,
@ -10,7 +10,7 @@ public enum EventLogEventType {
InstanceBackupFailed, InstanceBackupFailed,
} }
public static class EventLogEventTypeExtensions { static class EventLogEventTypeExtensions {
private static readonly Dictionary<EventLogEventType, EventLogSubjectType> SubjectTypes = new () { private static readonly Dictionary<EventLogEventType, EventLogSubjectType> SubjectTypes = new () {
{ EventLogEventType.InstanceLaunchSucceded, EventLogSubjectType.Instance }, { EventLogEventType.InstanceLaunchSucceded, EventLogSubjectType.Instance },
{ EventLogEventType.InstanceLaunchFailed, EventLogSubjectType.Instance }, { EventLogEventType.InstanceLaunchFailed, EventLogSubjectType.Instance },

View File

@ -0,0 +1,5 @@
namespace Phantom.Controller.Database.Enums;
public enum EventLogSubjectType {
Instance
}

View File

@ -1,5 +1,5 @@
using Phantom.Common.Data.Web.AuditLog; using Phantom.Controller.Database.Entities;
using Phantom.Controller.Database.Entities; using Phantom.Controller.Database.Enums;
namespace Phantom.Controller.Database.Repositories; namespace Phantom.Controller.Database.Repositories;

View File

@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Phantom.Common.Data.Web.AuditLog;
using Phantom.Controller.Database.Entities; using Phantom.Controller.Database.Entities;
using Phantom.Controller.Database.Enums;
using Phantom.Controller.Services.Audit;
namespace Phantom.Controller.Database.Repositories; namespace Phantom.Controller.Database.Repositories;

View File

@ -1,11 +1,12 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Phantom.Common.Data.Web.Users; using Phantom.Controller.Database;
using Phantom.Controller.Database.Entities; using Phantom.Controller.Database.Entities;
using Phantom.Controller.Services.Users.Roles;
using Phantom.Utils.Collections; using Phantom.Utils.Collections;
using Phantom.Utils.Tasks; using Phantom.Utils.Tasks;
namespace Phantom.Controller.Database.Repositories; namespace Phantom.Controller.Services.Users;
public sealed class RoleRepository { public sealed class RoleRepository {
private const int MaxRoleNameLength = 40; private const int MaxRoleNameLength = 40;

View File

@ -1,9 +1,6 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Phantom.Common.Data.Web.Users; 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.Controller.Database.Entities;
using Phantom.Utils.Collections; using Phantom.Utils.Collections;
using Phantom.Utils.Tasks; using Phantom.Utils.Tasks;
@ -16,10 +13,10 @@ public sealed class UserRepository {
private static UsernameRequirementViolation? CheckUsernameRequirements(string username) { private static UsernameRequirementViolation? CheckUsernameRequirements(string username) {
if (string.IsNullOrWhiteSpace(username)) { if (string.IsNullOrWhiteSpace(username)) {
return new IsEmpty(); return new UsernameRequirementViolation.IsEmpty();
} }
else if (username.Length > MaxUserNameLength) { else if (username.Length > MaxUserNameLength) {
return new TooLong(MaxUserNameLength); return new UsernameRequirementViolation.TooLong(MaxUserNameLength);
} }
else { else {
return null; return null;
@ -30,19 +27,19 @@ public sealed class UserRepository {
var violations = ImmutableArray.CreateBuilder<PasswordRequirementViolation>(); var violations = ImmutableArray.CreateBuilder<PasswordRequirementViolation>();
if (password.Length < MinimumPasswordLength) { if (password.Length < MinimumPasswordLength) {
violations.Add(new TooShort(MinimumPasswordLength)); violations.Add(new PasswordRequirementViolation.TooShort(MinimumPasswordLength));
} }
if (!password.Any(char.IsLower)) { if (!password.Any(char.IsLower)) {
violations.Add(new MustContainLowercaseLetter()); violations.Add(new PasswordRequirementViolation.LowercaseLetterRequired());
} }
if (!password.Any(char.IsUpper)) { if (!password.Any(char.IsUpper)) {
violations.Add(new MustContainUppercaseLetter()); violations.Add(new PasswordRequirementViolation.UppercaseLetterRequired());
} }
if (!password.Any(char.IsDigit)) { if (!password.Any(char.IsDigit)) {
violations.Add(new MustContainDigit()); violations.Add(new PasswordRequirementViolation.DigitRequired());
} }
return violations.ToImmutable(); return violations.ToImmutable();
@ -76,16 +73,16 @@ public sealed class UserRepository {
public async Task<Result<UserEntity, AddUserError>> CreateUser(string username, string password) { public async Task<Result<UserEntity, AddUserError>> CreateUser(string username, string password) {
var usernameRequirementViolation = CheckUsernameRequirements(username); var usernameRequirementViolation = CheckUsernameRequirements(username);
if (usernameRequirementViolation != null) { if (usernameRequirementViolation != null) {
return new NameIsInvalid(usernameRequirementViolation); return new AddUserError.NameIsInvalid(usernameRequirementViolation);
} }
var passwordRequirementViolations = CheckPasswordRequirements(password); var passwordRequirementViolations = CheckPasswordRequirements(password);
if (!passwordRequirementViolations.IsEmpty) { if (!passwordRequirementViolations.IsEmpty) {
return new PasswordIsInvalid(passwordRequirementViolations); return new AddUserError.PasswordIsInvalid(passwordRequirementViolations);
} }
if (await db.Ctx.Users.AnyAsync(user => user.Name == username)) { if (await db.Ctx.Users.AnyAsync(user => user.Name == username)) {
return new NameAlreadyExists(); return new AddUserError.NameAlreadyExists();
} }
var user = new UserEntity(Guid.NewGuid(), username, UserPasswords.Hash(password)); var user = new UserEntity(Guid.NewGuid(), username, UserPasswords.Hash(password));
@ -99,7 +96,7 @@ public sealed class UserRepository {
public Result<SetUserPasswordError> SetUserPassword(UserEntity user, string password) { public Result<SetUserPasswordError> SetUserPassword(UserEntity user, string password) {
var requirementViolations = CheckPasswordRequirements(password); var requirementViolations = CheckPasswordRequirements(password);
if (!requirementViolations.IsEmpty) { if (!requirementViolations.IsEmpty) {
return new Common.Data.Web.Users.SetUserPasswordErrors.PasswordIsInvalid(requirementViolations); return new SetUserPasswordError.PasswordIsInvalid(requirementViolations);
} }
user.PasswordHash = UserPasswords.Hash(password); user.PasswordHash = UserPasswords.Hash(password);

View File

@ -1,10 +1,11 @@
using NetMQ; using NetMQ;
using NetMQ.Sockets; using NetMQ.Sockets;
using Phantom.Common.Messages.Agent.BiDirectional;
using Phantom.Utils.Rpc.Message; using Phantom.Utils.Rpc.Message;
namespace Phantom.Controller.Rpc; namespace Phantom.Controller.Rpc;
public sealed class RpcConnectionToClient<TListener> { public sealed class RpcClientConnection<TListener> {
private readonly ServerSocket socket; private readonly ServerSocket socket;
private readonly uint routingId; private readonly uint routingId;
@ -14,14 +15,14 @@ public sealed class RpcConnectionToClient<TListener> {
internal event EventHandler<RpcClientConnectionClosedEventArgs>? Closed; internal event EventHandler<RpcClientConnectionClosedEventArgs>? Closed;
private bool isClosed; private bool isClosed;
internal RpcConnectionToClient(ServerSocket socket, uint routingId, MessageRegistry<TListener> messageRegistry, MessageReplyTracker messageReplyTracker) { internal RpcClientConnection(ServerSocket socket, uint routingId, MessageRegistry<TListener> messageRegistry, MessageReplyTracker messageReplyTracker) {
this.socket = socket; this.socket = socket;
this.routingId = routingId; this.routingId = routingId;
this.messageRegistry = messageRegistry; this.messageRegistry = messageRegistry;
this.messageReplyTracker = messageReplyTracker; this.messageReplyTracker = messageReplyTracker;
} }
public bool IsSame(RpcConnectionToClient<TListener> other) { public bool IsSame(RpcClientConnection<TListener> other) {
return this.routingId == other.routingId && this.socket == other.socket; return this.routingId == other.routingId && this.socket == other.socket;
} }
@ -62,7 +63,7 @@ public sealed class RpcConnectionToClient<TListener> {
return await messageReplyTracker.WaitForReply<TReply>(sequenceId, waitForReplyTime, waitForReplyCancellationToken); return await messageReplyTracker.WaitForReply<TReply>(sequenceId, waitForReplyTime, waitForReplyCancellationToken);
} }
public void Receive(IReply message) { public void Receive(ReplyMessage message) {
messageReplyTracker.ReceiveReply(message.SequenceId, message.SerializedReply); messageReplyTracker.ReceiveReply(message.SequenceId, message.SerializedReply);
} }
} }

View File

@ -8,14 +8,14 @@ using Serilog.Events;
namespace Phantom.Controller.Rpc; namespace Phantom.Controller.Rpc;
public static class RpcRuntime { public static class RpcRuntime {
public static Task Launch<TClientListener, TServerListener>(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) { public static Task Launch<TOutgoingListener, TIncomingListener>(RpcConfiguration config, IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions, Func<RpcClientConnection<TOutgoingListener>, TIncomingListener> listenerFactory, CancellationToken cancellationToken) {
return RpcRuntime<TClientListener, TServerListener>.Launch(config, messageDefinitions, listenerFactory, cancellationToken); return RpcRuntime<TOutgoingListener, TIncomingListener>.Launch(config, messageDefinitions, listenerFactory, cancellationToken);
} }
} }
internal sealed class RpcRuntime<TClientListener, TServerListener> : RpcRuntime<ServerSocket> { internal sealed class RpcRuntime<TOutgoingListener, TIncomingListener> : RpcRuntime<ServerSocket> {
internal static Task Launch(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) { internal static Task Launch(RpcConfiguration config, IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions, Func<RpcClientConnection<TOutgoingListener>, TIncomingListener> listenerFactory, CancellationToken cancellationToken) {
return new RpcRuntime<TClientListener, TServerListener>(config, messageDefinitions, listenerFactory, cancellationToken).Launch(); return new RpcRuntime<TOutgoingListener, TIncomingListener>(config, messageDefinitions, listenerFactory, cancellationToken).Launch();
} }
private static ServerSocket CreateSocket(RpcConfiguration config) { private static ServerSocket CreateSocket(RpcConfiguration config) {
@ -29,11 +29,11 @@ internal sealed class RpcRuntime<TClientListener, TServerListener> : RpcRuntime<
} }
private readonly RpcConfiguration config; private readonly RpcConfiguration config;
private readonly IMessageDefinitions<TClientListener, TServerListener> messageDefinitions; private readonly IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions;
private readonly Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory; private readonly Func<RpcClientConnection<TOutgoingListener>, TIncomingListener> listenerFactory;
private readonly CancellationToken cancellationToken; private readonly CancellationToken cancellationToken;
private RpcRuntime(RpcConfiguration config, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, Func<RpcConnectionToClient<TClientListener>, TServerListener> listenerFactory, CancellationToken cancellationToken) : base(config, CreateSocket(config)) { private RpcRuntime(RpcConfiguration config, IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions, Func<RpcClientConnection<TOutgoingListener>, TIncomingListener> listenerFactory, CancellationToken cancellationToken) : base(config, CreateSocket(config)) {
this.config = config; this.config = config;
this.messageDefinitions = messageDefinitions; this.messageDefinitions = messageDefinitions;
this.listenerFactory = listenerFactory; this.listenerFactory = listenerFactory;
@ -71,7 +71,7 @@ internal sealed class RpcRuntime<TClientListener, TServerListener> : RpcRuntime<
continue; continue;
} }
var connection = new RpcConnectionToClient<TClientListener>(socket, routingId, messageDefinitions.ToClient, replyTracker); var connection = new RpcClientConnection<TOutgoingListener>(socket, routingId, messageDefinitions.Outgoing, replyTracker);
connection.Closed += OnConnectionClosed; connection.Closed += OnConnectionClosed;
client = new Client(connection, messageDefinitions, listenerFactory(connection), logger, taskManager, cancellationToken); client = new Client(connection, messageDefinitions, listenerFactory(connection), logger, taskManager, cancellationToken);
@ -79,7 +79,7 @@ internal sealed class RpcRuntime<TClientListener, TServerListener> : RpcRuntime<
} }
LogMessageType(logger, routingId, data); LogMessageType(logger, routingId, data);
messageDefinitions.ToServer.Handle(data, client); messageDefinitions.Incoming.Handle(data, client);
} }
foreach (var client in clients.Values) { foreach (var client in clients.Values) {
@ -92,7 +92,7 @@ internal sealed class RpcRuntime<TClientListener, TServerListener> : RpcRuntime<
return; return;
} }
if (data.Length > 0 && messageDefinitions.ToServer.TryGetType(data, out var type)) { if (data.Length > 0 && messageDefinitions.Incoming.TryGetType(data, out var type)) {
logger.Verbose("Received {MessageType} ({Bytes} B) from {RoutingId}.", type.Name, data.Length, routingId); logger.Verbose("Received {MessageType} ({Bytes} B) from {RoutingId}.", type.Name, data.Length, routingId);
} }
else { else {
@ -101,7 +101,7 @@ internal sealed class RpcRuntime<TClientListener, TServerListener> : RpcRuntime<
} }
private bool CheckIsRegistrationMessage(ReadOnlyMemory<byte> data, ILogger logger, uint routingId) { private bool CheckIsRegistrationMessage(ReadOnlyMemory<byte> data, ILogger logger, uint routingId) {
if (messageDefinitions.ToServer.TryGetType(data, out var type) && messageDefinitions.IsRegistrationMessage(type)) { if (messageDefinitions.Incoming.TryGetType(data, out var type) && messageDefinitions.IsRegistrationMessage(type)) {
return true; return true;
} }
@ -109,18 +109,18 @@ internal sealed class RpcRuntime<TClientListener, TServerListener> : RpcRuntime<
return false; return false;
} }
private sealed class Client : MessageHandler<TServerListener> { private sealed class Client : MessageHandler<TIncomingListener> {
public RpcConnectionToClient<TClientListener> Connection { get; } public RpcClientConnection<TOutgoingListener> Connection { get; }
private readonly IMessageDefinitions<TClientListener, TServerListener> messageDefinitions; private readonly IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions;
public Client(RpcConnectionToClient<TClientListener> connection, IMessageDefinitions<TClientListener, TServerListener> messageDefinitions, TServerListener listener, ILogger logger, TaskManager taskManager, CancellationToken cancellationToken) : base(listener, logger, taskManager, cancellationToken) { public Client(RpcClientConnection<TOutgoingListener> connection, IMessageDefinitions<TOutgoingListener, TIncomingListener> messageDefinitions, TIncomingListener listener, ILogger logger, TaskManager taskManager, CancellationToken cancellationToken) : base(listener, logger, taskManager, cancellationToken) {
this.Connection = connection; this.Connection = connection;
this.messageDefinitions = messageDefinitions; this.messageDefinitions = messageDefinitions;
} }
protected override Task SendReply(uint sequenceId, byte[] serializedReply) { protected override Task SendReply(uint sequenceId, byte[] serializedReply) {
return Connection.Send(messageDefinitions.CreateReplyToServerMessage(sequenceId, serializedReply)); return Connection.Send(messageDefinitions.CreateReplyMessage(sequenceId, serializedReply));
} }
} }
} }

View File

@ -4,13 +4,13 @@ using Phantom.Controller.Rpc;
namespace Phantom.Controller.Services.Agents; namespace Phantom.Controller.Services.Agents;
sealed class AgentConnection { sealed class AgentConnection {
private readonly RpcConnectionToClient<IMessageToAgentListener> connection; private readonly RpcClientConnection<IMessageToAgentListener> connection;
internal AgentConnection(RpcConnectionToClient<IMessageToAgentListener> connection) { internal AgentConnection(RpcClientConnection<IMessageToAgentListener> connection) {
this.connection = connection; this.connection = connection;
} }
public bool IsSame(RpcConnectionToClient<IMessageToAgentListener> connection) { public bool IsSame(RpcClientConnection<IMessageToAgentListener> connection) {
return this.connection.IsSame(connection); return this.connection.IsSame(connection);
} }

View File

@ -52,7 +52,7 @@ public sealed class AgentManager {
return agents.ByGuid.ToImmutable(); return agents.ByGuid.ToImmutable();
} }
internal async Task<bool> RegisterAgent(AuthToken authToken, AgentInfo agentInfo, InstanceManager instanceManager, RpcConnectionToClient<IMessageToAgentListener> connection) { internal async Task<bool> RegisterAgent(AuthToken authToken, AgentInfo agentInfo, InstanceManager instanceManager, RpcClientConnection<IMessageToAgentListener> connection) {
if (!this.authToken.FixedTimeEquals(authToken)) { if (!this.authToken.FixedTimeEquals(authToken)) {
await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.InvalidToken)); await connection.Send(new RegisterAgentFailureMessage(RegisterAgentFailure.InvalidToken));
return false; return false;
@ -88,7 +88,7 @@ public sealed class AgentManager {
return true; return true;
} }
internal bool UnregisterAgent(Guid agentGuid, RpcConnectionToClient<IMessageToAgentListener> connection) { internal bool UnregisterAgent(Guid agentGuid, RpcClientConnection<IMessageToAgentListener> connection) {
if (agents.ByGuid.TryReplaceIf(agentGuid, static oldAgent => oldAgent.AsOffline(), oldAgent => oldAgent.Connection?.IsSame(connection) == true)) { if (agents.ByGuid.TryReplaceIf(agentGuid, static oldAgent => oldAgent.AsOffline(), oldAgent => oldAgent.Connection?.IsSame(connection) == true)) {
Logger.Information("Unregistered agent with GUID {Guid}.", agentGuid); Logger.Information("Unregistered agent with GUID {Guid}.", agentGuid);
return true; return true;

View File

@ -1,4 +1,4 @@
using Phantom.Common.Data; using Phantom.Common.Data.Agent;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Common.Messages.Agent; using Phantom.Common.Messages.Agent;
using Phantom.Common.Messages.Web; using Phantom.Common.Messages.Web;
@ -10,6 +10,7 @@ using Phantom.Controller.Services.Events;
using Phantom.Controller.Services.Instances; using Phantom.Controller.Services.Instances;
using Phantom.Controller.Services.Rpc; using Phantom.Controller.Services.Rpc;
using Phantom.Controller.Services.Users; using Phantom.Controller.Services.Users;
using Phantom.Controller.Services.Users.Permissions;
using Phantom.Utils.Tasks; using Phantom.Utils.Tasks;
namespace Phantom.Controller.Services; namespace Phantom.Controller.Services;
@ -49,11 +50,11 @@ public sealed class ControllerServices {
this.cancellationToken = shutdownCancellationToken; this.cancellationToken = shutdownCancellationToken;
} }
public AgentMessageListener CreateAgentMessageListener(RpcConnectionToClient<IMessageToAgentListener> connection) { public AgentMessageListener CreateAgentMessageListener(RpcClientConnection<IMessageToAgentListener> connection) {
return new AgentMessageListener(connection, AgentManager, AgentJavaRuntimesManager, InstanceManager, InstanceLogManager, EventLog, cancellationToken); return new AgentMessageListener(connection, AgentManager, AgentJavaRuntimesManager, InstanceManager, InstanceLogManager, EventLog, cancellationToken);
} }
public WebMessageListener CreateWebMessageListener(RpcConnectionToClient<IMessageToWebListener> connection) { public WebMessageListener CreateWebMessageListener(RpcClientConnection<IMessageToWebListener> connection) {
return new WebMessageListener(connection, UserManager, RoleManager); return new WebMessageListener(connection, UserManager, RoleManager);
} }

View File

@ -1,6 +1,6 @@
using Phantom.Common.Data.Backups; using Phantom.Common.Data.Backups;
using Phantom.Common.Data.Instance; using Phantom.Common.Data.Instance;
using Phantom.Common.Data.Web.EventLog; using Phantom.Controller.Database.Enums;
namespace Phantom.Controller.Services.Events; namespace Phantom.Controller.Services.Events;

View File

@ -1,8 +1,8 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Phantom.Common.Data.Web.EventLog;
using Phantom.Controller.Database; using Phantom.Controller.Database;
using Phantom.Controller.Database.Entities; using Phantom.Controller.Database.Entities;
using Phantom.Controller.Database.Enums;
using Phantom.Utils.Collections; using Phantom.Utils.Collections;
using Phantom.Utils.Tasks; using Phantom.Utils.Tasks;

View File

@ -1,5 +1,6 @@
using System.Text.Json; using System.Text.Json;
using Phantom.Controller.Database.Enums;
namespace Phantom.Common.Data.Web.EventLog; namespace Phantom.Controller.Services.Events;
public sealed record EventLogItem(DateTime UtcTime, Guid? AgentGuid, EventLogEventType EventType, EventLogSubjectType SubjectType, string SubjectId, JsonDocument? Data); public sealed record EventLogItem(DateTime UtcTime, Guid? AgentGuid, EventLogEventType EventType, EventLogSubjectType SubjectType, string SubjectId, JsonDocument? Data);

View File

@ -3,7 +3,7 @@ using System.Collections.Immutable;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Utils.Collections; using Phantom.Utils.Collections;
using Phantom.Utils.Events; using Phantom.Utils.Events;
using Serilog; using ILogger = Serilog.ILogger;
namespace Phantom.Controller.Services.Instances; namespace Phantom.Controller.Services.Instances;

View File

@ -5,9 +5,9 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <PropertyGroup>
<PackageReference Include="BCrypt.Net-Next.StrongName" /> <OutputType>Library</OutputType>
</ItemGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Common\Phantom.Common.Data\Phantom.Common.Data.csproj" /> <ProjectReference Include="..\..\Common\Phantom.Common.Data\Phantom.Common.Data.csproj" />
@ -18,4 +18,8 @@
<ProjectReference Include="..\Phantom.Controller.Rpc\Phantom.Controller.Rpc.csproj" /> <ProjectReference Include="..\Phantom.Controller.Rpc\Phantom.Controller.Rpc.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Audit\" />
</ItemGroup>
</Project> </Project>

View File

@ -14,7 +14,7 @@ using Phantom.Utils.Tasks;
namespace Phantom.Controller.Services.Rpc; namespace Phantom.Controller.Services.Rpc;
public sealed class AgentMessageListener : IMessageToControllerListener { public sealed class AgentMessageListener : IMessageToControllerListener {
private readonly RpcConnectionToClient<IMessageToAgentListener> connection; private readonly RpcClientConnection<IMessageToAgentListener> connection;
private readonly AgentManager agentManager; private readonly AgentManager agentManager;
private readonly AgentJavaRuntimesManager agentJavaRuntimesManager; private readonly AgentJavaRuntimesManager agentJavaRuntimesManager;
private readonly InstanceManager instanceManager; private readonly InstanceManager instanceManager;
@ -24,7 +24,7 @@ public sealed class AgentMessageListener : IMessageToControllerListener {
private readonly TaskCompletionSource<Guid> agentGuidWaiter = AsyncTasks.CreateCompletionSource<Guid>(); private readonly TaskCompletionSource<Guid> agentGuidWaiter = AsyncTasks.CreateCompletionSource<Guid>();
internal AgentMessageListener(RpcConnectionToClient<IMessageToAgentListener> connection, AgentManager agentManager, AgentJavaRuntimesManager agentJavaRuntimesManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager, EventLog eventLog, CancellationToken cancellationToken) { internal AgentMessageListener(RpcClientConnection<IMessageToAgentListener> connection, AgentManager agentManager, AgentJavaRuntimesManager agentJavaRuntimesManager, InstanceManager instanceManager, InstanceLogManager instanceLogManager, EventLog eventLog, CancellationToken cancellationToken) {
this.connection = connection; this.connection = connection;
this.agentManager = agentManager; this.agentManager = agentManager;
this.agentJavaRuntimesManager = agentJavaRuntimesManager; this.agentJavaRuntimesManager = agentJavaRuntimesManager;

View File

@ -9,26 +9,21 @@ using Phantom.Utils.Rpc.Message;
namespace Phantom.Controller.Services.Rpc; namespace Phantom.Controller.Services.Rpc;
public sealed class WebMessageListener : IMessageToControllerListener { public sealed class WebMessageListener : IMessageToControllerListener {
private readonly RpcConnectionToClient<IMessageToWebListener> connection; private readonly RpcClientConnection<IMessageToWebListener> connection;
private readonly UserManager userManager; private readonly UserManager userManager;
private readonly RoleManager roleManager; private readonly RoleManager roleManager;
internal WebMessageListener(RpcConnectionToClient<IMessageToWebListener> connection, UserManager userManager, RoleManager roleManager) { internal WebMessageListener(RpcClientConnection<IMessageToWebListener> connection, UserManager userManager, RoleManager roleManager) {
this.connection = connection; this.connection = connection;
this.userManager = userManager; this.userManager = userManager;
this.roleManager = roleManager; this.roleManager = roleManager;
} }
public async Task<NoReply> HandleRegisterWeb(RegisterWebMessage message) {
return default;
}
public Task<CreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUser message) { public Task<CreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUser message) {
return userManager.CreateOrUpdateAdministrator(message.Username, message.Password); return userManager.CreateOrUpdateAdministrator(message.Username, message.Password);
} }
public Task<NoReply> HandleReply(ReplyMessage message) { public Task<NoReply> HandleReply(ReplyMessage message) {
connection.Receive(message);
return Task.FromResult(NoReply.Instance); return Task.FromResult(NoReply.Instance);
} }
} }

View File

@ -1,13 +1,14 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Security.Claims;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users.Permissions;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Controller.Database; using Phantom.Controller.Database;
using Phantom.Controller.Database.Entities; using Phantom.Controller.Database.Entities;
using Phantom.Utils.Collections; using Phantom.Utils.Collections;
using Serilog; using Serilog;
namespace Phantom.Controller.Services.Users; namespace Phantom.Controller.Services.Users.Permissions;
public sealed class PermissionManager { public sealed class PermissionManager {
private static readonly ILogger Logger = PhantomLogger.Create<PermissionManager>(); private static readonly ILogger Logger = PhantomLogger.Create<PermissionManager>();
@ -41,28 +42,28 @@ public sealed class PermissionManager {
return allPermissions.Select(static permission => permission.Id).Except(existingPermissionIds).Order().ToImmutableArray(); return allPermissions.Select(static permission => permission.Id).Except(existingPermissionIds).Order().ToImmutableArray();
} }
private async Task<IdentityPermissions> FetchPermissionsForUserId(Guid userId) { private IdentityPermissions FetchPermissionsForUserId(Guid userId) {
await using var ctx = dbProvider.Eager(); using var ctx = dbProvider.Lazy();
var userPermissions = ctx.UserPermissions.Where(up => up.UserGuid == userId).Select(static up => up.PermissionId); 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); 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(await userPermissions.Union(rolePermissions).AsAsyncEnumerable().ToImmutableSetAsync()); return new IdentityPermissions(userPermissions.Union(rolePermissions).ToImmutableHashSet());
} }
// private IdentityPermissions GetPermissionsForUserId(Guid userId, bool refreshCache) { private IdentityPermissions GetPermissionsForUserId(Guid userId, bool refreshCache) {
// if (!refreshCache && userIdsToPermissionIds.TryGetValue(userId, out var userPermissions)) { if (!refreshCache && userIdsToPermissionIds.TryGetValue(userId, out var userPermissions)) {
// return userPermissions; return userPermissions;
// } }
// else { else {
// return userIdsToPermissionIds[userId] = FetchPermissionsForUserId(userId); return userIdsToPermissionIds[userId] = FetchPermissionsForUserId(userId);
// } }
// } }
//
// public IdentityPermissions GetPermissions(ClaimsPrincipal user, bool refreshCache = false) { public IdentityPermissions GetPermissions(ClaimsPrincipal user, bool refreshCache = false) {
// Guid? userId = UserManager.GetAuthenticatedUserId(user); Guid? userId = UserManager.GetAuthenticatedUserId(user);
// return userId == null ? IdentityPermissions.None : GetPermissionsForUserId(userId.Value, refreshCache); return userId == null ? IdentityPermissions.None : GetPermissionsForUserId(userId.Value, refreshCache);
// } }
//
// public bool CheckPermission(ClaimsPrincipal user, Permission permission, bool refreshCache = false) { public bool CheckPermission(ClaimsPrincipal user, Permission permission, bool refreshCache = false) {
// return GetPermissions(user, refreshCache).Check(permission); return GetPermissions(user, refreshCache).Check(permission);
// } }
} }

View File

@ -1,7 +1,7 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users.Permissions;
namespace Phantom.Controller.Services.Users; namespace Phantom.Controller.Services.Users.Roles;
public sealed record Role(Guid Guid, string Name, ImmutableArray<Permission> Permissions) { public sealed record Role(Guid Guid, string Name, ImmutableArray<Permission> Permissions) {
private static readonly List<Role> AllRoles = new (); private static readonly List<Role> AllRoles = new ();

View File

@ -3,6 +3,8 @@ using Microsoft.EntityFrameworkCore;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Controller.Database; using Phantom.Controller.Database;
using Phantom.Controller.Database.Entities; using Phantom.Controller.Database.Entities;
using Phantom.Controller.Services.Users.Permissions;
using Phantom.Controller.Services.Users.Roles;
using Phantom.Utils.Collections; using Phantom.Utils.Collections;
using Serilog; using Serilog;

View File

@ -1,8 +1,8 @@
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users;
using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Controller.Database; using Phantom.Controller.Database;
using Phantom.Controller.Database.Repositories; using Phantom.Controller.Database.Repositories;
using Phantom.Controller.Services.Users.Roles;
using Serilog; using Serilog;
namespace Phantom.Controller.Services.Users; namespace Phantom.Controller.Services.Users;
@ -69,28 +69,28 @@ sealed class UserManager {
user = result.Value; user = result.Value;
} }
else { else {
return new CreationFailed(result.Error); return new CreateOrUpdateAdministratorUserResult.CreationFailed(result.Error);
} }
} }
else { else {
var result = repository.SetUserPassword(user, password); var result = repository.SetUserPassword(user, password);
if (!result) { if (!result) {
return new UpdatingFailed(result.Error); return new CreateOrUpdateAdministratorUserResult.UpdatingFailed(result.Error);
} }
} }
var role = await new RoleRepository(db).GetByGuid(Role.Administrator.Guid); var role = await new RoleRepository(db).GetByGuid(Role.Administrator.Guid);
if (role == null) { if (role == null) {
return new AddingToRoleFailed(); return new CreateOrUpdateAdministratorUserResult.AddingToRoleFailed();
} }
await new UserRoleRepository(db).Add(user, role); await new UserRoleRepository(db).Add(user, role);
Logger.Information("Created administrator user \"{Username}\" (GUID {Guid}).", username, user.UserGuid); Logger.Information("Created administrator user \"{Username}\" (GUID {Guid}).", username, user.UserGuid);
return new Success(user.ToUserInfo()); return new CreateOrUpdateAdministratorUserResult.Success(user.ToUserInfo());
} catch (Exception e) { } catch (Exception e) {
Logger.Error(e, "Could not create or update administrator user \"{Username}\".", username); Logger.Error(e, "Could not create or update administrator user \"{Username}\".", username);
return new UnknownError(); return new CreateOrUpdateAdministratorUserResult.UnknownError();
} }
} }

View File

@ -1,5 +1,4 @@
using NetMQ; using NetMQ;
using Phantom.Common.Data;
using Phantom.Common.Data.Agent; using Phantom.Common.Data.Agent;
namespace Phantom.Controller; namespace Phantom.Controller;

View File

@ -1,5 +1,4 @@
using NetMQ; using NetMQ;
using Phantom.Common.Data;
using Phantom.Common.Data.Agent; using Phantom.Common.Data.Agent;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Utils.Cryptography; using Phantom.Utils.Cryptography;

View File

@ -149,8 +149,8 @@ The repository includes a [Rider](https://www.jetbrains.com/rider/) projects wit
- `Controller` starts the Controller. - `Controller` starts the Controller.
- `Web` starts the Web server. - `Web` starts the Web server.
- `Agent 1`, `Agent 2`, `Agent 3` start one of the Agents. - `Agent 1`, `Agent 2`, `Agent 3` start one of the Agents.
- `Controller + Agent` starts the Controller and Agent 1. - `Server + Agent` starts the Server and Agent 1.
- `Controller + Agent x3` starts the Controller and Agent 1, 2, and 3. - `Server + Agent x3` starts the Server and Agent 1, 2, and 3.
## Bootstrap ## Bootstrap

View File

@ -1,10 +1,9 @@
namespace Phantom.Utils.Rpc.Message; namespace Phantom.Utils.Rpc.Message;
public interface IMessageDefinitions<TClientListener, TServerListener> { public interface IMessageDefinitions<TOutgoingListener, TIncomingListener> {
MessageRegistry<TClientListener> ToClient { get; } MessageRegistry<TOutgoingListener> Outgoing { get; }
MessageRegistry<TServerListener> ToServer { get; } MessageRegistry<TIncomingListener> Incoming { get; }
bool IsRegistrationMessage(Type messageType); bool IsRegistrationMessage(Type messageType);
IMessage<TClientListener, NoReply> CreateReplyToServerMessage(uint sequenceId, byte[] serializedReply); IMessage<TOutgoingListener, NoReply> CreateReplyMessage(uint sequenceId, byte[] serializedReply);
IMessage<TServerListener, NoReply> CreateReplyToClientMessage(uint sequenceId, byte[] serializedReply);
} }

View File

@ -1,99 +0,0 @@
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));
}
}
}

View File

@ -45,7 +45,7 @@ public abstract class RpcRuntime<TSocket> where TSocket : ThreadSafeSocket {
// ignore // ignore
} finally { } finally {
await taskManager.Stop(); await taskManager.Stop();
await Disconnect(socket); await Disconnect();
socket.Dispose(); socket.Dispose();
NetMQConfig.Cleanup(); NetMQConfig.Cleanup();
@ -56,7 +56,7 @@ public abstract class RpcRuntime<TSocket> where TSocket : ThreadSafeSocket {
protected abstract void Connect(TSocket socket); protected abstract void Connect(TSocket socket);
protected abstract void Run(TSocket socket, MessageReplyTracker replyTracker, TaskManager taskManager); protected abstract void Run(TSocket socket, MessageReplyTracker replyTracker, TaskManager taskManager);
protected virtual Task Disconnect(TSocket socket) { protected virtual Task Disconnect() {
return Task.CompletedTask; return Task.CompletedTask;
} }
} }

View File

@ -1,7 +1,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Phantom.Web.Identity.Authentication;
using Phantom.Web.Services;
namespace Phantom.Web.Services.Authentication; namespace Phantom.Web.Identity;
sealed class PhantomIdentityMiddleware { sealed class PhantomIdentityMiddleware {
public const string LoginPath = "/login"; public const string LoginPath = "/login";

View File

@ -3,9 +3,11 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Utils.Cryptography; using Phantom.Utils.Cryptography;
using Phantom.Web.Services;
using Phantom.Web.Services.Authentication;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;
namespace Phantom.Web.Services.Authentication; namespace Phantom.Web.Identity.Authentication;
public sealed class PhantomLoginManager { public sealed class PhantomLoginManager {
private static readonly ILogger Logger = PhantomLogger.Create<PhantomLoginManager>(); private static readonly ILogger Logger = PhantomLogger.Create<PhantomLoginManager>();

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Phantom.Web.Services.Authorization;
namespace Phantom.Web.Services.Authorization; namespace Phantom.Web.Identity.Authorization;
sealed class PermissionBasedPolicyHandler : AuthorizationHandler<PermissionBasedPolicyRequirement> { sealed class PermissionBasedPolicyHandler : AuthorizationHandler<PermissionBasedPolicyRequirement> {
private readonly PermissionManager permissionManager; private readonly PermissionManager permissionManager;

View File

@ -1,5 +1,5 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users.Permissions;
namespace Phantom.Web.Services.Authorization; namespace Phantom.Web.Services.Authorization;

View File

@ -1,5 +1,5 @@
using System.Security.Claims; using System.Security.Claims;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users.Permissions;
namespace Phantom.Web.Services.Authorization; namespace Phantom.Web.Services.Authorization;

View File

@ -1,5 +1,5 @@
@using Microsoft.AspNetCore.Components.Authorization @using Microsoft.AspNetCore.Components.Authorization
@using Phantom.Common.Data.Web.Users @using Phantom.Common.Data.Web.Users.Permissions
@inject PermissionManager PermissionManager @inject PermissionManager PermissionManager
<AuthorizeView> <AuthorizeView>

View File

@ -5,10 +5,6 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Common\Phantom.Common.Data\Phantom.Common.Data.csproj" /> <ProjectReference Include="..\..\Common\Phantom.Common.Data\Phantom.Common.Data.csproj" />
<ProjectReference Include="..\..\Common\Phantom.Common.Data.Web\Phantom.Common.Data.Web.csproj" /> <ProjectReference Include="..\..\Common\Phantom.Common.Data.Web\Phantom.Common.Data.Web.csproj" />

View File

@ -2,7 +2,10 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server; using Microsoft.AspNetCore.Components.Server;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users.Permissions;
using Phantom.Web.Identity;
using Phantom.Web.Identity.Authentication;
using Phantom.Web.Identity.Authorization;
using Phantom.Web.Services.Authentication; using Phantom.Web.Services.Authentication;
using Phantom.Web.Services.Authorization; using Phantom.Web.Services.Authorization;

View File

@ -1,22 +0,0 @@
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) {}
}

View File

@ -1,5 +1,5 @@
@using Phantom.Web.Services @using Phantom.Web.Services
@using Phantom.Web.Services.Authentication @using Phantom.Web.Identity.Authentication
@inject INavigation Nav @inject INavigation Nav
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager

View File

@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users.Permissions;
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Web.Services.Authorization; using Phantom.Web.Services.Authorization;
using ILogger = Serilog.ILogger; using ILogger = Serilog.ILogger;

View File

@ -2,6 +2,6 @@
namespace Phantom.Web; namespace Phantom.Web;
public sealed record Configuration(ILogger Logger, string Host, ushort Port, string BasePath, string DataProtectionKeyFolderPath, CancellationToken CancellationToken) { public sealed record Configuration(ILogger Logger, string Host, ushort Port, string BasePath, string KeyFolderPath, CancellationToken CancellationToken) {
public string HttpUrl => "http://" + Host + ":" + Port; public string HttpUrl => "http://" + Host + ":" + Port;
} }

View File

@ -30,7 +30,7 @@ static class Launcher {
builder.Services.AddSingleton<IHostLifetime>(new NullLifetime()); builder.Services.AddSingleton<IHostLifetime>(new NullLifetime());
builder.Services.AddScoped<INavigation>(Navigation.Create(config.BasePath)); builder.Services.AddScoped<INavigation>(Navigation.Create(config.BasePath));
builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(config.DataProtectionKeyFolderPath)); builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(config.KeyFolderPath));
builder.Services.AddRazorPages(static options => options.RootDirectory = "/Layout"); builder.Services.AddRazorPages(static options => options.RootDirectory = "/Layout");
builder.Services.AddServerSideBlazor(); builder.Services.AddServerSideBlazor();

View File

@ -1,5 +1,5 @@
@using Phantom.Web.Services.Authorization @using Phantom.Web.Services.Authorization
@using Phantom.Common.Data.Web.Users @using Phantom.Common.Data.Web.Users.Permissions
@inject ServiceConfiguration Configuration @inject ServiceConfiguration Configuration
@inject PermissionManager PermissionManager @inject PermissionManager PermissionManager

View File

@ -1,9 +1,6 @@
@page "/audit" @page "/audit"
@attribute [Authorize(Permission.ViewAuditPolicy)] @attribute [Authorize(Permission.ViewAuditPolicy)]
@using Phantom.Common.Data.Web.AuditLog
@using Phantom.Common.Data.Web.Users
@using System.Collections.Immutable @using System.Collections.Immutable
@using Phantom.Web.Services.Instances
@implements IDisposable @implements IDisposable
@inject AuditLog AuditLog @inject AuditLog AuditLog
@inject InstanceManager InstanceManager @inject InstanceManager InstanceManager

View File

@ -1,10 +1,6 @@
@page "/events" @page "/events"
@attribute [Authorize(Permission.ViewEventsPolicy)] @attribute [Authorize(Permission.ViewEventsPolicy)]
@using Phantom.Common.Data.Web.EventLog
@using Phantom.Common.Data.Web.Users
@using System.Collections.Immutable @using System.Collections.Immutable
@using System.Diagnostics
@using Phantom.Web.Services.Instances
@implements IDisposable @implements IDisposable
@inject AgentManager AgentManager @inject AgentManager AgentManager
@inject EventLog EventLog @inject EventLog EventLog

View File

@ -1,5 +1,4 @@
@page "/instances/create" @page "/instances/create"
@using Phantom.Common.Data.Web.Users
@attribute [Authorize(Permission.CreateInstancesPolicy)] @attribute [Authorize(Permission.CreateInstancesPolicy)]
<h1>New Instance</h1> <h1>New Instance</h1>

View File

@ -1,9 +1,6 @@
@page "/instances/{InstanceGuid:guid}" @page "/instances/{InstanceGuid:guid}"
@attribute [Authorize(Permission.ViewInstancesPolicy)] @attribute [Authorize(Permission.ViewInstancesPolicy)]
@inherits PhantomComponent @inherits PhantomComponent
@using Phantom.Web.Services.Instances
@using Phantom.Common.Data.Web.Users
@using Phantom.Common.Data.Replies
@implements IDisposable @implements IDisposable
@inject InstanceManager InstanceManager @inject InstanceManager InstanceManager
@inject AuditLog AuditLog @inject AuditLog AuditLog

View File

@ -1,8 +1,5 @@
@page "/instances/{InstanceGuid:guid}/edit" @page "/instances/{InstanceGuid:guid}/edit"
@attribute [Authorize(Permission.CreateInstancesPolicy)] @attribute [Authorize(Permission.CreateInstancesPolicy)]
@using Phantom.Common.Data.Instance
@using Phantom.Common.Data.Web.Users
@using Phantom.Web.Services.Instances
@inherits PhantomComponent @inherits PhantomComponent
@inject InstanceManager InstanceManager @inject InstanceManager InstanceManager

View File

@ -1,6 +1,6 @@
@page "/login" @page "/login"
@using Phantom.Web.Services @using Phantom.Web.Services
@using Phantom.Web.Services.Authentication @using Phantom.Web.Identity.Authentication
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@attribute [AllowAnonymous] @attribute [AllowAnonymous]
@inject INavigation Navigation @inject INavigation Navigation

View File

@ -1,6 +1,6 @@
@page "/setup" @page "/setup"
@using Phantom.Utils.Tasks @using Phantom.Utils.Tasks
@using Phantom.Web.Services.Authentication @using Phantom.Web.Identity.Authentication
@using Phantom.Web.Services @using Phantom.Web.Services
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@using Phantom.Utils.Cryptography @using Phantom.Utils.Cryptography

View File

@ -1,7 +1,5 @@
@page "/users" @page "/users"
@using Phantom.Common.Data.Web.Users
@using System.Collections.Immutable @using System.Collections.Immutable
@using Phantom.Web.Services.Authorization
@attribute [Authorize(Permission.ViewUsersPolicy)] @attribute [Authorize(Permission.ViewUsersPolicy)]
@inject UserManager UserManager @inject UserManager UserManager
@inject UserRoleManager UserRoleManager @inject UserRoleManager UserRoleManager
@ -30,10 +28,10 @@
<tbody> <tbody>
@{ var myUserId = UserManager.GetAuthenticatedUserId(context.User); } @{ var myUserId = UserManager.GetAuthenticatedUserId(context.User); }
@foreach (var user in allUsers) { @foreach (var user in allUsers) {
var isMe = myUserId == user.Guid; var isMe = myUserId == user.UserGuid;
<tr> <tr>
<td> <td>
<code class="text-uppercase">@user.Guid</code> <code class="text-uppercase">@user.UserGuid</code>
</td> </td>
@if (isMe) { @if (isMe) {
<td class="fw-semibold">@user.Name</td> <td class="fw-semibold">@user.Name</td>
@ -41,7 +39,7 @@
else { else {
<td>@user.Name</td> <td>@user.Name</td>
} }
<td>@(userGuidToRoleDescription.TryGetValue(user.Guid, out var roles) ? roles : "?")</td> <td>@(userGuidToRoleDescription.TryGetValue(user.UserGuid, out var roles) ? roles : "?")</td>
@if (canEdit) { @if (canEdit) {
<td> <td>
@if (!isMe) { @if (!isMe) {
@ -65,7 +63,7 @@
@code { @code {
private ImmutableArray<UserInfo> allUsers = ImmutableArray<UserInfo>.Empty; private ImmutableArray<UserEntity> allUsers = ImmutableArray<UserEntity>.Empty;
private readonly Dictionary<Guid, string> userGuidToRoleDescription = new(); private readonly Dictionary<Guid, string> userGuidToRoleDescription = new();
private UserRolesDialog userRolesDialog = null!; private UserRolesDialog userRolesDialog = null!;
@ -84,27 +82,27 @@
} }
} }
private async Task RefreshUserRoles(UserInfo user) { private async Task RefreshUserRoles(UserEntity user) {
var roles = await UserRoleManager.GetUserRoles(user); var roles = await UserRoleManager.GetUserRoles(user);
userGuidToRoleDescription[user.Guid] = StringifyRoles(roles); userGuidToRoleDescription[user.UserGuid] = StringifyRoles(roles);
} }
private static string StringifyRoles(ImmutableArray<RoleInfo> roles) { private static string StringifyRoles(ImmutableArray<RoleEntity> roles) {
return roles.IsEmpty ? "-" : string.Join(", ", roles.Select(static role => role.Name)); return roles.IsEmpty ? "-" : string.Join(", ", roles.Select(static role => role.Name));
} }
private Task OnUserAdded(UserInfo user) { private Task OnUserAdded(UserEntity user) {
allUsers = allUsers.Add(user); allUsers = allUsers.Add(user);
return RefreshUserRoles(user); return RefreshUserRoles(user);
} }
private Task OnUserRolesChanged(UserInfo user) { private Task OnUserRolesChanged(UserEntity user) {
return RefreshUserRoles(user); return RefreshUserRoles(user);
} }
private void OnUserDeleted(UserInfo user) { private void OnUserDeleted(UserEntity user) {
allUsers = allUsers.Remove(user); allUsers = allUsers.Remove(user);
userGuidToRoleDescription.Remove(user.Guid); userGuidToRoleDescription.Remove(user.UserGuid);
} }
} }

View File

@ -2,16 +2,13 @@
using Phantom.Common.Logging; using Phantom.Common.Logging;
using Phantom.Utils.Cryptography; using Phantom.Utils.Cryptography;
using Phantom.Utils.IO; using Phantom.Utils.IO;
using Phantom.Utils.Rpc;
using Phantom.Utils.Runtime; using Phantom.Utils.Runtime;
using Phantom.Utils.Tasks; using Phantom.Utils.Tasks;
using Phantom.Web; using Phantom.Web;
using Phantom.Web.Services.Rpc;
var shutdownCancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
var shutdownCancellationToken = shutdownCancellationTokenSource.Token;
PosixSignals.RegisterCancellation(shutdownCancellationTokenSource, static () => { PosixSignals.RegisterCancellation(cancellationTokenSource, static () => {
PhantomLogger.Root.InformationHeading("Stopping Phantom Panel web..."); PhantomLogger.Root.InformationHeading("Stopping Phantom Panel web...");
}); });
@ -34,41 +31,26 @@ try {
var (controllerHost, controllerPort, webKeyToken, webKeyFilePath, webServerHost, webServerPort, webBasePath) = Variables.LoadOrStop(); var (controllerHost, controllerPort, webKeyToken, webKeyFilePath, webServerHost, webServerPort, webBasePath) = Variables.LoadOrStop();
var webKey = await WebKey.Load(webKeyToken, webKeyFilePath); string webKeysPath = Path.GetFullPath("./keys");
if (webKey == null) { CreateFolderOrStop(webKeysPath, Chmod.URWX);
return 1;
}
string dataProtectionKeysPath = Path.GetFullPath("./keys");
CreateFolderOrStop(dataProtectionKeysPath, Chmod.URWX);
var (controllerCertificate, webToken) = webKey.Value;
PhantomLogger.Root.InformationHeading("Launching Phantom Panel web..."); PhantomLogger.Root.InformationHeading("Launching Phantom Panel web...");
var taskManager = new TaskManager(PhantomLogger.Create<TaskManager>("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 { try {
var configuration = new Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, dataProtectionKeysPath, shutdownCancellationToken); var configuration = new Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, webKeysPath, cancellationTokenSource.Token);
var administratorToken = TokenGenerator.Create(60); var administratorToken = TokenGenerator.Create(60);
PhantomLogger.Root.Information("Your administrator token is: {AdministratorToken}", administratorToken); PhantomLogger.Root.Information("Your administrator token is: {AdministratorToken}", administratorToken);
PhantomLogger.Root.Information("For administrator setup, visit: {HttpUrl}{SetupPath}", configuration.HttpUrl, configuration.BasePath + "setup"); PhantomLogger.Root.Information("For administrator setup, visit: {HttpUrl}{SetupPath}", configuration.HttpUrl, configuration.BasePath + "setup");
var serviceConfiguration = new ServiceConfiguration(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken), shutdownCancellationToken); var serviceConfiguration = new ServiceConfiguration(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken), cancellationTokenSource.Token);
var webApplication = Launcher.CreateApplication(configuration, serviceConfiguration, taskManager); var webApplication = Launcher.CreateApplication(configuration, serviceConfiguration, taskManager);
await Launcher.Launch(configuration, webApplication); await Launcher.Launch(configuration, webApplication);
} finally { } finally {
shutdownCancellationTokenSource.Cancel(); cancellationTokenSource.Cancel();
await taskManager.Stop(); await taskManager.Stop();
rpcDisconnectSemaphore.Release();
await rpcTask;
rpcDisconnectSemaphore.Dispose();
} }
return 0; return 0;
@ -80,8 +62,7 @@ try {
PhantomLogger.Root.Fatal(e, "Caught exception in entry point."); PhantomLogger.Root.Fatal(e, "Caught exception in entry point.");
return 1; return 1;
} finally { } finally {
shutdownCancellationTokenSource.Dispose(); cancellationTokenSource.Dispose();
PhantomLogger.Root.Information("Bye!"); PhantomLogger.Root.Information("Bye!");
PhantomLogger.Dispose(); PhantomLogger.Dispose();
} }

View File

@ -7,8 +7,6 @@
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@using System.Diagnostics.CodeAnalysis @using System.Diagnostics.CodeAnalysis
@using Phantom.Common.Data @using Phantom.Common.Data
@using Phantom.Web.Services
@using Phantom.Web.Services.Instances
@inject INavigation Nav @inject INavigation Nav
@inject MinecraftVersions MinecraftVersions @inject MinecraftVersions MinecraftVersions
@inject AgentManager AgentManager @inject AgentManager AgentManager

View File

@ -1,5 +1,5 @@
@using Phantom.Web.Services.Instances @using Phantom.Web.Services.Instances
@using Phantom.Common.Data.Web.Users @using Phantom.Common.Data.Web.Users.Permissions
@using Phantom.Common.Data.Replies @using Phantom.Common.Data.Replies
@inherits PhantomComponent @inherits PhantomComponent
@inject InstanceManager InstanceManager @inject InstanceManager InstanceManager

View File

@ -1,7 +1,7 @@
@inherits PhantomComponent @inherits PhantomComponent
@using Phantom.Utils.Collections @using Phantom.Utils.Collections
@using System.Diagnostics @using System.Diagnostics
@using Phantom.Common.Data.Web.Users @using Phantom.Common.Data.Web.Users.Permissions
@implements IDisposable @implements IDisposable
@inject IJSRuntime Js; @inject IJSRuntime Js;
@inject InstanceLogManager InstanceLogManager @inject InstanceLogManager InstanceLogManager

View File

@ -1,6 +1,6 @@
@using Phantom.Web.Services.Instances @using Phantom.Controller.Services.Instances
@using Phantom.Controller.Services.Audit
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@using Phantom.Common.Data.Web.Users
@using Phantom.Common.Data.Minecraft @using Phantom.Common.Data.Minecraft
@using Phantom.Common.Data.Replies @using Phantom.Common.Data.Replies
@inherits PhantomComponent @inherits PhantomComponent

View File

@ -3,6 +3,8 @@
@using System.ComponentModel.DataAnnotations @using System.ComponentModel.DataAnnotations
@inherits PhantomComponent @inherits PhantomComponent
@inject IJSRuntime Js; @inject IJSRuntime Js;
@inject UserManager UserManager
@inject AuditLog AuditLog
<Form Model="form" OnSubmit="AddUser"> <Form Model="form" OnSubmit="AddUser">
<Modal Id="@ModalId" TitleText="Add User"> <Modal Id="@ModalId" TitleText="Add User">
@ -35,7 +37,7 @@
public string ModalId { get; set; } = string.Empty; public string ModalId { get; set; } = string.Empty;
[Parameter] [Parameter]
public EventCallback<UserInfo> UserAdded { get; set; } public EventCallback<UserEntity> UserAdded { get; set; }
private readonly AddUserFormModel form = new(); private readonly AddUserFormModel form = new();
@ -56,14 +58,14 @@
} }
switch (await UserManager.CreateUser(form.Username, form.Password)) { switch (await UserManager.CreateUser(form.Username, form.Password)) {
case Result<UserInfo, AddUserError>.Ok ok: case Result<UserEntity, AddUserError>.Ok ok:
await AuditLog.AddUserCreatedEvent(ok.Value); await AuditLog.AddUserCreatedEvent(ok.Value);
await UserAdded.InvokeAsync(ok.Value); await UserAdded.InvokeAsync(ok.Value);
await Js.InvokeVoidAsync("closeModal", ModalId); await Js.InvokeVoidAsync("closeModal", ModalId);
form.SubmitModel.StopSubmitting(); form.SubmitModel.StopSubmitting();
break; break;
case Result<UserInfo, AddUserError>.Fail fail: case Result<UserEntity, AddUserError>.Fail fail:
form.SubmitModel.StopSubmitting(fail.Error.ToSentences("\n")); form.SubmitModel.StopSubmitting(fail.Error.ToSentences("\n"));
break; break;
} }

View File

@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Phantom.Common.Data.Web.Users; using Phantom.Common.Data.Web.Users;
using Phantom.Common.Data.Web.Users.Permissions;
using Phantom.Web.Base; using Phantom.Web.Base;
using Phantom.Web.Components.Forms; using Phantom.Web.Components.Forms;

View File

@ -1,61 +0,0 @@
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);
}
}

View File

@ -12,6 +12,8 @@
@using Phantom.Web.Components.Forms @using Phantom.Web.Components.Forms
@using Phantom.Web.Components.Graphics @using Phantom.Web.Components.Graphics
@using Phantom.Web.Components.Tables @using Phantom.Web.Components.Tables
@using Phantom.Web.Identity
@using Phantom.Web.Identity.Authorization
@using Phantom.Web.Layout @using Phantom.Web.Layout
@using Phantom.Web.Shared @using Phantom.Web.Shared
@using Phantom.Web.Utils @using Phantom.Web.Utils