9.5.4 Seguridad con Action Filters

Otra forma de manejar la seguridad es mediante Action Filters. Los action filters como ya lo vimos en el tema 5.5.1 Agregando una excepción a todos nuestros servicios .Net cuenta con Action Filters, los cuales se ejecutan antes de cada servicio, aquí es donde validamos que el usuario tenga permiso para realizar la acción.

El filtro de tipo Acción (IActionFilter) cuenta con los siguientes métodos

  • OnActionExecuting: El código dentro de esta función se ejecuta antes de cada servicio.

  • OnActionExecuted: El código dentro de esta función se ejecuta después de cada servicio. Más adelante utilizare este método para guardar el historial de que usuario ha agregado o modificado registros.

Vamos a agregar una clase BaseController, la cual contendrá las propiedades y métodos comunes a todos los controllers. En esta clase vamos a agregar un objeto de nuestra clase PermisoDTO para identificar el nombre de la tabla que deseamos validar.

  1. Agregamos nuestra clase BaseController que hereda de la clase Controller, en la carpeta Controllers

BaseController.cs
public class BaseController : Controller
{
    private readonly CaducaContext _context;

    private readonly LocService _localizer;

    public PermisoDTO permiso;

    public BaseController(CaducaContext context, LocService localizer)
    {
        this._context = context;
        this._localizer = localizer;
        permiso = new PermisoDTO();
    }
}

2. Cambiamos nuestro controller ProductosController, para que herede de nuestra clase creada y agregamos los datos de nuestro objeto permisoDTO, en nuestro constructor agregamos nuestro constructor de la clase base.

ProductosController.cs
public class ProductosController : BaseController
{
    public ProductosController(CaducaContext context, 
                               LocService localize) 
                                          : base( context,  localize)
        {
            _context = context;
            productoDAO = new ProductoDAO(context, localize);
            this.permiso.Tabla = "Producto";
            this.permiso.RequiereAdministrador = false;
        }

3. En nuestra carpeta de Filters agregamos una nueva clase PermisoFilter que hereda de la clase IActionFilter.

4. En esta clase vamos a validar el permiso

PermisoFilter.cs
using System;
using System.Security.Claims;
using CaducaRest.Controllers;
using CaducaRest.Core;
using CaducaRest.DAO;
using CaducaRest.Models;
using CaducaRest.Resources;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace CaducaRest.Filters;

public class PermisoFilter: IActionFilter
{
    private readonly LocService _localizer;
    private readonly CaducaContext _context;

    public PermisoFilter(CaducaContext context, 
                         LocService localizer)
    {
        this._localizer = localizer;
        this._context = context;
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        
    }
    // Este método se ejecuta antes de cada acción (PUT, GET,
    // POST, PATCH, DELETE)
    public void OnActionExecuting(ActionExecutingContext context)
    {
        JsonResult jsonResult;
        CustomError customError = new CustomError
                             (403, "El usuario es inválido");
        //Obtenemos los datos del usuario
        ClaimsPrincipal Usuario = context.HttpContext.User;
        //Se revisa si en el token se incluye el claim con el 
        //id del usuario
        if (!Usuario.HasClaim(c => c.Type == ClaimTypes.Sid))
        {
            //regresamos un mensaje de error
            jsonResult = new JsonResult(customError)
            {
                StatusCode = 403,
                Value = customError.Message
            };
            context.Result = jsonResult;
            return;
        }
        var usuarioId = Convert.ToInt32(Usuario
                .FindFirst(c => c.Type == ClaimTypes.Sid).Value);

        //Obtenemos los datos del controller
        var controller = context.Controller as BaseController;
        var metodo = controller.Request.Method.ToLower();

        RolDAO rolDAO = new RolDAO(_context, _localizer);
        bool esAdministrador = rolDAO.EsAdministrador(usuarioId);
        //Si el recurso  requiere un usuario administrador 
        // se valida que el usuario sea administrador, 
        // si no es asi marcar error
        if (controller.permiso.RequiereAdministrador 
                 && !esAdministrador)
        {
            jsonResult = new JsonResult(customError)
            {
                StatusCode = 403,
                Value = customError.Message
            };
            context.Result = jsonResult;
            return;
        }
        else
        {
            string operacion = string.Empty;
            //obtenemos el tipo de método que se esta ejecutando
            switch (metodo)
            {
                case "get":
                    operacion = Operaciones.Consultar.Name;
                    break;
                case "post":
                    operacion = Operaciones.Crear.Name;
                    break;
                case "put":
                case "patch":
                    operacion = Operaciones.Modificar.Name;
                    break;
                case "delete":
                    operacion = Operaciones.Borrar.Name;
                    break;
            }
            //Se revisa si el usuario tiene autorización para
            // realizar la acción
            RolTablaPermisoDAO rolTablaPermisoDAO =
                  new RolTablaPermisoDAO(_context, _localizer);
            if (!rolTablaPermisoDAO.TienePermiso(usuarioId, 
                          controller.permiso.Tabla, operacion))
            {
                jsonResult = new JsonResult(customError)
                {
                    StatusCode = 403,
                    Value = customError.Message
                };
                context.Result = jsonResult;
                return;
            }
        }
    }
}

Si deseamos agregar esta validación a nuestros servicios agregamos nuestra clase PermisoFilter como atributo a nuestros Controller

ProductosController.cs
 [TypeFilter(typeof(PermisoFilter))]
 public class ProductosController : BaseController

Puedes agregar el action filter también para solo un método.

ClientesController.cs
[EnableQuery]
[TypeFilter(typeof(PermisoFilter))]
public IActionResult Get()

Puedes agregar un breakpoint en el método OnActionExecuting para comprobar que antes de cada servicio se manda llamar el código de este método y comprobar que se valida correctamente los permisos.

Puedes ver la documentación oficial aquí

Last updated