10

I have an MVC 5 C# intranet web application where we have over 30 Active Directory roles in use, and permissions are often a changing thing due to the business culture.

To make things easy for myself, I thought I would try something like this to determine who is allowed access to a controller action or child action.

/* This function runs a LINQ query and outputs a comma delimited string of 
   approved active directory roles.
*/

    private static string _approvedRoles = 
            Helpers.QueryableExtensions.GetApprovedRoles("FourCourseAudit");

    // GET: FourCourseAudits    
    [Authorize(Roles = _approvedRoles)]
    public ActionResult Index(string searchBy="All", 
          string orderBy="Campus", string orderDir="Asc")
    {

    // and so on... 

Unfortunately, I get this compile time error: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type.

This is where I am after trying other approaches with the _approvedRoles variable, such as public const string and public string. I placed the GetApprovedRoles function in the model, in the repository (where it is now), and in the body of the controller.

I know the roles are good because if I use this: [Authorize(Roles="DOMAIN\Role1,DOMAIN\Role2")] it works. And that's not a feasible option for me because the roles change and this is a very large MVC site. Is there some way I can let Roles be a variable?

  • related: http://stackoverflow.com/questions/2827799/using-variables-within-attributes-in-c-sharp – Kritner Jul 29 '15 at 15:23
  • Attributes (as the error says) are not dynamic in that they can't have values that change runtime. I don't use MVC in ASP but the other frameworks I use allow me to examine the context of the request to get the user's roles and compare them inside the method and return an error if they are not allowed... – Ron Beyer Jul 29 '15 at 15:25
  • so you want to override the attribute – Callum Linington Jul 29 '15 at 15:26

4 Answers4

13

The arguments need to be known at compile time, but your query happens at run time.

The easiest way to do this is to create a custom AuthorizeAttribute. Inside the Authorize() method, you can do any checks you want, including querying a database. You can also pass custom parameters into the constructor, if you want more flexibility in reusing the attribute. As an example:

public class RoleAuthorizeAttribute : AuthorizeAttribute
{
    // or inject it
    private DbContext _db = new DbContext();

    private string _filter;

    public RoleAuthorizeAttribute(string filter)
    {
        _filter = filter;
    }

    /// <summary>
    /// Check authorization
    /// </summary>
    /// <param name="filterContext"></param>
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var currentUser = HttpContext.Current.User;

        // do some checks, query a database, whatever
        string approvedRoles =  Helpers.QueryableExtensions.GetApprovedRoles(_filter);

        if (!currentUser.IsInRole(...))
        {
            filterContext.Result = new RedirectToRouteResult("Error", new RouteValueDictionary());
        }
    }
}

And to use:

[RoleAuthorize("FourCourseAudit")]
mfanto
  • 14,168
  • 6
  • 51
  • 61
  • I also sometimes directly inherit from `Attribute` which is what `AuthorizeAttribute` inherits from – Callum Linington Jul 29 '15 at 15:30
  • This looks promising. How would I apply this override in relation to a specific string, such as "FourCourseAudit"? In my example, I returned approved roles using that string as the where filter. –  Jul 29 '15 at 15:47
  • I updated with an example of how to pass a parameter to the attribute, like the "FourCourseAudit". In terms of how you actually check that, it depends on what your GetApprovedRoles() returns, and what your business logic should be. – mfanto Jul 29 '15 at 15:55
  • Almost there. No compile time errors, but when I tried it, I got an HTTP 500 error. Do I have to save this class in a particular namespace or folder? –  Jul 29 '15 at 16:03
  • Nope, it can go anywhere. What is the error? You might need to enable breaking on thrown exceptions (In the Debug menu > Exceptions > and make sure Common Language Runtime Exceptions is checked for Thrown). – mfanto Jul 29 '15 at 16:49
  • I got it to work. In my case I had to use `filterContext.Result = new RedirectToRouteResult (new RouteValueDictionary (new { controller = "Home", action = "Forbidden" }));` –  Jul 29 '15 at 16:56
  • Ah yeah, that line assumed a route named `Error`. The stuff inside `OnAuthorization()` is just for example. Feel free to change any of it to support your specific use. – mfanto Jul 29 '15 at 17:10
  • This solved it for me as we want to configure access roles in Web.config – smarty Apr 27 '17 at 14:04
10

This is a simple version for any who want it:

public class AuthorizeRolesAttribute : AuthorizeAttribute
{
    public AuthorizeRolesAttribute(params string[] roles)
    {
        Roles = String.Join(",", roles);
    }
}

Usage is then:

[AuthorizeRoles(RoleConstant1,RoleConstant2)]
Christos
  • 53,228
  • 8
  • 76
  • 108
dalcam
  • 1,027
  • 11
  • 28
1

In order the compilation of your code to be successfull, you should do what is stated in the error message you have posted.

For instance, you could try something like this:

[Authorize(Roles = "Admin,User")]

In simple terms the values you pass to the an attribute as parameters should be known at the time of compilation. They cannot be evaluated at the runtime.

Update

As I stated in my comment below:

The only alternative is to build a custom attribute and get there the approved roles. Then you could check if the current's user role is in the approved roles and your custom attribute - your filter - would allow or not to the use to call this action.

Christos
  • 53,228
  • 8
  • 76
  • 108
  • Thank you. I am already aware of that. I was hoping that there was another approach that would allow for use of a variable. –  Jul 29 '15 at 15:25
  • The only alternative is to build a custom attribute and get there the approved roles. Then you could check if the current's user role is in the approved roles and your custom attribute - your filter - would allow or not to the use to call this action. – Christos Jul 29 '15 at 15:26
0

you can always define new class that extend from AuthorizeAttribute and ovveride AuthorizeCore and HandleUnauthorizedRequest and create a static class with all constant role declare that how i do in my application.

example of code in my custom class attribute inside AuthorizeCore method

    public string Groups { get; set; } // this will be my new attribute
    var groups = Groups.Split(',').ToList();

    var context = new PrincipalContext(ContextType.Domain,"myDomain");
    var userPrincipal = UserPrincipal.FindByIdentity(context,IdentityType.SamAccountName,httpContext.User.Identity.Name);

    foreach(var group in groups){ // will iterate the attribute and check if that user log is in that group
     if(userPrincipal.IsMemberOf(context, IdentityType.Name, group)){
                            return true;
     }
   }
DarkVision
  • 1,373
  • 3
  • 20
  • 33