Reputation: 1561
Example:
We have two user types.
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:
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:
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!)
Upvotes: 1
Views: 1196
Reputation: 7338
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
Upvotes: 2