Gdzie powinny odbywać się kontrole uprawnień użytkowników oraz MVC i przez kogo?

26

Czy kontrola modelu powinna odbywać się w modelu lub kontrolerze? A kto powinien obsługiwać sprawdzanie uprawnień, obiekt użytkownika lub pomocnika UserManagement?

Gdzie to się powinno stać?

Sprawdzanie w kontrolerze:

class MyController {
  void performSomeAction() {
    if (user.hasRightPermissions()) {
      model.someAction();
    }
  }
  ...

Kontrola w kontrolerze ułatwia wykonywanie modeli przez proste działania, dzięki czemu możemy zachować całą logikę dla kontrolerów.

Sprawdzanie w modelu:

class MyModel {
  void someAction() {
    if (user.hasRightPermissions()) {
      ...
    }
  }
  ...

Umieszczając kontrole w modelu, komplikujemy model, ale również upewniamy się, że przypadkowo nie pozwalamy użytkownikom robić rzeczy, których nie powinni robić w kontrolerze.

A przez kogo?

Po osiedleniu się na miejscu, kto powinien przeprowadzać kontrole? Użytkownik?

Class User {
  bool hasPermissions(int permissionMask) {
    ...
  }
  ...

Ale tak naprawdę nie jest obowiązkiem użytkownika wiedzieć, do czego on lub ona może uzyskać dostęp, więc może jakaś klasa pomocnicza?

Class UserManagement {
  bool hasPermissions(User user, int permissionMask) {
    ...
  }
  ...

Wiem, że często zadaje się tylko jedno pytanie, no cóż, ale myślę, że można na nie razem dobrze odpowiedzieć.

kba
źródło

Odpowiedzi:

20

Jak zwykle „to zależy”

  • kontrole uprawnień będą działać funkcjonalnie wszędzie tam, gdzie wygodnie jest je umieścić,
  • ale jeśli zadajesz pytanie techniczne, odpowiedź może brzmieć: „umieść kontrole w obiekcie, który jest właścicielem danych wymaganych do przeprowadzenia kontroli” (prawdopodobnie jest to kontroler).
  • ale jeśli zadajesz filozoficzne pytanie, sugeruję alternatywną odpowiedź: nie pokazuj użytkownikom działań, których nie mogą wykonywać .

Tak więc w tym drugim przypadku może być zaimplementowane sprawdzanie uprawnień w kontrolerze jako właściwość logiczna i powiązanie tej właściwości z właściwością Visible przycisku lub panelu w interfejsie użytkownika, który kontroluje akcję

dla użytkownika frustrujące jest wyświetlanie przycisków działań, których nie mogę wykonać; czuję się jakbym nie był zabawny;)

Steven A. Lowe
źródło
Nasza aplikacja implementuje trzeci scenariusz, z tym wyjątkiem, że nie ukrywamy kontrolek, wyłączamy je. Niestety, wszystko odbywa się w programie Winforms z tyłu kodu, więc nie ma to tak naprawdę znaczenia dla pytania OP.
Dave Nay,
11
„frustrujące jest wyświetlanie przycisków działań, których nie mogę wykonać” -> Spróbuj poprawić swój własny post :)
Rowan Freeman
5
Nie wystarczy po prostu ukryć przyciski akcji, których użytkownik nie może wykonać, serwer musi sprawdzać każde żądanie uprawnień. Trzeci punktor nie jest „alternatywną odpowiedzią”, jest coś do zrobienia oprócz sprawdzania uprawnień po stronie serwera.
Flimm
@Flimm zgodził się, jeśli żądania są obsługiwane przez serwer; konkretne pytanie dotyczyło klasy Controller
Steven A. Lowe
7

Bezpieczeństwo jest zagadnieniem przekrojowym, dlatego należy je wdrażać w wielu warstwach. Poniżej znajduje się przykład dla MVC, ale koncepcja ma zastosowanie do innych architektur i / lub wzorców, wystarczy zidentyfikować punkty egzekwowania.

Gdzie to się powinno stać?

Widoki mogą zawierać elementy interfejsu użytkownika (widżety, przyciski, menu itp.), Które muszą być wyświetlane lub nie dla niektórych użytkowników, na podstawie ich uprawnień. Może to być obowiązkiem silnika widoku , ponieważ nie chcesz, aby każdy widok sam sobie z tym poradził. W zależności od rodzaju elementów, na których przeprowadzasz autoryzację, przenieś tę odpowiedzialność w inne miejsce. Pomyśl na przykład o menu, w którym niektóre elementy muszą być wyświetlane, a niektóre nie. Elementy można gdzieś zaimplementować jako listę i filtrować tę listę na podstawie uprawnień, a następnie przesłać ją do widoku.

Kontrolery odpowiadają na żądania, więc jeśli użytkownik nie ma uprawnień do wykonania akcji, należy ją sprawdzić przed wywołaniem akcji, przenosząc odpowiedzialność na wywołującego akcję zamiast trzymać ją w kontrolerze. Ma to tę zaletę, że utrzymuje kontroler w czystości, a jeśli coś zmieni się w uprawnieniach, nie musisz przesiewać kontrolerów, aby zastosować te zmiany.

Zasoby są wyświetlane na podstawie uprawnień. Zwykle odbywa się to na poziomie bazy danych , ponieważ nie chcesz wyciągać wszystkiego z bazy danych, a następnie stosować uprawnień.

Jak widać, w zależności od tego, co chcesz autoryzować, istnieją różne miejsca, w których należy to zrobić. Celem jest zachowanie jak najmniej dyskretnej postawy, dzięki czemu przy zmianie polityki bezpieczeństwa możesz ją łatwo zastosować, najlepiej bez zmiany kodu aplikacji. Może to nie dotyczyć małych aplikacji, w których zestaw uprawnień jest dość mały i nie zmienia się zbyt często. Jednak w aplikacjach dla przedsiębiorstw historia jest zupełnie inna.

Kto powinien to zrobić?

Najwyraźniej nie model. Każda warstwa powinna mieć punkt egzekwowania, który obsługuje autoryzację. Kursywą powyżej zaznaczono możliwy punkt egzekwowania dla każdego poziomu.

Spójrz na XACML . Nie musisz go wdrażać w obecnej postaci, ale da ci wskazówki, którymi możesz się kierować.

diabelnie
źródło
To najlepsza odpowiedź. Z jakiegoś powodu najwyższy i inni zajmują się różnicami między kontrolerem a widokiem lub widokiem i modelem, czego nie wymaga OP. Dzięki!
redFur
1

Używam następującego schematu. Warto powiedzieć, że większość kontroli uprawnień użytkowników można podzielić na dwa ogólne przypadki:

  • dostęp użytkownika do akcji kontrolera na podstawie roli użytkownika bez sprawdzania parametrów akcja wywoływana jest za pomocą,
  • dostęp użytkownika do modelu na podstawie dowolnej logiki lub relacji między danym użytkownikiem a określonym modelem.

Dostęp do akcji kontrolera bez sprawdzania atrybutów jest zwykle implementowany w ramach MVC. To jest w ogóle proste: definiujesz reguły, twoi użytkownicy mają rolę. Po prostu sprawdzasz, czy użytkownik ma uprawnienia do wyszukiwania akcji, sprawdzając swoją rolę w regułach.

Dostęp użytkownika do konkretnego modelu powinien być zdefiniowany w modelu. (Aktor jest podstawową klasą użytkownika. Załóżmy, że może to być klient, sprzedawca lub gość).

interface ICheckAccess
{
    public function checkAccess(Actor $actor, $role);
}

class SomeModel implements ICheckAccess
{
    public function checkAccess(Actor $actor, $role)
    {
        // Your permissions logic can be as sophisticated as you want.
    }
}

Umieszczenie tej logiki w modelu przynosi pewien zysk. Metodę kontroli dostępu można dziedziczyć, nie trzeba tworzyć żadnych dodatkowych klas, można korzystać z ogólnych zalet OOP.

Następnie, aby uprościć sprawdzanie dostępu, przyjmujemy pewne założenia, które prawie zawsze są wdrażane już dla uproszczenia i dobrego stylu:

  • zwykle kontrolery są powiązane z pewną klasą modelu;
  • akcje sprawdzane pod kątem dostępu przyjmują identyfikator pojedynczego modelu jako parametr;
  • do tego parametru można zawsze uzyskać jednolity dostęp z metody podstawowej klasy kontrolera;
  • akcja jest umieszczana w kontrolerze odpowiadającym modelowi, który wykonuje akcję id.

Przy tych założeniach akcje, które używają identyfikatora modelu mogą być powiązane z konkretną instancją modelu. W rzeczywistości większość działań można łatwo przekształcić i przenieść zgodnie z założeniami podanymi powyżej.

Następnie należy zdefiniować i odziedziczyć podstawową klasę abstrakcyjnego kontrolera.

abstract class ModelController
{
    // Retrieve model from database using id from action parameter.
    public abstract function loadModel($id);

    // Returns rules for user role to pass to SomeModel::checkAccess()
    // Something like array('view' => 'viewer', 'delete' => 'owner', 'update' => 'owner')
    public abstract function modelRules();

    public abstract fucntion getIdParameter();

    public function filterModelAccess()
    {
        $id = $this->getIdParameter();
        if(!$this->checkModelAccess($id))
            throw new HttpException(403);
    }

    public function checkModelAccess($id)
    {
        $model = $this->loadModel($id);
        $actor = My::app()->getActor();
        $rules = $this->modelRules();
        $role = $rules[My::app()->getActionName()];
        return $model->chechAccess($actor, $role);
    }
}

Możesz wywołać metodę SomeController :: checkModelAccess ($ id), kiedy konstruujesz swoje menu i decydujesz, czy pokazać jakiś link.

George Sovetov
źródło
Przepraszam za PHP.
George Sovetov
1

Zarówno w modelu, jak i widoku

W widoku - ponieważ interfejs użytkownika nie powinien pokazywać elementów interfejsu użytkownika, które są ograniczone do bieżącego użytkownika

(np. powiedzmy, że przycisk „Usuń” powinien być pokazywany osobom z odpowiednimi uprawnieniami)

W modelu - ponieważ Twoja aplikacja prawdopodobnie ma jakiś interfejs API, prawda? Interfejs API musi również sprawdzać uprawnienia i prawdopodobnie ponownie używa modelu.

(np. masz przycisk „Usuń” w interfejsie użytkownika i metodę interfejsu API „http: / server / API / DeleteEntry / 123” w tym samym czasie

jitbit
źródło
Dlaczego wybrałeś model zamiast kontrolera?
Flimm
nie jestem pewien, dlaczego przeglądać, modelować, a nie w kontrolerze, gdzie odbywa się to przez większość czasu.
VP.
@VP kontroler nie ma mocy, aby pokazywać / ukrywać elementy interfejsu użytkownika (inne niż przekazywanie bool-var DO
WIDOKU
Nie wiem, zwykle wszystko odbywa się w warstwie kontrolera, dlatego byłem ciekawy.
VP.
0

MVC to wzorzec prezentacji. Jako taki widok i administrator powinien ponosić odpowiedzialność wyłącznie za prezentację. Niektóre uprawnienia dotyczą prezentacji, np. Tryb ekspercki, eksperymentalne funkcje interfejsu użytkownika lub różne projekty. Mogą być obsługiwane przez kontroler MVC.

Wiele innych rodzajów uprawnień ma zastosowanie na kilku warstwach aplikacji. Na przykład, jeśli chcesz mieć użytkowników, którzy mogą tylko przeglądać dane, a nie zmieniać rzeczy:

  • warstwa prezentacji musi ukrywać funkcje edycji
  • Jeśli mimo to wywoływana jest funkcja edycji, może / powinna zostać wykryta (przez specyficzne dla aplikacji części warstwy biznesowej, a nie jej część domenową - TrainEditor, a nie Train) i prawdopodobnie spowoduje wyjątek
  • Warstwa dostępu do danych może również sprawdzać zapisy, ale w przypadku bardziej złożonych rodzajów uprawnień, które szybko wymagają zbyt dużej wiedzy na temat warstwy biznesowej, aby być dobrym pomysłem.

W tym podejściu występuje pewne powielanie. Ale ponieważ prezentacja jest zwykle niestabilna, można uzasadnić sprawdzenie uprawnień w zwykle bardziej stabilnej części aplikacji, nawet jeśli oznacza to pewne zbędne kontrole w przypadku, gdy warstwa prezentacji działa zgodnie z przeznaczeniem.

Patrick
źródło