ASP.Net MVC Authentication - Hide Element in View based on roles - asp.net

Is there a possibility to hand over the Result of the Authorize-Attribute to the View?
Let's assume I want to hide 5 links in my Index view based on the memberships of a User.
[Authorize(Roles = "Admin")]
public ActionResult Index(){
....
}
The code above will prevent all users that are not part of the Admin-Group from visiting the Index page.
#{
if(User.IsInRole("Admin"){
Some link to be hidden
}
}
This code will hide the link if the User is not part of the Admin role. This is basically what I want BUT using this method I have to change the role name on every hidden link if the role would change.
Isn't there something like a combination of both? (Schema see below)
[Authorize(Roles = "Admin")] //This will pass true to the View if the User is a member of the group "Admin"
public ActionResult Index(){
....
}
#{
if(User.IsAuthenticated){ //This will read the "Token" and if it's true the if statement will get executed.
Some link to be hidden
}
}
So - if the User is in Role "Admin" the link will be shown. Is this possible?

You could use ViewBag and ViewData among other things, but I'd suggest passing a model back to the view with properties indicating whether to display the links or not.
public class YourViewModel()
{
public bool ShowHiddenLinks { get; set; }
// ... whatever other properties
}
In your controller you'd then do:
[Authorize(Roles = "Admin")]
public ActionResult Index()
{
var yourVm = new YourViewModel();
yourVm.ShowHiddenLinks = true;
return View(yourVm);
}
And your view becomes:
#model YourViewModel
/* ShowHiddenLinks is true & this view is meant for admins only,
so show admin-related links */
#if (Model.ShowHiddenLinks)
{
Some link to be hidden
}
I've named the viewmodel property ShowHiddenLinks on purpose, so that it becomes re-usable for views meant for other users as well. You can of course extend the viewmodel to feature properties for other roles (e.g. a view which is accessible by admins and moderators, each with their own distinct set of hidden links), or create one viewmodel per role—it all depends on the scenario.

Related

Implement multiple roles on the same data in MVC (ASP)

Example:
We have two user types.
SupplierUser
FactoryUser
They both interface with basically the same data, but in general FactoryUsers can edit much more of this information than the SupplierUser.
Using ASP.NET 4.5, I am implementing all of this using MVC.
Some summarized use cases: (Assume logged in)
FactoryUser:
Editable messages page, which shows suppliers their latest announcements.
Order confirmation page, and order view page.
Supplier edit page (for updating addresses etc of multiple suppliers)
SupplierUser:
- Can see messages from specific Factory.
- Can create orders, send and view.
- Can edit their own information
As you can see this is just a lot of editing of information with various permission. My question is, Where should I be starting the separations?
With regards to:
Models - I think this one stays as one with the database
ViewModels - Do I write different views for each role? If so, 2 files/classes?
Controllers - Same, do I write different functions?? Classes? If so then what is the point is having [Authorize role], just to protect from unauthorized access & not intended to split?
Views - Do I try to use the same views for most parts and just somehow include logic about if they have "edit" buttons or not?
Partial Views - Can they be used for the "edit" buttons that may or may not be on the view?
View Layouts - ?
Filters - I can do some fancy logic and put everything in entirely 2 different folders (the whole MVC) and then split it at route/authorize level
Routing - ?
At each one of the above, I can see a possibility to split the logic depending on the type of user. But of course, I want to do this in the simplest and most sane way possible.
Is there some document somewhere which specifies how this should be done, or otherwise any wise people out there who have done this before and encountered all the issues?
Thanks
(first question!)
One way to do this is to create features. e.g View Orders, Create Order, Update Order, Delete Order
These features will then be assigned to a Role - and the Role can be assigned to a User
So the DB will look something like this:
Now when the user logs in, you read all the features assigned to the user and save them in the session (Create a SessionHandler Class).
// Login Function - You can call from Controller
public UserDTO Login(string username, string password)
{
var user = dbContext.Users.FirstOrDefault(s => s.Username == username && s.Password == password);
if(user == null) return null; // login failed
var model = new UserDTO()
{
UserId = user.UserId,
Features = user.Role.Features.Select(s => s.FeatureName).ToList()
};
return model;
}
Sample of UserDTO class
public class UserDTO
{
public int UserId {get;set;}
public List<string> Features {get;set;}
}
Sample of SessionHandler
public class SessionHandler
{
private const string SessionKey = "UserSession";
public static UserDTO UserSession
{
get
{
return HttpContext.Current.Session[SessionKey] != null
? (UserDTO)HttpContext.Current.Session[SessionKey]
: null;
}
set { HttpContext.Current.Session[SessionKey] = value; }
}
}
So in your controller call the Login Function and assign to UserSession in SessionHandler
[HttpPost]
public ActionResult Login(LoginModel model)
{
var user = Login(model.username, model.password);
if(user == null) return View(model);
SessionHandler.UserSession = user;
// TODO: redirect to Home Page - after login
return RedirectToAction("Index", "Home");
}
Then what you can do in your views is check if the user can perform a certain action, so e.g. if you are on the View Orders page - you can hide the Create Order Button if user does NOT have permission:
#model WhateverDTO
// Check if user has Create Order Feature in Role
#if (SessionHandler.UserSession.Features.Contains("Create Order"))
{
// Yes, User has permission - then Render the Button
<button> Create Order </button>
}
Also you can add checks in the Controller(Server side) - Which will provide extra security to your application, using the Authorise Attribute:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
if (httpContext.Session == null)
return false;
// Checking Authenticaiton
var userSettings = SessionHandler.UserSession;
if (userSettings == null)
return true;
//Checking Authorization
if (Roles.Length == 0)
return true;
var actionFeatures = Roles.Split(',');
if (!actionFeatures.Any(s => userSettings.Features.Contains(s)))
throw new UnauthorizedAccessException("You do not have permission to perform this action.");
return true;
}
}
and then decorate your Actions,
[CustomAuthorize(Roles = "Create Order")]
// Pass feature name for Roles - if user doesn't have permission to Create Order - the "You do not have permission to perform this action." exception will get thrown
public ActionResult CreateOrder()
{
return View(new CreateOrderDTO());
}
[HttpPost]
[CustomAuthorize(Roles = "Create Order")]
// Pass feature name for Roles - if user doesn't have permission to Create Order - the "You do not have permission to perform this action." exception will get thrown
public ActionResult CreateOrder(CreateOrderDTO model)
{
return View(model);
}
The good thing about the above method - is that you can add as many user Roles as you need - without changing the Code.
Models - I think this one stays as one with the database
Models are same - same DB same models
ViewModels - Do I write different views for each role? If so, 2 files/classes?
No, don't complicate things - use same ViewModel / DTO
Controllers - Same, do I write different functions?? Classes? If so then what is the point is having [Authorize role], just to protect from unauthorized access & not intended to split?
No need for separate actions/views or controllers
Views - Do I try to use the same views for most parts and just somehow include logic about if they have "edit" buttons or not?
Yes, use same views - Hide/Show actions based on User Role/ Feature
Partial Views - Can they be used for the "edit" buttons that may or may not be on the view?
No need for Partial Views for buttons
View Layouts - ?
Filters - I can do some fancy logic and put everything in entirely 2 different folders (the whole MVC) and then split it at route/authorize level
Routing - ?
No

How to display view model validation for a view that has multiple forms on it?

I'm trying to better understand how to properly structure my ASP.NET MVC code to handle a situation where a single view contains multiple forms. I feel that it makes sense to submit the forms to their own action methods, so that each form can benefit from its own view model parameter binding and validation, and to avoid putting all form parameters into 1 larger, monolithic view model.
I'm trying to code this pattern, but I can't seem to tie the loose ends together.
I've written some example action methods below, along with example view model classes, that I think demonstrate what I'm trying to achieve. Lets say that I've got an Item Detail action method and view. On this Detail view, I've got two forms - one that creates a new Comment and another that creates a new Note. Both Comment and Note forms POST to their own action methods - DetailNewComment and DetailNewNote.
On success, these POST handler action methods work just fine. On an invalid model state though, I return View(model) so that I can display the issues on the original Detail view. This tries to render a view named Brief though, instead of Detail. If I use the overloaded View call that allows me to specify which view to render, then now I have issues with the different view model classes that I'm using. The specific view model classes now no longer work with the original DetailViewModel.
I get the feeling that I'm doing this completely wrong. How am I supposed to be handling this scenario with multiple forms? Thanks!
public ActionResult Detail(int id)
{
var model = new ItemDetailViewModel
{
Item = ItemRepository.Get(id)
};
return View(model);
}
[HttpPost]
public ActionResult DetailNewComment(int id, ItemDetailNewCommentViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var comment = CommentRepository.Insert(new Comment
{
Text = model.Text
});
return RedirecToAction("Detail", new { id = id; });
}
[HttpPost]
public ActionResult DetailNewNote(int id, ItemDetailNewNoteViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var note = NoteRepository.Insert(new Note
{
Text = model.Text
});
return RedirectToAction("Detail", new { id = id; });
}
... with view models something like ...
public class ItemDetailViewModel
{
public Item Item { get; set; }
}
public class ItemDetailNewCommentViewModel
{
public string Text { get; set; }
}
public class ItemDetailNewNoteViewModel
{
public string Text { get; set; }
}
For your case I'd recommend to have a master model for example your
ItemDetailViewModel class to which you'll add a property for each sub-model
public class ItemDetailViewModel
{
public Item Item { get; set; }
public ItemDetailNewCommentViewModel NewCommentModel {get;set;}
public ItemDetailNewNoteViewModel NoteModel {get;set;}
}
Your Detail view will be the master view and the other two will be partial views.
Master view will receive an instance of ItemDetailViewModel as model and inside view you will render your partials by passing Model.NewCommentModel and Model.NoteModel as their corresponding models. For being able to use separate actions for each form, instead of regular forms you can use ajax forms, thus you will send to the server only relevant information without altering the rest of the master view.
The chief problem here is what happens when the user messes up and their post doesn't pass validation server-side. If you choose to take them to a page where just the one form is presented, then you can post to a different action, but if you want both forms re-displayed, then they both should point to the same action.
Really, you just have to make a choice. I've seen sites handle it both ways. Personally, I prefer to re-display the original form, which means handling both forms in the same action. It can lead to bloat, but you can factor out a lot of logic from the action such that you end up with mostly just a branch depending on which form was submitted.

Include JavaScript based on ActiveDirectory Membership

I've got a single page application and in the start page I've linked to the ActionResult "_ADMIN()"
#Html.Action("_ADMIN")
Depending on the user's group membership, this ActionResult returns either an EmptyResult or its view:
public ActionResult _ADMIN()
{
if (User.IsInRole("admin"))
{
return View();
}
else
{
return new EmptyResult();
}
}
Its view consists of this one line:
#Scripts.Render("~/bundles/admin")
This works but feels suboptimal. I could copy the start page and create seperate ones for non-admins and admins, but that would be even more suboptimal. I'm using MVC 4.
What's a better way to include a bundle of JavaScript only if the current user is in a certain Active Directory group?
You can add scripts depending on roles. Like that :
if (User.IsInRole("admin"))
{
#Scripts.Render("~/bundles/admin")
}
And on your Action method you add attribute then the user will be automatically redirected to the login page if it does not have rights. You can add this attribute also on whole class.
[Authorize(Roles = "admin")]
I would only show the link to admin users:
if (User.IsInRole("admin"))
{
#Html.Action("_ADMIN")
}
Then, in the event a non-admin tried to access the action method, I would block the request at the action method:
[Authorize(Roles = "admin")]
public ActionResult _ADMIN()
{
return View();
}

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

How to share a common object in each page request of a ASP.net MVC 4 webapp?

I come from "regular" asp.net so i'm a bit (totally) lost with MVC.
What I was doing with my own asp.net programmation pattern :
I have one custom class objet which represent the "page" and its properties (like mypage.loadJquery, mypage.isLogged, mypage.Title, custom cache logic, etc.)
This class is instanciate once on top of each ASHX page, I then manipulate a stringbuilder to produce HTML and spit it right at the browser at the end.
Having only one request on the ASHX page, i can use my page object instanciated at the top till the end when calling final response.write
Now i'm trying to go for MVC. I "kind of" understood the M/V/C model and the routing concept. I would like to keep my custom "page" object but I lost my page life cycle and I definitely don't know how to instanciate my page object ONCE in at the top of every call.
I need this instanciated ONCE shared object across every models, controllers, views, partial views, htmlhelper...
I realize MVC pattern might be confusing for me at this moment, bu how could I try to reproduce my need ?
(Very concrete exemple : On every request i need to check if the user is logged via his cookies. If it is I round trip the database to get user infos. Then I DO NEED THESE INFOS ON PRATICALLY EVERY model / controller / view of the app, but of course don't want to round back each time to security check and database querying, how can i have these info on the whole mvc cyle ?)
In my project I create interface IViewModel that contains all fields that I need in my layout/masterpage and set is as model of it so I can easily use them:
IViewModel.cs
public interface IViewModel
{
string Title { get; set; }
User User { get; set; }
}
Layout.cshtml
#model IViewModel
<html>
<head>
<title>#Model.Title</title>
</head>
<body>
#if (Model.User.IsAuthenticated) {
You are logged as #Model.User.Name
}
</body>
</html>
All my models implement that interface (or inherit from ViewModelBase that is default implementation of that class). Additionally I have custom action filter that check if returned ActionResult is (Partial)ViewResult and if Model of it implement my IViewModel interface and fill data in that interface.
public FillViewModelAttribute : ActionFilterAttribute
{
public override OnActionExecuted(ActionExecutedContext context)
{
var viewResult = context.Result as ViewResult;
if (viewResult != null && viewResult.Model is IViewModel)
{
var viewModel = (IViewModel)viewResult.Model;
// fill data
}
}
}
I created many projects like this. Basically, you can create a base controller class where all the other controllers inherit from it.
[Authorize]
public class BaseController : Controller
{
private Instructor _G_USER = null;
protected Instructor G_USER
{
get
{
if (_G_USER == null)
{
_G_USER = Your DB query here
ViewData["G_USER"] = _G_USER;
}
return _G_USER;
}
}
}
Then in your every child class, you can do
[Authorize]
public class YourController : BaseController
{
public ActionResult Index()
{
if(!G_USER.CAN_DO_THIS) throw new NoPermissionException();
return View();
}
}
To use the User in the view, create an extension method.
public static class ExtentionMethods
{
public static USER G_USER(this ViewPage page)
{
return (USER)page.ViewData["G_USER"];
}
}
Then use in the page like this
<%=this.G_USER().....%>

Resources