From b8b0d571ab417fe9d878380cc9d464450a78b2b4 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Mon, 23 Dec 2024 15:54:14 +0100 Subject: [PATCH] implemented automatic database cleanup --- src/HopFrame.Database/HopDbContextBase.cs | 34 +++++++++++++ src/HopFrame.Database/Models/Permission.cs | 6 +++ .../HopFrameAuthenticationExtensions.cs | 48 +++++++++++++------ 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/src/HopFrame.Database/HopDbContextBase.cs b/src/HopFrame.Database/HopDbContextBase.cs index cd03860..cd8656a 100644 --- a/src/HopFrame.Database/HopDbContextBase.cs +++ b/src/HopFrame.Database/HopDbContextBase.cs @@ -8,6 +8,8 @@ namespace HopFrame.Database; /// public abstract class HopDbContextBase : DbContext { + public static IList> SaveHandlers = new List>(); + public virtual DbSet Users { get; set; } public virtual DbSet Permissions { get; set; } public virtual DbSet Tokens { get; set; } @@ -36,4 +38,36 @@ public abstract class HopDbContextBase : DbContext { .WithOne(t => t.Token) .OnDelete(DeleteBehavior.Cascade); } + + private void OnSaving() { + var orphanedPermissions = Permissions + .Where(p => p.UserId == null && p.GroupName == null && p.TokenId == null) + .ToList(); + + foreach (var handler in SaveHandlers) { + handler.Invoke(this); + } + + Permissions.RemoveRange(orphanedPermissions); + } + + public override int SaveChanges() { + OnSaving(); + return base.SaveChanges(); + } + + public override int SaveChanges(bool acceptAllChangesOnSuccess) { + OnSaving(); + return base.SaveChanges(acceptAllChangesOnSuccess); + } + + public override Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) { + OnSaving(); + return base.SaveChangesAsync(cancellationToken); + } + + public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken()) { + OnSaving(); + return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken); + } } \ No newline at end of file diff --git a/src/HopFrame.Database/Models/Permission.cs b/src/HopFrame.Database/Models/Permission.cs index 658a90e..512d086 100644 --- a/src/HopFrame.Database/Models/Permission.cs +++ b/src/HopFrame.Database/Models/Permission.cs @@ -18,12 +18,18 @@ public class Permission { [ForeignKey("UserId"), JsonIgnore] public virtual User User { get; set; } + public Guid? UserId { get; set; } + [ForeignKey("GroupName"), JsonIgnore] public virtual PermissionGroup Group { get; set; } + + [MaxLength(255)] + public string GroupName { get; set; } [ForeignKey("TokenId"), JsonIgnore] public virtual Token Token { get; set; } + public Guid? TokenId { get; set; } } public interface IPermissionOwner; diff --git a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs index f6a52e8..90b0387 100644 --- a/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs +++ b/src/HopFrame.Security/Authentication/HopFrameAuthenticationExtensions.cs @@ -1,3 +1,5 @@ +using HopFrame.Database; +using HopFrame.Database.Models; using HopFrame.Security.Authentication.OpenID; using HopFrame.Security.Authentication.OpenID.Implementation; using HopFrame.Security.Authentication.OpenID.Options; @@ -17,32 +19,48 @@ public static class HopFrameAuthenticationExtensions { /// /// Configures the WebApplication to use the authentication and authorization of the HopFrame API /// - /// The service provider to add the services to + /// The service provider to add the services to /// The configuration used to configure HopFrame authentication /// Configuration for how the HopFrame services get set up /// - public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection service, ConfigurationManager configuration, HopFrameConfig config = null) { + public static IServiceCollection AddHopFrameAuthentication(this IServiceCollection services, ConfigurationManager configuration, HopFrameConfig config = null) { config ??= new HopFrameConfig(); - service.AddSingleton(config); - service.AddScoped(typeof(ICacheProvider), config.CacheProvider); - service.TryAddSingleton(); - service.AddScoped(); + services.AddSingleton(config); + services.AddScoped(typeof(ICacheProvider), config.CacheProvider); + services.TryAddSingleton(); + services.AddScoped(); if (config.CacheProvider == typeof(MemoryCacheProvider)) - service.AddMemoryCache(); + services.AddMemoryCache(); - service.AddHttpClient(); - service.AddScoped(); + services.AddHttpClient(); + services.AddScoped(); - service.AddOptionsFromConfiguration(configuration); - service.AddOptionsFromConfiguration(configuration); - service.AddOptionsFromConfiguration(configuration); + services.AddOptionsFromConfiguration(configuration); + services.AddOptionsFromConfiguration(configuration); + services.AddOptionsFromConfiguration(configuration); - service.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme(HopFrameAuthentication.SchemeName, _ => {}); - service.AddAuthorization(); + services.AddAuthentication(HopFrameAuthentication.SchemeName).AddScheme(HopFrameAuthentication.SchemeName, _ => {}); + services.AddAuthorization(); + + HopDbContextBase.SaveHandlers.Add(context => { + var section = configuration.GetSection("HopFrame:Authentication"); + var accessToken = section?.GetSection("AccessToken")?.Get()?.ConstructTimeSpan ?? new HopFrameAuthenticationOptions().AccessTokenTime; + var refreshToken = section?.GetSection("RefreshToken")?.Get()?.ConstructTimeSpan ?? new HopFrameAuthenticationOptions().RefreshTokenTime; - return service; + var now = DateTime.Now; + var accessTokenExpiry = now - accessToken; + var refreshTokenExpiry = now - refreshToken; + var invalidTokens = context.Tokens + .Where(t => + (t.Type == Token.AccessTokenType && t.CreatedAt < accessTokenExpiry) || + (t.Type == Token.RefreshTokenType && t.CreatedAt < refreshTokenExpiry)) + .ToList(); + context.Tokens.RemoveRange(invalidTokens); + }); + + return services; } } \ No newline at end of file