Worthy7
Worthy7

Reputation: 1561

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

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:

  1. Models - I think this one stays as one with the database
  2. ViewModels - Do I write different views for each role? If so, 2 files/classes?
  3. 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?
  4. 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?
  5. Partial Views - Can they be used for the "edit" buttons that may or may not be on the view?
  6. View Layouts - ?
  7. 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
  8. 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!)

Upvotes: 1

Views: 1196

Answers (1)

Dawood Awan
Dawood Awan

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:

enter image description here

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

Related Questions