using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using SIGCM2.Application.Abstractions.Persistence; namespace SIGCM2.Api.Authorization; /// /// Authorization handler for . /// Reads the "rol" claim from the authenticated user, queries /// for the role's assigned permissions, and succeeds if at least one matches (OR semantics). /// No caching — UDT-006 design decision D1: always authoritative from DB. /// public sealed class PermissionAuthorizationHandler : AuthorizationHandler { private readonly IRolPermisoRepository _rolPermisoRepo; private readonly ILogger _logger; public PermissionAuthorizationHandler( IRolPermisoRepository rolPermisoRepo, ILogger logger) { _rolPermisoRepo = rolPermisoRepo; _logger = logger; } protected override async Task HandleRequirementAsync( AuthorizationHandlerContext context, RequirePermissionAttribute requirement) { // 1. Must be authenticated — defense-in-depth (AuthorizeAttribute already requires it) if (context.User?.Identity?.IsAuthenticated != true) { return; // implicit Fail — nothing Succeeded } // 2. Extract "rol" claim — JwtBearer is configured with RoleClaimType="rol" var rolCodigo = context.User.FindFirst("rol")?.Value; if (string.IsNullOrWhiteSpace(rolCodigo)) { _logger.LogWarning( "Authorization failed — token missing 'rol' claim for user {User}", context.User.Identity?.Name); context.Fail(new AuthorizationFailureReason(this, "missing_rol_claim")); return; } // 3. Load permissions assigned to this role — no cache (UDT-006 D1) var permisos = await _rolPermisoRepo.GetByRolCodigoAsync(rolCodigo); var permisoCodes = permisos.Select(p => p.Codigo).ToHashSet(StringComparer.Ordinal); // 4. OR semantics — any single match is enough var matched = requirement.PermissionCodes .FirstOrDefault(code => permisoCodes.Contains(code)); if (matched is not null) { context.Succeed(requirement); return; } // 5. Stash required permission for ForbiddenProblemDetailsHandler (Batch 3) if (context.Resource is HttpContext httpContext) { httpContext.Items["RequiredPermission"] = requirement.PermissionCodes[0]; } context.Fail(new AuthorizationFailureReason(this, $"missing_permission:{string.Join('|', requirement.PermissionCodes)}")); } }