Передача Func как параметр атрибута для обеспечения маршрутов MVC

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

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

Некоторые действия для company1 только, некоторые не.

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

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

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

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

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

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

8
задан DaveRandom 26 February 2013 в 00:22
поделиться

2 ответа

Предложение Некроса будет работать, однако вам придется вызывать его помощник 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()   
{   
...   
}  
4
ответ дан 5 December 2019 в 23:13
поделиться

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

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

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

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

1
ответ дан 5 December 2019 в 23:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: