How to check record ownership in controllers?
An application permits users to create records. For our purposes, let's call those records Goals.
One user should not be able to see Goals created by another user.
What is the best method for preventing UserA from accessing UserB's Goal?
I can do it like this:
//using asp.net membership
Gui开发者_如何学编程d uId = (Guid)System.Web.Security.Membership.GetUser().ProviderUserKey;
//goal records contain a foreign key to users, so I know who owns what
Goal theGoal = db.Goals.SingleOrDefault(g => g.GoalId == goalId
&& g.UserId == uId);
if (null == theGoal)
{
ViewData["error"] = "Can't find that goal.";
return View("Error");
}
else
{
return View(theGoal);
}
This works fine. The problem is that I've got similar code littered in every action that accesses goals.
Is there a more re-usable way of accomplishing this?
I thought of implementing it as an Authorization Filter. 2 problems with that scheme:
1) Requires the filter to know about and use the DB.
2) Requires 2 queries(1 in the filter, another in the action) instead of just the 1 query in the action that I have now.What's a more DRY way of accomplishing this?
A custom model binder is a great place to do this:
public class GoalModelBinder : DefaultModelBinder
{
private readonly IGoalRepository _repository;
public GoalModelBinder(IGoalRepository repository)
{
_repository = repository;
}
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
// Here the default model binder does his job of binding stuff
// like the goal id which you would use in the repository to check
var goal = base.CreateModel(controllerContext, bindingContext, modelType) as Goal;
var user = controllerContext.HttpContext.User;
var theGoal = _repository.GetGoal(user, goal);
if (theGoal == null)
{
throw new HttpException(403, "Not authorized");
}
// It's OK, we've checked that the Goal belongs to the user
// => return it
return theGoal;
}
}
and then in your Application_Start
register this model binder:
// some implementation of your repo
var sqlRepo = new SqlGoalRepository();
ModelBinders.Binders.Add(typeof(Goal), new GoalModelBinder(sqlRepo));
Now your controller action becomes less littered:
[Authorize]
public ActionResult Edit(Goal goal)
{
// if we get that far we are fine => we've got our goal
// and we are sure that it belongs to the currently logged user
return View(goal);
}
精彩评论