Dynamically assign controller action permissions to roles in asp.net MVC - asp.net

I am working on asp.net mvc 5. I want to assign permissions of controllers' action methods to roles dynamically without hard conding the roles in Authorize attribute.
Here is the scenario -
In my project, I have four roles - Student, Teacher, Program Officer and Admin.
I want that admin can change the accessability of each role whenever he wishes. I do not want to hard code the authorize attribute with role names before every action name of controller because admin will not then be able to change the permissions of each role.
I want to create a page where every action method of controller will be listed as checkbox and admin can select action checkboxes for a role. Then that role users will get accessability of those action methods.
Here I want to make the UI as following -
Can anyone please help me to do this by giving any suggestion or code or link?

Imagine you have service which returns array of roles based on controller and action name like this:
public class RoleProvider
{
public string[] Get(string controller, string action)
{
// get your roles based on the controller and the action name
// wherever you want such as db
// I hardcoded for the sake of simplicity
return new string[]{"Student", "Teacher"};
}
}
Now you can write your own authorization attribute something like this:
public class DynamicRoleAuthorizeAttribute: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var controller = httpContext.Request.RequestContext
.RouteData.GetRequiredString("controller");
var action = httpContext.Request.RequestContext
.RouteData.GetRequiredString("action");
// feed the roles here
Roles = string.Join("," ,_rolesProvider.Get(controller, action));
return base.AuthorizeCore(httpContext);
}
}
Now use your custom authorization attribute instead of older one like this:
[DynamicRoleAuthorize]
public ActionResult MyAction()
{
}

I think the only way is to implement your own Authorize Attribute where you can implement your own logic for authorization.
And in your case you should have a table where associate roles and controllers action and check this table in your custom Authorize Attribute.

While this does not give you the dynamics web page assignment you're looking for, If you are flexible in your approach... you can set up an Enum list of Roles Admin, Editor editor etc, and pass them as a parameter object (ENUM) as a param, so that the DynamicRoleAuthorize can use it load the roles that are allowed
from vivians blog The constructor accepts parameters of type object, that is the little trick. If you use parameters of type Enum, you will get the same error message as above. We can do that because an Enum is an object.
To ensure that we are passing parameters of type Enum, we check the type of every roles. If one role is not of type Enum, the constructor will throw an ArgumentException.
Then we set the standard Roles property with the name of our roles with the string.Join method.
using System;
using System.Linq;
using System.Web.Mvc;
namespace MvcApplication.HowTo.Attributes
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class AuthorizeEnumAttribute : AuthorizeAttribute
{
public AuthorizeEnumAttribute(params object[] roles)
{
if (roles.Any(r => r.GetType().BaseType != typeof(Enum)))
throw new ArgumentException("roles");
this.Roles = string.Join(",", roles.Select(r => Enum.GetName(r.GetType(), r)));
}
}
}

Related

Need advice of where to put custom user authorization in ASP.NET Core

I need advice of where to put custom user authorization code in ASP.NET Core. I am somewhat a ASP.NET Framework developer and normally I will add code to Global.asax as a session_onstart event to look up a SQL table where users profile are stored that is used to determine what they can view in the rest of the application. With Global.asax this is only cause once per user session, so what I would like to do is the same kind of approach in ASP.NET Core which I am kind of new to but need advice where that check should be done
I would like to do is the same kind of approach in ASP.NET Core which
I am kind of new to but need advice where that check should be done
Well, based on your description, in asp.net core you can achieve that in many ways. For instances, you could set in following places:
program.cs/startup.cs files
Using Middleware file
Using Action Filter
Let's, consider below example using action filter
Role and permissison:
First we are defining the role and the permission.
public enum Role
{
User,
Admin,
SuperAdmin
}
public enum Permission
{
Read,
Create,
Update,
Delete
}
Authorization On Action Filter:
public class AuthorizeActionFilter : IAuthorizationFilter
{
private readonly Role _role;
private readonly Permission _permission;
public AuthorizeActionFilter(Role item, Permission action)
{
_role = item;
_permission = action;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var isAuthorized = context.HttpContext.User.Claims.Any(c => c.Type == _role.ToString() && c.Value == _permission.ToString());
if (!isAuthorized)
{
context.Result = new ForbidResult();
}
}
}
Note: Check your user claim from the HttpContext if that containts either Admin or Read authorization.
Controller:
[Authorize(Role.User, Permission.Read)]
public IActionResult MemberList()
{
var memberList = _context.Members.ToList();
return View(memberList);
}
Output:
You even can implement that using Middleware. Asp.net 6 now providing couple of other mechanism now a days, you could have a look below official implementations as well.
Role-based authorization
Claims-based authorization
Policy-based authorization
Custom Action Filter

How can I get current user ID using IHttpContextAccessor?

I am currently using .NET Core and I am trying to get the current user ID. I understand that System.Web is not available in NET Core, so I tried using IHttpContextAccessor.
I have also added this into my Startup.cs: service.AddHttpContextAccessor()
I then tried looking up for the user id by doing this in my controller:
public Controller(IHttpContextAccessor httpContextAccessor){
var userId = httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)
}
But it always returns null, does anyone knows why and able to advise? I have also disabled anonymousAuthentication and set windowsAuthentication to true.
Edit
The only changes I did to my Startup.cs is in the ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddHttpContextAccessor();
}
Are you sure, that you need IHttpContextAccessor?
There is a better way to get Id from claims actually:
You can use ControllerBase.User property, but at first make sure that your controller is derived from Controller or ControllerBase (last one is without views support).
public class YourController : Controller
{
...
var id = User.FindFirstValue(ClaimTypes.NameIdentifier)
...
}
There is a better way to get Id from claims actually:
You can use the ControllerBase.User property, but at first, make sure that your controller is derived from Controller or ControllerBase (the last one is without views support).
Use the following code snippet to get ASPNET ID
string currentUser = _http.HttpContext.User.Claims.ToList().Find(r => r.Type == "id").Value;
If you've included "ClaimTypes.Name" with the username value in claims when token was generated.
Then you can access the username using IHttpContextAccessor, if respective dependency is added, like:
var currentUserName = _httpContextAccessor.HttpContext.User.Claims.First(c => c.Type == ClaimTypes.Name).Value;
Then you can access the user like:
var user = await _userManager.FindByNameAsync(currentUserName);
and hence the userId:
var userId = user.Id;
You'll need an instance of UserManager<ApplicationUser>. The easiest way to get an instance of this class is add it as an argument to your class constructor and it will be provided via dependency injection.
string userId = UserManager.GetUserId(httpContextAccessor.HttpContext.User);
Don't forget to check for null values.

Get Current Principal as my Custom Application User in ASP.Net Core Identity

In previous versions of ASP.NET, if I wanted to have a custom class as my current logged in user, what I did was: I let the FormsAuthentication module do its work, and then, in the PostAuthenticateRequest event I replaced the current Principal (HttpContext.Current.User) with my custom principal object that I fetched from the database (with some caching for performance).
How can I achieve the same in ASP.NET Identity? I have my own ApplicationUser (not the default that comes with the EntityFramework ASP.NET Identity) and my own UserStore.
In every authenticated request, I have the HttpContext.User as a ClaimsPrincipal object. Is there a way to replace that with my CustomClaimsPrincipal?
Is there another, better way, to retrieve the current ApplicationUser instance based on the current ClaimsPrincipal?
If you have your own IUserStore you can implement IUserClaimStore to customize the claims identity which is passed to the claims principal.
If you need to replace the default claims principal you should implement the IUserClaimsPrincipalFactory and pass your implementation to the SignInManager and register the configured manager to your owin context.
It should look like this along the lines.
(Assuming you are using ASP.NET Core Identity, for Identity v2 the interfaces and constructors may differ!)
class CustomClaimsFactory<TUser> : Microsoft.AspNetCore.Identity.IUserClaimsPrincipalFactory<TUser>
where TUser : class
{
public Task<ClaimsPrincipal> CreateAsync(TUser user)
{
// create and return your custom principal
}
}
class OwinStartup
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(CreateSignInManager);
}
Microsoft.AspNetCore.Identity.SignInManager CreateSignInManager()
{
UserManager manager; // the manager that uses your custom IUserStore
IHttpContextAccessor accessor; // I don't know where to get it from...
var factory = new CustomClaimsFactory();
return new SignInManager(manager, accessor, factory, null, null, null);
}
}
For ASP.Net Core the OWIN-like startup configuration is done via dependency injection.

Dynamic role providing in asp.net mvc (Roles are not fixed It is keep updating)

I am aware simple role provider in which if i need to restrict particular action i have to simply write Authorize(Roles = "Admin") or if i need to restrict particular part of view i nned to write #if(User.IsInRole("Admin")).
But my question is that what if my roles are not fixed and it is stored in database and my super admin can able to edit and delete them.
My requirement is that superadmin can add,update,delete roles and also create different users and maintain the roles of those users.
I have done lot of googling and found something as follows
[AttributeUsage (AttributeTargets.Method|AttributeTargets.Class,Inherited = true,AllowMultiple=true) ]
public class CustomRole : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase context)
{
Respository db = new Respository();
if (db.UserMasters.Where(x => x.user_name == context.User.Identity.Name).Count() > 0)
{
return true;
}
else { return false; }
}
}
Here i can use this code to authorize action method as follows
[CustomRole]
public ActionResult Details(int id = 0)
{
Employee employee = db.Employees.Find(id);
if (employee == null)
{
return HttpNotFound();
}
return View(employee);
}
Here my this action method is protected but what if i want to protect some part of view by this custom method. How to use this functionality to achieve functionality as User.IsInRole("Admin")?
your requirement will get in 3 steps
1- Create all default roles, store it in database.i.e- roleid,rolename
2- When creating new user map userid with roleid.
3- also make one table for all permission which you have to give.
4- make seperate ui for admin to change the roles of each user.
database will be like below image.
and ui will be like this.
try this yousrelf..
Fully answering your question might be out of scope for StackOverflow, since it would basically require writing most of an application for you, but here's the general idea.
Write a helper class similar to this:
public class ModuleHelper
{
public static bool UserCanAccessModule(string moduleIdentifier)
{
bool canAccess = false;
/*
Call into your service with current User.Name and module identifier (integer, GUID, whatever).
Return result of whether user has the required role for the specified module
*/
try
{
canAccess = service.CanUserAccessModule(User.Identity.Name, moduleIdentifier);
}
catch
{
// catching all exceptions, since this is a UI helper
}
return canAccess;
}
// etcetera...
}
I'd suggest wrapping it in the root namespace of your application; otherwise, add a reference to this class's namespace in the system.web.webPages.razor section of the web.config in the Views folder. Then, you can do something like:
<div class="col-sm-3 col-md-2 sidebar">
#if (ModuleHelper.UserCanAccessModule("moduleXYZ"))
{
#Html.Action("moduleXYZ")
}
</div>
This obviously assumes a lot, but the idea isn't new or all that complicated in practice. The logic of the service is relatively simple:
Look up the user
Look up the "action" or "module"
Look for intersection (if any) between the roles assigned to each.
No intersection means user doesn't have the required role.
Tieson T. has a great answer to your question already, so what I'll provide here is an alternative method if you wanted to keep all of your authorization steps all in controllers.
Consider separating the different aspects (or restricted parts) of your main view into a partial view (or views) that perform the restricted functionality. Then, instead of using: #Html.RenderPartial("ViewName", Model) you can set up your partials to be returned from controller actions decorated with the ChildActionOnly Attribute by using the RenderAction Html Helper.
For example:
<div class="col-sm-3 col-md-2 sidebar">
#Html.RenderAction("RestrictedContent")
</div>
Then in your controller class
public class RestrictedController : Controller {
public RestrictedController() : base() {
}
[ChildActionOnly()]
[CustomRole()]
public ActionResult RestrictedContent() {
return PartialView("RestrictedPartial");
} // end action RestrictedContent
} // end class
The only consideration with this approach will be in your custom attribute to interrogate the the IsChildAction property to avoid rendering a redirect or whatever your attribute does in the case the user is not authorized since you'll probably want to just not render anything.
For Example (in your custom attribute class):
public override void OnAuthorization(AuthorizationContext filterContext) {
if(filterContext.IsChildAction) {
filterContext.Result = new EmptyResult(); // return an empty result instead of performing a redirect.
} else {
base.OnAuthorization(filterContext); // continue with custom authorization if it is not a child action
} // end if/else
} // end method OnAuthorization
Phil Haack has an article describing the usage of the RenderAction method here: http://haacked.com/archive/2009/11/18/aspnetmvc2-render-action.aspx/
Also, see here for an interesting discussion on the differences between Action and RenderAction. The difference between Html.Action and Html.RenderAction

Using object Helper Methods to implement authorization rules

I have the following:-
I am working on an asset management system using Asp.net MVC4 with windows authentication enabled.
The system allow to specify what actions a group of users can do(for example certain group can have the authority to add new physical asset , while they can only read certain logical asset, and so on).
So I found that using the build-in Asp.net role management, will not allow me to have the level of flexibility I want. So I decided to do the following:-
I have created a table named “group” representing the user groups. Where users are stored in active directory.
I have created a table named ”Security Role” which indicate what are the permission levels each group have on each asset type(edit, add, delete or view)per asset type.
Then on each action methods , I will use Helper methods to implement and check if certain users are within the related group that have the required permission ,, something such as
On the Car model object I will create a new helper method
Public bool HaveReadPermison(string userName) {
//check if this user is within a group than have Read permission on CARS, //OR is within a GROUP THAT HAVE HIGHER PERMISON SUCH AS EDIT OR ADD OR //DELETE.
}
Next, On the Action method, I will check if the user has the Read permission or not by calling the action method:-
public ActionResult ViewDetails(int id) { // to view transportation asset type
Car car = repository.GetCar(id);
if (!car.HaveReadPermision(User.Identity.Name)) {
if (car == null)
return View("NotFound");
else
return View(car);
}
else
return view (“Not Authorized”);
So can anyone advice if my approach will be valid or it will cause problem I am unaware about.
Regards
In my opinion, once you have decided to use the ASP membership and role providers you can keep leveraging them also for authorization, simply using the Authorize attribute. This will also allow to restrict access by user names and roles.
What the attribute won't do is Action-based authorization. In that case there are a few options but in my opinion this could be brilliantly resolved by a Custom Action Filter based loosely on the following code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var user = filterContext.HttpContext.User.Identity.Name; // or get from DB
if (!Can(user, Action, Model)) // implement this method based on your tables and logic
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
Yes, it is vaguely inspired to CanCan, which is a nice Ruby gem for this kind of things.
Returning Unauthorized (401) will also instruct your server to redirect to the login page if one is specified. You may want to work on that logic if you want to redirect somewhere else. In that case you should do:
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary { { "Controller", "Home" }, { "Action", "Index" } });
and choose the appropriate controller/action pair.
You can use the attribute like this:
[CheckUserPermissions(Action = "edit", Model = "car")]
public ActionResult Edit(int id = 0)
{
//..
}
Let me know if that works nicely for you.
The approach you took looks reasonable, but I would add few changes:
What if you forgot to call HaveReadPermision method? And checking authotization from Actions is not the cleanest solution either, that is not an Action reponsibility.
It is better to keep authorization logic separately. For instance you can create a decorator over you repository which will check the permissions of the current User:
public class AuthorizationDecorator: IRepository
{
public AuthorizationDecorator(IRepository realRepository, IUserProvider userProvider)
{
this.realRepository = realRepository;
this.userProvider = userProvider;
}
public Car GetCar(int id)
{
if(this.UserHaveReadPermission(this.userProvider.GetUserName(), Id))
{
return this.realRepository.GetCar(id);
}
else
{
throw new UserIsNotAuthorizedException();
}
}
private bool UserHaveReadPermission(string username, int id)
{
//do your authorization logic here
}
}
IUserProvider will return curent user name from httpRequest.
After doing the change you don't need to warry about authorization when writing Actions

Resources