From 4aab0112248ad679fe6f486e4bb9e2b440802cb3 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Sun, 22 Dec 2024 18:08:05 +0100 Subject: [PATCH] Fixed Database update problem + added group management endpoints --- .../Controller/GroupController.cs | 74 +++++++++++++++++++ .../Extensions/ServiceCollectionExtensions.cs | 3 +- src/HopFrame.Api/Logic/IGroupLogic.cs | 14 ++++ .../Logic/Implementation/GroupLogic.cs | 66 +++++++++++++++++ .../Implementation/GroupRepository.cs | 31 ++++++-- .../Implementation/UserRepository.cs | 39 +++++++++- 6 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 src/HopFrame.Api/Controller/GroupController.cs create mode 100644 src/HopFrame.Api/Logic/IGroupLogic.cs create mode 100644 src/HopFrame.Api/Logic/Implementation/GroupLogic.cs diff --git a/src/HopFrame.Api/Controller/GroupController.cs b/src/HopFrame.Api/Controller/GroupController.cs new file mode 100644 index 0000000..fdfbc07 --- /dev/null +++ b/src/HopFrame.Api/Controller/GroupController.cs @@ -0,0 +1,74 @@ +using HopFrame.Api.Logic; +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Security.Authorization; +using HopFrame.Security.Claims; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace HopFrame.Api.Controller; + +[ApiController, Route("api/v1/groups")] +public class GroupController(IOptions permissions, IPermissionRepository perms, ITokenContext context, IGroupLogic groups) : ControllerBase { + + private async Task AuthorizeRequest(string permission) { + return await perms.HasPermission(context.AccessToken, permission); + } + + [HttpGet, Authorized] + public async Task>> GetGroups() { + if (!await AuthorizeRequest(permissions.Value.Groups.Read)) + return Unauthorized(); + + return await groups.GetGroups(); + } + + [HttpGet("default"), Authorized] + public async Task>> GetDefaultGroups() { + if (!await AuthorizeRequest(permissions.Value.Groups.Read)) + return Unauthorized(); + + return await groups.GetDefaultGroups(); + } + + [HttpGet("user/{userId}"), Authorized] + public async Task>> GetUserGroups(string userId) { + if (!await AuthorizeRequest(permissions.Value.Groups.Read)) + return Unauthorized(); + + return await groups.GetUserGroups(userId); + } + + [HttpGet("{name}"), Authorized] + public async Task> GetGroup(string name) { + if (!await AuthorizeRequest(permissions.Value.Groups.Read)) + return Unauthorized(); + + return await groups.GetGroup(name); + } + + [HttpPost, Authorized] + public async Task> CreateGroup([FromBody] PermissionGroup group) { + if (!await AuthorizeRequest(permissions.Value.Groups.Create)) + return Unauthorized(); + + return await groups.CreateGroup(group); + } + + [HttpPut, Authorized] + public async Task> UpdateGroup([FromBody] PermissionGroup group) { + if (!await AuthorizeRequest(permissions.Value.Groups.Update)) + return Unauthorized(); + + return await groups.UpdateGroup(group); + } + + [HttpDelete("{name}"), Authorized] + public async Task DeleteGroup(string name) { + if (!await AuthorizeRequest(permissions.Value.Groups.Delete)) + return Unauthorized(); + + return await groups.DeleteGroup(name); + } + +} \ No newline at end of file diff --git a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs index 936231f..19436eb 100644 --- a/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs +++ b/src/HopFrame.Api/Extensions/ServiceCollectionExtensions.cs @@ -19,7 +19,7 @@ public static class ServiceCollectionExtensions { /// The configuration used to configure HopFrame authentication /// The data source for all HopFrame entities public static void AddHopFrame(this IServiceCollection services, ConfigurationManager configuration) where TDbContext : HopDbContextBase { - var controllers = new List { typeof(UserController) }; + var controllers = new List { typeof(UserController), typeof(GroupController) }; var defaultAuthenticationSection = configuration.GetSection("HopFrame:Authentication:DefaultAuthentication"); if (!defaultAuthenticationSection.Exists() || configuration.GetValue("HopFrame:Authentication:DefaultAuthentication")) @@ -48,6 +48,7 @@ public static class ServiceCollectionExtensions { services.TryAddSingleton(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddHopFrameAuthentication(configuration); } diff --git a/src/HopFrame.Api/Logic/IGroupLogic.cs b/src/HopFrame.Api/Logic/IGroupLogic.cs new file mode 100644 index 0000000..48bdd45 --- /dev/null +++ b/src/HopFrame.Api/Logic/IGroupLogic.cs @@ -0,0 +1,14 @@ +using HopFrame.Database.Models; + +namespace HopFrame.Api.Logic; + +public interface IGroupLogic { + Task>> GetGroups(); + Task>> GetDefaultGroups(); + Task>> GetUserGroups(string userId); + Task> GetGroup(string name); + + Task> CreateGroup(PermissionGroup group); + Task> UpdateGroup(PermissionGroup group); + Task DeleteGroup(string name); +} \ No newline at end of file diff --git a/src/HopFrame.Api/Logic/Implementation/GroupLogic.cs b/src/HopFrame.Api/Logic/Implementation/GroupLogic.cs new file mode 100644 index 0000000..dd3e5b7 --- /dev/null +++ b/src/HopFrame.Api/Logic/Implementation/GroupLogic.cs @@ -0,0 +1,66 @@ +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; + +namespace HopFrame.Api.Logic.Implementation; + +internal sealed class GroupLogic(IGroupRepository groups, IUserRepository users) : IGroupLogic { + public async Task>> GetGroups() { + return LogicResult>.Ok(await groups.GetPermissionGroups()); + } + + public async Task>> GetDefaultGroups() { + return LogicResult>.Ok(await groups.GetDefaultGroups()); + } + + public async Task>> GetUserGroups(string id) { + if (!Guid.TryParse(id, out var userId)) + return LogicResult>.BadRequest("Invalid user id"); + + var user = await users.GetUser(userId); + + if (user is null) + return LogicResult>.NotFound("That user does not exist"); + + return LogicResult>.Ok(await groups.GetUserGroups(user)); + } + + public async Task> GetGroup(string name) { + var group = await groups.GetPermissionGroup(name); + + if (group is null) + return LogicResult.NotFound("That group does not exist"); + + return LogicResult.Ok(group); + } + + public async Task> CreateGroup(PermissionGroup group) { + if (group is null) + return LogicResult.BadRequest("Provide a group"); + + if (!group.Name.StartsWith("group.")) + return LogicResult.BadRequest("Group names must start with 'group.'"); + + if (await groups.GetPermissionGroup(group.Name) != null) + return LogicResult.Conflict("That group already exists"); + + return LogicResult.Ok(await groups.CreatePermissionGroup(group)); + } + + public async Task> UpdateGroup(PermissionGroup group) { + if (await groups.GetPermissionGroup(group.Name) == null) + return LogicResult.NotFound("That user does not exist"); + + await groups.EditPermissionGroup(group); + return LogicResult.Ok(group); + } + + public async Task DeleteGroup(string name) { + var group = await groups.GetPermissionGroup(name); + + if (group is null) + return LogicResult.NotFound("That group does not exist"); + + await groups.DeletePermissionGroup(group); + return LogicResult.Ok(); + } +} \ No newline at end of file diff --git a/src/HopFrame.Database/Repositories/Implementation/GroupRepository.cs b/src/HopFrame.Database/Repositories/Implementation/GroupRepository.cs index 547e193..b190ce6 100644 --- a/src/HopFrame.Database/Repositories/Implementation/GroupRepository.cs +++ b/src/HopFrame.Database/Repositories/Implementation/GroupRepository.cs @@ -33,19 +33,38 @@ internal sealed class GroupRepository(TDbContext context) : IGroupRe } public async Task EditPermissionGroup(PermissionGroup group) { - var orig = await context.Groups.SingleOrDefaultAsync(g => g.Name == group.Name); - + var orig = await context.Groups + .Include(g => g.Permissions) // Include related entities + .SingleOrDefaultAsync(g => g.Name == group.Name); + if (orig is null) return; - var entity = context.Groups.Update(orig); + // Update the main entity's properties + orig.IsDefaultGroup = group.IsDefaultGroup; + orig.Description = group.Description; - entity.Entity.IsDefaultGroup = group.IsDefaultGroup; - entity.Entity.Description = group.Description; - entity.Entity.Permissions = group.Permissions; + // Update the permissions + foreach (var permission in group.Permissions) { + var existingPermission = orig.Permissions.FirstOrDefault(p => p.Id == permission.Id); + if (existingPermission != null) { + // Update existing permission + context.Entry(existingPermission).CurrentValues.SetValues(permission); + } else { + // Add new permission + orig.Permissions.Add(permission); + } + } + + // Remove deleted permissions + foreach (var permission in orig.Permissions.ToList().Where(permission => group.Permissions.All(p => p.Id != permission.Id))) { + orig.Permissions.Remove(permission); + context.Permissions.Remove(permission); // Ensure it gets removed from the database + } await context.SaveChangesAsync(); } + public async Task CreatePermissionGroup(PermissionGroup group) { group.CreatedAt = DateTime.Now; await context.Groups.AddAsync(group); diff --git a/src/HopFrame.Database/Repositories/Implementation/UserRepository.cs b/src/HopFrame.Database/Repositories/Implementation/UserRepository.cs index c642466..3e4e1b8 100644 --- a/src/HopFrame.Database/Repositories/Implementation/UserRepository.cs +++ b/src/HopFrame.Database/Repositories/Implementation/UserRepository.cs @@ -69,10 +69,45 @@ internal sealed class UserRepository(TDbContext context, IGroupRepos .SingleOrDefaultAsync(entry => entry.Id == user.Id); if (entry is null) return; + // Update the main entity's properties entry.Email = user.Email; entry.Username = user.Username; - entry.Permissions = user.Permissions; - entry.Tokens = user.Tokens; + + // Update Permissions + foreach (var permission in user.Permissions) { + var existingPermission = entry.Permissions.FirstOrDefault(p => p.Id == permission.Id); + if (existingPermission != null) { + // Update existing permission + context.Entry(existingPermission).CurrentValues.SetValues(permission); + } else { + // Add new permission + entry.Permissions.Add(permission); + } + } + + // Remove deleted permissions + foreach (var permission in entry.Permissions.ToList().Where(permission => user.Permissions.All(p => p.Id != permission.Id))) { + entry.Permissions.Remove(permission); + context.Permissions.Remove(permission); // Ensure it gets removed from the database + } + + // Update Tokens + foreach (var token in user.Tokens) { + var existingToken = entry.Tokens.FirstOrDefault(t => t.TokenId == token.TokenId); + if (existingToken != null) { + // Update existing token + context.Entry(existingToken).CurrentValues.SetValues(token); + } else { + // Add new token + entry.Tokens.Add(token); + } + } + + // Remove deleted tokens + foreach (var token in entry.Tokens.ToList().Where(token => user.Tokens.All(t => t.TokenId != token.TokenId))) { + entry.Tokens.Remove(token); + context.Tokens.Remove(token); // Ensure it gets removed from the database + } await context.SaveChangesAsync(); }