Проверка владения моделью
В моем контроллере перед изменением модели (обновленной или удаленной) я пытаюсь проверить, действительно ли пользователь, выполняющий действие, владеет объектом, который они пытаются изменить.
В настоящее время я делаю это на уровне метода, и он кажется немного лишним.
[HttpPost]
public ActionResult Edit(Notebook notebook)
{
if (notebook.UserProfileId != WebSecurity.CurrentUserId) { return HttpNotFound(); }
if (ModelState.IsValid)
{
db.Entry(notebook).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(notebook);
}
Существует ли общий способ сделать это, который может быть повторно использован для разных моделей?
Можно ли сделать это с помощью ActionFilter
?
Ответы
Ответ 1
Я вижу одну проблему с тем, что у вас есть - вы полагаетесь на ввод пользователя для выполнения проверки безопасности.
Рассмотрите свой код
if (notebook.UserProfileId != WebSecurity.CurrentUserId)
Ноутбук пришел из привязки модели. Таким образом, UserProfileId исходит из привязки модели. И вы можете с радостью подделать это - например, я использую Firefox TamperData, чтобы изменить значение скрытого UserProfileId, чтобы он соответствовал моему входу и прочь, я ухожу.
То, что я делаю (в службе, а не в контроллере), находится на посту, оттягивающем запись из базы данных на основе уникального идентификатора (например, Edit/2 будет использовать 2), а затем проверки пользователя .Identity.Name(ну, переданный параметр идентификации) в отношении текущего поля владельца, которое у меня есть в моей записи возвращенной базы данных.
Поскольку я отказываюсь от базы данных (репозитория, что бы то ни было), для этого атрибут не будет работать, и я не уверен, что вы все равно могли бы быть достаточно обобщенными в подход атрибутов.
Ответ 2
Подход к фильму может выглядеть так:
public class VerifyOwnership : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
foreach(var parameter in filterContext.ActionParameters)
{
var owned = paramter.Value as IHaveAnOwner;
if(owned != null)
{
if(owned.OwnerId != WebSecurity.CurrentUserId)
{
// ... not found or access denied
}
}
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
}
}
Это предполагает, что такие модели, как Notebook, реализуют определенный интерфейс.
public interface IHaveAnOwner
{
int OwnerId { get; set; }
}
Blowdart имеет хорошую точку зрения, что пользователь может вмешиваться в OwnerId в сообщение. Я уверен, что они могли бы также вмешаться в свой билет на авторизацию, но они должны были знать другой пользовательский билет и подделывать оба, чтобы получить идентификаторы для соответствия другому пользователю, я полагаю.
Ответ 3
Фильтр звучит как подход ok, но он несколько ограничен. Было бы неплохо, если бы у вас был такой фильтр:
[RequireOwnership<Notebook>(n => n.UserProfileId)]
... но Attributes
ограничены, в которых разрешены типы данных, и я не думаю, что эти дженерики тоже разрешены. Таким образом, у вас может быть атрибут [RequireOwnership]
, который работает, проверяя свойства модели с помощью отражения, или вместо этого вы можете создать настраиваемый валидатор, где ваша модель выглядит следующим образом:
public class Notebook
{
[MatchesCurrentUserId]
public int UserProfileId { get; set; }
}
Тогда вам нужно проверить ModelState.IsValid
.
Edit:
Мне пришла другая опция. Вы можете использовать фильтр в сочетании с атрибутом вашей модели (не обязательно должен быть ValidationAttribute
). Фильтр может проверить ваши модели запросов и проверить свойства с помощью [MatchesCurrentUserId]
по сравнению с текущим идентификатором пользователя.
Ответ 4
Когда я делал такие вещи в прошлом, это было действительно не намного лучше. Для наших проектов у нас будет метод, который примет объект Notebook
и проверит его против текущего пользователя.
Вы можете перегрузить этот метод всеми вашими типами объектов и использовать согласованный метод проверки доступа. Извините, что лучший способ я знаю.
[HttpPost]
public ActionResult Edit(Notebook notebook)
{
if(!SessionUser.LoggedInUser.CheckAccess(notebook))
return HttpNotFound();
//Other code...
}
P.S. SessionUser
- это особый класс, который мы имели в основном для того, чтобы управлять тем, кто вошел в систему в то время. Вы можете написать что-то подобное, но не ожидайте, что оно будет по умолчанию в .NET.