ASP.NET MVC3 Role and Permission Management -> With Runtime Permission Assignment
Asked Answered
B

4

18

ASP.NET MVC allows users the ability to assign permissions to functionality (i.e. Actions) at Design Time like so.

[Authorize(Roles = "Administrator,ContentEditor")]
public ActionResult Foo()
{
    return View();
}

To actually check the permission, one might use the following statement in a (Razor) view:

@if (User.IsInRole("ContentEditor"))
{
    <div>This will be visible only to users in the ContentEditor role.</div>
}

The problem with this approach is that all permissions must be set up and assigned as attributes at design time. (Attributes are compiled in with the DLL so I am presently aware of no mechanism to apply attributes (to allow additional permissions) such as [Authorize(Roles = "Administrator,ContentEditor")] at runtime.

In our use case, the client needs to be able to change what users have what permissions after deployment.

For example, the client may wish to allow a user in the ContentEditor role to edit some content of a particular type. Perhaps a user was not allowed to edit lookup table values, but now the client wants to allow this without granting the user all the permissions in the next higher role. Instead, the client simply wants to modify the permissions available to the user's current role.

What options are strategies are available to allow permissions on MVC Controllers/Views/Actions to be defined outside of attributes (as in a database) and evaluated and applied at runtime?

If possible, we would very much like to stick as closely as we can to the ASP.NET Membership and Role Provider functionality so that we can continue to leverage the other benefits it provides.

Thank you in advance for any ideas or insights.

Barkeeper answered 2/9, 2011 at 16:19 Comment(0)
R
21

What options are strategies are available to allow permissions on MVC Controllers/Views/Actions to be defined outside of attributes (as in a database) and evaluated and applied at runtime?

A custom Authorize attribute is one possibility to achieve this:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        Roles = ... go ahead and fetch those roles dynamically from wherever they are stored
        return base.AuthorizeCore(httpContext);
    }
}

and then:

[MyAuthorize]
public ActionResult Foo()
{
    return View();
}
Revivalism answered 2/9, 2011 at 16:22 Comment(3)
I like this approach because it is simple and straightforward. I am going to test it this afternoon.Barkeeper
Correct me if I'm wrong here but is it not inefficient to look up the Roles every time the custom attribute is referenced? Would it not be better to assign the roles to the User object once to allow User.IsInRole be used, which should be possible in Application_AuthenticateRequest: Context.User = new GenericPrincipal(Context.User.Identity, roles);Intercostal
Ciaran Bruen question looks to me like being a misunderstanding of the Roles semantic in this responses. They are not the roles of the user, they are the roles allowed for the current request. The go ahead and fetch those roles ... part is supposed to extract some data from the httpContext then execute some logic to infer which roles are required for this httpContext. (Msdn is a bit misleading on this subject too, but last line of remarks section clarify it. msdn.microsoft.com/en-us/library/… )Vallie
B
16

As I'm lazy I couldn't be bothered rolling my own attribute and used FluentSecurity for this. In addition to the ability to apply rules at run time it allows a custom way to check role membership. In my case I have a configuration file setting for each role, and then I implement something like the following;

// Map application roles to configuration settings
private static readonly Dictionary<ApplicationRole, string> 
    RoleToConfigurationMapper = new Dictionary<ApplicationRole, string>
        {
            { ApplicationRole.ExceptionLogViewer, "ExceptionLogViewerGroups" }
        };

the application roles are then applied like so

SecurityConfigurator.Configure(
    configuration =>
    {
        configuration.GetAuthenticationStatusFrom(() =>
            HttpContext.Current.User.Identity.IsAuthenticated);
        configuration.GetRolesFrom(() => 
            GetApplicationRolesForPrincipal(HttpContext.Current.User));
        configuration.ForAllControllers().DenyAnonymousAccess();
        configuration.For<Areas.Administration.Controllers.LogViewerController>()
            .RequireRole(ApplicationRole.ExceptionLogViewer);
    });

filters.Add(new HandleSecurityAttribute());

and then the check is performed by

public static object[] GetApplicationRolesForPrincipal(IPrincipal principal)
{
    if (principal == null)
    {
        return new object[0];
    }

    List<object> roles = new List<object>();
    foreach (KeyValuePair<ApplicationRole, string> configurationMap in
             RoleToConfigurationMapper)
    {
        string mappedRoles = (string)Properties.Settings.Default[configurationMap.Value];

        if (string.IsNullOrEmpty(mappedRoles))
        {
            continue;
        }

        string[] individualRoles = mappedRoles.Split(',');
        foreach (string indvidualRole in individualRoles)
        {
            if (!roles.Contains(configurationMap.Key) && principal.IsInRole(indvidualRole))
            {
                roles.Add(configurationMap.Key);
                if (!roles.Contains(ApplicationRole.AnyAdministrationFunction))
                {
                    roles.Add(ApplicationRole.AnyAdministrationFunction);
                }
            }
        }
    }

    return roles.ToArray();
}

You could of course pull roles from a database. The nice thing about this is that I can apply different rules during development, plus someone has already done the hard work for me!

Bruxelles answered 2/9, 2011 at 16:48 Comment(3)
Blowdart, I also like the idea of using Fluent anything so Fluent Security is also interesting to me. I am going to take a look at this tonight and compare it with Darin's solution. Both of these are excellent approaches with different strengths.Barkeeper
Let me know if you need any help getting started with Fluent Security. I'm @kristofferahl on twitter or you can drop me a line on the website (fluentsecurity.net).Electromotive
Thanks. Looks like Fluent is something I could really use right now.Cogitative
H
2

You could also consider doing task/activity based security and dynamically assign permission to perform those tasks to different groups

http://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-do-activity-based-checks/

You would need to mangle the provider a little bit to work with this but it is possible to stay inline with the .net authorisation

http://www.lhotka.net/weblog/PermissionbasedAuthorizationVsRolebasedAuthorization.aspx

Housel answered 27/8, 2012 at 6:28 Comment(0)
H
0

If you need to do Method or Controller based authorization (deny access to the whole method or controller) then you can override OnAuthorization in the controller base and do your ouwn authorization. You can then build a table to lookup what permissions are assigned to that controller/method and go from there.

You can also do a custom global filter, which is very similar.

Another option, using your second approach, is to say something like this:

@if (User.IsInRole(Model.MethodRoles)) 
{ 
    <div>This will be visible only to users in the ContentEditor role.</div> 
} 

And then in your controller populate MethodRoles with the roles assigned to that method.

Hotfoot answered 2/9, 2011 at 16:23 Comment(2)
Mystere Man, What side effects would there be if I override OnAuthorization? I'd like to take a look and see what is happening there. Thanks for the info.Barkeeper
@Anthony Gatlin - I'm not sure what you mean by side-effects. It's just an extension point as part of the pipeline. You make whatever authorization decisions you want, and then processing continues.Hotfoot

© 2022 - 2024 — McMap. All rights reserved.