MVC Dynamic Page Permissions Using Authorize Attribute? - asp.net

I'm working on setting up my user permissions for my company's site, and we have several different roles and permissions that will have to be created. I have found some awesome information on creating the actual roles and groups, as well as how to implement them from here. However, this still requires the roles to be hard-coded into the authorize tag, is there a way to dynamically populate the authorize tag, so that I can have a page on the site that I can quickly assign different permissions to different pages, without having to just back into the code and modify the permission set for every single page I create?

Implement the following custom authorise attribute.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
public CustomAuthorizeAttribute (params string[] roleKeys)
{
var roles = new List<string>();
var allRoles = (NameValueCollection)ConfigurationManager.GetSection("CustomRoles");
foreach(var roleKey in roleKeys) {
roles.AddRange(allRoles[roleKey].Split(new []{','}));
}
Roles = string.Join(",", roles);
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
if (filterContext.Result is HttpUnauthorizedResult)
{
filterContext.Result = new RedirectResult("~/Error/AcessDenied");
}
}
}
Then add the following to the web.config
<section name="CustomRoles" type="System.Configuration.NameValueFileSectionHandler,System, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
and then, as an example
<CustomRoles>
<add key="UsersPagePermission" value="HR,Accounts,Developers" />
</CustomRoles>
The on your controller or action or in the global filters (whichever you prefer :)) add the attribute
e.g.
[CustomAuthorize("UsersPagePermission")]
public class UserController : Controller
This will allow you to modify the web.config rather than code to change permissions.

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 to redirect unauthorized users with ASP.NET MVC 6

I want to know how to redirect users. I have a Controller Index() and I want only users with the role "Student" can enter there! So I use
[Authorize(Roles="Student")]
I wonder how can I redirect users who do not have this role to the homepage
MVC5 (and older):
You can do this by changing the loginUrl attribute on your web.config. Change it to the desired route:
<authentication mode="Forms">
<forms loginUrl="~/Home/Index" timeout="2880" />
</authentication>
MVC6:
In MVC6 you can try this (inside the Startup.cs):
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookieAuthenticationOptions>(options =>
{
options.LoginPath = new PathString("/Home/Index");
});
}
There is a method floating around that works for MVC5. I assume it would work for MVC6 as well.
Within your Controller, create a Custom Auth method like so.
public class YourCustomAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
// If they are authorized, handle accordingly
if (this.AuthorizeCore(filterContext.HttpContext))
{
base.OnAuthorization(filterContext);
}
else
{
// Otherwise redirect to your specific authorized area
filterContext.Result = new RedirectResult("~/YourController/Unauthorized");
}
}
}
Then change your data annotations to
[YourCustomAuthorize(Roles = "Admin")]
public class UserController : Controller
{
// Omitted for brevity
}
Did you try to use session for this?
I'm guessing you have login page then after login classify the session ASAP
then simple If condition will do.
<%If Session("userRole")="Student" Then%>
This is the text version of the page
<%Else%>
Response.Redirect("notavailablepage.html")
<%End If%>

Page reachable if user connected or not.

I am currently working on a website with MVC5 and Identity.
I have a page which is reachable when a user is connected (with an account) but also when he is not. If he is connected I want to display some information .
If he is not connected I want to display less information and I want to invite him to log in.
I don't know how to implement such a page. I have this controller :
[Authorize]
public class PController : Controller
{
private int UserId;
public ActionResult Index(int userId ) {
UserId = Convert.ToInt32(((ClaimsIdentity) User.Identity).FindFirst("test").Value);
PModel model = new PModel(UserId);
return View(model);
}
}
and my model :
public class PModel {
public int UserId { get; set; }
public PModel(int userId) {
........
}
In both cases, I would like to call the Index method, and do the processings according to the connexion in the model :
public PModel(int userId) {
if(userConnected) {
} else {
}
}
I don't know whether it's possible like this.
You don't do this in your model (typically). If you want to use the default forms authentication model that comes with ASP.net, you need to set it up in your web.config and provide appropriate login/logout actions etc. In the config, you have something like this:
<authentication mode="Forms">
<forms loginUrl="~/Account/LogOn" timeout="30" name=".MySite" protection="All"/>
</authentication>
The Authorize attribute on your action will stop the code within that method from running if the user is not authenticated. The forms authentication code will redirect the user to the 'loginUrl' specified in your web.config if they come directly to a page in your site without logging in.
From your comment, I have misread your question. You will need to remove the Authorize attribute altogether then, and you can set your model userConnected property based on the Request.IsAuthenticated property in the controller.

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

Execute some code for each request for ASP.NET (aspx and cshtml )

Is there a way to write some code that would be executed for each request to a .aspx or a .cshtml page in asp.net 4.5 apart from using a base page class. it is a very huge project and making changes to all pages to use a base page is a nightmare. Also i am not sure how would this be done for a cshtml page since they don't have a class.
Can we use the Application_BeginRequest and target only the aspx and cshtml files since the website is running in integrated mode.?
basically, i have to check if a user who is accessing the website has a specific ip address against a database and if yes then allow access otherwise redirect.
we are using IIS8 and ASP.Net 4.5 and ASP.Net Razor Web Pages
Also i am not sure how would this be done for a cshtml page since they don't have a class.
You could place a _ViewStart.cshtml file whose contents will get executed on each request.
Alternatively you could write a custom Http Module:
public class MyModule: IHttpModule
{
public void Init(HttpApplication app)
{
app.BeginRequest += new EventHandler(OnBeginRequest);
}
public void Dispose()
{
}
public void OnBeginRequest(object s, EventArgs e)
{
// this code here's gonna get executed on each request
}
}
and then simply register this module in your web.config:
<system.webServer>
<modules>
<add name="MyModule" type="SomeNamespace.MyModule, SomeAssembly" />
</modules>
...
</system.webServer>
or if you are running in Classic Mode:
<system.web>
<httpModules>
<add name="MyModule" type="SomeNamespace.MyModule, SomeAssembly" />
</httpModules>
</system.web>
basically, i have to check if a user who is accessing the website has
a specific ip address against a database and if yes then allow access
otherwise redirect.
Inside the OnBeginRequest method you could get the current user IP:
public void OnBeginRequest(object sender, EventArgs e)
{
var app = sender as HttpApplication;
var request = app.Context.Request;
string ip = request.UserHostAddress;
// do your checks against the database
}
Asp.net MVC filters are especially designed for that purpose.
You would implement ActionFilterAttribute like this (maybe put this new class in a Filters folder in your webapp solution):
public class IpFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string ip = filterContext.HttpContext.Request.UserHostAddress;
if(!testIp(ip))
{
if (true /* You want to use a route name*/)
filterContext.Result = new RedirectToRouteResult("badIpRouteName");
else /* you want an url */
filterContext.Result = new RedirectResult("~/badIpController/badIpAction");
}
base.OnActionExecuting(filterContext);
}
private bool testIp(string inputIp)
{
return true /* do you ip test here */;
}
}
Then you have to decorate any action that would perform the ipcheck with IpFilter like so :
[IpFilter]
public ActionResult AnyActionWhichNeedsGoodIp()
{
/* do stuff */
}

Resources