views:

75

answers:

2

I am wondering how to ensure that an employee cannot access information from another company). This is not an authentication / roles based question on applying roles or permissions to actions, but rather ensuring that the data somebody is try to access actually belongs to them.

Users belong to a department which in turn belongs to a company. In the below example the calendar belongs to a company. How do I ensure that the user can only edit calendars within their own company.

public ActionResult Edit(int? calendarId)
{
       ...
}

So when a user browses to /Calendars/55 how do I ensure that the calendar belongs to the same company as the user? So that for example, you can't tamper with the URL and put in an id for a calendar that belongs to another company.

Approach 1

public ActionResult Edit(int? calendarId)
{
    // get company that calendar belongs to
    int calCompanyId = ..
    if (CurrentUser.CompanyId != calCompanyId)
        return RedirectToAction(....); // cannot access resource

       ...
}

Whilst this would work, I'm wondering if there is a better way to handle this sort of problem. Possibly that wouldn't require putting in checks for every single action across all controllers like this.

Is there a typical pattern used to solve a problem like this where you need to check the user has access to the particular route? I would think this is a pretty typical problem for applications that have resources(companies, departments etc.) and need to ensure one company/user cannot access another companies data.

Edit:

As pointed out by @jfar, I could use the CompanyId but it's not available in most routes. Should I consider changing my route structure to always include this to make these checks easier? Doing this would be a fair amount of work and would probably 'uglify' the routes though.

Upon thinking about this problem further I think there may be no other choice then to put something similar to an 'IsOwner(....)' check within most actions. The reason for this is a couple fold;

  • Not all items accessible by a route have a CompanyId property on them that can be easily compared to against the user's companyId for example
  • A resource in a route might have a departmentId which is in turned owned by a company. So in this scenario I would have to implement an overloaded 'IsOwner' that knows to check a departmentId or follow the reference up to the company that owns the department

At this stage I thinking along the lines that I will have an 'IsOwner' method simlar to what @jfar posted that can check if a resource that is directly linked to a company is owned by that company, whilst providing some overloaded versions.

CompanyOwns(CurrentUser.CompanyId, model);
DepartmentOwns(CurrentUser.departmentId, model);

All my database models implement an interface that makes finding them by id easier.

public interface IAmIdentifiable<T>
{
   T Id { get; }
}

I might think about adding some more interfaces to the model to aid in the aforementioned helper process like

public interface IAmOwnedByACompany
{
   int CompanyId { get; }
}

public interface IAmOwnedByADepartment
{
   int DepartmentId { get; }
}

This will make checking objects in 'IsOwner' type methods via reflection easier. I haven't had time to completely think this through but believe @jfar was right when he said in this sort of scenario, you really do have to some form of manual checking in each method when determine whether a user should have access to a particular route (which represents a resource). By implementing some interfaces and some clever 'IsOwner' type methods, I hope to make these checks quite simple.

A: 

You could add this into your base controller. Every request, if an RouteParameter named {CompanyId} comes in, automatically check to make sure the CurrentUser.CompanyId matches it.

    protected User CurrentUser { get; set; }
    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);

        if( RouteData.Values["CompanyId"] != null )
            if (CurrentUser.CompanyId != RouteData.Values["CompanyId"] )
                //Redirect to wherever

        //your not restricted from getting the companyid from the route
        //you could get the id from the logged in user, session, or any other method

    }

Update:

You could create a helper that uses reflection to check if entities have the right owner. Code written on the fly, could have an error.

    public bool IsOwner<T>(T model, int companyId)
    {
        var prop = model.GetType().GetProperty("CompanyId");

        if (prop == null)
            return false;

        var modelCompanyId = (int)prop.GetValue(model, null);

        return modelCompanyId == companyId;
    }
jfar
This is interesting and close to what I want but not exactly. As I'm not passing in a companyId but a calendar Id. So my url looks like localhost/calendar/edit/55. 55 is the id of the calendar, I'd need to look up the company Id from.
Joshua Hayes
@Joshua, updated my question with another technique.
jfar
Thanks jfar. That still requires model checks in each action method though. I'm still interested to see a few more opinions on this as I believe its a pretty common problem.
Joshua Hayes
It is common and model checks in every action is just what has to happen. You could just as easily put this in a base controller class instead of coding manual checks. I do almost the same thing in a base controller to make sure FK's match up with who the currently logged in user is.
jfar
Can you provide a simple example for this? You mention putting this in a base controller to avoid manual checks.
Joshua Hayes
@Joshua Hayes thats the first code example.
jfar
@jfar The one that requires you have the id in each url? I can't help but feel there has to be a less intrusive way than that (though the re-use was what I am after). The IsOwner<t>(..) example is what I had thought of initially in sorts but wanted a more uniform way to lock down the site that won't require one of those checks in every action of every controller.
Joshua Hayes
@jfar Your answer wasn't quite what I was after, but did have some useful information and nobody else has replied on this so I'm going to accept your answer. Thanks for your input.
Joshua Hayes
@Joshua Hayes - Josh, you can get the CompanyId from ANY PLACE YOU WANT. I just posted the route value because it was the first thing on my mind. Your question does not specify a lot of the little implementation details like where your getting these ids from. Sorry of this seems blunt but I don't like being accepted by "default" when the question doesn't include enough details. Hopefully your next questions include more detail. ;)
jfar
@jfar Apologies for accepting by default, but I could have left it unaccepted. But I felt the effort you put in (which did have some useful ideas and is likely to lead me to a solution) warranted it. I have updated my question with more information as to why I accepted your solution. Thanks for your help!
Joshua Hayes
A: 

@jfar

Just thinking further about your suggestion to have the company id in the url. Is there a way to pass the id in the url without actually making it visible on the client's browser so you don't have to visibly alter the url routing structure? Kind of like MVC's convention of not always displaying the action attribute by making a default route value?

(Tried to add this as a comment but maybe I don't have enough reputation to do this?)

Ted
This only works for default static routes. For example, if you specify the default action as 'Index' for a controller and no action is specified.
Joshua Hayes