0

I have the following:-

  1. I am working on an asset management system using Asp.net MVC4 with windows authentication enabled.

  2. 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).

  3. 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

John John
  • 1
  • 72
  • 238
  • 501

2 Answers2

1

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.

Tallmaris
  • 7,605
  • 3
  • 28
  • 58
  • thanks for the reply; can you explain what u mean by "What the attribute won't do is Action-based authorization" – John John Jul 04 '13 at 14:28
  • Yeah that is confusing. What I mean is that the AuthorizeAttribute can authorize on a per-user basis or on a per-role basis but not on a specific model action (like "view" cars but not "edit" cars). – Tallmaris Jul 08 '13 at 07:57
1

The approach you took looks reasonable, but I would add few changes:

  1. 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

Alexandr Mihalciuc
  • 2,537
  • 15
  • 12
  • thank for the reply, so creating my own Groups and Roles tables is not a bad practice in asp.net. as usually i relay on the built-in role provider, but on this application i have many scenarios for managing the authorization which could not be achived by the built-in authorization. – John John Jul 04 '13 at 14:27
  • As far as I undestand you have a authorization for each entity in database, so in this case it is acceptable solution – Alexandr Mihalciuc Jul 04 '13 at 14:35