diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs index ce3c6d3e..5c5c14f9 100644 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs @@ -2,11 +2,13 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using MoonCore.Exceptions; using MoonCore.Extended.Abstractions; using MoonCore.Extended.Helpers; using MoonCore.Models; using Moonlight.ApiServer.Database.Entities; +using Moonlight.ApiServer.Services; using Moonlight.Shared.Http.Requests.Admin.Users; using Moonlight.Shared.Http.Responses.Admin.Users; @@ -166,7 +168,7 @@ public class UsersController : Controller [HttpDelete("{id}")] [Authorize(Policy = "permissions:admin.users.delete")] - public async Task Delete([FromRoute] int id) + public async Task Delete([FromRoute] int id, [FromQuery] bool force = false) { var user = await UserRepository .Get() @@ -175,6 +177,16 @@ public class UsersController : Controller if (user == null) throw new HttpApiException("No user with that id found", 404); - await UserRepository.Remove(user); + var deletionService = HttpContext.RequestServices.GetRequiredService(); + + if (!force) + { + var validationResult = await deletionService.Validate(user); + + if (!validationResult.IsAllowed) + throw new HttpApiException($"Unable to delete user", 400, validationResult.Reason); + } + + await deletionService.Delete(user, force); } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Interfaces/IUserDeleteHandler.cs b/Moonlight.ApiServer/Interfaces/IUserDeleteHandler.cs new file mode 100644 index 00000000..5365e5f1 --- /dev/null +++ b/Moonlight.ApiServer/Interfaces/IUserDeleteHandler.cs @@ -0,0 +1,10 @@ +using Moonlight.ApiServer.Database.Entities; +using Moonlight.ApiServer.Models; + +namespace Moonlight.ApiServer.Interfaces; + +public interface IUserDeleteHandler +{ + public Task Validate(User user); + public Task Delete(User user, bool force); +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Models/UserDeleteValidateResult.cs b/Moonlight.ApiServer/Models/UserDeleteValidateResult.cs new file mode 100644 index 00000000..e4dcc1dd --- /dev/null +++ b/Moonlight.ApiServer/Models/UserDeleteValidateResult.cs @@ -0,0 +1,27 @@ +namespace Moonlight.ApiServer.Models; + +public class UserDeleteValidationResult +{ + public bool IsAllowed { get; set; } + public string Reason { get; set; } + + public static UserDeleteValidationResult Allow() + { + return new UserDeleteValidationResult() + { + IsAllowed = true + }; + } + + public static UserDeleteValidationResult Deny() + => Deny("No reason provided"); + + public static UserDeleteValidationResult Deny(string reason) + { + return new UserDeleteValidationResult() + { + IsAllowed = false, + Reason = reason + }; + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Services/UserService.cs b/Moonlight.ApiServer/Services/UserService.cs new file mode 100644 index 00000000..a58324d5 --- /dev/null +++ b/Moonlight.ApiServer/Services/UserService.cs @@ -0,0 +1,42 @@ +using MoonCore.Extended.Abstractions; +using Moonlight.ApiServer.Database.Entities; +using Moonlight.ApiServer.Interfaces; +using Moonlight.ApiServer.Models; + +namespace Moonlight.ApiServer.Services; + +public class UserDeletionService +{ + private readonly IUserDeleteHandler[] Handlers; + private readonly DatabaseRepository UserRepository; + + public UserDeletionService( + IEnumerable handlers, + DatabaseRepository userRepository + ) + { + UserRepository = userRepository; + Handlers = handlers.ToArray(); + } + + public async Task Validate(User user) + { + foreach (var handler in Handlers) + { + var result = await handler.Validate(user); + + if (!result.IsAllowed) + return result; + } + + return UserDeleteValidationResult.Allow(); + } + + public async Task Delete(User user, bool force) + { + foreach (var handler in Handlers) + await Delete(user, force); + + await UserRepository.Remove(user); + } +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Startup/Startup.Auth.cs b/Moonlight.ApiServer/Startup/Startup.Auth.cs index 593ee009..04abceb7 100644 --- a/Moonlight.ApiServer/Startup/Startup.Auth.cs +++ b/Moonlight.ApiServer/Startup/Startup.Auth.cs @@ -6,6 +6,7 @@ using MoonCore.Extended.JwtInvalidation; using MoonCore.Permissions; using Moonlight.ApiServer.Implementations; using Moonlight.ApiServer.Interfaces; +using Moonlight.ApiServer.Services; namespace Moonlight.ApiServer.Startup; @@ -47,6 +48,8 @@ public partial class Startup if (Configuration.Authentication.EnableLocalOAuth2) WebApplicationBuilder.Services.AddScoped(); + WebApplicationBuilder.Services.AddScoped(); + return Task.CompletedTask; }