niedziela, 31 marca 2013

ASP.NET MVC Mass Assignment

Mass Assignment to sposób na wysłanie parametrów które normalnie nie powinny się znaleźć w żądaniu POST.

Zobaczmy na prosty przykład obrazujący opisany powyżej problem:

Model:

Code:
    public class ApplicationUser
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool SecretField { get; set; }
    }

Na podstawie modelu tworzymy widok edycji danych użytkownika:

Code:
@model MassAssignment.Models.ApplicationUser

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <fieldset>
        <legend>ApplicationUser</legend>

        @Html.HiddenFor(model => model.Id)

        <div class="editor-label">
            @Html.LabelFor(model => model.FirstName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.FirstName)
            @Html.ValidationMessageFor(model => model.FirstName)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.LastName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.LastName)
            @Html.ValidationMessageFor(model => model.LastName)
        </div>
        @*
        <div class="editor-label">
            @Html.LabelFor(model => model.SecretField)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.SecretField)
            @Html.ValidationMessageFor(model => model.SecretField)
        </div>
        *@
        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}


Pole SecreetField zostało za komentowane (nie pojawi się w źródłach wygenerowanego dokumentu).

W kontrolerze wprowadzamy prosty kod który uaktualni obiekt użytkownika na podstawie przekazanych przez Binding wartości:

Code:
        [HttpPost]
        public ActionResult Edit(ApplicationUser user)
        {
            if (ModelState.IsValid)
            {
                var u = userService.GetUserById(user.Id);
                UpdateModel(u);
            }

            return RedirectToAction("GetUsers");
        }

Rozwiązanie można powiedzieć książkowe - poza jednym szczegółem - brakuje SaveChanges na obiekcie Context, którego akurat w tym przypadku nie mam (nie korzystam z bazy danych a ze statycznej kolekcji).

Wydawałoby się że powyższy przykład jest poprawny. Jednak jest jedna rzecz którą przeoczyliśmy podczas tworzenia tego rozwiązania - nasi użytkownicy. Zobaczmy co mogłoby się stać, gdyby ktoś chciał wykorzystać aplikację w sposób który nie chcemy:


Przepraszam za słabą jakość, ale chodzi tutaj o samo sedno problemu. Mianowicie jeżeli użytkownik będzie chciał zaatakować naszą stroną i w naszym modelu znajdzie się pole typu IsAdmin, użytkownik może przesłać ten parametr bezpośrednio do mechanizmu bindowania (np. tak jak ja za pomocą QueryString).

W jaki sposób możemy zapobiec takiemu zachowaniu? Otóż istnieje kilka rozwiązań:

1. Stworzenie osobnych modeli do edycji, wyświetlania
2. Skorzystanie z atrybutu Bind:

Code:
public ActionResult Edit([Bind(Exclude = "SecretField")]ApplicationUser user)

Kolejne pola, które nie mają brać udziału w bindowaniu wymieniamy po przecinku. Możemy oczywiście także wskazać, które pola mają brać udział w bindowaniu.

3. Metoda UpdateModel jako jeden z argumentów przyjmuje tablicę pól, które mają zostać uaktualnione:

Code:
UpdateModel(u, includeProperties: new string[] {"FirstName", "LastName"});

4. Kolejną możliwością jest stworzenie np. interfejsu:

Code:
    public interface IEditModel
    {
        string FirstName { get; set; }
        string LastName { get; set; }
    }

następnie w metodzie UpdateModel podajemy ten model:

Code:
UpdateModel<IEditModel>(u);


5. Jeszcze jedną możliwością, jest nałożenie atrybuty ReadOnly na dane pole w modelu:

Code:
[ReadOnly(true)]

Mechanizm bindowania respektuje tak nałoży atrybut na dane pole.

Brak komentarzy:

Prześlij komentarz