I would like to authorize an ApplicationUser
based on their ApplicationRole
s' RoleClaim
s. I would like to implement this like so:
public class RoleClaimAuthorizeAttribute : AuthorizeAttribute
{
public RoleClaim RoleClaim { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
foreach (ApplicationRole role in Roles)
{
if ((RoleClaim & role.Claims) > 0)
{
return true;
}
}
return false;
}
}
Then I could decorate controller actions like so:
[RoleClaimAuthorize(RoleClaim =
RoleClaim.CanCreateRoles |
RoleClaim.CanReadRoles |
RoleClaim.CanDeleteRoles |
RoleClaim.CanUpdateRoles
)]
//
// GET: /Roles/
public ActionResult Index()
{
return View(_roleManager.Roles);
}
The problem I'm encountering is any method I can find of reaching an ApplicationUser
's ApplicationRole
s from my custom authorize attribute returns a string array of ApplicationRole.Name
not an array of ApplicationRole
so I can't get to ApplicationRole.Claims
. I'm also using Unity instead of Owin to handle ApplicationRoleManager
so I can't request ApplicationRoleManager
via HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>()
.
So how can I get to a collection of ApplicationRole
objects for the current user and thus ApplicationRole.Claims
?
Or if it's a more appropriate solution, how can I store a string array of the current ApplicationUser
's ApplicationRole
s' RoleClaim
s in HttpContext
much like how roles are stored? I'm aware my authorization attribute couldn't work as described in this situation but it's a situation I can work with nonetheless.
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser<Guid, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public override Guid Id { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, Guid> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, Guid> manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
// Add custom user claims here
return userIdentity;
}
}
public class ApplicationRole : IdentityRole<Guid, ApplicationUserRole>
{
public ApplicationRole() : base()
{
this.Id = Guid.NewGuid();
}
public ApplicationRole(string name)
: this()
{
this.Name = name;
}
public ApplicationRole(string name, params string[] claims)
: this(name)
{
Claims = (RoleClaim)Enum.Parse(typeof(RoleClaim), String.Join(",", claims));
}
public RoleClaim Claims { get; set; }
}
[Flags]
public enum RoleClaim : int
{
CanCreateUsers = 1,
CanReadUsers = 2,
CanUpdateUsers = 4,
CanDeleteUsers = 8,
CanCreateRoles = 16,
CanReadRoles = 32,
CanUpdateRoles = 64,
CanDeleteRoles = 128,
CanCreateTests = 256,
CanReadTests = 512,
CanUpdateTests = 1024,
CanDeleteTests = 2048
}
public class ApplicationRoleManager : RoleManager<ApplicationRole, Guid>
{
public ApplicationRoleManager(IRoleStore<ApplicationRole, Guid> store) : base(store)
{
}
}
If you registered the role manager in Unity you could retrieve everywhere, including your custom attribute, simply by calling following method:
var roleManager = DependencyResolver.Current.GetService<ApplicationRoleManager>();
Or if you don't want to use the resolver directly you could use Unity's property injection feature so Unity automatically injects the role manager in the custom attribute which in explained here. Then call roleManager.FindByNameAsync()
method to retrieve the role object.
But this approach is not recommended because in each call your code hits the database to retrieve claims. It is much better to store user's claims in ClaimsIdentity
when user signs in then retrieve them within the attribute like this:
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
private readonly ApplicationRoleManager _roleManager;
public ApplicationSignInManager(ApplicationUserManager userManager,
IAuthenticationManager authenticationManager,
ApplicationRoleManager rolemanager)
: base(userManager, authenticationManager)
{
//inject the role manager to the sign in manager
_roleManager=rolemanager;
}
public override async Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
var ident= await user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
// add your custom claims here
var userRoles=user.Roles.Select(r=>r.RoleId);
ident.AddClaims(_roleManager.Roles.Where(r => userRoles.Any(ur => ur == r.Id))
.Select(r=>r.Claims).ToList()
.Select(c => new Claim("RoleClaims", c.ToString())));
return ident;
}
}
Now RoleClaims
are added as claims when user signs in. You could retrieve them in the attribute like this:
public class RoleClaimAuthorizeAttribute : AuthorizeAttribute
{
public RoleClaim RoleClaim { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
foreach (var claims in GetClaims(httpContext.User.Identity as ClaimsIdentity))
{
if ((RoleClaim & claims) > 0)
{
return true;
}
}
return false;
}
private IEnumerable<RoleClaim> GetClaims(ClaimsIdentity ident)
{
return ident==null
? Enumerable.Empty<RoleClaim>()
: ident.Claims.Where(c=>c.Type=="RoleClaims")
.Select(c=>(RoleClaim)Enum.Parse(typeof(RoleClaim), c.Value));
}
}