How to ensure a user owns or belongs to a resource when navigating to a route (ASP.NET MVC)
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 ensu开发者_StackOverflow中文版re 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.
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;
}
精彩评论