Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing Func as an attribute parameter to secure MVC routes

I'm trying to secure my MVC routes from a set of users that meet a set of criteria. Since MVC seems to use attributes quite a bit and Steven Sanderson uses one for security extensibility in his pro MVC book I started heading down this route, but I'd like to define the rule contextually based on the action I am applying it to.

Some actions are for employees only, some aren't.

Some actions are for company1 only, some aren't.

So I was thinking this type of usage...

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

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

Looks pretty clean to me and is really pretty easy to implement, but I get the following compiler error:

'BlockUsersWhere' is not a valid named attribute argument because it is not a valid attribute parameter type

Apparently you can not use a Func as an attribute argument. Any other suggestions to get around this issue or something else that provides the simple usage we've come to love in our MVC projects?

like image 905
jjr2527 Avatar asked Jul 28 '10 21:07

jjr2527


2 Answers

Necros' suggestion would work, however you would have to invoke his SecurityGuard helper in the body of every action method.

If you would still like to go with the declarative attribute-based approach (which has the advantage that you can apply the attribute to the whole Controller) you could write your own 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
    }
}

Then you would use it as follows

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

[CustomAuthorize(EmployeeOnly = true)]   
public ActionResult EmployeeOnlyAction()   
{   
...   
}  
like image 88
marcind Avatar answered Nov 07 '22 16:11

marcind


Since you can only use constants, types or array initializers in attribute parameters, they probably won't do, or at least the won't be as flexible.

Alternatively, you could use something similar I came up with when solving this problem.

This is the 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);
}

Then create an expression builder:

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

Implement the security expressions:

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

You can add as many custom expressions as you need. The infrastructure is a bit complicated, but then usage is really simple:

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

You can also chain the expression, and use it as a condition in view fro example (using SecurityGuard.Require()).

Sry for long post, hope this helps.

like image 1
Necros Avatar answered Nov 07 '22 17:11

Necros