In my application a role has several permissions. And I want users to have access to actions dependent on permission, not the role.
So suppose:
- Admin has perm1, perm2, perm3,
- SuperAdmin has all the permissons that admin has + perm4 and perm5.
- Also, there are some minor guys also who have perm1, perm3, perm6, perm7.
I want to do the following: I want action to be accessible by guy who has suppose perm3 or perm4. those two permissions are from two different roles. but beside perm3 Admin has perm1 and perm2, this action will be also accessible by minor guys who have perm3 (its not obligatory to be admin or superadmin).
So you understand what I mean right ? I want to realise this in ASP.NET MVC 4. So I suppose I will need to make my own AuthorizeAttribute
, My own IIdentity
and write some methods in global.asax. There's also a Membership in ASP.NET Will I need to touch it ? I don't know how to get all things together. Can anyone help me out?
public class PermissionAttribute : AuthorizeAttribute
{
private readonly IAccountService _accountService;
private readonly IEnumerable<PermissionEnum> _permissions;
public PermissionAttribute(params PermissionEnum[] permissions):
this(DependencyResolver.Current.GetService<IAccountService>())
{
_permissions = permissions;
}
protected PermissionAttribute(IAccountService accountService)
{
_accountService = accountService;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if(!_permissions.Any(x=>_accountService.HasPermission(filterContext.HttpContext.User.Identity.Name,(int)x)))
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden);
base.OnAuthorization(filterContext);
}
}
Basically you have to create your own AuthorizeAttribute, but use IIdentity from .NET. What you have described here is a Claim based system for authentication and authorization.
Most likely you will have to throw away the Membership from ASP.NET or use just part of it. As far as I know, it is not built with claims in mind.
In .NET 4.5 the guys added the class: ClaimsPrincipal, which implements the interface IPrincipal. This class can be used to implement custom authentication and authorization based on claims.
So, when the user is authenticated, you can add the claims on the thread:
var id = new ClaimsIdentity(claims, "Dummy");
var principal = new ClaimsPrincipal(new[] { id });
Thread.CurrentPrincipal = principal;
and then later on just use the claims you find on the Thread.CurrentPrincipal.
In ASP.NET MVC, you can do the following steps:
create a Delegating Handler which authenticates the user. If the user is authenticated, then claims are added to the thread principal. Ideally, this delegating handler should be as high up the chain as possible so that you have the claims available everywhere in the execution chain. Also remember to set the HttpContext.Current.User with the same principal
public class AuthHandler : DelegatingHandler{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
//authenticate user
//get claims
//create principal
var newPrincipal = CreatePrincipal(claims);
Thread.CurrentPrincipal = newPrincipal;
if (HttpContext.Current != null)
HttpContext.Current.User = newPrincipal;
return await base.SendAsync(request, cancellationToken);
}
}
Create a filter which authorizes based on the claims added to the Thread Principal. Here you can do something like compare the current route with the information found in the claims.
So i think what you says is: ActionA is accessible only if user has perm1,perm2 and similarly ActionB is accessible when user has perm1 and perm3
The code i gave is for illustration, i did not compile it. But will give you the picture of the approach i am stating
STEP 1: You can proceed with creating a permission enum attributed with Flags attribute
STEP 2: Add claims to current principal based on user permission stored in your data store.
STEP 3: When Action is invoked authorize access against claims
[Flags]
enum PermType
{
None = 0x0,
Perm1 = 0x1,
perm2 = 0x2,
perm3 = 0x4,
perm4 = 0x8,
perm5 = 0x10
}
Adding claims to the CurrentPrincipal
var currentPrincipal = ClaimsPrincipal.Current;
var cms = currentPrincipal.Claims;
var permissions = PermType.Perm1 | PermType.perm2;
var claims = cms.ToList();
claims.Add(new Claim("Action1", permissions.ToString()));
claims.Add(new Claim("Action2", permissions.ToString()));
claims.Add(new Claim("Action3", permissions.ToString()));
System.Threading.Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims));
check if user can access a particular action
public bool CanAccessThisAction(string acionName,PermType requiredPerms)
{
var claim = principal.Claims.FirstOrDefault(c => c.Type == acionName);
if (customPermissionClaim != null)
{
//check if required permission is present in claims for this user
//return true/false
}
return false;
}
on Action
public ActionResult TestAction(string id)
{
if(CanAccessThisAction("TestAction",PermType.Perm1|PermType.perm3|PermType.perm5))
{
//do your work here
}
else
{
//redirect user to some other page which says user is not authorized
}
}