UDT-006: Middleware de Autorización (RBAC enforcement) #10
@@ -0,0 +1,58 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization.Policy;
|
||||
|
||||
namespace SIGCM2.Api.Authorization;
|
||||
|
||||
/// <summary>
|
||||
/// Custom IAuthorizationMiddlewareResultHandler that emits a structured ProblemDetails
|
||||
/// response for 403 Forbidden outcomes (authenticated user, missing permission).
|
||||
///
|
||||
/// For 401 Unauthorized and successful outcomes, delegates to the default handler
|
||||
/// so the existing JWT Bearer challenge flow is unaffected (REQ-B-07).
|
||||
///
|
||||
/// Registered as singleton in Program.cs — depends only on framework services.
|
||||
/// </summary>
|
||||
public sealed class ForbiddenProblemDetailsHandler : IAuthorizationMiddlewareResultHandler
|
||||
{
|
||||
private static readonly AuthorizationMiddlewareResultHandler DefaultHandler = new();
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
};
|
||||
|
||||
public async Task HandleAsync(
|
||||
RequestDelegate next,
|
||||
HttpContext context,
|
||||
AuthorizationPolicy policy,
|
||||
PolicyAuthorizationResult authorizeResult)
|
||||
{
|
||||
// Only intercept 403s for authenticated users.
|
||||
// If the user is not authenticated, the 401 challenge is handled by JwtBearer (REQ-B-07).
|
||||
if (authorizeResult.Forbidden && context.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
var requiredPermission = context.Items["RequiredPermission"] as string;
|
||||
|
||||
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
context.Response.ContentType = "application/problem+json; charset=utf-8";
|
||||
|
||||
var problem = new
|
||||
{
|
||||
type = "https://sigcm2.local/errors/forbidden",
|
||||
title = "Acceso denegado",
|
||||
status = 403,
|
||||
detail = "No tenés el permiso requerido para ejecutar esta acción.",
|
||||
permisoRequerido = requiredPermission,
|
||||
};
|
||||
|
||||
await context.Response.WriteAsync(
|
||||
JsonSerializer.Serialize(problem, SerializerOptions));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Delegate 401 challenges and successful outcomes to the default handler
|
||||
await DefaultHandler.HandleAsync(next, context, policy, authorizeResult);
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ builder.Services.AddInfrastructure(builder.Configuration);
|
||||
// Authorization — handler lives in Api layer; DO NOT move to Infrastructure DI
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
|
||||
builder.Services.AddSingleton<IAuthorizationMiddlewareResultHandler, ForbiddenProblemDetailsHandler>();
|
||||
|
||||
// Controllers with exception filter
|
||||
builder.Services.AddControllers(opts =>
|
||||
|
||||
Reference in New Issue
Block a user