Forma correcta de implementar comprobaciones de autorización en ASP MVC

8

En ASP MVC tenemos el atributo Autorizar para realizar una comprobación a nivel de controlador o a nivel de método de controlador. Pero, ¿qué sucede si necesita verificar los permisos dentro de un método de control? Algunos usuarios con permisos de revancha pueden adjuntar archivos o hacer que la publicación del blog sea difícil. Entonces, cuando está creando una nueva publicación, debe realizar todas estas comprobaciones adicionales antes de guardar el modelo. En Laravel existe el concepto de habilidades en las que puede realizar comprobaciones dentro de un método de control para ver si un usuario tiene la capacidad de realizar las acciones relevantes. Del mismo modo, puede usar esas abilitis en las vistas para ver qué elemento mostrar u ocultar: todo esto sale de la caja.

¿Hay algo similar en ASP MVC? ¿Cómo implementaría la verificación de permisos dentro de un método de controlador? ¿Creas una clase de permiso con propiedades como

public class Permissions
{
    private readonly IPrincipal user;

    public Permissions (IPrincipal user)
    {
        this.user = user;
    }

    public bool CanUploadFiles
    {
        get { return user.IsInAnyRole("Standard", "Admin"); }
    }

    public bool CanDeleteItems
    {
        get { return user.IsInRole("Admin"); }
    }

     public bool CanLockPost
    {
        get { return user.IsInRole("Admin"); }
    }

    // other permissions
}

Luego dentro de la acción del controlador:

 public ActionResult Create(PostViewModel viewModel)
 {
       var permissions = new Permissions(User);

        if (ModelState.IsValid)
        {
                var post = new Post
                {
                   if (permissions.CanLockPost)
                    {
                        post.IsLocked = viewModel.IsLocked;
                    }
                    if (permissions.CanStickyPost)
                    {
                        post.IsSticky = viewModel.IsSticky;
                    }
                    // Set other properties
                }

               _postRepository.Add(post);
        }   
  }

O guardar los permisos en la base de datos. Me gustaría escuchar sus opiniones sobre cómo implementa las verificaciones a un nivel más granular que simplemente a un controlador o a un nivel de acción del controlador. Algunos ejemplos de código para demostrar serían útiles.

    
pregunta adam78 22.07.2016 - 19:01

4 respuestas

1

Echa un vistazo a esto: enlace

"La forma en que se crean y administran estos roles depende del almacén de respaldo del proceso de autorización. Los roles se exponen al desarrollador a través de la propiedad IsInRole en la clase ClaimsPrincipal".

Entonces, mientras estés usando el IsInRole () del director para verificar los roles, creo que estás en el camino correcto.

Otros pensamientos aleatorios para usted: para facilitar el uso de las comprobaciones de permisos, si son específicas de un modelo de vista, puede exponerlas en la clase de modelo de vista. De esa manera, la vista de la maquinilla de afeitar puede verificar el modelo de vista para ver lo que debe / no debe mostrar al usuario.

También puede usar esas mismas comprobaciones de permisos para generar errores de validación implementando IValidatableObject en la clase de modelo de vista y colocando sus comprobaciones personalizadas en el método Validar. Luego, puede manejar los errores de permisos que impiden guardar como los errores de validación.

    
respondido por el Bryan 27.10.2016 - 19:28
1

Estamos implementando esto utilizando métodos de extensión a la interfaz IPrincipal:

public static class PermissionExtenions
{
    public bool CanUploadFiles(this IPrincipal user)
    {
        return user.IsInAnyRole("Standard", "Admin");
    }

    public bool CanDeleteItems(this IPrincipal user)
    {
        return user.IsInRole("Admin");
    }
}

Lo bueno de esto es que no necesitas crear un nuevo objeto que envuelva el IPrincipal . Si agrega una directiva using en la parte superior de sus controladores, y la agrega a los espacios de nombres al archivo Views / Web.config, puede usar esto en controladores y vistas accediendo a la propiedad User .

También puede usar esto en sus propios atributos de Autorización personalizados:

public void OnAuthorization(AuthorizationContext filterContext)
{
    var controller = filterContext.Controller as Controller;

    if (controller == null)
        return;

    var user = controller.User;

    if (!user.CanUploadFiles())
    {
        // redirect to some page
    }
    
respondido por el Greg Burghardt 25.01.2017 - 22:28
0

Puede imitar la estructura de AuthorizeAttribute. En uno de mis proyectos anteriores, segregé la funcionalidad "autorizable" en diferentes métodos y adjunté un atributo de autorización personalizado a esos métodos. Entonces, si necesita autorizar una subfunción de los métodos de su controlador, ponga esa funcionalidad en su propio método.

Puede pasar datos estáticos al constructor del atributo para indicar un identificador del objeto autorizable, y su atributo puede hacer la lógica de autorización que necesite para el objeto autorizable, teniendo en contexto el principio de seguridad del usuario solicitante / identidad del servicio, y la acción de seguridad.

Cómo autorizas algo es una historia completamente diferente. Recomendaría seleccionar un modelo de autorización probado (como RBAC, ABAC, ReBAC, etc.) y ver si hay bibliotecas de código abierto preexistentes que implementen esos modelos. En general, se recomienda que no cree su propio modelo de autorización.

    
respondido por el Mackers 22.07.2016 - 21:36
0

He usado atributos personalizados en combinación con el CQRS y el patrón decorador. Hablar es barato, así que solo te daré el código. Tenga en cuenta que el atributo se puede usar en el nivel del controlador O en el nivel del comando, o en ambos si está realmente paranoico.

Nota: este no es todo mi código, el creador de SimpleInjector (Steven van Deursen) inspiró esto en una publicación que ya no puedo encontrar. Todo esto está conectado a través de IoC (SimpleInjector).

/// <summary>
/// For the Command Side
/// </summary>
[Permission(Permission.StickyPost)]
public class StickyPostCommand
{
    public int PostId { get; set; }
}

public class StickyPostCommandHandler : ICommandHandler<StickyPostCommand>
{
    public void Handle(StickyPostCommand command)
    {
        //We only handle what we are here to handle (stickying a post), not checking permissions
    }
}

/// <summary>
/// This decorates all commands, and does some checks before passing it on to the real command (decoartee)
/// </summary>
/// <typeparam name="TCommand"></typeparam>
public class PermissionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
    private readonly MessagePermissionChecker<TCommand> permissionChecker;
    private readonly ICommandHandler<TCommand> decoratee;

    public PermissionCommandHandlerDecorator(MessagePermissionChecker<TCommand> permissionChecker, ICommandHandler<TCommand> decoratee)
    {
        this.decoratee = decoratee;
        this.permissionChecker = permissionChecker;
    }
    public void Handle(TCommand command)
    {
        this.permissionChecker.CheckPermissionForCurrentUser();
        this.decoratee.Handle(command);
    }
}

/// <summary>
/// Pulls the custom attribute out of the command, delegate to the UserPermissionChecker if it has an attribute
/// </summary>
/// <typeparam name="TMessage"></typeparam>
public class MessagePermissionChecker<TMessage>
{
    private static readonly Guid? permissionId;
    private readonly IUserPermissionChecker permissionChecker;

    static MessagePermissionChecker()
    {
        var permissionAttribute = typeof(TMessage).GetCustomAttribute<PermissionAttribute>();
        if (permissionAttribute != null)
        {
            permissionId = Guid.Parse(permissionAttribute.PermissionId);
        }
    }

    public MessagePermissionChecker(IUserPermissionChecker permissionChecker)
    {
        this.permissionChecker = permissionChecker;
    }

    public void CheckPermissionForCurrentUser()
    {
        if (permissionId.HasValue)
        {
            this.permissionChecker.CheckPermission(permissionId);
        }
    }
}

public interface IUserPermissionChecker
{
    void CheckPermission(Guid id);
}

public class UserPermissionChecker : IUserPermissionChecker
{
    public void CheckPermission(Guid id)
    {
        //Go out to the database, check a role, whatever
        throw new SecurityException("Negative ghostrider, you can't do that!");
    }
}

/// <summary>
/// For the Controller Side
/// </summary>
public class PermissionActionFilter : IActionFilter<PermissionAttribute>
{
    private readonly IUserPermissionChecker _permissionChecker;

    public PermissionActionFilter(IUserPermissionChecker permissionChecker)
    {
        _permissionChecker = permissionChecker;
    }

    public void OnActionExecuting(PermissionAttribute attribute, ActionExecutingContext context)
    {
        this._permissionChecker.CheckPermission(Guid.Parse(attribute.PermissionId));
    }
}
    
respondido por el Jack 24.02.2017 - 23:53

Lea otras preguntas en las etiquetas