2010-07-28 3 views
8

Я пытаюсь защитить свои маршруты MVC от набора пользователей, соответствующих набору критериев. Поскольку MVC, похоже, использует атрибуты совсем немного, и Стивен Сандерсон использует один для повышения безопасности в своей книге pro MVC, я начал продвигаться по этому маршруту, но я бы хотел определить правило в контексте, основанного на действии, к которому я его применяю.Передача Func в качестве параметра атрибута для защиты маршрутов MVC

Некоторые действия предназначены только для сотрудников, а некоторые нет.

Некоторые действия предназначены только для компании1, некоторые нет.

Так я думал, этот тип использования ...

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

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

Выглядит довольно чистый для меня, и это действительно довольно легко реализовать, но я получаю следующее сообщение об ошибке компилятора:

«BlockUsersWhere» не является допустимым аргументом имени атрибута, поскольку он не является допустимым параметром атрибута типа

По-видимому, вы не можете использовать аргумент Func как атрибут. Любые другие предложения, чтобы обойти эту проблему или что-то еще, что обеспечивает простое использование, которое мы полюбили в наших проектах MVC?

ответ

4

Предложение Necros будет работать, однако вам нужно будет вызвать его вспомогательный помощник SecurityGuard в теле каждого действия.

Если вы еще хотели бы пойти с подходом декларативных атрибутов на основе (который имеет то преимущество, что вы можете применить атрибут ко всему контроллер) вы можете написать свой собственный 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 
    } 
} 

Тогда вы используйте его следующим образом:

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

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

Спасибо, это первое, о чем я думал, но при таком подходе я теряю возможность создавать уникальные правила на основе контекста, не обновляя также класс атрибута. Я, скорее всего, поеду по этому маршруту. – jjr2527

+0

Да, атрибуты могут передавать только ограниченный объем информации. Они действительно должны только назначать метаданные. Более сложное поведение принадлежит коду. – marcind

1

Поскольку вы можете использовать константы, типы или инициализаторы массива в параметрах атрибутов, они, вероятно, не будут делать, или, по крайней мере, не будут такими гибкими.

В качестве альтернативы вы можете использовать что-то подобное, с чем я столкнулся при решении этой проблемы.

Это 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); 
} 

Затем создайте построитель выражений:

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

Реализовать выражения безопасности:

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

Вы можете добавить столько пользовательских выражений, как вы необходимость. Инфраструктура немного сложнее, но использование очень просто:

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

Вы также можете приковать выражение, и использовать его в качестве условия в Скрин примере (с использованием SecurityGuard.Require()).

Sry для длинного поста, надеюсь, что это поможет.

Смежные вопросы