Commit a5b10c7d authored by Eric Dieckman's avatar Eric Dieckman
Browse files

Add auditing

parent 91a322da
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
using Cals.Visor.Infrastructure.Contexts;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Cals.Visor.Api.Controllers;

[Route("api/[controller]")]
[ApiController]
public class AuditHistoryExampleController : ControllerBase
{
    private readonly ApplicationDbContext _context;

    public AuditHistoryExampleController(ApplicationDbContext context) => _context = context;

    [HttpGet("{table}/{id}/history")]
    public async Task<IActionResult> GetHistory(string table, string id)
    {
        var logs = await _context.AuditLogs
            .Where(x => x.TableName == table && x.EntityId == id)
            .OrderByDescending(x => x.CreatedAt)
            .ToListAsync();

        return Ok(logs);
    }
}
+26 −0
Original line number Diff line number Diff line
using Cals.Authentication;
using Cals.Visor.Identity;
using Cals.Visor.Models.Identity;
using Microsoft.Extensions.Options;

namespace Cals.Visor.Api.Identity;
@@ -51,4 +53,28 @@ public class IdentityMiddleware

        await _next(context);
    }

    private async Task<AppUser> EnsureUserIsRegistered(IApplicationUser user)
    {
        // unique is required to do anything with user registration
        if (user.UniqueId == null)
            throw new Exception("User must have an non-null UniqueId to be registered");

        AppUser userToUpdate = user.ToAppUser();

        // is this user already registered?
        AppUser? userAtApi = await _apiClient.AppUsers.GetUserAsync(user.UniqueId);

        if (userAtApi != null)
        {
            userToUpdate.AppUserId = userAtApi.AppUserId;
            await _apiClient.AppUsers.UpdateUserAsync(userToUpdate);
            return userToUpdate;
        }

        userAtApi = await _apiClient.AppUsers.RegisterUserAsync(userToUpdate);

        return userAtApi;
    }

}
+28 −0
Original line number Diff line number Diff line
using Microsoft.Extensions.Primitives;

namespace Cals.Visor.Api.Middleware;

/// <summary>
/// Middleware to attach a unique correlation ID to every request
/// </summary>
/// <remarks>
/// All changes inside one API call can be grouped
/// </remarks>
public class CorrelationIdMiddleware
{
    private const string HeaderName = "X-Correlation-ID";
    private readonly RequestDelegate _next;

    public CorrelationIdMiddleware(RequestDelegate next) => _next = next;

    public async Task Invoke(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue(HeaderName, out StringValues correlationId))
            correlationId = Guid.NewGuid().ToString();

        context.Items["CorrelationId"] = correlationId;
        context.Response.Headers[HeaderName] = correlationId;

        await _next(context);
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ using Azure.Core;
using Azure.Identity;
using Cals.Visor.Api.Authorization;
using Cals.Visor.Api.Identity;
using Cals.Visor.Api.Middleware;
using Cals.Visor.Application;
using Cals.Visor.Application.Identity;
using Cals.Visor.Infrastructure;
@@ -113,6 +114,8 @@ builder.Host.UseDefaultServiceProvider(o =>

WebApplication app = builder.Build();

app.UseMiddleware<CorrelationIdMiddleware>();

app.UseStaticFiles();

// AFTER UseStaticFiles, UseExceptionHandler or UseStatusCodePagesWithReExecute but BEFORE UseAuthentication and UseAuthorization
+19 −10
Original line number Diff line number Diff line
@@ -9,30 +9,38 @@ namespace Cals.Visor.Infrastructure.Contexts;
// TODO - change to internal - only use repositories to talk to Context
public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
    private readonly AuditSaveChangesInterceptor _auditInterceptor;

    public ApplicationDbContext(
        DbContextOptions<ApplicationDbContext> options,
        AuditSaveChangesInterceptor auditInterceptor)
        : base(options)
    {
        // ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        _auditInterceptor = auditInterceptor;
    }

    public DbSet<AppUser> Users => Set<AppUser>();
    public DbSet<Person> People => Set<Person>();
    public DbSet<PaloAltoFirewall> PaloAltoFirewalls => Set<PaloAltoFirewall>();
    public DbSet<Subnet> Subnets => Set<Subnet>();
    
    public DbSet<Area> Areas => Set<Area>();
    public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
    public DbSet<InfobloxDomain> InfobloxDomains => Set<InfobloxDomain>();
    public DbSet<InfobloxGroup> InfobloxGroups => Set<InfobloxGroup>();
    public DbSet<InfobloxNetwork> InfobloxNetworks => Set<InfobloxNetwork>();
    public DbSet<InfobloxRole> InfobloxRoles => Set<InfobloxRole>();
    public DbSet<InfobloxRoleAccess> InfobloxRoleAccess => Set<InfobloxRoleAccess>();
    public DbSet<Location> Locations => Set<Location>();
    public DbSet<LocationVlan> LocationVlans => Set<LocationVlan>();
    public DbSet<PaloAltoAccessDomain> PaloAltoAccessDomains => Set<PaloAltoAccessDomain>();
    public DbSet<PaloAltoFirewall> PaloAltoFirewalls => Set<PaloAltoFirewall>();
    public DbSet<Person> People => Set<Person>();
    public DbSet<Vlan> Vlans => Set<Vlan>();
    public DbSet<VlanUseType> VlanUseTypes => Set<VlanUseType>();
    public DbSet<PaloAltoVsys> PaloAltoVsyses => Set<PaloAltoVsys>();
    public DbSet<Subnet> Subnets => Set<Subnet>();
    public DbSet<WiscNicGroup> WiscNicGroups => Set<WiscNicGroup>();
    public DbSet<WiscNicGroupMembershipType> WiscNicGroupMembershipTypes => Set<WiscNicGroupMembershipType>();
    public DbSet<InfobloxDomain> InfobloxDomains => Set<InfobloxDomain>();
    public DbSet<InfobloxGroup> InfobloxGroups => Set<InfobloxGroup>();
    public DbSet<InfobloxNetwork> InfobloxNetworks => Set<InfobloxNetwork>();
    public DbSet<InfobloxRole> InfobloxRoles => Set<InfobloxRole>();
    public DbSet<InfobloxRoleAccess> InfobloxRoleAccess => Set<InfobloxRoleAccess>();
    

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
@@ -58,7 +66,8 @@ public class ApplicationDbContext : DbContext
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSnakeCaseNamingConvention();
            .UseSnakeCaseNamingConvention()
            .AddInterceptors(_auditInterceptor);
    }

    public override int SaveChanges(bool acceptAllChangesOnSuccess)
Loading