Skip to content

Commit

Permalink
Merge pull request #593 from osu-tournament-rating/feature/audit-blaming
Browse files Browse the repository at this point in the history
API automatic audit blaming
  • Loading branch information
myssto authored Feb 7, 2025
2 parents eb4c48b + 0b5ed6a commit 9cb12a7
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 17 deletions.
14 changes: 6 additions & 8 deletions API/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using API.Services.Interfaces;
using API.SwaggerGen;
using API.SwaggerGen.Filters;
using API.Utilities;
using API.Utilities.Extensions;
using Asp.Versioning;
using AutoMapper;
Expand Down Expand Up @@ -550,15 +551,12 @@ await context.HttpContext.Response.WriteAsync(

#region Database Context

builder.Services.AddDbContext<OtrContext>(o =>
builder.Services.AddScoped<AuditBlamingInterceptor>();
builder.Services.AddDbContext<OtrContext>((services, options) =>
{
o.UseNpgsql(
builder
.Configuration.BindAndValidate<ConnectionStringsConfiguration>(
ConnectionStringsConfiguration.Position
)
.DefaultConnection
);
options
.UseNpgsql(builder.Configuration.BindAndValidate<ConnectionStringsConfiguration>(ConnectionStringsConfiguration.Position).DefaultConnection)
.AddInterceptors(services.GetRequiredService<AuditBlamingInterceptor>());
});

// The Redis cache is registered as a singleton because it is meant to be re-used across instances
Expand Down
40 changes: 40 additions & 0 deletions API/Utilities/AuditBlamingInterceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using API.Utilities.Extensions;
using Database.Entities.Interfaces;
using Database.Interceptors;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;

namespace API.Utilities;

/// <summary>
/// Extends the <see cref="AuditingInterceptor"/> to automatically blame actions on the currently authenticated user
/// </summary>
/// <param name="httpContextAccessor">Http context accessor</param>
[UsedImplicitly]
public class AuditBlamingInterceptor(IHttpContextAccessor httpContextAccessor) : AuditingInterceptor
{
private readonly HttpContext? _httpContext = httpContextAccessor.HttpContext;

protected override void OnSavingChanges(DbContext context)
{
// Generate audits
base.OnSavingChanges(context);

// Get the currently authenticated user's id from the http context
if (_httpContext == null || _httpContext.User.Identity is { IsAuthenticated: false })
{
return;
}

if (!_httpContext.User.TryGetSubjectId(out var subjectId))
{
return;
}

// Blame any created audits
context.ChangeTracker
.Entries<IAuditEntity>()
.ToList()
.ForEach(entry => entry.Entity.ActionUserId = subjectId);
}
}
4 changes: 3 additions & 1 deletion DataWorkerService/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Database;
using Database.Entities;
using Database.Interceptors;
using Database.Repositories.Implementations;
using Database.Repositories.Interfaces;
using DataWorkerService.AutomationChecks;
Expand Down Expand Up @@ -91,7 +92,8 @@
ConnectionStringsConfiguration.Position
)
.DefaultConnection
);
)
.AddInterceptors(new AuditingInterceptor());
});

AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
Expand Down
2 changes: 1 addition & 1 deletion Database/Entities/AuditEntityBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public abstract class AuditEntityBase<TAuditable, TAudit> : IAuditEntity
public int? ReferenceId { get; private set; }

[Column("action_user_id")]
public int? ActionUserId { get; private set; }
public int? ActionUserId { get; set; }

[Column("action_type")]
public AuditActionType ActionType { get; private set; }
Expand Down
2 changes: 1 addition & 1 deletion Database/Entities/Interfaces/IAuditEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public interface IAuditEntity : IEntity
/// <summary>
/// Id of the <see cref="User"/> that took action on the record
/// </summary>
public int? ActionUserId { get; }
public int? ActionUserId { get; set; }

Check notice on line 33 in Database/Entities/Interfaces/IAuditEntity.cs

View workflow job for this annotation

GitHub Actions / Qodana for .NET

Type member is never used (non-private accessibility)

Accessor 'ActionUserId.get' is never used

/// <summary>
/// The type of action taken on the entity being audited
Expand Down
2 changes: 1 addition & 1 deletion Database/Interceptors/AuditingInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public int SavedChanges(SaveChangesCompletedEventData eventData, int result)
return result;
}

private void OnSavingChanges(DbContext context)
protected virtual void OnSavingChanges(DbContext context)
{
// Cache the current change list to avoid detecting changes multiple times
var trackedEntries = context.ChangeTracker.Entries().ToList();
Expand Down
5 changes: 0 additions & 5 deletions Database/OtrContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ namespace Database;
[SuppressMessage("ReSharper", "IdentifierTypo")]
public class OtrContext(DbContextOptions<OtrContext> options) : DbContext(options)
{
private readonly AuditingInterceptor _auditingInterceptor = new();

/// <summary>
/// SQL function for getting the current timestamp
/// </summary>
Expand Down Expand Up @@ -57,9 +55,6 @@ public class OtrContext(DbContextOptions<OtrContext> options) : DbContext(option
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<UserSettings> UserSettings { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.AddInterceptors(_auditingInterceptor);

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var auditChangesConverter = new ValueConverter<IDictionary<string, AuditChangelogEntry>, string>(
Expand Down

0 comments on commit 9cb12a7

Please sign in to comment.