MVC Custom Authorize Attribute From Database - c#

I made my own custom Role table in my database and I wanted to also create a custom authorize attribute along with it.
Here is what I have so far, but I'm not really sure how to proceed:
private List<RoleModel> Roles;
private IRoleRepository repo;
private ICustomerRepository cust;
public bool CheckRoles(string UserId)
{
try
{
Roles = repo.GetAll().ToList();
CustomerModel Customer = cust.Get(UserId);
int CustomerRole = Customer.RoleId;
RoleModel role = Roles.First(x => x.id == CustomerRole);
}
catch(Exception e)
{
return false;
}
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
string UserId = filterContext.HttpContext.User.Identity.GetUserId();
}
If anyone can help me finish this I would greatly appreciate it.
Thanks!

I think that create a custom AuthorizeAttribute is not a good idea. It is a good practice to use the standard AuthorizeAttribute.
It is a common case to have its own Role table. It is better to override how to set the roles of the identity of your principal and to use the Roles property of AuthorizeAttribute. Set the role as a claim once when the user is logging; it will be better than retrieve the role from database in the custom Authorize attribute at each request.
Set your claim CalimTypes.Role, and then protect your controllers/actions with :
[Authorize(Roles = "admin")]

Related

How to implement Permission Based Access Control with Asp.Net Core

I am trying to implement permission based access control with aspnet core. For dynamically managing user roles and permissions(create_product, delete_product etc.), they are stored in the database. Data Model is like http://i.stack.imgur.com/CHMPE.png
Before aspnet core (in MVC 5) i was using custom AuthorizeAttribute like below to handle the issue:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private readonly string _permissionName { get; set; }
[Inject]
public IAccessControlService _accessControlService { get; set; }
public CustomAuthorizeAttribute(string permissionName = "")
{
_permissionName = permissionName;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var user = _accessControlService.GetUser();
if (PermissionName != "" && !user.HasPermission(_permissionName))
{
// set error result
filterContext.HttpContext.Response.StatusCode = 403;
return;
}
filterContext.HttpContext.Items["CUSTOM_USER"] = user;
}
}
Then i was using it in action method like below:
[HttpGet]
[CustomAuthorize(PermissionEnum.PERSON_LIST)]
public ActionResult Index(PersonListQuery query){ }
Additionally, i was using HttpContext.Items["CUSTOM_USER"] in views to show or hide html part:
#if (CurrentUser.HasPermission("<Permission Name>"))
{
}
When i decided to switch aspnet core, all my plan was failed. Because there was no virtual OnAuthorization method in the AuthorizeAttribute. I tried some ways to solve problem. Those are below:
Using new policy based authorization(i think it is not suitable for
my scenerio)
Using custom AuthorizeAttribute and AuthorizationFilter(i read this
post https://stackoverflow.com/a/35863514/5426333 but i couldn’t change it properly)
Using custom middleware(how to get AuthorizeAttribute of current
action?)
Using ActionFilter(is it correct for security purpose?)
I couldn’t decide which way is the best for my scenerio and how to implement it.
First question: Is MVC5 implementation bad practice?
Second question: Do you have any suggest to implement aspnet core?
Based on the comments, here an example on how to use the policy based authorization:
public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(PermissionEnum permission)
{
Permission = permission;
}
public PermissionEnum Permission { get; }
}
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IUserPermissionsRepository permissionRepository;
public PermissionHandler(IUserPermissionsRepository permissionRepository)
{
if(permissionRepository == null)
throw new ArgumentNullException(nameof(permissionRepository));
this.permissionRepository = permissionRepository;
}
protected override void Handle(AuthorizationContext context, PermissionRequirement requirement)
{
if(context.User == null)
{
// no user authorizedd. Alternatively call context.Fail() to ensure a failure
// as another handler for this requirement may succeed
return null;
}
bool hasPermission = permissionRepository.CheckPermissionForUser(context.User, requirement.Permission);
if (hasPermission)
{
context.Succeed(requirement);
}
}
}
And register it in your Startup class:
services.AddAuthorization(options =>
{
UserDbContext context = ...;
foreach(var permission in context.Permissions)
{
// assuming .Permission is enum
options.AddPolicy(permission.Permission.ToString(),
policy => policy.Requirements.Add(new PermissionRequirement(permission.Permission)));
}
});
// Register it as scope, because it uses Repository that probably uses dbcontext
services.AddScope<IAuthorizationHandler, PermissionHandler>();
And finally in the controller
[HttpGet]
[Authorize(Policy = PermissionEnum.PERSON_LIST.ToString())]
public ActionResult Index(PersonListQuery query)
{
...
}
The advantage of this solution is that you can also have multiple handlers for a requirement, i.e. if first one succeed the second handler can determine it's a fail and you can use it with resource based authorization with little extra effort.
The policy based approach is the preferred way to do it by the ASP.NET Core team.
From blowdart:
We don't want you writing custom authorize attributes. If you need to do that we've done something wrong. Instead you should be writing authorization requirements.
I had same requirement and i have done it as below and it works fine for me. I am using .Net Core 2.0 Webapi
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Method
, AllowMultiple = true
, Inherited = true)]
public class CheckAccessAttribute : AuthorizeAttribute, IAuthorizationFilter
{
private string[] _permission;
public CheckAccessAttribute(params string[] permission)
{
_permission = permission;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
return;
}
IRepository service =
(IRepositoryWrapper)context.HttpContext.RequestServices.GetService(typeof(IRepository));
var success = service.CheckAccess(userName, _permission.ToList());
if (!success)
{
context.Result = JsonFormatter.GetErrorJsonObject(
CommonResource.error_unauthorized,
StatusCodeEnum.Forbidden);
return;
}
return;
}
}
In Controller use it like below
[HttpPost]
[CheckAccess(Permission.CreateGroup)]
public JsonResult POST([FromBody]Group group)
{
// your code api code here.
}
For a solution that doesn't require you to add a policy for each permission see my answer for another question.
It lets you decorate your Controllers and Actions with any custom attributes you wish, and access them in your AuthorizationHandler.

Customize asp.net identity role claim

I'm using asp.net identity with ClaimsPrincipal and ClaimsIdentity. The identity is created by a custom usermanager and sent to the clients as a bearer token.
All works fine, but I would like to instruct asp.net to use a custom claim type for role, instead of the standard System.Security.Claims.ClaimTypes.Role (http://schemas.microsoft.com/ws/2008/06/identity/claims/role).
Is it possibile?
Edit: I would like to use the standard Authorize attribute with role constructor, ex: Authorize(Roles="staff") and let the framework to check the claims, but my custom role claim type ("myapproleclaim") instead of the standard one.
You can do things like,
public class CustomPrinciple : ClaimsPrincipal
{
public CustomPrinciple(ClaimsIdentity identity) : base(identity)
{
}
public override bool IsInRole(string role)
{
return HasClaim("myRoleClaimType", role);
}
}
[TestClass]
public class CustomRoleTest
{
[TestMethod]
public void testing_custom_role_type()
{
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim("myRoleClaimType", "role1"));
var principle = new CustomPrinciple(identity);
Assert.IsTrue(principle.IsInRole("role1"));
Assert.IsFalse(principle.IsInRole("role2"));
}
}

How to allow users with Admin rights to access controller

I have an application in which I use Windows Authentication. Without getting too detailed my app sets up a series of users and gives then admin rights to create absences. If they have no admin rights then they can't change users or create absences.
I want to restrict access to certain controllers/actions based on the whether the admin flag in my database is set to true. The users where I work belong to multiple groups there is no administrator group which I can include in the Authorize attribute roles string.
I followed the tutorial here but as I have a database first entity framework model the entities class inherits from DbContext not from the identity context.
When I run the app my code raises an error saying: "An exception of type 'System.InvalidOperationException' occurred in mscorlib.dll but was not handled in user code
Additional information: The entity type IdentityRole is not part of the model for the current context." I click to view the details and I see this "The entity type IdentityRole is not part of the model for the current context."
This is the code fragment of where the error occurs:
AbsencesEntities context = new AbsencesEntities();
AbsenceRepository absenceRepository = new AbsenceRepository(context);
IdentityResult IdRoleResult;
IdentityResult IdUserResult;
// Create a RoleStore object by using the UserSecurity object.
// The RoleStore is only allowed to contain IdentityRole objects.
var roleStore = new RoleStore<IdentityRole>(context);
// Create a RoleManager object that is only allowed to contain IdentityRole objects
// When creating the RoleManager object, you pass in (as a parameter) a new RoleStore
var roleMgr = new RoleManager<IdentityRole>(roleStore);
// Then, you create the "canEdit" role if it doesn't already exist
if(!roleMgr.RoleExists("canEdit"))
{
IdRoleResult = roleMgr.Create(new IdentityRole { Name = "canEdit" });
}
Just to clarify I don't have any other context specified in the config file.
There must be a way for me to use Windows Authentication and use the LAN ID returned to check if it exists in the database. Then use that to check if the Admin flag is true in the database.
You can inherit from authorizeattribute and override authorizecore, then just decorate your controller and/or methods with the attribute to handle this scenario. For instance:
public class PageAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (/*Rolemanager check*/) {
return true;
}
return false;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{"action", "PageDenied"}
,
{"controller", "Authorization"}
});
}
}
You can use the httpContext.User.Identity.Name for checking the username.
[PageAuthorize]
public class PageController : Controller
{}
Set Authorize attribute to whole controller:
[Authorize(Roles = "Administrator")]
public class AdminController : Controller
{
but to make it work you need to:
Create custom principal and identity:
public class CustomIdentity : IIdentity
{
private bool _IsAdmin;
public bool IsAdmin
{
get { return _IsAdmin; }
}
// other properties
public CustomIdentity(string Login)
{
using(DbContext db = new DbContext())
{
User user = db.Users.FirstOrDefault(u => u.Login.Equals(Login, StringComparison.CurrentCultureIgnoreCase));
_IsAdmin = user.IsAdmin;
}
}
}
public class CustomPrincipal : IPrincipal
{
private CustomIdentity _Identity;
public CustomPrincipal(string Login)
{
_Identity = new CustomIdentity(Login);
}
public bool IsInRole(string role)
{
if (_Identity != null)
{
return role == "Administrator"? _Identity.IsAdmin: false;
}
else
{
return false;
}
}
//other properties and code
}
In global.asax override PostAuthRequest:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (User.Identity != null && User.Identity.IsAuthenticated)
{
CustomPrincipal opPrincipal = new CustomPrincipal(User.Identity.Name);
HttpContext.Current.User = opPrincipal;
}
}
You should add column Login and same property to User table and class.

ASP.NET MVC Authorize attribute with a parameter

I am beginning to decorate my controller classes with
[Authorize(Roles = #"DOMAIN\ADGroup")]
What would be the best method to change that explicit string to a parameter that collects the role assignment from a database, thus allowing flexibility in role assignment that an Admin area can sit on top off.
For example say I have three roles, for arguments sake
ReadOnly
ReadandWrite
Admin
And I want to map those roles to Multiple AD groups
For example
ReadOnly --> DOMAIN\Group1, DOMAIN\Group2, DOMAIN\Group3
ReadandWrite--> DOMAIN\GroupWrite, DOMAIN\GroupManagers
Admin --> DOMAIN\DomainAdmins
This will be editable, I can modify the mapping from role to any AD group I choose in the Admin area of my application.
How can my Authorize attributes take advantage of this?
You can extend the AuthorizeAttribute class. I did it like the following:
public class ExtendedAuthorizeAttribute : AuthorizeAttribute
{
protected string permission;
protected string group;
public ExtendedAuthorizeAttribute(string Permission, string Group)
{
permission = Permission;
group = Group;
}
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
var can = PermissionManager.Can(httpContext.User, permission, group);
if(can.HasValue)
return can.Value;
return base.AuthorizeCore(httpContext);
}
}
Make your own role provider that returns a list of your ReadOnly etc roles based on the current user's AD roles. Then you can use the Authorize attribute to refer to those roles instead.

Is this Custom Principal in Base Controller ASP.NET MVC 3 terribly inefficient?

Despite the fact that I've been on here for a while, this is my first ever question on SO, so please be gentle with me.
I'm using ASP.NET MVC 3 and I want to create a custom Principal so I can store a bit more info about the current user than is standard thus not have to go to the database too often. It's fairly standard stuff that I'm after. Let's just say email address and user id in the first instance.
I have decided to store the object in the cache as I am aware that it is not advised to store it in the session.
I also don't want to have to keep casting the User object, so I wanted to override the User object in the controller. So I can just go User.UserId and be guaranteed of something.
So I created a custom principal like this:
public class MyPrincipal : IPrincipal
{
public MyPrincipal(IIdentity ident, List<string> roles, string email, Guid userId)
{
this._identity = ident;
this._roles = roles;
this._email = email;
this._userId = userId;
}
IIdentity _identity;
public IIdentity Identity
{
get { return _identity; }
}
private List<string> _roles;
public bool IsInRole(string role)
{
return _roles.Contains(role);
}
private string _email;
public string Email
{
get { return _email; }
}
private Guid _userId;
public Guid UserId
{
get { return _userId; }
}
}
And I have a Base Controller like this:
public class BaseController : Controller
{
protected virtual new MyPrincipal User
{
get
{
if (base.User is MyPrincipal)
{
return base.User as MyPrincipal;
}
else
{
return new MyPrincipal(base.User.Identity, new List<string>(0), "", Guid.Empty );
}
}
}
protected override void OnAuthorization(AuthorizationContext filterContext)
{
if (User != null)
{
if (User.Identity.IsAuthenticated)
{
if (User.Identity is FormsIdentity)
{
FormsIdentity id = base.User.Identity as FormsIdentity;
MyPrincipal principal = (MyPrincipal)filterContext.HttpContext.Cache.Get(id.Name);
if (principal == null)
{
MembershipUser user = Membership.GetUser();
// Create and populate your Principal object with the needed data and Roles.
principal = new MyPrincipal(id, Roles.GetRolesForUser(id.Name).ToList(), user.Email, (Guid)user.ProviderUserKey);
filterContext.HttpContext.Cache.Add(
id.Name,
principal,
null,
System.Web.Caching.Cache.NoAbsoluteExpiration,
new System.TimeSpan(0, 30, 0),
System.Web.Caching.CacheItemPriority.Default,
null);
}
filterContext.HttpContext.User = principal;
System.Threading.Thread.CurrentPrincipal = principal;
base.OnAuthorization(filterContext);
}
}
}
}
}
If you have a look you will quickly realise that if the user has not logged in then any call to the User object will have to run through this bit of code:
return new MyPrincipal(base.User.Identity, new List<string>(0), "", Guid.Empty );
and this feels terribly inefficient to me, although it's only creating empty objects for the missing stuff.
It works fine.
So I guess I want to know if this is actually okay and I should stop being so anal about performance and efficiency, or if my fears are correct, in which case what should I be doing instead? [Please don't say "Getting a life, mate!"]
No - there is nothing specifically wrong with this code from a performance stand point that stands out. PLENTY of objects are creating on the back end in ASP.NET, your single object is a drop in the bucket. Since class instantiation is extremely fast I wouldn't be concerned about it.
Why are you ignoring sessions here? Session information doesn't have expiration dates, so there is no extra check behind the scenes. Unless you are using an out of proc session server, there is no serialization of your object (none with the cache either).
The cache is for every user - so you right a chance (albeit slight) of a code error returning the wrong principal where a cache being per user - does not run the risk of that.
If you want this available for all requests there (not just MVC based) I would consider setting this in Application_PostAuthenticateRequest
This post may be of use. Notice the use of userdata in the authentication ticket.
ASP.NET MVC - Set custom IIdentity or IPrincipal

Categories

Resources