22

I need to enable my admin user to change access permissions for users on the fly, such that they can create new Roles and add permissions to those Roles.

I want to be able to create an Authorize attribute to stick above my controller class that I can add roles to from a database, so that I don't have to 'set' the roles during development, as in [Authorize(Roles="Role1, Role2")] etc.

So something like [Authorize(Roles = GetListOfRoles()]

I found this question - ASP.NET MVC Authorize user with many roles which does something similar but maybe there's a way to change this such that it gets a list of permissions/roles from the db?

barnacle.m
  • 1,824
  • 3
  • 34
  • 74

2 Answers2

19

This is how I pulled off an attribute that could authorize users per method based on the permissions of the role of that user. I hope this helps somebody else:

/// <summary>
/// Custom authorization attribute for setting per-method accessibility 
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class SetPermissionsAttribute : AuthorizeAttribute
{
    /// <summary>
    /// The name of each action that must be permissible for this method, separated by a comma.
    /// </summary>
    public string Permissions { get; set; }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        SalesDBContext db = new SalesDBContext();
        UserManager<ApplicationUser> userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
        ApplicationDbContext dbu = new ApplicationDbContext();

        bool isUserAuthorized = base.AuthorizeCore(httpContext);

        string[] permissions = Permissions.Split(',').ToArray();

        IEnumerable<string> perms = permissions.Intersect(db.Permissions.Select(p => p.ActionName));
        List<IdentityRole> roles = new List<IdentityRole>();

        if (perms.Count() > 0)
        {
            foreach (var item in perms)
            {
                var currentUserId = httpContext.User.Identity.GetUserId();
                var relatedPermisssionRole = dbu.Roles.Find(db.Permissions.Single(p => p.ActionName == item).RoleId).Name;
                if (userManager.IsInRole(currentUserId, relatedPermisssionRole))
                {
                    return true;
                }
            }
        }
        return false;
    }
}
barnacle.m
  • 1,824
  • 3
  • 34
  • 74
  • 1
    how to use `SetPermissionsAttribute ` with action method ? what i need to pass along with `SetPermissionsAttribute ` ? – Mou Jan 05 '17 at 12:54
  • It's been awhile,but if I remember correctly it's something like `[SetPermissions="Action1, Action2"]` but i'm not sure. – barnacle.m Nov 10 '17 at 09:52
  • 1
    Everyone should take note that this was awhile ago and the actual LINQ queries could be improved upon, and the database context and user manager should be injected, or the repository should be injected (if using repository pattern). – barnacle.m Nov 10 '17 at 09:53
  • 1
    Just my 2 cents tip, generally it is not a good idea to make a call to a database from an attribute. – Tohid May 08 '18 at 07:10
  • 1
    @Tohid but how does the default MVC Authorize attribute check your role etc without reading something from a database?? – barnacle.m May 09 '18 at 19:09
  • @barnacle.m - my comment (attributes are better not to call db) was general. However, `AuthorizeAttribute` doesn't call the db either. In an RBAC model, user's roles get embedded in the auth token the first time user authenticates. From then on, accessed granted by comparing the token roles with the roles a method would accept, like `Authorize("Admin")`. If the accepted role ("Admin" in this case) is also dynamic and must be figured out at runtime, I don't know the best practice in this case. – Tohid May 12 '18 at 06:49
10

What about something like this:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class MyCustomAuthorizationAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        // Do some logic here to pull authorised roles from backing store (AppSettings, MSSQL, MySQL, MongoDB etc)
        ...
        // Check that the user belongs to one or more of these roles 
        bool isUserAuthorized = ....;

        if(isUserAuthorized) 
            return true;

        return base.AuthorizeCore(httpContext);
    }
}

You could use it with a database, or simply maintain a list of authorized roles in the web.config.

Mick Walker
  • 3,772
  • 6
  • 44
  • 70
  • 5
    I think I know where you're going with this, but for the sake of others who can gain knowledge from this q/a, can you edit your answer to make it more holistic (like include the logic of authorizing a user, maybe also include a list of IdentityRoles from the db as an example), and then I'll mark it as accepted. – barnacle.m May 08 '15 at 08:50