Pierwsze pytanie
Proszę, czy możesz mi wyjaśnić, jak najprostszą listę ACL można zaimplementować w MVC.
Oto pierwsze podejście do korzystania z listy ACL w kontrolerze ...
<?php
class MyController extends Controller {
public function myMethod() {
//It is just abstract code
$acl = new Acl();
$acl->setController('MyController');
$acl->setMethod('myMethod');
$acl->getRole();
if (!$acl->allowed()) die("You're not allowed to do it!");
...
}
}
?>
Jest to bardzo złe podejście, a minusem jest to, że musimy dodać fragment kodu ACL do metody każdego kontrolera, ale nie potrzebujemy żadnych dodatkowych zależności!
Następnym podejściem jest utworzenie wszystkich metod kontrolera private
i dodanie kodu ACL do __call
metody kontrolera .
<?php
class MyController extends Controller {
private function myMethod() {
...
}
public function __call($name, $params) {
//It is just abstract code
$acl = new Acl();
$acl->setController(__CLASS__);
$acl->setMethod($name);
$acl->getRole();
if (!$acl->allowed()) die("You're not allowed to do it!");
...
}
}
?>
Jest lepszy niż poprzedni kod, ale główne minusy to ...
- Wszystkie metody kontrolera powinny być prywatne
- Musimy dodać kod ACL do metody __call każdego kontrolera.
Następnym podejściem jest umieszczenie kodu ACL w kontrolerze nadrzędnym, ale nadal musimy zachować prywatność wszystkich metod kontrolera podrzędnego.
Jakie jest rozwiązanie? A jaka jest najlepsza praktyka? Gdzie powinienem wywołać funkcje ACL, aby zdecydować o zezwoleniu lub zakazie wykonania metody.
Drugie Pytanie
Drugie pytanie dotyczy zdobycia roli za pomocą ACL. Wyobraźmy sobie, że mamy gości, użytkowników i znajomych użytkownika. Użytkownik ma ograniczony dostęp do przeglądania swojego profilu, który mogą wyświetlać tylko znajomi. Wszyscy goście nie mogą wyświetlić profilu tego użytkownika. Oto logika ...
- musimy upewnić się, że wywoływana metoda to profile
- musimy wykryć właściciela tego profilu
- musimy wykryć, czy przeglądający jest właścicielem tego profilu, czy nie
- musimy przeczytać zasady ograniczeń dotyczące tego profilu
- musimy zdecydować, czy wykonać, czy nie wykonać metody profilu
Główne pytanie dotyczy wykrycia właściciela profilu. Możemy wykryć, kto jest właścicielem profilu, wykonując tylko metodę modelu $ model-> getOwner (), ale Acl nie ma dostępu do modelu. Jak możemy to zaimplementować?
Mam nadzieję, że moje myśli są jasne. Przepraszam za mój angielski.
Dziękuję Ci.
źródło
if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()
(innego, wyświetlacz „Nie masz dostępu do profilu tego użytkownika” lub coś podobnego, że nie rozumiem?.Odpowiedzi:
Pierwsza część / odpowiedź (implementacja ACL)
Moim skromnym zdaniem najlepszym sposobem podejścia do tego byłoby użycie wzoru dekoratora. Zasadniczo oznacza to, że bierzesz swój przedmiot i umieszczasz go w innym obiekcie, który będzie działał jak ochronna powłoka. To NIE wymagałoby rozszerzenia oryginalnej klasy. Oto przykład:
A tak wyglądałoby użycie tego rodzaju struktury:
Jak możesz zauważyć, to rozwiązanie ma kilka zalet:
Controller
Ale jest też jeden poważny problem z tą metodą - nie można natywnie sprawdzić, czy zabezpieczony obiekt implementuje i interfejs (co dotyczy również wyszukiwania istniejących metod) lub jest częścią jakiegoś łańcucha dziedziczenia.
Część druga / odpowiedź (RBAC dla obiektów)
W tym przypadku główną różnicą, którą powinieneś zauważyć, jest to, że same obiekty domeny (na przykład
Profile
:) zawierają szczegółowe informacje o właścicielu. Oznacza to, że abyś mógł sprawdzić, czy (i na jakim poziomie) użytkownik ma do niego dostęp, będzie wymagał zmiany tej linii:Zasadniczo masz dwie opcje:
Podaj listę ACL z odpowiednim obiektem. Ale musisz uważać, aby nie naruszyć prawa Demeter :
Poproś o wszystkie istotne szczegóły i podaj ACL tylko to, czego potrzebuje, co sprawi, że będzie nieco bardziej przyjazny dla testów jednostkowych:
Kilka filmów, które mogą pomóc w wymyśleniu własnej implementacji:
Dodatkowe uwagi
Wydaje się, że masz dość powszechne (i całkowicie błędne) zrozumienie tego, czym jest Model w MVC. Model nie jest klasą . Jeśli masz nazwę klasy
FooBarModel
lub coś, co dziedziczyAbstractModel
, to robisz to źle.We właściwym MVC Model jest warstwą zawierającą bardzo dużo klas. Dużą część zajęć można podzielić na dwie grupy ze względu na odpowiedzialność:
- Logika biznesowa domeny
( czytaj więcej : tutaj i tutaj ):
Instancje z tej grupy zajęć zajmują się obliczaniem wartości, sprawdzaniem różnych warunków, implementacją reguł sprzedażowych i całą resztą, którą nazwałbyś „logiką biznesową”. Nie mają pojęcia, w jaki sposób dane są przechowywane, gdzie są przechowywane, a nawet czy istnieje możliwość przechowywania na pierwszym miejscu.
Obiekt biznesowy domeny nie zależy od bazy danych. Podczas tworzenia faktury nie ma znaczenia, skąd pochodzą dane. Może pochodzić z SQL lub ze zdalnego interfejsu API REST, a nawet zrzut ekranu dokumentu MSWord. Logika biznesowa nie zmienia się.
- Dostęp do danych i ich przechowywanie
Instancje utworzone z tej grupy klas są czasami nazywane obiektami dostępu do danych. Zwykle struktury implementujące Data Mapper wzorzec (nie mylić z ORMami o tej samej nazwie .. brak relacji). To jest miejsce, w którym znajdowałyby się Twoje instrukcje SQL (lub może Twój DomDocument, ponieważ przechowujesz je w XML).
Oprócz dwóch głównych części istnieje jeszcze jedna grupa instancji / klas, o której należy wspomnieć:
- Usługi
Tutaj do gry wkraczają Twoje komponenty i komponenty innych firm. Na przykład możesz pomyśleć o „uwierzytelnianiu” jako o usłudze, którą można zapewnić samodzielnie lub za pomocą zewnętrznego kodu. Również „nadawca poczty” byłby usługą, która mogłaby łączyć jakiś obiekt domeny z PHPMailer lub SwiftMailer lub z twoim własnym komponentem wysyłającym pocztę.
Innym źródłem usług są abstrakcje w domenach i warstwach dostępu do danych. Tworzone są w celu uproszczenia kodu używanego przez kontrolery. Na przykład: utworzenie nowego konta użytkownika może wymagać pracy z kilkoma obiektami domeny i programami mapującymi . Ale korzystając z usługi, będzie potrzebować tylko jednej lub dwóch linii w kontrolerze.
Przy wykonywaniu usług należy pamiętać o tym, że cała warstwa ma być cienka . W usługach nie ma logiki biznesowej. Są tam tylko po to, aby żonglować obiektami domeny, komponentami i mapowaniem.
Jedną z ich cech wspólnych jest to, że usługi nie wpływają bezpośrednio na warstwę View i są autonomiczne do tego stopnia, że mogą być (i często są przerywane) używane poza samą strukturą MVC. Również takie samowystarczalne struktury znacznie ułatwiają migrację do innego frameworka / architektury ze względu na wyjątkowo niskie sprzężenie między usługą a resztą aplikacji.
źródło
Request
instancji (lub jej odpowiednika). Administrator jedynie pobiera dane zRequest
instancji i większość z nich przekazuje do odpowiednich serwisów (część z nich też jest przeglądana). Usługi wykonują operacje, które im nakazałeś. Następnie, gdy widok generuje odpowiedź, żąda danych od usług i na podstawie tych informacji generuje odpowiedź. Wspomnianą odpowiedzią może być HTML utworzony z wielu szablonów lub tylko nagłówek lokalizacji HTTP. Zależy od stanu ustawionego przez kontroler.ACL i kontrolery
Po pierwsze: są to najczęściej różne rzeczy / warstwy. Kiedy krytykujesz przykładowy kod kontrolera, łączy on oba razem - najwyraźniej zbyt ciasno.
tereško przedstawił już sposób, w jaki można to bardziej oddzielić od wzoru dekoratora.
Cofnąłbym się najpierw o krok wstecz, aby znaleźć pierwotny problem, z którym się borykasz, i trochę go przedyskutować.
Z jednej strony chcesz mieć kontrolery, które po prostu wykonują pracę, do której są polecane (polecenie lub akcja, nazwijmy to polecenie).
Z drugiej strony chcesz mieć możliwość umieszczenia listy ACL w swojej aplikacji. Przedmiotem tych list ACL powinno być - jeśli dobrze zrozumiałem twoje pytanie - kontrolowanie dostępu do niektórych poleceń twoich aplikacji.
Ten rodzaj kontroli dostępu wymaga zatem czegoś innego, co łączy te dwa elementy. Na podstawie kontekstu, w którym polecenie jest wykonywane, rozpoczyna się lista ACL i należy podjąć decyzję, czy określone polecenie może zostać wykonane przez określony podmiot (np. Użytkownika).
Podsumujmy w tym miejscu, co mamy:
Komponent ACL jest tutaj centralny: musi wiedzieć przynajmniej coś o poleceniu (aby precyzyjnie zidentyfikować polecenie) i musi być w stanie zidentyfikować użytkownika. Użytkownicy są zwykle łatwo identyfikowani za pomocą unikalnego identyfikatora. Ale często w aplikacjach internetowych są użytkownicy, którzy nie są w ogóle identyfikowani, często nazywani gośćmi, anonimowymi, wszyscy itd. W tym przykładzie zakładamy, że lista ACL może zużywać obiekt użytkownika i hermetyzować te szczegóły. Obiekt użytkownika jest powiązany z obiektem żądania aplikacji i lista ACL może go używać.
A co z identyfikacją polecenia? Twoja interpretacja wzorca MVC sugeruje, że polecenie jest złożone z nazwy klasy i nazwy metody. Jeśli przyjrzymy się bliżej, dla polecenia są nawet argumenty (parametry). Więc ważne jest, aby zapytać, co dokładnie identyfikuje polecenie? Nazwa klasy, nazwa metody, liczba lub nazwy argumentów, a nawet dane zawarte w którymkolwiek z argumentów lub połączenie tego wszystkiego?
W zależności od poziomu szczegółowości potrzebnego do zidentyfikowania polecenia w liście ACL, może się to znacznie różnić. Na przykład zachowajmy to po prostu i określmy, że polecenie jest identyfikowane przez nazwę klasy i nazwę metody.
Zatem kontekst tego, w jaki sposób te trzy części (ACL, Polecenie i Użytkownik) należą do siebie nawzajem, jest teraz bardziej jasny.
Można powiedzieć, że z wyimaginowanym komponentem ACL możemy już wykonać następujące czynności:
Zobacz tylko, co się dzieje: dzięki umożliwieniu identyfikacji zarówno polecenia, jak i użytkownika, lista ACL może wykonać swoją pracę. Zadanie listy ACL nie jest związane z pracą obiektu użytkownika i konkretną komendą.
Brakuje tylko jednej części, to nie może żyć w powietrzu. I tak nie jest. Musisz więc zlokalizować miejsce, w którym musi zostać uruchomiona kontrola dostępu. Przyjrzyjmy się, co dzieje się w standardowej aplikacji internetowej:
Wiemy, że aby zlokalizować to miejsce, musi ono nastąpić przed wykonaniem konkretnego polecenia, więc możemy zredukować tę listę i wystarczy spojrzeć na następujące (potencjalne) miejsca:
W pewnym momencie aplikacji wiesz, że określony użytkownik zażądał wykonania konkretnego polecenia. Wykonujesz już tutaj pewnego rodzaju listę ACL: Jeśli użytkownik zażąda polecenia, które nie istnieje, nie zezwalasz na jego wykonanie. Zatem gdziekolwiek zdarzy się to w Twojej aplikacji, może być dobrym miejscem na dodanie „prawdziwych” kontroli ACL:
Polecenie zostało zlokalizowane i możemy go zidentyfikować, aby lista ACL mogła sobie z nim poradzić. W przypadku, gdy polecenie nie jest dozwolone dla użytkownika, polecenie nie zostanie wykonane (akcja). Może
CommandNotAllowedResponse
zamiastCommandNotFoundResponse
dla przypadku, żądanie nie może zostać przekształcone w konkretne polecenie.Często nazywane jest miejsce, w którym mapowanie konkretnego żądania HTTPRequest jest mapowane na polecenie routingiem . Ponieważ Routing ma już zadanie zlokalizowania polecenia, dlaczego nie rozszerzyć go, aby sprawdzić, czy polecenie jest rzeczywiście dozwolone na liście ACL? Na przykład poprzez rozszerzenie
Router
do ACL świadomy routera:RouterACL
. Jeśli twój router jeszcze nie znaUser
,Router
to nie jest to właściwe miejsce, ponieważ aby ACL działało nie tylko polecenie, ale także użytkownik musi być zidentyfikowany. Więc to miejsce może się różnić, ale jestem pewien, że możesz łatwo zlokalizować miejsce, które chcesz rozszerzyć, ponieważ jest to miejsce, które spełnia wymagania użytkownika i polecenia:Użytkownik jest dostępny od początku, najpierw polecenie z
Request(Command)
.Więc zamiast umieszczać kontrole ACL wewnątrz konkretnej implementacji każdego polecenia, umieszczasz je przed nim. Nie potrzebujesz żadnych ciężkich wzorców, magii lub czegokolwiek, ACL wykonuje swoją pracę, użytkownik wykonuje swoją pracę, a zwłaszcza polecenie wykonuje swoją pracę: tylko polecenie, nic więcej. Komenda nie ma interesu, aby wiedzieć, czy mają do niej zastosowanie role, czy jest gdzieś strzeżona, czy nie.
Więc po prostu oddzielaj rzeczy, które do siebie nie należą. Użyj nieznacznego przeformułowania zasady pojedynczej odpowiedzialności (SRP) : Powinien być tylko jeden powód do zmiany polecenia - ponieważ polecenie się zmieniło. Nie dlatego, że teraz wprowadzasz ACL do swojej aplikacji. Nie dlatego, że zmienisz obiekt użytkownika. Nie dlatego, że przeprowadzasz migrację z interfejsu HTTP / HTML do interfejsu SOAP lub wiersza poleceń.
ACL w twoim przypadku kontroluje dostęp do polecenia, a nie samo polecenie.
źródło
Jedną z możliwości jest zawinięcie wszystkich kontrolerów w inną klasę, która rozszerza kontroler i delegowanie wszystkich wywołań funkcji do opakowanej instancji po sprawdzeniu autoryzacji.
Możesz również zrobić to bardziej na początku, w dyspozytorze (jeśli twoja aplikacja rzeczywiście go ma) i wyszukać uprawnienia na podstawie adresów URL, zamiast metod kontrolnych.
edycja : czy potrzebujesz dostępu do bazy danych, serwera LDAP itp. jest ortogonalne do pytania. Chodziło mi o to, że można zaimplementować autoryzację na podstawie adresów URL zamiast metod kontrolera. Jest to bardziej niezawodne, ponieważ zazwyczaj nie będziesz zmieniać swoich adresów URL (adresy URL są rodzajem interfejsu publicznego), ale równie dobrze możesz zmienić implementacje kontrolerów.
Zwykle masz jeden lub kilka plików konfiguracyjnych, w których mapujesz określone wzorce adresów URL na określone metody uwierzytelniania i dyrektywy autoryzacji. Dyspozytor przed wysłaniem żądania do kontrolerów ustala, czy użytkownik jest uprawniony, a jeśli nie, przerywa wysyłkę.
źródło