using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Tiku.Application.Security; using Tiku.Domain.Operations; using Tiku.Infrastructure.Persistence; namespace Tiku.Api.Security; internal sealed class AuditingAuthorizationMiddlewareResultHandler( IServiceScopeFactory scopeFactory, ILogger logger) : IAuthorizationMiddlewareResultHandler { private readonly AuthorizationMiddlewareResultHandler fallback = new(); public async Task HandleAsync( RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult) { if (authorizeResult.Forbidden && context.User.Identity?.IsAuthenticated == true) { try { await using var scope = scopeFactory.CreateAsyncScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); dbContext.AuditLogs.Add(new AuditLog { TenantId = Guid.TryParse(context.User.FindFirst(TikuClaimTypes.TenantId)?.Value, out var tenantId) ? tenantId : null, ActorUserId = Guid.TryParse(context.User.FindFirst(TikuClaimTypes.UserId)?.Value, out var userId) ? userId : null, Action = "authorization.access_denied", TargetType = "http_endpoint", TargetId = context.Request.Path, IpAddress = context.Connection.RemoteIpAddress?.ToString(), UserAgent = context.Request.Headers.UserAgent.ToString(), Details = JsonSerializer.SerializeToElement(new { Method = context.Request.Method, Path = context.Request.Path.Value, Realm = context.User.FindFirst(TikuClaimTypes.Realm)?.Value, Failure = authorizeResult.AuthorizationFailure?.FailureReasons.Select(reason => reason.Message).ToArray() }) }); await dbContext.SaveChangesAsync(context.RequestAborted); } catch (Exception exception) { logger.LogWarning(exception, "Failed to persist authorization denial audit event."); } } await fallback.HandleAsync(next, context, policy, authorizeResult); } }