Проверка владения моделью

В моем контроллере перед изменением модели (обновленной или удаленной) я пытаюсь проверить, действительно ли пользователь, выполняющий действие, владеет объектом, который они пытаются изменить.

В настоящее время я делаю это на уровне метода, и он кажется немного лишним.

[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.