2011-02-03 8 views
7

In altre parole, è un'idea davvero stupida?Come si crea un AuthorizeAttribute personalizzato specifico per area, controller e azione?

[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); 
    } 
} 

UPDATE Sto cercando di evitare quanto segue, in uno scenario in cui si abbiano i permessi di ruolo estremamente granulari, perché i ruoli sono impostati su una base per-client e attaccati a gruppi di utenti:

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(); 
    } 
} 

Qualcuno può illuminarmi in modo sicuro per scrivere questo AuthorizeRouteAttribute per accedere alle informazioni sul percorso e utilizzarlo come nome del ruolo? Come dice Levi, il RouteData.Values ​​non è sicuro.

L'utilizzo dell'esecuzione di httpContext.Request.Path è più sicuro o migliore?

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); 
} 

Questo illustra forse il problema un po 'più:

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; 
} 

risposta

18

prega non fanno fare questo.

Se davvero necessario, è possibile utilizzare il Tipo del controller o la MethodInfo dell'azione di prendere decisioni di sicurezza. Ma basare tutto su delle stringhe è un problema. Ricorda, non è garantito il mapping 1: 1 dei valori di routing al controller attuale. Se stai usando la tupla di routing (a, b, c) per convalidare l'accesso a SomeController :: SomeAction ma qualcuno scopre che (a, b ', c) colpisce anche la stessa azione, quella persona può aggirare i tuoi meccanismi di sicurezza.

Modifica di rispondere ai commenti:

Si ha accesso al tipo del controller e MethodInfo dell'azione tramite proprietà ActionDescriptor il filterContext del parametro . Questo è l'unico modo sicuro per determinare quale azione eseguirà in realtà durante l'elaborazione della pipeline MVC, perché è possibile che la ricerca non corrisponda esattamente a ciò che accade dietro le quinte con MVC. Una volta ottenuto il Type/MethodInfo/qualunque, è possibile utilizzare qualsiasi informazione desiderata (come i nomi completi) per prendere decisioni sulla sicurezza.

Come esempio pratico, considerare un'area MyArea con un controller FooController e un'azione TheAction. Normalmente il modo in cui si colpire questo FooController :: TheAction è tramite questo URL:

/MyArea/Foo/TheAction

e il routing dà la tupla (Area = "MyArea", regolatore = " Foo ", Action =" TheAction ").

Tuttavia, è anche possibile colpire FooController :: TheAction tramite questo URL:

/Foo/TheAction

e il routing darà la tupla (Area = "", regolatore = "Foo ", Azione =" TheAction ").Ricorda, le aree sono associate ai percorsi, non ai controller. E poiché un controller può essere colpito da più percorsi (se le definizioni corrispondono), un controller può anche essere associato logicamente a più aree. Questo è il motivo per cui diciamo agli sviluppatori di non utilizzare mai le route (o le aree o il tag > di posizione > per estensione per prendere decisioni sulla sicurezza.

Inoltre, c'è un bug nella tua classe in quanto è mutabile (muta la sua proprietà di ruoli in OnAuthorization). Gli attributi del filtro di azione devono essere immutabili, poiché possono essere memorizzati nella cache da parti della pipeline e riutilizzati. A seconda di dove questo attributo è dichiarato nella propria applicazione, si apre un attacco di temporizzazione, che un visitatore del sito malintenzionato potrebbe quindi sfruttare per concedersi l'accesso a qualsiasi azione desideri.

Per ulteriori informazioni, consultare anche le mie risposte a:

+0

Si prega di aggiungere alla risposta per mostrare come il vostro suggerimento funziona in codice? Stiamo usando le aree, quindi dovrebbe riflettere questo come i controllori e le azioni. Per disinteresse, puoi realmente condividere una corrispondenza con un controller o un'azione (ad esempio, come suggerito:/myarea/mycontroller/myaction '; membri di DROP TABLE; - /)? Sicuramente MVC non corrisponderà al controller o all'azione in primo luogo? – Junto

+0

Risposta aggiornata per rispondere alla tua domanda. – Levi

+0

Ciao Levi, ho capito che posso usare (string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; string action = filterContext.ActionDescriptor.ActionName;) ma non riesco ad accedere al nome dell'area nello stesso modo. Tuttavia, non è disponibile alcun AreaName. Dove posso individuarlo? Un semplice esempio di codice chiuderebbe questa domanda. – Junto

5

Se si vuole fare questo, prendendo la raccomandazione di Levi in ​​considerazione, la risposta è la seguente:

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); 
     } 
    } 
} 

Non volevo fornire un HttpU nauthorizedResult nel caso in cui un utente non sia in ruolo, perché il risultato è inviare l'utente alla pagina di accesso. Considerando che sono già registrati, questo è estremamente complicato per l'utente.

+1

E invece di usare OnAuthorization per usare AuthorizeCore - http://stackoverflow.com/questions/5989100/asp-net-mvc-3-custom-authorisation? – gw0

+0

@ gw0, come si suppone di ottenere 'filterContext' in' AuthorizeCore'? –

+0

@Junto, qual è il problema di chiamare 'base.OnAuthorization (filterContext); 'alla fine del tuo custom' OnAuthorization() '? –

1

Questo è un breve preavviso! Assicurati di utilizzare filterContext.RouteData.DataTokens["area"]; anziché filterContext.RouteData.Values["area"];

Buona fortuna.

Problemi correlati