using System.Security.Claims;
using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
namespace adas_core.Authentication.Attributes;
public class PermissionAuthorizeAttribute(string source, string? resourceIdHeader = null)
: AuthorizeAttribute, IAsyncAuthorizationFilter
{
///
/// Performs asynchronous authorization by validating the user's roles, username, and source header against the permission service.
/// For the Panel source, it verifies that at least one of the user's roles grants access to the panel; for other sources, it requires a resource identifier header (such as DisplayId or UnitId) and checks access to the corresponding resource.
/// If any required value is missing or no role grants the needed permission, the response is set to .
///
/// The for the current authorization request, providing access to the HTTP context, services, and the result to set when authorization fails.
/// Thrown when the cannot be resolved from the request services.
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
// Obtener el servicio de permisos.
var permissionService = context.HttpContext.RequestServices.GetService()
?? throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
var user = context.HttpContext.User;
// Obtener el header que indica la fuente.
var headerSource = context.HttpContext.Request.Headers[source].FirstOrDefault();
// Obtener el identificador del usuario
var username = user.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
// Obtiene la lista de roles (valores) del usuario.
var userRoles = user.Claims
.Where(c => c.Type == ClaimTypes.Role)
.Select(c => c.Value)
.ToList();
// Procesa cada rol: si contiene 2 guiones bajos, elimina el tercer segmento.
var processedRoles = userRoles.Select(role =>
{
var parts = role.Split('_');
return parts.Length == 2 ? parts[0] : role;
}).ToList();
// Validar que se tenga al menos un rol, username y headerSource.
if (!processedRoles.Any() || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(headerSource))
{
context.Result = new ForbidResult();
return;
}
// Parseamos el header para obtener la fuente.
var source1 =
(PermissionEnum.SourcePermissionsEnum)Enum.Parse(typeof(PermissionEnum.SourcePermissionsEnum),
headerSource);
var hasPermission = false;
if (source1 == PermissionEnum.SourcePermissionsEnum.Panel)
{
// Si la fuente es PANEL, verificamos que alguno de los roles tenga acceso.
foreach (var roleStr in processedRoles)
{
if (!Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj)) continue;
var role = (PermissionEnum.RolesType)roleObj;
if (!await permissionService.HasAccessToPanel(username, role, source1)) continue;
hasPermission = true;
break;
}
if (!hasPermission) context.Result = new ForbidResult();
}
else if (resourceIdHeader != null)
{
// Para otras fuentes, se requiere un header que indique el resourceId.
var resourceId = context.HttpContext.Request.Headers[resourceIdHeader].FirstOrDefault();
if (string.IsNullOrEmpty(resourceId))
{
context.Result = new ForbidResult();
return;
}
// Verificar que al menos uno de los roles tenga acceso al recurso.
foreach (var roleStr in processedRoles)
if (Enum.TryParse(typeof(PermissionEnum.RolesType), roleStr, out var roleObj))
{
var role = (PermissionEnum.RolesType)roleObj;
hasPermission = resourceIdHeader switch
{
"DisplayId" => await permissionService.HasAccessToDisplay(username, role, source1, resourceId),
"UnitId" => await permissionService.HasAccessToUnit(username, role, source1, resourceId),
_ => false
};
if (hasPermission)
break;
}
if (!hasPermission) context.Result = new ForbidResult();
}
}
}