2011-02-03 10 views
7

En otras palabras, ¿es esta una idea realmente estúpida?¿Cómo creo un AuthorizeAttribute personalizado que sea específico para el área, el controlador y la acción?

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 
public class AuthorizeActionAttribute : AuthorizeAttribute 
{ 
    public override void OnAuthorization(AuthorizationContext filterContext) 
    { 
     // get the area, controller and action 
     var area = filterContext.RouteData.Values["area"]; 
     var controller = filterContext.RouteData.Values["controller"]; 
     var action = filterContext.RouteData.Values["action"]; 
     string verb = filterContext.HttpContext.Request.HttpMethod; 

     // these values combined are our roleName 
     string roleName = String.Format("{0}/{1}/{2}/{3}", area, controller, action, verb); 

     // set role name to area/controller/action name 
     this.Roles = roleName; 

     base.OnAuthorization(filterContext); 
    } 
} 

ACTUALIZACIÓN que estoy tratando de evitar lo siguiente, en un escenario en el que tenemos muy granulares permisos de función debido a que los papeles son de configuración en función de cada cliente y se unen a grupos de usuarios:

public partial class HomeController : Controller 
{ 
    [Authorize(Roles = "/supplierarea/homecontroller/indexaction/")] 
    public virtual ActionResult Index() 
    { 
     return View(); 
    } 

    [Authorize(Roles = "/supplierarea/homecontroller/aboutaction/")] 
    public virtual ActionResult About() 
    { 
     return View(); 
    } 
} 

¿Alguien me puede aclarar de una manera segura para escribir este AuthorizeRouteAttribute para acceder a la información de la ruta y usar esto como el nombre de la función? Como dice Levi, el RouteData.Values ​​no es seguro.

¿El uso de la ejecución de httpContext.Request.Path es una práctica más segura o mejor?

public override void OnAuthorization(AuthorizationContext filterContext) 
{ 
    if (filterContext == null) 
    { 
     throw new ArgumentNullException("filterContext"); 
    } 

    if (!filterContext.HttpContext.User.Identity.IsAuthenticated) 
    { 
     // auth failed, redirect to login page 
     filterContext.Result = new HttpUnauthorizedResult(); 
     return; 
    } 

    var path = filterContext.HttpContext.Request.Path; 
    var verb = filterContext.HttpContext.Request.HttpMethod; 

    // these values combined are our roleName 
    string roleName = String.Format("{0}/{1}", path, verb); 

    if (!filterContext.HttpContext.User.IsInRole(roleName)) 
    { 
     // role auth failed, redirect to login page 
     filterContext.Result = new HttpUnauthorizedResult(); 
     // P.S. I want to tell the logged in user they don't 
     // have access, not ask them to login. They are already 
     // logged in! 
     return; 
    } 

    // 
    base.OnAuthorization(filterContext); 
} 

Esto tal vez ilustra el problema un poco más lejos:

enum Version 
{ 
    PathBasedRole, 
    InsecureButWorks, 
    SecureButMissingAreaName 
} 

string GetRoleName(AuthorizationContext filterContext, Version version) 
{ 
    // 
    var path = filterContext.HttpContext.Request.Path; 
    var verb = filterContext.HttpContext.Request.HttpMethod; 

    // recommended way to access controller and action names 
    var controller = 
     filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; 
    var action = 
     filterContext.ActionDescriptor.ActionName; 
    var area = "oh dear...."; // mmmm, where's thearea name??? 

    // 
    var insecureArea = filterContext.RouteData.Values["area"]; 
    var insecureController = filterContext.RouteData.Values["controller"]; 
    var insecureAction = filterContext.RouteData.Values["action"]; 

    string pathRoleName = 
     String.Format("{0}/{1}", path, verb); 
    string insecureRoleName = 
     String.Format("{0}/{1}/{2}/{3}", 
     insecureArea, 
     insecureController, 
     insecureAction, 
     verb); 
    string secureRoleName = 
     String.Format("{0}/{1}/{2}/{3}", 
     area, 
     controller, 
     action, 
     verb); 

    string roleName = String.Empty; 

    switch (version) 
    { 
     case Version.InsecureButWorks: 
      roleName = insecureRoleName; 
      break; 
     case Version.PathBasedRole: 
      roleName = pathRoleName; 
      break; 
     case Version.SecureButMissingAreaName: 
      // let's hope they don't choose this, because 
      // I have no idea what the area name is 
      roleName = secureRoleName; 
      break; 
     default: 
      roleName = String.Empty; 
      break; 
    } 

    return roleName; 
} 

Respuesta

18

Por favor, no lo hacen hacer esto.

Si realmente necesita, se puede utilizar el tipo del controlador o el MethodInfo de la acción a tomar decisiones de seguridad. Pero basar todo en cadenas es pedir problemas. Recuerde, no hay una asignación garantizada 1: 1 de los valores de enrutamiento al controlador real. Si está utilizando la tupla de enrutamiento (a, b, c) para validar el acceso a SomeController :: SomeAction pero alguien descubre que (a, b ', c) también realiza esa misma acción, esa persona puede omitir sus mecanismos de seguridad.

Editar para responder a los comentarios:

Usted tiene acceso a Tipo de controlador y MethodInfo de la acción a través de la propiedad ActionDescriptor la filterContext del parámetro . Esta es la única forma segura de determinar qué acción ejecutará realmente cuando se está procesando la tubería MVC, porque es posible que su búsqueda no coincida exactamente con MVC tras bambalinas. Una vez que tenga Type/MethodInfo/whatever, puede usar la información que desee (como sus nombres completos) para tomar decisiones de seguridad.

Como ejemplo práctico, considere un área MyArea con un controlador FooController y una acción TheAction. Normalmente, la manera que lo haría ir a este FooController :: theaction es a través de esta URL:

/MiArea/Foo/theaction

y enrutamiento da la tupla (Área = "MiArea", Controlador = " Foo ", Acción =" La Acción ").

Sin embargo, también se puede golpear FooController :: theaction a través de esta URL:

/Foo/theaction

y enrutamiento dará la tupla (Área = "", Controlador = "Foo ", Acción =" La Acción ").Recuerde, las áreas están asociadas a rutas, no a controladores. Y dado que un controlador puede ser afectado por múltiples rutas (si las definiciones coinciden), entonces un controlador también puede asociarse lógicamente con múltiples áreas. Es por eso que les decimos a los desarrolladores que nunca usen las rutas (o áreas o la etiqueta de ubicación < >, por extensión) para tomar decisiones de seguridad.

Además, hay un error en su clase en que es mutable (muta su propiedad Roles en OnAuthorization). Los atributos de filtro de acción deben ser inmutables, ya que pueden almacenarse en caché por partes de la canalización y reutilizarse. Dependiendo de dónde se declare este atributo en su aplicación, esto abre un ataque de temporización, que un visitante del sitio malicioso podría aprovechar para acceder a cualquier acción que desee.

Para obtener más información, véase también mis respuestas en:

+0

¿Podría agregar a su respuesta para mostrar cómo funciona su sugerencia en el código? Estamos usando áreas, por lo que tendría que reflejar esto, así como los controladores y las acciones. Fuera de interés, ¿realmente puede emparejar parcialmente con un controlador o acción (es decir, como se sugiere:/myarea/mycontroller/myaction '; Miembros de DROP TABLE; - /)? ¿Seguramente MVC no coincidirá con el controlador o la acción en primer lugar? – Junto

+0

Respuesta actualizada para responder a su pregunta. – Levi

+0

Hola Levi, entiendo que puedo usar (string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; string action = filterContext.ActionDescriptor.ActionName;) pero no puedo obtener acceso al nombre del área de la misma manera. Sin embargo, no hay AreaName disponible. ¿Dónde puedo ubicar eso? Un simple ejemplo de código cerraría esta pregunta. – Junto

5

Si quieres hacer esto, teniendo la recomendación de Levi en cuenta, la respuesta es la siguiente:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Web; 
using System.Web.Mvc; 
using System.Web.Security; 

namespace MvcApplication1.Extension.Attribute 
{ 
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 
    public class AuthorizeActionAttribute : AuthorizeAttribute 
    { 
     /// <summary> 
     /// Called when a process requests authorization. 
     /// </summary> 
     /// <param name="filterContext">The filter context, which encapsulates information for using <see cref="T:System.Web.Mvc.AuthorizeAttribute"/>.</param> 
     /// <exception cref="T:System.ArgumentNullException">The <paramref name="filterContext"/> parameter is null.</exception> 
     public override void OnAuthorization(AuthorizationContext filterContext) 
     { 
      if (filterContext == null) 
      { 
       throw new ArgumentNullException("filterContext"); 
      } 

      if (!filterContext.HttpContext.User.Identity.IsAuthenticated) 
      { 
       // auth failed, redirect to login page 
       filterContext.Result = new HttpUnauthorizedResult(); 

       return; 
      } 

      // these values combined are our roleName 
      string roleName = GetRoleName(filterContext); 

      if (!filterContext.HttpContext.User.IsInRole(roleName)) 
      { 
       filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page."); 
       filterContext.Result = new RedirectResult("~/Error/Unauthorized"); 

       return; 
      } 

      // 
      base.OnAuthorization(filterContext); 
     } 

     /// <summary> 
     /// Gets the name of the role. Theorectical construct that illustrates a problem with the 
     /// area name. RouteData is apparently insecure, but the area name is available there. 
     /// </summary> 
     /// <param name="filterContext">The filter context.</param> 
     /// <param name="version">The version.</param> 
     /// <returns></returns> 
     string GetRoleName(AuthorizationContext filterContext) 
     { 
      // 
      var verb = filterContext.HttpContext.Request.HttpMethod; 

      // recommended way to access controller and action names 
      var controllerFullName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerType.FullName; 
      var actionName = filterContext.ActionDescriptor.ActionName; 

      return String.Format("{0}.{1}-{2}", controllerFullName, actionName, verb); 
     } 
    } 
} 

No quise proporcionar un HttpU nauthorizedResult en el caso de que un usuario no esté en el rol, porque el resultado es enviar al usuario a la página de inicio de sesión. Teniendo en cuenta que ya están conectados, esto es extremadamente confuso para el usuario.

+1

¿Qué tal, en lugar de utilizar OnAuthorization para usar AuthorizeCore - http://stackoverflow.com/questions/5989100/asp-net-mvc-3-custom-authorisation? – gw0

+0

@ gw0, ¿Cómo se supone que se obtiene el 'filterContext' en' AuthorizeCore'? –

+0

@Junto, ¿cuál es el problema de llamar 'base '.OnAuthorization (filterContext); 'al final de su costumbre' OnAuthorization() '? –

1

¡Esto es un aviso corto! Asegúrese de utilizar filterContext.RouteData.DataTokens["area"]; en lugar de filterContext.RouteData.Values["area"];

Buena suerte.

Cuestiones relacionadas