tags:

views:

79

answers:

4

To allow users and site admins to view/add/edit/delete data in my application I decided on this route:

routes.MapRoute("ClientRoute",
       "{account}/{controller}/{action}/{id}",
       new { controller = "Home", action = "Index", id = "" });

which results in routes like: mvcapp.net/1234/contact/add.

To keep users {except admins} from accessing other client's data I have added the following code in my controller actions.

...
   var model = repos.GetSomeData();
   if (User.IsInRole("Admin") == false) {
    if (account == Profile["Client"])
       return View(model);
    else
       return View("WrongClient");
    }
...

What is the best way to do this?

SOLUTION I WENT WITH

public class BaseController : Controller {
  protected override OnActionExecuting(ActionExecutingContect filterContext) {
      if (filterContext.RouteData.Values["account"] != null) {
         string client = filterContext.RouteData.Values["account"].ToString();
         if (User.IsInRole("admin") == false) {
            if (Profile.Clients.Contains(account) == false)
              filterContext.Result = new ViewResult() {ViewName = "WrongClient"};
          }
      }
  }
}
+1  A: 

You can write your repository methods so that they only return client data for the appropriate account number. Just pass the account number to the repository method.

If you're concerned about passing user information into the repository method (as Jabe discusses in his comment below), then you can return an IQueryable from the repository, and run a Linq query against that to do your security trimming.

Robert Harvey
What if you're an admin and need access to a specific user's info (assuming I want to use the same views created for the users)?
Baddie
You can pass an admin flag to the repository method also.
Robert Harvey
Probably controversial, but the repository should not know about users. IMHO it should more be like "give ID, get data".
Jabe
If I pass a flag to the repository wouldn't I need to duplicate the code for each action?
Baddie
You're going to need to call a method in your repository from each controller action anyway.
Robert Harvey
A: 

For this specific example, Azam Sharp has a possible solution on his blog. I literally stumbled across this article no more than five minutes ago. Hope it helps!

joseph.ferris
Looking again - this does not directly address your needs, but I still think it could be adapted to what you are trying to do.
joseph.ferris
I'm not wild about the way the blog poster handles deletes. Delete actions should always be accomplished by a POST, not by a GET.
Robert Harvey
@Robert - Generally, I agree. I am more for the general idea of the article, as opposed to the implementation. Good example of what a BaseController might do, though.
joseph.ferris
Yes, I liked the BaseController example.
Robert Harvey
A: 

Is it actually necessary to have the client be part of the route considering you already have that information at your disposal from the profile?

As far as handling this it the repository level (as mentioned above) - this can get a little tricky, as could possibly prevent access to data needed internally for some business process as well as not letting a user access it. Of course, you could create separate filtered/unfiltered methods to handle this. Also, this may be the way to go no intermixing of client data is ever permissible.

Most of the time in our application, it is only the top level item that is being added/inserted/deleted which needs to have access restricted in some fashion. It is also only a limited set of data that needs restricted in this fashion, so I usually write code that throws an exception of some kind right in the controller should the access rules for that item be violated.

If the rules for your app are very similar there are a number of ways you can keep from repeating the code. A custom ActionFilter or custom Controller base class both come to mind. If each row in your db has a client id, or some such scheme, another option would be to have your domain objects each implement an interface exposing this id. You can then write reusable code (mixin-style extension methods, etc...) that uses this interface as a basis for applying your various security rules.

Krazzy
It's not necessary have the client in the route. I thought it would be easier for the users { client and admin }.
dr
Wouldn't it actually make it more difficult for users? To directly access a certain url they would have to also enter the client id. Looking at the code above, you also have to validate this value against an already "known" profile value. Unless I'm missing something (which is quite possible) it seems like all of this is completely superfluous. So long as the value contained by the profile is secure and correct, you should not have to deal with the route value at all.
Krazzy
My clients have 1 or more account numbers and they know them by heart. Their lives, aka $$, depends on these numbers. That doesn't mean its not superfluous. Its just an idea to keep them from having to always pick from a list.
dr
A: 

I'm not sure what Profile["AccountNumber"] is doing (or where Profile comes from), but assuming you can create that object anytime (or if its already created);

You can do the following (place this in your controller):

protected override void ExecuteCore()
{
    var model = repos.GetSomeData(int.Parse(base.RouteData.Values["client"])));
    if (User.IsInRole("Admin") == false && Profile["AccountNumber"].ToString() != model.AccountNumber)
    {
          ViewData["Error"] = "You can't access this page";
          View("WrongClient").ExecuteResult(ControllerContext);
    }
    else
          base.ExecuteCore();

}

You can put this in all the controllers you need, or have the controllers inherit a base controller class that implements this function.

Idea taken from: http://forums.asp.net/t/1382514.aspx

Baddie
HttpContext.Profile
dr
In that case, I recommend just checking to see if "client" matches the user's id (assuming you use clientid as the User.Identity.Name)
Baddie
Paul Balmire's response ( 5th or 6th entry ) led me to the solution i chose.
dr