I don't like the built in membership providers. I've decided to roll my own. I'm trying to come up with a good method for performing authorization at the action level. Here are the requirements that I'm trying to go by:
ReadWrite
has permission for Read
. Just like or'ing with an enum.NOTE: Some think this set of requirements is too broad (see comments). I don't think so, I think they're fairly straightforward.
The biggest showstopper is attribute usage. There can only be "constant expressions, typeof expressions or array creation expression of an attribute parameter type".
I was thinking of perhaps having something like this to make operations have static access. Inside of the attribute, it would "convert" the int to the actual Permission or something...:
public static class Operations
{
public static class SectionA
{
public const int Read = 1;
public const int ReadWrite = 2;
}
public static class SectionB
{
// ... and so on...
}
}
But it really limits composition. I'm sure you're thinking "why don't you go the enum route?" well I want to plan for things to change and don't want to limit to 32 (int) or 64 (long) operations and have to do a massive rewrite later (also in the db that's just ugly).
Also, if there is a better alternative than attributes on actions/controllers, then I'm all ears for suggestions.
EDIT: Also from this post, I've read about the BitArray
class. It seems kind of ugly, especially with the arbitrary storage in the database.
First of all, I have to thank you for sucking me into answering this ;)
This is a long answer, and is only a starting point. You have to figure out how to assign roles to users and how to recreate them in the AuthenticateRequest
.
If this does not answer your question, I hope it will be an inspiration. Enjoy!
Decorate the controller actions
I started to decorate the two actions in the default HomeController
:
[AuthorizeRoles(Role.Read)]
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
[AuthorizeRoles(Role.Write)]
public ActionResult About()
{
return View();
}
All users in the ReadWrite role should then be granted access. I opted here to use an enum as a type safe placeholder for the magic strings. The role of this enum is nothing else than being a placeholder. There are no composite enum values, that has to be maintained somewhere else. More on that later.
public enum Role
{
Read,
Write,
ReadWrite
}
Implement a new authorization attribute
Since the strings are gone, I need a new authorize attribute:
public class AuthorizeRolesAttribute : AuthorizeAttribute
{
private readonly RoleSet authorizedRoles;
public AuthorizeRolesAttribute(params Role[] roles)
{
authorizedRoles = new RoleSet(roles);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return authorizedRoles.Includes(httpContext.User);
}
}
The RoleSet
wraps a set of enum values and verifies if an IPrincipal
is a member of one of them:
public class RoleSet
{
public RoleSet(IEnumerable<Role> roles)
{
Names = roles.Select(role => role.ToString());
}
public bool Includes(IPrincipal user)
{
return Names.Any(user.IsInRole);
}
public bool Includes(string role)
{
return Names.Contains(role);
}
public IEnumerable<string> Names { get; private set; }
}
Maintain roles
The CompositeRoleSet
is where composite roles are registered and handled. CreateDefault()
is where all composites are registered.
Resolve()
will take a list of roles (enum values) and convert the composites to their single counterparts.
public class CompositeRoleSet
{
public static CompositeRoleSet CreateDefault()
{
var set = new CompositeRoleSet();
set.Register(Role.ReadWrite, Role.Read, Role.Write);
return set;
}
private readonly Dictionary<Role, Role[]> compositeRoles = new Dictionary<Role, Role[]>();
private void Register(Role composite, params Role[] contains)
{
compositeRoles.Add(composite, contains);
}
public RoleSet Resolve(params Role[] roles)
{
return new RoleSet(roles.SelectMany(Resolve));
}
private IEnumerable<Role> Resolve(Role role)
{
Role[] roles;
if (compositeRoles.TryGetValue(role, out roles) == false)
{
roles = new[] {role};
}
return roles;
}
}
Wiring it up
We need an authenticated user to work on. I cheated and hard-coded one in global.asax:
public MvcApplication()
{
AuthenticateRequest += OnAuthenticateRequest;
}
private void OnAuthenticateRequest(object sender, EventArgs eventArgs)
{
var allRoles = CompositeRoleSet.CreateDefault();
var roles = allRoles.Resolve(Role.ReadWrite);
Context.User = new ApplicationUser(roles);
}
Finally, we need an IPrincipal
which understand all this:
public class ApplicationUser : IPrincipal
{
private readonly RoleSet roles;
public ApplicationUser(RoleSet roles)
{
this.roles = roles;
}
public bool IsInRole(string role)
{
return roles.Includes(role);
}
public IIdentity Identity
{
get { return new GenericIdentity("User"); }
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With