Передача Func в качестве параметра атрибута для защиты маршрутов MVC - PullRequest
8 голосов
/ 29 июля 2010

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

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

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

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

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

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

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

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

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

Ответы [ 2 ]

4 голосов
/ 29 июля 2010

Предложение Некроса сработает, однако вам придется вызывать его 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()   
{   
...   
}  
1 голос
/ 29 июля 2010

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

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

Это 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()).

Прошу прощения за длинный пост, надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...