mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-25 16:42:54 +01:00
Compare commits
3 Commits
a6bdc6db12
...
0a29b6f21e
Author | SHA1 | Date | |
---|---|---|---|
0a29b6f21e | |||
8bee9bc763 | |||
091d7d8df2 |
@ -1,12 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="MemoryPack" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -1,22 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Web.Users;
|
|
||||||
|
|
||||||
[MemoryPackable]
|
|
||||||
[MemoryPackUnion(0, typeof(Success))]
|
|
||||||
[MemoryPackUnion(1, typeof(CreationFailed))]
|
|
||||||
[MemoryPackUnion(2, typeof(UpdatingFailed))]
|
|
||||||
[MemoryPackUnion(3, typeof(AddingToRoleFailed))]
|
|
||||||
public partial interface ICreateOrUpdateAdministratorUserResult {
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record Success(UserInfo User) : ICreateOrUpdateAdministratorUserResult;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record CreationFailed(AddUserError Error) : ICreateOrUpdateAdministratorUserResult;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record UpdatingFailed(SetUserPasswordError Error) : ICreateOrUpdateAdministratorUserResult;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record AddingToRoleFailed : ICreateOrUpdateAdministratorUserResult;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Web.Users;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record RoleInfo(
|
|
||||||
[property: MemoryPackOrder(0)] Guid Guid,
|
|
||||||
[property: MemoryPackOrder(1)] string Name
|
|
||||||
);
|
|
@ -1,9 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Web.Users;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record UserInfo(
|
|
||||||
[property: MemoryPackOrder(0)] Guid Guid,
|
|
||||||
[property: MemoryPackOrder(1)] string Name
|
|
||||||
);
|
|
@ -1,19 +0,0 @@
|
|||||||
namespace Phantom.Common.Data.Web.Users;
|
|
||||||
|
|
||||||
public abstract record UsernameRequirementViolation {
|
|
||||||
private UsernameRequirementViolation() {}
|
|
||||||
|
|
||||||
public sealed record IsEmpty : UsernameRequirementViolation;
|
|
||||||
|
|
||||||
public sealed record TooLong(int MaxLength) : UsernameRequirementViolation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class UsernameRequirementViolationExtensions {
|
|
||||||
public static string ToSentence(this UsernameRequirementViolation violation) {
|
|
||||||
return violation switch {
|
|
||||||
UsernameRequirementViolation.IsEmpty => "Username must not be empty.",
|
|
||||||
UsernameRequirementViolation.TooLong v => "Username must not be longer than " + v.MaxLength + " character(s).",
|
|
||||||
_ => "Unknown error."
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +1,8 @@
|
|||||||
using Phantom.Common.Data.Web.Users;
|
using Phantom.Common.Messages.Web.BiDirectional;
|
||||||
using Phantom.Common.Messages.Web.BiDirectional;
|
|
||||||
using Phantom.Common.Messages.Web.ToController;
|
|
||||||
using Phantom.Utils.Rpc.Message;
|
using Phantom.Utils.Rpc.Message;
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web;
|
namespace Phantom.Common.Messages.Web;
|
||||||
|
|
||||||
public interface IMessageToControllerListener {
|
public interface IMessageToControllerListener {
|
||||||
Task<ICreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUser message);
|
|
||||||
Task<NoReply> HandleReply(ReplyMessage message);
|
Task<NoReply> HandleReply(ReplyMessage message);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Phantom.Common.Logging\Phantom.Common.Logging.csproj" />
|
<ProjectReference Include="..\Phantom.Common.Logging\Phantom.Common.Logging.csproj" />
|
||||||
<ProjectReference Include="..\Phantom.Common.Data\Phantom.Common.Data.csproj" />
|
<ProjectReference Include="..\Phantom.Common.Data\Phantom.Common.Data.csproj" />
|
||||||
<ProjectReference Include="..\Phantom.Common.Data.Web\Phantom.Common.Data.Web.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Utils\Phantom.Utils.Rpc\Phantom.Utils.Rpc.csproj" />
|
<ProjectReference Include="..\..\Utils\Phantom.Utils.Rpc\Phantom.Utils.Rpc.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
using MemoryPack;
|
|
||||||
using Phantom.Utils.Rpc.Message;
|
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web.ToController;
|
|
||||||
|
|
||||||
[MemoryPackable(GenerateType.VersionTolerant)]
|
|
||||||
public sealed partial record CreateOrUpdateAdministratorUser(
|
|
||||||
[property: MemoryPackOrder(0)] string Username,
|
|
||||||
[property: MemoryPackOrder(1)] string Password
|
|
||||||
) : IMessageToController {
|
|
||||||
public Task<NoReply> Accept(IMessageToControllerListener listener) {
|
|
||||||
return listener.CreateOrUpdateAdministratorUser(this);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +1,19 @@
|
|||||||
using Phantom.Common.Logging;
|
using Phantom.Common.Logging;
|
||||||
using Phantom.Common.Messages.Web.BiDirectional;
|
using Phantom.Common.Messages.Web.BiDirectional;
|
||||||
using Phantom.Common.Messages.Web.ToController;
|
|
||||||
using Phantom.Utils.Rpc.Message;
|
using Phantom.Utils.Rpc.Message;
|
||||||
|
|
||||||
namespace Phantom.Common.Messages.Web;
|
namespace Phantom.Common.Messages.Web;
|
||||||
|
|
||||||
public static class WebMessageRegistries {
|
public static class WebMessageRegistries {
|
||||||
public static MessageRegistry<IMessageToControllerListener> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController)));
|
|
||||||
public static MessageRegistry<IMessageToWebListener> ToWeb { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToWeb)));
|
public static MessageRegistry<IMessageToWebListener> ToWeb { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToWeb)));
|
||||||
|
public static MessageRegistry<IMessageToControllerListener> ToController { get; } = new (PhantomLogger.Create("MessageRegistry", nameof(ToController)));
|
||||||
|
|
||||||
public static IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener> Definitions { get; } = new MessageDefinitions();
|
public static IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener> Definitions { get; } = new MessageDefinitions();
|
||||||
|
|
||||||
static WebMessageRegistries() {
|
static WebMessageRegistries() {
|
||||||
ToController.Add<CreateOrUpdateAdministratorUser>(1);
|
|
||||||
ToController.Add<ReplyMessage>(127);
|
|
||||||
|
|
||||||
ToWeb.Add<ReplyMessage>(127);
|
ToWeb.Add<ReplyMessage>(127);
|
||||||
|
|
||||||
|
ToController.Add<ReplyMessage>(127);
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener> {
|
private sealed class MessageDefinitions : IMessageDefinitions<IMessageToWebListener, IMessageToControllerListener> {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Phantom.Common.Data.Web.Users;
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Database.Entities;
|
namespace Phantom.Controller.Database.Entities;
|
||||||
|
|
||||||
@ -17,8 +16,4 @@ public sealed class UserEntity {
|
|||||||
Name = name;
|
Name = name;
|
||||||
PasswordHash = null!;
|
PasswordHash = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserInfo ToUserInfo() {
|
|
||||||
return new UserInfo(UserGuid, Name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
<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.Logging\Phantom.Common.Logging.csproj" />
|
<ProjectReference Include="..\..\Common\Phantom.Common.Logging\Phantom.Common.Logging.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
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.Controller.Database.Enums;
|
||||||
@ -9,11 +10,13 @@ namespace Phantom.Controller.Services.Audit;
|
|||||||
|
|
||||||
public sealed partial class AuditLog {
|
public sealed partial class AuditLog {
|
||||||
private readonly IDatabaseProvider databaseProvider;
|
private readonly IDatabaseProvider databaseProvider;
|
||||||
|
private readonly AuthenticationStateProvider authenticationStateProvider;
|
||||||
private readonly TaskManager taskManager;
|
private readonly TaskManager taskManager;
|
||||||
private readonly CancellationToken cancellationToken;
|
private readonly CancellationToken cancellationToken;
|
||||||
|
|
||||||
public AuditLog(IDatabaseProvider databaseProvider, TaskManager taskManager, CancellationToken cancellationToken) {
|
public AuditLog(IDatabaseProvider databaseProvider, AuthenticationStateProvider authenticationStateProvider, TaskManager taskManager, CancellationToken cancellationToken) {
|
||||||
this.databaseProvider = databaseProvider;
|
this.databaseProvider = databaseProvider;
|
||||||
|
this.authenticationStateProvider = authenticationStateProvider;
|
||||||
this.taskManager = taskManager;
|
this.taskManager = taskManager;
|
||||||
this.cancellationToken = cancellationToken;
|
this.cancellationToken = cancellationToken;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ using Phantom.Controller.Database;
|
|||||||
using Phantom.Controller.Minecraft;
|
using Phantom.Controller.Minecraft;
|
||||||
using Phantom.Controller.Rpc;
|
using Phantom.Controller.Rpc;
|
||||||
using Phantom.Controller.Services.Agents;
|
using Phantom.Controller.Services.Agents;
|
||||||
using Phantom.Controller.Services.Audit;
|
|
||||||
using Phantom.Controller.Services.Events;
|
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;
|
||||||
@ -20,9 +19,7 @@ namespace Phantom.Controller.Services;
|
|||||||
public sealed class ControllerServices {
|
public sealed class ControllerServices {
|
||||||
private TaskManager TaskManager { get; }
|
private TaskManager TaskManager { get; }
|
||||||
private MinecraftVersions MinecraftVersions { get; }
|
private MinecraftVersions MinecraftVersions { get; }
|
||||||
|
|
||||||
private AuditLog AuditLog { get; }
|
|
||||||
|
|
||||||
private AgentManager AgentManager { get; }
|
private AgentManager AgentManager { get; }
|
||||||
private AgentJavaRuntimesManager AgentJavaRuntimesManager { get; }
|
private AgentJavaRuntimesManager AgentJavaRuntimesManager { get; }
|
||||||
private EventLog EventLog { get; }
|
private EventLog EventLog { get; }
|
||||||
@ -41,15 +38,13 @@ public sealed class ControllerServices {
|
|||||||
this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, ControllerServices>());
|
this.TaskManager = new TaskManager(PhantomLogger.Create<TaskManager, ControllerServices>());
|
||||||
this.MinecraftVersions = new MinecraftVersions();
|
this.MinecraftVersions = new MinecraftVersions();
|
||||||
|
|
||||||
this.AuditLog = new AuditLog(databaseProvider, TaskManager, shutdownCancellationToken);
|
|
||||||
|
|
||||||
this.AgentManager = new AgentManager(agentAuthToken, databaseProvider, TaskManager, shutdownCancellationToken);
|
this.AgentManager = new AgentManager(agentAuthToken, databaseProvider, TaskManager, shutdownCancellationToken);
|
||||||
this.AgentJavaRuntimesManager = new AgentJavaRuntimesManager();
|
this.AgentJavaRuntimesManager = new AgentJavaRuntimesManager();
|
||||||
this.EventLog = new EventLog(databaseProvider, TaskManager, shutdownCancellationToken);
|
this.EventLog = new EventLog(databaseProvider, TaskManager, shutdownCancellationToken);
|
||||||
this.InstanceManager = new InstanceManager(AgentManager, MinecraftVersions, databaseProvider, shutdownCancellationToken);
|
this.InstanceManager = new InstanceManager(AgentManager, MinecraftVersions, databaseProvider, shutdownCancellationToken);
|
||||||
this.InstanceLogManager = new InstanceLogManager();
|
this.InstanceLogManager = new InstanceLogManager();
|
||||||
|
|
||||||
this.UserManager = new UserManager(databaseProvider, AuditLog);
|
this.UserManager = new UserManager(databaseProvider);
|
||||||
this.RoleManager = new RoleManager(databaseProvider);
|
this.RoleManager = new RoleManager(databaseProvider);
|
||||||
this.UserRoleManager = new UserRoleManager(databaseProvider);
|
this.UserRoleManager = new UserRoleManager(databaseProvider);
|
||||||
this.PermissionManager = new PermissionManager(databaseProvider);
|
this.PermissionManager = new PermissionManager(databaseProvider);
|
||||||
@ -63,7 +58,7 @@ public sealed class ControllerServices {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public WebMessageListener CreateWebMessageListener(RpcClientConnection<IMessageToWebListener> connection) {
|
public WebMessageListener CreateWebMessageListener(RpcClientConnection<IMessageToWebListener> connection) {
|
||||||
return new WebMessageListener(connection, AuditLog, UserManager, RoleManager, UserRoleManager);
|
return new WebMessageListener(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Initialize() {
|
public async Task Initialize() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
@ -1,64 +1,15 @@
|
|||||||
using Phantom.Common.Messages.Web;
|
using Phantom.Common.Messages.Web;
|
||||||
using Phantom.Common.Messages.Web.BiDirectional;
|
using Phantom.Common.Messages.Web.BiDirectional;
|
||||||
using Phantom.Common.Messages.Web.ToController;
|
|
||||||
using Phantom.Controller.Database.Entities;
|
|
||||||
using Phantom.Controller.Rpc;
|
using Phantom.Controller.Rpc;
|
||||||
using Phantom.Controller.Services.Audit;
|
|
||||||
using Phantom.Controller.Services.Users;
|
|
||||||
using Phantom.Controller.Services.Users.Roles;
|
|
||||||
using Phantom.Utils.Rpc.Message;
|
using Phantom.Utils.Rpc.Message;
|
||||||
using Phantom.Utils.Tasks;
|
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Rpc;
|
namespace Phantom.Controller.Services.Rpc;
|
||||||
|
|
||||||
public sealed class WebMessageListener : IMessageToControllerListener {
|
public sealed class WebMessageListener : IMessageToControllerListener {
|
||||||
private readonly RpcClientConnection<IMessageToWebListener> connection;
|
private readonly RpcClientConnection<IMessageToWebListener> connection;
|
||||||
private readonly AuditLog auditLog;
|
|
||||||
private readonly UserManager userManager;
|
|
||||||
private readonly RoleManager roleManager;
|
|
||||||
private readonly UserRoleManager userRoleManager;
|
|
||||||
|
|
||||||
internal WebMessageListener(RpcClientConnection<IMessageToWebListener> connection, AuditLog auditLog, UserManager userManager, RoleManager roleManager, UserRoleManager userRoleManager) {
|
internal WebMessageListener(RpcClientConnection<IMessageToWebListener> connection) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.auditLog = auditLog;
|
|
||||||
this.userManager = userManager;
|
|
||||||
this.roleManager = roleManager;
|
|
||||||
this.userRoleManager = userRoleManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ICreateOrUpdateAdministratorUserResult> CreateOrUpdateAdministratorUser(CreateOrUpdateAdministratorUser message) {
|
|
||||||
UserEntity administratorUser = null!;
|
|
||||||
|
|
||||||
var existingUser = await userManager.GetByName(message.Username);
|
|
||||||
if (existingUser == null) {
|
|
||||||
var result = await userManager.CreateUser(message.Username, message.Password);
|
|
||||||
switch (result) {
|
|
||||||
case Result<UserEntity, AddUserError>.Ok ok:
|
|
||||||
administratorUser = ok.Value;
|
|
||||||
await auditLog.AddAdministratorUserCreatedEvent(administratorUser);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Result<UserEntity, AddUserError>.Fail fail:
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.CreationFailed(fail.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var result = await userManager.SetUserPassword(existingUser.UserGuid, message.Password);
|
|
||||||
if (result is Result<SetUserPasswordError>.Fail fail) {
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.UpdatingFailed(fail.Error);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
administratorUser = existingUser;
|
|
||||||
await auditLog.AddAdministratorUserModifiedEvent(administratorUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var administratorRole = await roleManager.GetByGuid(Role.Administrator.Guid);
|
|
||||||
if (administratorRole == null || !await userRoleManager.Add(administratorUser, administratorRole)) {
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.AddingToRoleFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.Success(administratorUser.ToUserInfo());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<NoReply> HandleReply(ReplyMessage message) {
|
public Task<NoReply> HandleReply(ReplyMessage message) {
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Web.Users;
|
namespace Phantom.Controller.Services.Users;
|
||||||
|
|
||||||
public abstract record AddUserError {
|
public abstract record AddUserError {
|
||||||
private AddUserError() {}
|
private AddUserError() {}
|
||||||
|
|
||||||
public sealed record NameIsInvalid(UsernameRequirementViolation Violation) : AddUserError;
|
public sealed record NameIsEmpty : AddUserError;
|
||||||
|
|
||||||
public sealed record PasswordIsInvalid(ImmutableArray<PasswordRequirementViolation> Violations) : AddUserError;
|
public sealed record NameIsTooLong(int MaximumLength) : AddUserError;
|
||||||
|
|
||||||
public sealed record NameAlreadyExists : AddUserError;
|
public sealed record NameAlreadyExists : AddUserError;
|
||||||
|
|
||||||
|
public sealed record PasswordIsInvalid(ImmutableArray<PasswordRequirementViolation> Violations) : AddUserError;
|
||||||
|
|
||||||
public sealed record UnknownError : AddUserError;
|
public sealed record UnknownError : AddUserError;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class AddUserErrorExtensions {
|
public static class AddUserErrorExtensions {
|
||||||
public static string ToSentences(this AddUserError error, string delimiter) {
|
public static string ToSentences(this AddUserError error, string delimiter) {
|
||||||
return error switch {
|
return error switch {
|
||||||
AddUserError.NameIsInvalid e => e.Violation.ToSentence(),
|
AddUserError.NameIsEmpty => "Name cannot be empty.",
|
||||||
|
AddUserError.NameIsTooLong e => "Name cannot be longer than " + e.MaximumLength + " character(s).",
|
||||||
|
AddUserError.NameAlreadyExists => "Name is already occupied.",
|
||||||
AddUserError.PasswordIsInvalid e => string.Join(delimiter, e.Violations.Select(static v => v.ToSentence())),
|
AddUserError.PasswordIsInvalid e => string.Join(delimiter, e.Violations.Select(static v => v.ToSentence())),
|
||||||
AddUserError.NameAlreadyExists => "Username is already occupied.",
|
|
||||||
_ => "Unknown error."
|
_ => "Unknown error."
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
namespace Phantom.Common.Data.Web.Users;
|
namespace Phantom.Controller.Services.Users;
|
||||||
|
|
||||||
public abstract record PasswordRequirementViolation {
|
public abstract record PasswordRequirementViolation {
|
||||||
private PasswordRequirementViolation() {}
|
private PasswordRequirementViolation() {}
|
@ -1,4 +1,4 @@
|
|||||||
namespace Phantom.Common.Data.Web.Users.Permissions;
|
namespace Phantom.Controller.Services.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 ();
|
@ -5,7 +5,7 @@ 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 ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Users.Permissions;
|
namespace Phantom.Controller.Services.Users.Permissions;
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using Phantom.Controller.Services.Users.Permissions;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Users.Roles;
|
namespace Phantom.Controller.Services.Users.Roles;
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ 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 ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Users.Roles;
|
namespace Phantom.Controller.Services.Users.Roles;
|
||||||
|
|
||||||
@ -49,13 +49,11 @@ public sealed class UserRoleManager {
|
|||||||
await using var ctx = databaseProvider.Provide();
|
await using var ctx = databaseProvider.Provide();
|
||||||
|
|
||||||
var userRole = await ctx.UserRoles.FindAsync(user.UserGuid, role.RoleGuid);
|
var userRole = await ctx.UserRoles.FindAsync(user.UserGuid, role.RoleGuid);
|
||||||
if (userRole != null) {
|
if (userRole == null) {
|
||||||
return true;
|
userRole = new UserRoleEntity(user.UserGuid, role.RoleGuid);
|
||||||
|
ctx.UserRoles.Add(userRole);
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
userRole = new UserRoleEntity(user.UserGuid, role.RoleGuid);
|
|
||||||
ctx.UserRoles.Add(userRole);
|
|
||||||
await ctx.SaveChangesAsync();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.Error(e, "Could not add user \"{UserName}\" (GUID {UserGuid}) to role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
Logger.Error(e, "Could not add user \"{UserName}\" (GUID {UserGuid}) to role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||||
return false;
|
return false;
|
||||||
@ -70,12 +68,10 @@ public sealed class UserRoleManager {
|
|||||||
await using var ctx = databaseProvider.Provide();
|
await using var ctx = databaseProvider.Provide();
|
||||||
|
|
||||||
var userRole = await ctx.UserRoles.FindAsync(user.UserGuid, role.RoleGuid);
|
var userRole = await ctx.UserRoles.FindAsync(user.UserGuid, role.RoleGuid);
|
||||||
if (userRole == null) {
|
if (userRole != null) {
|
||||||
return true;
|
ctx.UserRoles.Remove(userRole);
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.UserRoles.Remove(userRole);
|
|
||||||
await ctx.SaveChangesAsync();
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.Error(e, "Could not remove user \"{UserName}\" (GUID {UserGuid}) from role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
Logger.Error(e, "Could not remove user \"{UserName}\" (GUID {UserGuid}) from role \"{RoleName}\" (GUID {RoleGuid}).", user.Name, user.UserGuid, role.Name, role.RoleGuid);
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
namespace Phantom.Common.Data.Web.Users;
|
namespace Phantom.Controller.Services.Users;
|
||||||
|
|
||||||
public abstract record SetUserPasswordError {
|
public abstract record SetUserPasswordError {
|
||||||
private SetUserPasswordError() {}
|
private SetUserPasswordError() {}
|
@ -1,26 +1,25 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
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.Audit;
|
|
||||||
using Phantom.Controller.Services.Users.Roles;
|
|
||||||
using Phantom.Utils.Collections;
|
using Phantom.Utils.Collections;
|
||||||
using Phantom.Utils.Tasks;
|
using Phantom.Utils.Tasks;
|
||||||
using Serilog;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Users;
|
namespace Phantom.Controller.Services.Users;
|
||||||
|
|
||||||
sealed class UserManager {
|
public sealed class UserManager {
|
||||||
private static readonly ILogger Logger = PhantomLogger.Create<UserManager>();
|
private static readonly ILogger Logger = PhantomLogger.Create<UserManager>();
|
||||||
|
|
||||||
private readonly IDatabaseProvider databaseProvider;
|
private const int MaxUserNameLength = 40;
|
||||||
private readonly AuditLog auditLog;
|
|
||||||
|
|
||||||
public UserManager(IDatabaseProvider databaseProvider, AuditLog auditLog) {
|
private readonly IDatabaseProvider databaseProvider;
|
||||||
|
|
||||||
|
public UserManager(IDatabaseProvider databaseProvider) {
|
||||||
this.databaseProvider = databaseProvider;
|
this.databaseProvider = databaseProvider;
|
||||||
this.auditLog = auditLog;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Guid? GetAuthenticatedUserId(ClaimsPrincipal user) {
|
public static Guid? GetAuthenticatedUserId(ClaimsPrincipal user) {
|
||||||
@ -58,10 +57,10 @@ sealed class UserManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (UserValidation.VerifyPassword(user, password)) {
|
switch (UserPasswords.Verify(user, password)) {
|
||||||
case PasswordVerificationResult.SuccessRehashNeeded:
|
case PasswordVerificationResult.SuccessRehashNeeded:
|
||||||
try {
|
try {
|
||||||
UserValidation.SetPassword(user, password);
|
UserPasswords.Set(user, password);
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.Warning(e, "Could not rehash password for \"{Username}\".", user.Name);
|
Logger.Warning(e, "Could not rehash password for \"{Username}\".", user.Name);
|
||||||
@ -79,74 +78,62 @@ sealed class UserManager {
|
|||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ICreateOrUpdateAdministratorUserResult> CreateAdministratorUser(string username, string password) {
|
|
||||||
await using var editor = new Editor(databaseProvider);
|
|
||||||
|
|
||||||
var createUserResult = await editor.CreateUser(username, password);
|
|
||||||
if (!createUserResult) {
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.CreationFailed(createUserResult.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserEntity administratorUser = null!;
|
|
||||||
|
|
||||||
var existingUser = await userManager.GetByName(message.Username);
|
|
||||||
if (existingUser == null) {
|
|
||||||
var result = await userManager.CreateUser(message.Username, message.Password);
|
|
||||||
switch (result) {
|
|
||||||
case Result<UserEntity, AddUserError>.Ok ok:
|
|
||||||
administratorUser = ok.Value;
|
|
||||||
await auditLog.AddAdministratorUserCreatedEvent(administratorUser);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Result<UserEntity, AddUserError>.Fail fail:
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.CreationFailed(fail.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var result = await userManager.SetUserPassword(existingUser.UserGuid, message.Password);
|
|
||||||
if (result is Result<SetUserPasswordError>.Fail fail) {
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.UpdatingFailed(fail.Error);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
administratorUser = existingUser;
|
|
||||||
await auditLog.AddAdministratorUserModifiedEvent(administratorUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var administratorRole = await roleManager.GetByGuid(Role.Administrator.Guid);
|
|
||||||
if (administratorRole == null || !await userRoleManager.Add(administratorUser, administratorRole)) {
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.AddingToRoleFailed();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ICreateOrUpdateAdministratorUserResult.Success(administratorUser.ToUserInfo());
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Result<UserEntity, AddUserError>> CreateUser(string username, string password) {
|
public async Task<Result<UserEntity, AddUserError>> CreateUser(string username, string password) {
|
||||||
await using var editor = new Editor(databaseProvider);
|
if (string.IsNullOrWhiteSpace(username)) {
|
||||||
return await editor.CreateUser(username, password);
|
return Result.Fail<UserEntity, AddUserError>(new AddUserError.NameIsEmpty());
|
||||||
|
}
|
||||||
|
else if (username.Length > MaxUserNameLength) {
|
||||||
|
return Result.Fail<UserEntity, AddUserError>(new AddUserError.NameIsTooLong(MaxUserNameLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
var requirementViolations = UserPasswords.CheckRequirements(password);
|
||||||
|
if (!requirementViolations.IsEmpty) {
|
||||||
|
return Result.Fail<UserEntity, AddUserError>(new AddUserError.PasswordIsInvalid(requirementViolations));
|
||||||
|
}
|
||||||
|
|
||||||
|
UserEntity newUser;
|
||||||
|
try {
|
||||||
|
await using var ctx = databaseProvider.Provide();
|
||||||
|
|
||||||
|
if (await ctx.Users.AnyAsync(user => user.Name == username)) {
|
||||||
|
return Result.Fail<UserEntity, AddUserError>(new AddUserError.NameAlreadyExists());
|
||||||
|
}
|
||||||
|
|
||||||
|
newUser = new UserEntity(Guid.NewGuid(), username);
|
||||||
|
UserPasswords.Set(newUser, password);
|
||||||
|
|
||||||
|
ctx.Users.Add(newUser);
|
||||||
|
await ctx.SaveChangesAsync();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.Error(e, "Could not create user \"{Name}\".", username);
|
||||||
|
return Result.Fail<UserEntity, AddUserError>(new AddUserError.UnknownError());
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Information("Created user \"{Name}\" (GUID {Guid}).", username, newUser.UserGuid);
|
||||||
|
return Result.Ok<UserEntity, AddUserError>(newUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<SetUserPasswordError>> SetUserPassword(Guid guid, string password) {
|
public async Task<Result<SetUserPasswordError>> SetUserPassword(Guid guid, string password) {
|
||||||
UserEntity foundUser;
|
UserEntity foundUser;
|
||||||
|
|
||||||
await using (var ctx = databaseProvider.Provide()) {
|
await using (var ctx = databaseProvider.Provide()) {
|
||||||
var user = await ctx.Users.FindAsync(guid);
|
var user = await ctx.Users.FindAsync(guid);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return new SetUserPasswordError.UserNotFound();
|
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.UserNotFound());
|
||||||
}
|
}
|
||||||
|
|
||||||
foundUser = user;
|
foundUser = user;
|
||||||
try {
|
try {
|
||||||
var requirementViolations = UserValidation.CheckPasswordRequirements(password);
|
var requirementViolations = UserPasswords.CheckRequirements(password);
|
||||||
if (!requirementViolations.IsEmpty) {
|
if (!requirementViolations.IsEmpty) {
|
||||||
return new SetUserPasswordError.PasswordIsInvalid(requirementViolations);
|
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.PasswordIsInvalid(requirementViolations));
|
||||||
}
|
}
|
||||||
|
|
||||||
UserValidation.SetPassword(user, password);
|
UserPasswords.Set(user, password);
|
||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.Error(e, "Could not change password for user \"{Name}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
Logger.Error(e, "Could not change password for user \"{Name}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
||||||
return new SetUserPasswordError.UnknownError();
|
return Result.Fail<SetUserPasswordError>(new SetUserPasswordError.UnknownError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,73 +142,19 @@ sealed class UserManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DeleteUserResult> DeleteByGuid(Guid guid) {
|
public async Task<DeleteUserResult> DeleteByGuid(Guid guid) {
|
||||||
await using var editor = new Editor(databaseProvider);
|
await using var ctx = databaseProvider.Provide();
|
||||||
return await editor.DeleteUserByGuid(guid);
|
var user = await ctx.Users.FindAsync(guid);
|
||||||
}
|
if (user == null) {
|
||||||
|
return DeleteUserResult.NotFound;
|
||||||
private sealed class Editor : IAsyncDisposable {
|
|
||||||
public ApplicationDbContext Ctx => cachedContext ??= databaseProvider.Provide();
|
|
||||||
|
|
||||||
private readonly IDatabaseProvider databaseProvider;
|
|
||||||
private ApplicationDbContext? cachedContext;
|
|
||||||
|
|
||||||
public Editor(IDatabaseProvider databaseProvider) {
|
|
||||||
this.databaseProvider = databaseProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ValueTask DisposeAsync() {
|
try {
|
||||||
return cachedContext?.DisposeAsync() ?? ValueTask.CompletedTask;
|
ctx.Users.Remove(user);
|
||||||
}
|
await ctx.SaveChangesAsync();
|
||||||
|
return DeleteUserResult.Deleted;
|
||||||
public Task<UserEntity?> GetByName(string username) {
|
} catch (Exception e) {
|
||||||
return Ctx.Users.FirstOrDefaultAsync(user => user.Name == username);
|
Logger.Error(e, "Could not delete user \"{Name}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
||||||
}
|
return DeleteUserResult.Failed;
|
||||||
|
|
||||||
public async Task<Result<UserEntity, AddUserError>> CreateUser(string username, string password) {
|
|
||||||
var usernameRequirementViolation = UserValidation.CheckUsernameRequirements(username);
|
|
||||||
if (usernameRequirementViolation != null) {
|
|
||||||
return new AddUserError.NameIsInvalid(usernameRequirementViolation);
|
|
||||||
}
|
|
||||||
|
|
||||||
var passwordRequirementViolations = UserValidation.CheckPasswordRequirements(password);
|
|
||||||
if (!passwordRequirementViolations.IsEmpty) {
|
|
||||||
return new AddUserError.PasswordIsInvalid(passwordRequirementViolations);
|
|
||||||
}
|
|
||||||
|
|
||||||
UserEntity newUser;
|
|
||||||
try {
|
|
||||||
if (await Ctx.Users.AnyAsync(user => user.Name == username)) {
|
|
||||||
return new AddUserError.NameAlreadyExists();
|
|
||||||
}
|
|
||||||
|
|
||||||
newUser = new UserEntity(Guid.NewGuid(), username);
|
|
||||||
UserValidation.SetPassword(newUser, password);
|
|
||||||
|
|
||||||
Ctx.Users.Add(newUser);
|
|
||||||
await Ctx.SaveChangesAsync();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.Error(e, "Could not create user \"{Name}\".", username);
|
|
||||||
return new AddUserError.UnknownError();
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Information("Created user \"{Name}\" (GUID {Guid}).", username, newUser.UserGuid);
|
|
||||||
return newUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<DeleteUserResult> DeleteUserByGuid(Guid guid) {
|
|
||||||
var user = await Ctx.Users.FindAsync(guid);
|
|
||||||
if (user == null) {
|
|
||||||
return DeleteUserResult.NotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Ctx.Users.Remove(user);
|
|
||||||
await Ctx.SaveChangesAsync();
|
|
||||||
return DeleteUserResult.Deleted;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.Error(e, "Could not delete user \"{Name}\" (GUID {Guid}).", user.Name, user.UserGuid);
|
|
||||||
return DeleteUserResult.Failed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,19 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Phantom.Controller.Database.Entities;
|
using Phantom.Controller.Database.Entities;
|
||||||
|
|
||||||
namespace Phantom.Controller.Services.Users;
|
namespace Phantom.Controller.Services.Users;
|
||||||
|
|
||||||
static class UserValidation {
|
static class UserPasswords {
|
||||||
private static PasswordHasher<UserEntity> Hasher { get; } = new ();
|
private static PasswordHasher<UserEntity> Hasher { get; } = new ();
|
||||||
|
|
||||||
private const int MaxUserNameLength = 40;
|
private const int MinimumLength = 16;
|
||||||
private const int MinimumPasswordLength = 16;
|
|
||||||
|
public static ImmutableArray<PasswordRequirementViolation> CheckRequirements(string password) {
|
||||||
public static UsernameRequirementViolation? CheckUsernameRequirements(string username) {
|
|
||||||
if (string.IsNullOrWhiteSpace(username)) {
|
|
||||||
return new UsernameRequirementViolation.IsEmpty();
|
|
||||||
}
|
|
||||||
else if (username.Length > MaxUserNameLength) {
|
|
||||||
return new UsernameRequirementViolation.TooLong(MaxUserNameLength);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ImmutableArray<PasswordRequirementViolation> CheckPasswordRequirements(string password) {
|
|
||||||
var violations = ImmutableArray.CreateBuilder<PasswordRequirementViolation>();
|
var violations = ImmutableArray.CreateBuilder<PasswordRequirementViolation>();
|
||||||
|
|
||||||
if (password.Length < MinimumPasswordLength) {
|
if (password.Length < MinimumLength) {
|
||||||
violations.Add(new PasswordRequirementViolation.TooShort(MinimumPasswordLength));
|
violations.Add(new PasswordRequirementViolation.TooShort(MinimumLength));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password.Any(char.IsLower)) {
|
if (!password.Any(char.IsLower)) {
|
||||||
@ -43,11 +31,11 @@ static class UserValidation {
|
|||||||
return violations.ToImmutable();
|
return violations.ToImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetPassword(UserEntity user, string password) {
|
public static void Set(UserEntity user, string password) {
|
||||||
user.PasswordHash = Hasher.HashPassword(user, password);
|
user.PasswordHash = Hasher.HashPassword(user, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PasswordVerificationResult VerifyPassword(UserEntity user, string password) {
|
public static PasswordVerificationResult Verify(UserEntity user, string password) {
|
||||||
return Hasher.VerifyHashedPassword(user, user.PasswordHash, password);
|
return Hasher.VerifyHashedPassword(user, user.PasswordHash, password);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,8 +26,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Common.Data", "Comm
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Common.Data.Tests", "Common\Phantom.Common.Data.Tests\Phantom.Common.Data.Tests.csproj", "{435D7981-DFDA-46A0-8CD8-CD8C117935D7}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Common.Data.Tests", "Common\Phantom.Common.Data.Tests\Phantom.Common.Data.Tests.csproj", "{435D7981-DFDA-46A0-8CD8-CD8C117935D7}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Common.Data.Web", "Common\Phantom.Common.Data.Web\Phantom.Common.Data.Web.csproj", "{BC969D0B-0019-48E0-9FAF-F5CC906AAF09}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Common.Logging", "Common\Phantom.Common.Logging\Phantom.Common.Logging.csproj", "{D7F55010-B3ED-42A5-8D83-E754FFC5F2A2}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Common.Logging", "Common\Phantom.Common.Logging\Phantom.Common.Logging.csproj", "{D7F55010-B3ED-42A5-8D83-E754FFC5F2A2}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Common.Messages.Agent", "Common\Phantom.Common.Messages.Agent\Phantom.Common.Messages.Agent.csproj", "{95B55357-F8F0-48C2-A1C2-5EA997651783}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Common.Messages.Agent", "Common\Phantom.Common.Messages.Agent\Phantom.Common.Messages.Agent.csproj", "{95B55357-F8F0-48C2-A1C2-5EA997651783}"
|
||||||
@ -60,7 +58,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Web.Bootstrap", "We
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Web.Components", "Web\Phantom.Web.Components\Phantom.Web.Components.csproj", "{3F4F9059-F869-42D3-B92C-90D27ADFC42D}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Web.Components", "Web\Phantom.Web.Components\Phantom.Web.Components.csproj", "{3F4F9059-F869-42D3-B92C-90D27ADFC42D}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Web.Services", "Web\Phantom.Web.Services\Phantom.Web.Services.csproj", "{7B0EEE34-A586-4629-AC51-16757DE53261}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phantom.Web.Identity", "Web\Phantom.Web.Identity\Phantom.Web.Identity.csproj", "{A9870842-FE7A-4760-95DC-9D485DDDA31F}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
@ -92,10 +90,6 @@ Global
|
|||||||
{435D7981-DFDA-46A0-8CD8-CD8C117935D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{435D7981-DFDA-46A0-8CD8-CD8C117935D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{435D7981-DFDA-46A0-8CD8-CD8C117935D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{435D7981-DFDA-46A0-8CD8-CD8C117935D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{435D7981-DFDA-46A0-8CD8-CD8C117935D7}.Release|Any CPU.Build.0 = Release|Any CPU
|
{435D7981-DFDA-46A0-8CD8-CD8C117935D7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{BC969D0B-0019-48E0-9FAF-F5CC906AAF09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{BC969D0B-0019-48E0-9FAF-F5CC906AAF09}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{BC969D0B-0019-48E0-9FAF-F5CC906AAF09}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{BC969D0B-0019-48E0-9FAF-F5CC906AAF09}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{D7F55010-B3ED-42A5-8D83-E754FFC5F2A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{D7F55010-B3ED-42A5-8D83-E754FFC5F2A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{D7F55010-B3ED-42A5-8D83-E754FFC5F2A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D7F55010-B3ED-42A5-8D83-E754FFC5F2A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D7F55010-B3ED-42A5-8D83-E754FFC5F2A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D7F55010-B3ED-42A5-8D83-E754FFC5F2A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@ -156,10 +150,10 @@ Global
|
|||||||
{3F4F9059-F869-42D3-B92C-90D27ADFC42D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3F4F9059-F869-42D3-B92C-90D27ADFC42D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{3F4F9059-F869-42D3-B92C-90D27ADFC42D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3F4F9059-F869-42D3-B92C-90D27ADFC42D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3F4F9059-F869-42D3-B92C-90D27ADFC42D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3F4F9059-F869-42D3-B92C-90D27ADFC42D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{7B0EEE34-A586-4629-AC51-16757DE53261}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A9870842-FE7A-4760-95DC-9D485DDDA31F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{7B0EEE34-A586-4629-AC51-16757DE53261}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A9870842-FE7A-4760-95DC-9D485DDDA31F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{7B0EEE34-A586-4629-AC51-16757DE53261}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A9870842-FE7A-4760-95DC-9D485DDDA31F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{7B0EEE34-A586-4629-AC51-16757DE53261}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A9870842-FE7A-4760-95DC-9D485DDDA31F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{418BE1BF-9F63-4B46-B4E4-DF64C3B3DDA7} = {F5878792-64C8-4ECF-A075-66341FF97127}
|
{418BE1BF-9F63-4B46-B4E4-DF64C3B3DDA7} = {F5878792-64C8-4ECF-A075-66341FF97127}
|
||||||
@ -171,7 +165,6 @@ Global
|
|||||||
{95B55357-F8F0-48C2-A1C2-5EA997651783} = {01CB1A81-8950-471C-BFDF-F135FDDB2C18}
|
{95B55357-F8F0-48C2-A1C2-5EA997651783} = {01CB1A81-8950-471C-BFDF-F135FDDB2C18}
|
||||||
{6E798DEB-8921-41A2-8AFB-E4416A9E0704} = {01CB1A81-8950-471C-BFDF-F135FDDB2C18}
|
{6E798DEB-8921-41A2-8AFB-E4416A9E0704} = {01CB1A81-8950-471C-BFDF-F135FDDB2C18}
|
||||||
{435D7981-DFDA-46A0-8CD8-CD8C117935D7} = {D781E00D-8563-4102-A0CD-477A679193B5}
|
{435D7981-DFDA-46A0-8CD8-CD8C117935D7} = {D781E00D-8563-4102-A0CD-477A679193B5}
|
||||||
{BC969D0B-0019-48E0-9FAF-F5CC906AAF09} = {01CB1A81-8950-471C-BFDF-F135FDDB2C18}
|
|
||||||
{A0F1C595-96B6-4DBF-8C16-6B99223F8F35} = {0AB9471E-6228-4EB7-802E-3102B3952AAD}
|
{A0F1C595-96B6-4DBF-8C16-6B99223F8F35} = {0AB9471E-6228-4EB7-802E-3102B3952AAD}
|
||||||
{E3AD566F-384A-489A-A3BB-EA3BA400C18C} = {0AB9471E-6228-4EB7-802E-3102B3952AAD}
|
{E3AD566F-384A-489A-A3BB-EA3BA400C18C} = {0AB9471E-6228-4EB7-802E-3102B3952AAD}
|
||||||
{81625B4A-3DB6-48BD-A739-D23DA02107D1} = {0AB9471E-6228-4EB7-802E-3102B3952AAD}
|
{81625B4A-3DB6-48BD-A739-D23DA02107D1} = {0AB9471E-6228-4EB7-802E-3102B3952AAD}
|
||||||
@ -185,6 +178,6 @@ Global
|
|||||||
{7CA2E5FE-E507-4DC6-930C-E18711A9F856} = {92B26F48-235F-4500-BD55-800F06A0BA39}
|
{7CA2E5FE-E507-4DC6-930C-E18711A9F856} = {92B26F48-235F-4500-BD55-800F06A0BA39}
|
||||||
{83FA86DB-34E4-4C2C-832C-90F491CA10C7} = {92B26F48-235F-4500-BD55-800F06A0BA39}
|
{83FA86DB-34E4-4C2C-832C-90F491CA10C7} = {92B26F48-235F-4500-BD55-800F06A0BA39}
|
||||||
{3F4F9059-F869-42D3-B92C-90D27ADFC42D} = {92B26F48-235F-4500-BD55-800F06A0BA39}
|
{3F4F9059-F869-42D3-B92C-90D27ADFC42D} = {92B26F48-235F-4500-BD55-800F06A0BA39}
|
||||||
{7B0EEE34-A586-4629-AC51-16757DE53261} = {92B26F48-235F-4500-BD55-800F06A0BA39}
|
{A9870842-FE7A-4760-95DC-9D485DDDA31F} = {92B26F48-235F-4500-BD55-800F06A0BA39}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@ -2,44 +2,15 @@
|
|||||||
|
|
||||||
public abstract record Result<TValue, TError> {
|
public abstract record Result<TValue, TError> {
|
||||||
private Result() {}
|
private Result() {}
|
||||||
|
|
||||||
public abstract TValue Value { get; init; }
|
|
||||||
public abstract TError Error { get; init; }
|
|
||||||
|
|
||||||
public static implicit operator Result<TValue, TError>(TValue value) {
|
|
||||||
return new Ok(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator Result<TValue, TError>(TError error) {
|
public sealed record Ok(TValue Value) : Result<TValue, TError>;
|
||||||
return new Fail(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator bool(Result<TValue, TError> result) {
|
|
||||||
return result is Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed record Ok(TValue Value) : Result<TValue, TError> {
|
public sealed record Fail(TError Error) : Result<TValue, TError>;
|
||||||
public override TError Error {
|
|
||||||
get => throw new InvalidOperationException("Attempted to get error from Ok result.");
|
|
||||||
init {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed record Fail(TError Error) : Result<TValue, TError> {
|
|
||||||
public override TValue Value {
|
|
||||||
get => throw new InvalidOperationException("Attempted to get value from Fail result.");
|
|
||||||
init {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract record Result<TError> {
|
public abstract record Result<TError> {
|
||||||
private Result() {}
|
private Result() {}
|
||||||
|
|
||||||
public static implicit operator Result<TError>(TError error) {
|
|
||||||
return new Fail(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed record Ok : Result<TError> {
|
public sealed record Ok : Result<TError> {
|
||||||
internal static Ok Instance { get; } = new ();
|
internal static Ok Instance { get; } = new ();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Phantom.Common.Logging;
|
||||||
using Phantom.Utils.Cryptography;
|
using Phantom.Utils.Cryptography;
|
||||||
using Phantom.Web.Identity.Interfaces;
|
using Phantom.Web.Identity.Interfaces;
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Phantom.Common.Logging;
|
||||||
using Phantom.Utils.Tasks;
|
using Phantom.Utils.Tasks;
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
@ -1,5 +1,4 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Phantom.Web.Services.Authorization;
|
|
||||||
|
|
||||||
namespace Phantom.Web.Identity.Authorization;
|
namespace Phantom.Web.Identity.Authorization;
|
||||||
|
|
@ -1,6 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Phantom.Common.Data.Web.Users.Permissions;
|
|
||||||
|
|
||||||
namespace Phantom.Web.Services.Authorization;
|
namespace Phantom.Web.Identity.Authorization;
|
||||||
|
|
||||||
sealed record PermissionBasedPolicyRequirement(Permission Permission) : IAuthorizationRequirement;
|
sealed record PermissionBasedPolicyRequirement(Permission Permission) : IAuthorizationRequirement;
|
@ -1,5 +1,5 @@
|
|||||||
@using Microsoft.AspNetCore.Components.Authorization
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
@using Phantom.Common.Data.Web.Users.Permissions
|
@using Phantom.Web.Identity.Data
|
||||||
@inject PermissionManager PermissionManager
|
@inject PermissionManager PermissionManager
|
||||||
|
|
||||||
<AuthorizeView>
|
<AuthorizeView>
|
6
Web/Phantom.Web.Identity/Interfaces/ILoginEvents.cs
Normal file
6
Web/Phantom.Web.Identity/Interfaces/ILoginEvents.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace Phantom.Web.Identity.Interfaces;
|
||||||
|
|
||||||
|
public interface ILoginEvents {
|
||||||
|
void UserLoggedIn(UserEntity user);
|
||||||
|
void UserLoggedOut(Guid userGuid);
|
||||||
|
}
|
26
Web/Phantom.Web.Identity/Phantom.Web.Identity.csproj
Normal file
26
Web/Phantom.Web.Identity/Phantom.Web.Identity.csproj
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<SupportedPlatform Include="browser" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.Web" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Common\Phantom.Common.Logging\Phantom.Common.Logging.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Utils\Phantom.Utils\Phantom.Utils.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
@ -2,16 +2,13 @@
|
|||||||
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.Permissions;
|
|
||||||
using Phantom.Web.Identity;
|
|
||||||
using Phantom.Web.Identity.Authentication;
|
using Phantom.Web.Identity.Authentication;
|
||||||
using Phantom.Web.Identity.Authorization;
|
using Phantom.Web.Identity.Authorization;
|
||||||
using Phantom.Web.Services.Authorization;
|
|
||||||
|
|
||||||
namespace Phantom.Web.Services;
|
namespace Phantom.Web.Identity;
|
||||||
|
|
||||||
public static class PhantomWebServices {
|
public static class PhantomIdentityExtensions {
|
||||||
public static void AddPhantomServices(this IServiceCollection services, CancellationToken cancellationToken) {
|
public static void AddPhantomIdentity(this IServiceCollection services, CancellationToken cancellationToken) {
|
||||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(ConfigureIdentityCookie);
|
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(ConfigureIdentityCookie);
|
||||||
services.AddAuthorization(ConfigureAuthorization);
|
services.AddAuthorization(ConfigureAuthorization);
|
||||||
|
|
||||||
@ -21,8 +18,8 @@ public static class PhantomWebServices {
|
|||||||
services.AddScoped<IAuthorizationHandler, PermissionBasedPolicyHandler>();
|
services.AddScoped<IAuthorizationHandler, PermissionBasedPolicyHandler>();
|
||||||
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
|
services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UsePhantomServices(this IApplicationBuilder application) {
|
public static void UsePhantomIdentity(this IApplicationBuilder application) {
|
||||||
application.UseAuthentication();
|
application.UseAuthentication();
|
||||||
application.UseAuthorization();
|
application.UseAuthorization();
|
||||||
application.UseWhen(PhantomIdentityMiddleware.AcceptsPath, static app => app.UseMiddleware<PhantomIdentityMiddleware>());
|
application.UseWhen(PhantomIdentityMiddleware.AcceptsPath, static app => app.UseMiddleware<PhantomIdentityMiddleware>());
|
||||||
@ -40,7 +37,7 @@ public static class PhantomWebServices {
|
|||||||
o.LogoutPath = PhantomIdentityMiddleware.LogoutPath;
|
o.LogoutPath = PhantomIdentityMiddleware.LogoutPath;
|
||||||
o.AccessDeniedPath = PhantomIdentityMiddleware.LoginPath;
|
o.AccessDeniedPath = PhantomIdentityMiddleware.LoginPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ConfigureAuthorization(AuthorizationOptions o) {
|
private static void ConfigureAuthorization(AuthorizationOptions o) {
|
||||||
foreach (var permission in Permission.All) {
|
foreach (var permission in Permission.All) {
|
||||||
o.AddPolicy(permission.Id, policy => policy.Requirements.Add(new PermissionBasedPolicyRequirement(permission)));
|
o.AddPolicy(permission.Id, policy => policy.Requirements.Add(new PermissionBasedPolicyRequirement(permission)));
|
@ -1,11 +0,0 @@
|
|||||||
using System.Security.Claims;
|
|
||||||
using Phantom.Common.Data.Web.Users.Permissions;
|
|
||||||
|
|
||||||
namespace Phantom.Web.Services.Authorization;
|
|
||||||
|
|
||||||
public class PermissionManager {
|
|
||||||
// TODO
|
|
||||||
public bool CheckPermission(ClaimsPrincipal user, Permission permission, bool refreshCache = false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\..\Common\Phantom.Common.Data\Phantom.Common.Data.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Common\Phantom.Common.Data.Web\Phantom.Common.Data.Web.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<AdditionalFiles Include="Authorization\PermissionView.razor" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -1,5 +0,0 @@
|
|||||||
namespace Phantom.Web.Services.Users;
|
|
||||||
|
|
||||||
public sealed class UserManager {
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +1,6 @@
|
|||||||
namespace Phantom.Web.Base;
|
using Phantom.Web.Identity.Interfaces;
|
||||||
|
|
||||||
|
namespace Phantom.Web.Base;
|
||||||
|
|
||||||
sealed class LoginEvents : ILoginEvents {
|
sealed class LoginEvents : ILoginEvents {
|
||||||
private readonly AuditLog auditLog;
|
private readonly AuditLog auditLog;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Phantom.Common.Data.Web.Users.Permissions;
|
|
||||||
using Phantom.Common.Logging;
|
using Phantom.Common.Logging;
|
||||||
using ILogger = Serilog.ILogger;
|
using ILogger = Serilog.ILogger;
|
||||||
|
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Phantom.Utils.Tasks;
|
using Phantom.Utils.Tasks;
|
||||||
using Phantom.Web.Base;
|
using Phantom.Web.Base;
|
||||||
|
using Phantom.Web.Identity;
|
||||||
using Phantom.Web.Identity.Interfaces;
|
using Phantom.Web.Identity.Interfaces;
|
||||||
using Phantom.Web.Services;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Phantom.Web;
|
namespace Phantom.Web;
|
||||||
|
|
||||||
static class Launcher {
|
public static class Launcher {
|
||||||
public static WebApplication CreateApplication(Configuration config, ServiceConfiguration serviceConfiguration, TaskManager taskManager) {
|
public static WebApplication CreateApplication(Configuration config, ServiceConfiguration serviceConfiguration, TaskManager taskManager) {
|
||||||
var assembly = typeof(Launcher).Assembly;
|
var assembly = typeof(Launcher).Assembly;
|
||||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
|
var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
|
||||||
@ -26,13 +26,15 @@ static class Launcher {
|
|||||||
|
|
||||||
builder.Services.AddSingleton(serviceConfiguration);
|
builder.Services.AddSingleton(serviceConfiguration);
|
||||||
builder.Services.AddSingleton(taskManager);
|
builder.Services.AddSingleton(taskManager);
|
||||||
builder.Services.AddPhantomServices(config.CancellationToken);
|
|
||||||
|
|
||||||
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.KeyFolderPath));
|
builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(config.KeyFolderPath));
|
||||||
|
|
||||||
|
builder.Services.AddPhantomIdentity(config.CancellationToken);
|
||||||
|
builder.Services.AddScoped<ILoginEvents, LoginEvents>();
|
||||||
|
|
||||||
builder.Services.AddRazorPages(static options => options.RootDirectory = "/Layout");
|
builder.Services.AddRazorPages(static options => options.RootDirectory = "/Layout");
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
|
|
||||||
@ -51,7 +53,7 @@ static class Launcher {
|
|||||||
|
|
||||||
application.UseStaticFiles();
|
application.UseStaticFiles();
|
||||||
application.UseRouting();
|
application.UseRouting();
|
||||||
application.UsePhantomServices();
|
application.UsePhantomIdentity();
|
||||||
|
|
||||||
application.MapControllers();
|
application.MapControllers();
|
||||||
application.MapBlazorHub();
|
application.MapBlazorHub();
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
@using Phantom.Common.Data.Web.Users.Permissions
|
@inject ServiceConfiguration Configuration
|
||||||
@inject ServiceConfiguration Configuration
|
|
||||||
@inject PermissionManager PermissionManager
|
@inject PermissionManager PermissionManager
|
||||||
|
|
||||||
<div class="navbar navbar-dark">
|
<div class="navbar navbar-dark">
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
@page "/setup"
|
@page "/setup"
|
||||||
@using Phantom.Common.Data.Web.Users
|
|
||||||
@using Phantom.Utils.Cryptography
|
|
||||||
@using Phantom.Utils.Tasks
|
@using Phantom.Utils.Tasks
|
||||||
@using Phantom.Web.Identity.Authentication
|
@using Phantom.Web.Identity.Authentication
|
||||||
@using Phantom.Web.Services.Users
|
|
||||||
@using Microsoft.AspNetCore.Identity
|
|
||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
|
@using Phantom.Utils.Cryptography
|
||||||
@using System.Security.Cryptography
|
@using System.Security.Cryptography
|
||||||
@attribute [AllowAnonymous]
|
@attribute [AllowAnonymous]
|
||||||
@inject ServiceConfiguration ServiceConfiguration
|
@inject ServiceConfiguration ServiceConfiguration
|
||||||
@inject PhantomLoginManager LoginManager
|
@inject PhantomLoginManager LoginManager
|
||||||
@inject UserManager UserManager
|
@inject UserManager UserManager
|
||||||
@inject RoleManager<> RoleManager
|
@inject RoleManager RoleManager
|
||||||
@inject UserRoleManager UserRoleManager
|
@inject UserRoleManager UserRoleManager
|
||||||
@inject AuditLog AuditLog
|
@inject AuditLog AuditLog
|
||||||
|
|
||||||
@ -100,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
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:
|
||||||
var administratorUser = ok.Value;
|
var administratorUser = ok.Value;
|
||||||
await AuditLog.AddAdministratorUserCreatedEvent(administratorUser);
|
await AuditLog.AddAdministratorUserCreatedEvent(administratorUser);
|
||||||
|
|
||||||
@ -110,15 +107,15 @@
|
|||||||
|
|
||||||
return Result.Ok<string>();
|
return Result.Ok<string>();
|
||||||
|
|
||||||
case Result<UserInfo, AddUserError>.Fail fail:
|
case Result<UserEntity, AddUserError>.Fail fail:
|
||||||
return Result.Fail(fail.Error.ToSentences("\n"));
|
return Result.Fail(fail.Error.ToSentences("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Fail("Unknown error.");
|
return Result.Fail("Unknown error.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Result<string>> UpdateAdministrator(UserInfo existingUser) {
|
private async Task<Result<string>> UpdateAdministrator(UserEntity existingUser) {
|
||||||
switch (await UserManager.SetUserPassword(existingUser.Guid, form.Password)) {
|
switch (await UserManager.SetUserPassword(existingUser.UserGuid, form.Password)) {
|
||||||
case Result<SetUserPasswordError>.Ok:
|
case Result<SetUserPasswordError>.Ok:
|
||||||
await AuditLog.AddAdministratorUserModifiedEvent(existingUser);
|
await AuditLog.AddAdministratorUserModifiedEvent(existingUser);
|
||||||
return Result.Ok<string>();
|
return Result.Ok<string>();
|
||||||
|
@ -20,10 +20,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Common\Phantom.Common.Logging\Phantom.Common.Logging.csproj" />
|
|
||||||
<ProjectReference Include="..\..\Utils\Phantom.Utils\Phantom.Utils.csproj" />
|
|
||||||
<ProjectReference Include="..\Phantom.Web.Components\Phantom.Web.Components.csproj" />
|
<ProjectReference Include="..\Phantom.Web.Components\Phantom.Web.Components.csproj" />
|
||||||
<ProjectReference Include="..\Phantom.Web.Services\Phantom.Web.Services.csproj" />
|
<ProjectReference Include="..\Phantom.Web.Identity\Phantom.Web.Identity.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@using Phantom.Common.Data.Web.Users.Permissions
|
@using Phantom.Controller.Services.Instances
|
||||||
|
@using Phantom.Controller.Services.Audit
|
||||||
@using Phantom.Common.Data.Replies
|
@using Phantom.Common.Data.Replies
|
||||||
@inherits PhantomComponent
|
@inherits PhantomComponent
|
||||||
@inject InstanceManager InstanceManager
|
@inject InstanceManager InstanceManager
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
@inherits PhantomComponent
|
@inherits PhantomComponent
|
||||||
@using Phantom.Utils.Collections
|
@using Phantom.Utils.Collections
|
||||||
|
@using Phantom.Utils.Events
|
||||||
@using System.Diagnostics
|
@using System.Diagnostics
|
||||||
@using Phantom.Common.Data.Web.Users.Permissions
|
@using Phantom.Controller.Services.Instances
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
@inject IJSRuntime Js;
|
@inject IJSRuntime Js;
|
||||||
@inject InstanceLogManager InstanceLogManager
|
@inject InstanceLogManager InstanceLogManager
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
@using Phantom.Common.Data.Web.Users
|
@using Phantom.Controller.Services.Users
|
||||||
@using Phantom.Utils.Tasks
|
@using Phantom.Utils.Tasks
|
||||||
|
@using Phantom.Controller.Database.Entities
|
||||||
|
@using Phantom.Controller.Services.Audit
|
||||||
@using System.ComponentModel.DataAnnotations
|
@using System.ComponentModel.DataAnnotations
|
||||||
@inherits PhantomComponent
|
@inherits PhantomComponent
|
||||||
@inject IJSRuntime Js;
|
@inject IJSRuntime Js;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
@using Phantom.Common.Data.Web.Users
|
@using Phantom.Controller.Database.Entities
|
||||||
|
@using Phantom.Controller.Services.Audit
|
||||||
|
@using Phantom.Controller.Services.Users
|
||||||
@inherits UserEditDialogBase
|
@inherits UserEditDialogBase
|
||||||
@inject UserManager UserManager
|
@inject UserManager UserManager
|
||||||
@inject AuditLog AuditLog
|
@inject AuditLog AuditLog
|
||||||
@ -17,8 +19,8 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
protected override async Task DoEdit(UserInfo user) {
|
protected override async Task DoEdit(UserEntity user) {
|
||||||
switch (await UserManager.DeleteByGuid(user.Guid)) {
|
switch (await UserManager.DeleteByGuid(user.UserGuid)) {
|
||||||
case DeleteUserResult.Deleted:
|
case DeleteUserResult.Deleted:
|
||||||
await AuditLog.AddUserDeletedEvent(user);
|
await AuditLog.AddUserDeletedEvent(user);
|
||||||
await OnEditSuccess();
|
await OnEditSuccess();
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
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.Permissions;
|
|
||||||
using Phantom.Web.Base;
|
using Phantom.Web.Base;
|
||||||
using Phantom.Web.Components.Forms;
|
using Phantom.Web.Components.Forms;
|
||||||
|
|
||||||
@ -15,14 +13,14 @@ public abstract class UserEditDialogBase : PhantomComponent {
|
|||||||
public string ModalId { get; set; } = string.Empty;
|
public string ModalId { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<UserInfo> UserModified { get; set; }
|
public EventCallback<UserEntity> UserModified { get; set; }
|
||||||
|
|
||||||
protected readonly FormButtonSubmit.SubmitModel SubmitModel = new();
|
protected readonly FormButtonSubmit.SubmitModel SubmitModel = new();
|
||||||
|
|
||||||
private UserInfo? EditedUser { get; set; } = null;
|
private UserEntity? EditedUser { get; set; } = null;
|
||||||
protected string EditedUserName { get; private set; } = string.Empty;
|
protected string EditedUserName { get; private set; } = string.Empty;
|
||||||
|
|
||||||
internal async Task Show(UserInfo user) {
|
internal async Task Show(UserEntity user) {
|
||||||
EditedUser = user;
|
EditedUser = user;
|
||||||
EditedUserName = user.Name;
|
EditedUserName = user.Name;
|
||||||
await BeforeShown(user);
|
await BeforeShown(user);
|
||||||
@ -31,7 +29,7 @@ public abstract class UserEditDialogBase : PhantomComponent {
|
|||||||
await Js.InvokeVoidAsync("showModal", ModalId);
|
await Js.InvokeVoidAsync("showModal", ModalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Task BeforeShown(UserInfo user) {
|
protected virtual Task BeforeShown(UserEntity user) {
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +51,7 @@ public abstract class UserEditDialogBase : PhantomComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract Task DoEdit(UserInfo user);
|
protected abstract Task DoEdit(UserEntity user);
|
||||||
|
|
||||||
protected async Task OnEditSuccess() {
|
protected async Task OnEditSuccess() {
|
||||||
await UserModified.InvokeAsync(EditedUser);
|
await UserModified.InvokeAsync(EditedUser);
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
@using Phantom.Common.Data.Web.Users
|
@using Phantom.Controller.Database.Entities
|
||||||
|
@using Phantom.Controller.Services.Audit
|
||||||
|
@using Phantom.Controller.Services.Users
|
||||||
@inherits UserEditDialogBase
|
@inherits UserEditDialogBase
|
||||||
|
@inject UserManager UserManager
|
||||||
|
@inject RoleManager RoleManager
|
||||||
|
@inject UserRoleManager UserRoleManager
|
||||||
|
@inject AuditLog AuditLog
|
||||||
|
|
||||||
<Modal Id="@ModalId" TitleText="Manage User Roles">
|
<Modal Id="@ModalId" TitleText="Manage User Roles">
|
||||||
<Body>
|
<Body>
|
||||||
@ -23,13 +29,13 @@
|
|||||||
|
|
||||||
private List<RoleItem> items = new();
|
private List<RoleItem> items = new();
|
||||||
|
|
||||||
protected override async Task BeforeShown(UserInfo user) {
|
protected override async Task BeforeShown(UserEntity user) {
|
||||||
var userRoles = await UserRoleManager.GetUserRoleGuids(user);
|
var userRoles = await UserRoleManager.GetUserRoleGuids(user);
|
||||||
var allRoles = await RoleManager.GetAll();
|
var allRoles = await RoleManager.GetAll();
|
||||||
this.items = allRoles.Select(role => new RoleItem(role, userRoles.Contains(role.RoleGuid))).ToList();
|
this.items = allRoles.Select(role => new RoleItem(role, userRoles.Contains(role.RoleGuid))).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task DoEdit(UserInfo user) {
|
protected override async Task DoEdit(UserEntity user) {
|
||||||
var userRoles = await UserRoleManager.GetUserRoleGuids(user);
|
var userRoles = await UserRoleManager.GetUserRoleGuids(user);
|
||||||
var addedToRoles = new List<string>();
|
var addedToRoles = new List<string>();
|
||||||
var removedFromRoles = new List<string>();
|
var removedFromRoles = new List<string>();
|
||||||
@ -37,7 +43,7 @@
|
|||||||
|
|
||||||
foreach (var item in items) {
|
foreach (var item in items) {
|
||||||
var shouldHaveRole = item.Checked;
|
var shouldHaveRole = item.Checked;
|
||||||
if (shouldHaveRole == userRoles.Contains(item.Role.Guid)) {
|
if (shouldHaveRole == userRoles.Contains(item.Role.RoleGuid)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +70,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sealed class RoleItem {
|
private sealed class RoleItem {
|
||||||
public RoleInfo Role { get; }
|
public RoleEntity Role { get; }
|
||||||
public bool Checked { get; set; }
|
public bool Checked { get; set; }
|
||||||
|
|
||||||
public RoleItem(RoleInfo role, bool @checked) {
|
public RoleItem(RoleEntity role, bool @checked) {
|
||||||
this.Role = role;
|
this.Role = role;
|
||||||
this.Checked = @checked;
|
this.Checked = @checked;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user