2010-07-28 10 views
8

Estoy tratando de proteger mis rutas MVC de un conjunto de usuarios que cumplen un conjunto de criterios. Dado que MVC parece usar atributos bastante y Steven Sanderson usa uno para la extensibilidad de la seguridad en su libro pro MVC, comencé a seguir esta ruta, pero me gustaría definir la regla contextualmente en función de la acción a la que la estoy aplicando.Pasar Func como un parámetro de atributo para asegurar las rutas MVC

Algunas acciones son solo para empleados, otras no.

Algunas acciones son solo para company1, otras no.

Así que estaba pensando este tipo de uso ...

[DisableAccess(BlockUsersWhere = u => u.Company != "Acme")] 
public ActionResult AcmeOnlyAction() 
{ 
... 
} 

[DisableAccess(BlockUsersWhere = u => u.IsEmployee == false)] 
public ActionResult EmployeeOnlyAction() 
{ 
... 
} 

ve bastante limpio para mí y es realmente bastante fácil de implementar, pero me sale el siguiente error de compilación:

'BlockUsersWhere' no es un argumento de atributo con nombre válido porque no es un parámetro de atributo válido tipo

Aparentemente no se puede usar un Func como argumento de atributo. ¿Alguna otra sugerencia para evitar este problema u otra cosa que proporcione el uso simple que hemos llegado a amar en nuestros proyectos de MVC?

Respuesta

4

La sugerencia de Necros funcionaría, sin embargo, debería invocar su ayudante SecurityGuard en el cuerpo de cada método de acción.

Si aún desea ir con el enfoque basado en los atributos declarativos (que tiene la ventaja de que se puede aplicar el atributo a todo el Controlador) se podría escribir su propio AuthorizeAttribute

public class CustomAuthorizeAttribute : AuthorizeAttribute { 
    public bool EmployeeOnly { get; set; } 
    private string _company; 

    public string Company { 
     get { return _company; } 
     set { _company = value; } 
    } 


    protected override bool AuthorizeCore(HttpContextBase httpContext) { 
     return base.AuthorizeCore(httpContext) && MyAuthorizationCheck(httpContext); 
    } 

    private bool MyAuthorizationCheck(HttpContextBase httpContext) { 
     IPrincipal user = httpContext.User; 

     if (EmployeeOnly && !VerifyUserIsEmployee(user)) { 
      return false; 
     } 

     if (!String.IsNullOrEmpty(Company) && !VerifyUserIsInCompany(user)) { 
      return false; 
     } 

     return true; 
    } 

    private bool VerifyUserIsInCompany(IPrincipal user) { 
     // your check here 
    } 

    private bool VerifyUserIsEmployee(IPrincipal user) { 
     // your check here 
    } 
} 

entonces sería úselo de la siguiente manera

[CustomAuthorize(Company = "Acme")] 
public ActionResult AcmeOnlyAction() 
{ 
... 
} 

[CustomAuthorize(EmployeeOnly = true)] 
public ActionResult EmployeeOnlyAction() 
{ 
... 
} 
+0

Gracias, esto es lo primero que pensé, pero con este enfoque pierdo la capacidad de crear reglas únicas en un contexto sin tener que actualizar la clase de atributo. Es probable que me dirija a esta ruta. – jjr2527

+0

Sí, los atributos solo pueden transmitir una cantidad limitada de información. Realmente deberían ser solo acerca de la asignación de metadatos. Comportamientos más complejos pertenecen en el código. – marcind

1

Dado que solo puede usar constantes, tipos o inicializadores de matriz en los parámetros de atributos, probablemente no funcionarán, o al menos no serán tan flexibles.

Alternativamente, podría utilizar algo similar que se me ocurrió al resolver este problema.

Esta es la API:

public static class SecurityGuard 
{ 
    private const string ExceptionText = "Permission denied."; 

    public static bool Require(Action<ISecurityExpression> action) 
    { 
     var expression = new SecurityExpressionBuilder(); 
     action.Invoke(expression); 
     return expression.Eval(); 
    } 

    public static bool RequireOne(Action<ISecurityExpression> action) 
    { 
     var expression = new SecurityExpressionBuilder(); 
     action.Invoke(expression); 
     return expression.EvalAny(); 
    } 

    public static void ExcpetionIf(Action<ISecurityExpression> action) 
    { 
     var expression = new SecurityExpressionBuilder(); 
     action.Invoke(expression); 
     if(expression.Eval()) 
     { 
      throw new SecurityException(ExceptionText); 
     } 
    } 
} 

public interface ISecurityExpression 
{ 
    ISecurityExpression UserWorksForCompany(string company); 
    ISecurityExpression IsTrue(bool expression); 
} 

A continuación, cree un generador de expresiones:

public class SecurityExpressionBuilder : ISecurityExpression 
{ 
    private readonly List<SecurityExpression> _expressions; 

    public SecurityExpressionBuilder() 
    { 
     _expressions = new List<SecurityExpression>(); 
    } 

    public ISecurityExpression UserWorksForCompany(string company) 
    { 
     var expression = new CompanySecurityExpression(company); 
     _expressions.Add(expression); 
     return this; 
    } 

    public ISecurityExpression IsTrue(bool expr) 
    { 
     var expression = new BooleanSecurityExpression(expr); 
     _expressions.Add(expression); 
     return this; 
    } 

    public bool Eval() 
    { 
     return _expressions.All(e => e.Eval()); 
    } 

    public bool EvalAny() 
    { 
     return _expressions.Any(e => e.Eval()); 
    } 
} 

Aplicar las expresiones de seguridad:

internal abstract class SecurityExpression 
{ 
    public abstract bool Eval(); 
} 

internal class BooleanSecurityExpression : SecurityExpression 
{ 
    private readonly bool _result; 

    public BooleanSecurityExpression(bool expression) 
    { 
     _result = expression; 
    } 

    public override bool Eval() 
    { 
     return _result; 
    } 
} 

internal class CompanySecurityExpression : SecurityExpression 
{ 
    private readonly string _company; 

    public CompanySecurityExpression(string company) 
    { 
     _company = company; 
    } 

    public override bool Eval() 
    { 
     return (WhereverYouGetUser).Company == company; 
    } 
} 

Usted puede agregar tantas expresiones personalizadas a medida que necesitar. La infraestructura es un poco complicado, pero entonces el uso es muy simple:

public ActionResult AcmeOnlyAction() 
{ 
    SecurityGuard.ExceptionIf(s => s.UserWorksForCompany("Acme")); 
} 

También puede encadenar la expresión, y utilizarlo como una condición en la vista lado a otro (usando SecurityGuard.Require()).

Sry para publicación larga, espero que esto ayude.

Cuestiones relacionadas