Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Rozwiązanie problemu z dostępem
Forum PHP.pl > Forum > PHP > Object-oriented programming
mma
Witam!
Od pewnego czasu poznaje mvc i natrafiłem na ciekawy tutorial na http://www.phpit.net/article/simple-mvc-php5/ .
Bardzo mi podeszło to co tam jest przedstawione - myśle, że na początek jest idealne. I, że będe to mugł (mvc) wykorzystywać jako podstawe w kolejnych aplikacjach PHP.

Ale do rzeczy, przechodząc przez ten tutorial - przerabiając cześciowo itp. , zastanawia mnie jeden fakt...
Jak zrobić dosęp do panelu administracyjnego , chciałbym to zrobić w ruterze no i właśnie, nie za bardzo wiem który z pomysłów wybrać.
Mam dwa pomysły na dzień dzisiejszy - jeden to sytuacja w którym sprawdzany jet czy podkatalog w sciezce jest katalogiem administracyjnym
np:
  1. <?php
  2. //$parts - cześci ścieżki np admin/index, admin =>[0] index=>[1]
  3. if ($parts[0] == ADMIN_FOLDER) {
  4. if (!($this->_registry->auth->isLoggedIn() && $this->_registry->user->isAdmin()))
  5. $this->redirect('index');
  6. }
  7. ?>

Powyższa sytuacja zdaje się być dobra gdy użytkownika zaogowany to admin i nikt więcej.

Drugie rozwiązanie to system praw zapisywanych/odczytywanych z bazy danych (zserializowana tabela).

W ruterze wykonywana by była funkcja mapująca (ja to tak widze) - sprawdzająca otrzymaje prawa z powiązaniami prawo - kontroler.

Jeśli ktoś się zastanawia o co mi chodzi, to chodzi o to czy ma ktoś jakies inne pomysły na rozwiązanie tego zadania, które można by tak w około >80% używać w przyszłości.
Pozdrawiam.
splatch
Cytat(mma @ 4.12.2007, 11:22:08 ) *
W ruterze wykonywana by była funkcja mapująca (ja to tak widze) - sprawdzająca otrzymaje prawa z powiązaniami prawo - kontroler.

Tworzysz odrębną klasę do sprawdzania uprawnień, następnie przekazujesz referencje do FrontController-a i w nim weryfikujesz czy user ma prawo do akcji.

  1. <?php
  2.  
  3. // ogólne akcje widziane przez wszystkich
  4. interface Action {
  5. public function execute();
  6. }
  7.  
  8. // akcja wymagająca logowania/uprawnień
  9. interface SecureAction extends Action {
  10. public function getCredentials();
  11. }
  12.  
  13. class AdminIndex implements SecureAction {
  14. public function execute() {
  15. // tu jakaś logika
  16. }
  17.  
  18. public function getCredentials() {
  19. return array('Admin.Index');
  20. }
  21.  
  22. }
  23.  
  24. // obsługa uprawnień
  25. interface SecurityManager {
  26. public function canExecute(SecureAction $a);
  27. }
  28.  
  29. interface User {
  30. public function getName();
  31. public function getCredentials(); // uprawnienia użytkownika
  32. }
  33.  
  34. // jakaś implementacja security managera
  35. class DummySecurityManager implements SecurityManager {
  36. public function canExecute(SecureAction $a, User $user) {
  37. $cred = $a->getCredentials();
  38. $userCred = $user->getCredentials();
  39.  
  40. $diff = array_diff($cred, $userCred);
  41. if (sizeof($diff) == 0) {
  42. throw new AuthorizationException($diff);
  43. }
  44. return true;
  45. }
  46. }
  47.  
  48. class FrontController implements Controller {
  49.  
  50. public function __construct(SecurityManager $man) {
  51.  
  52. }
  53.  
  54. // i tu dalej coś tam...
  55. }
  56.  
  57. new FrontControler(new DummySecurityManager());
  58. ?>
mma
Podoba mi sie to rozwiązanie, zaraz je przetestuje.
destroyerr
@splatch, mam pytanie do Ciebie i do Twojego przykładu. Takie rozwiązanie jak zaprezentowałeś sprawdza się gdy chodzi o stały dostęp do jakieś akcji. Jak byś rozwiązał tutaj dynamiczny dostęp do akcji, np. user może dodawać artykuł, ala edytować go mogą administratorzy, moderatorzy i user, który dodał ten artykuł. Gdzie to sprawdzać?? W SecureAction::getCredentials() pobierać dane z modelu??
splatch
Jeśli idzie o sprawdzanie uprawnień na takim stopniu można rozszerzyć interfejs o metodę isAuthorized, która odczyta rekord i zweryfikuje czy dana osoba ma uprawnienia do danego rekordu. Jest to poniekąd już fragment logiki aplikacji. Zazwyczaj w takim przypadku tworzyłem oddzielną akcję:
  1. <?php
  2. interface SecureAction extends Action {
  3. public function getCredentials();
  4. public function isAuthorized();
  5. }
  6.  
  7. class EditPost implements SecureAction {
  8.  
  9. public function isAuthorized(RequestDataHolder $hd) {
  10. $user = $this->getContext()->getUser();
  11.  
  12. // sprawdzenie czy użytkownik ma uprawnienie administracyjne
  13. if ($user->hasCredential('Admin.Post')) {
  14. return true;
  15. }
  16.  
  17. // co ktoś chce chce odczytać?
  18. $post = $hd->getParameter('post_id');
  19. if ($obj = PostPeer::retrieveByPK($post)) {
  20. // porównanie ID autora rekordu z zapisanym w sesji
  21. return $obj->getAuthor()->getId() == $user->getAttribute('user_id');
  22. }
  23. return false;
  24. }
  25. }
  26.  
  27. ?>
Sedziwoj
Ogólnie edycję bym rozdzielił od widoku... wtedy nie ma tego problemu.
Pojawia się natomiast gdy mamy nałożone ograniczenie na dostęp do np. artykułów, gdy użytkownik ma dostęp tylko do pewnej puli. Ponieważ można to dopiero zweryfikować mając dane artykułu.
Bo np. to co przestawił splatch ma pewną wadę pobiera dwa razy te same dane.
Można by było np.
- sprawdzanie ogólnego dostępu do akcji
- inicjalizacja, pobranie danych
- sprawdzenie dostępu do danych danych.

Ja to rozwiązywałem w kodzie akcji przez przekazanie odpowiednich danych do obiektu autoryzacji. Ale u mnie po prostu różne rzeczy (coś jak posty, komentarze, artykuły) mają wspólną grupę na której to podstawie jest dostęp ustalany.

Tylko się tak zastanawiam czy tworzyć abstrakcję (w tej metodzie co splatch proponuje) klas akcji z autoryzacją, czy mieć niezależny obiekt autoryzujący.

Bo np. jak ktoś nie ma dostępu, ładuje się tylko inny szablon, gdzie jest np. tylko tytuł artykułu i formularz logowania.
splatch
Sedziwoj nie musimy mieć do czynienia z dwoma zapytaniami. Ponieważ wiemy, że metoda isAuthorized zostanie wykonana wcześniej możemy przypisać wynik do pola w danej klasie i normalnie odczytać w execute.
W skrajnym wypadku można stworzyć kolejny miks - SecurityManager, który potrafi również autoryzować dostęp do obiektów. Wówczas akcja zwracała by typ obiektu a SecurityManager na podstawie dostarczonych informacji sprawdzałby co użytkownik może zrobić.
Sedziwoj
Liczyłem że zostanie skrytykowana (pozytywnie lub nie) metoda autoryzacji dostępu do treści w kodzie akcji, może się jeszcze doczekam.
Co do metody isAuthorized() to trochę jednak mnie zastanawia, ponieważ czasem zanim dostaniem się do danych nam potrzebnych do autoryzacji musimy wydobyć inne, a wtedy metoda nie robi tylko tego co powinna. Do tego sprawdzenia
  1. <?php
  2. if ($user->hasCredential('Admin.Post')) {
  3. return true;
  4. }
  5. ...
  6. $obj->getAuthor()->getId() == $user->getAttribute('user_id');
  7. ?>

powinny być nie na tym poziomie, tylko obiekt autoryzacji to powinien wiedzieć, przekazujemy autora i mamy boolean, nie obchodzi nas czy ma dostęp bo jest adminem czy dlatego że jest autorem.
Do tego nie wiemy co mamy robić kiedy ktoś nie ma dostępu do treści, przy edycji to jest raczej oczywiste, przy podanym przeze mnie przykładzie dostępu do artykułu już nie. Ponieważ nie chcemy nic robić więcej niż wyświetlić inny szablon. Oczywiście ta metoda mogła by go zmienić i zwrócić true ale to moim zdanie mijanie się z celem.

Sam nie jestem do niczego przekonany, więc chętnie wysłucham wszelkich konstruktywnych uwag.
splatch
Cytat(Sedziwoj @ 6.12.2007, 22:03:05 ) *
Liczyłem że zostanie skrytykowana (pozytywnie lub nie) metoda autoryzacji dostępu do treści w kodzie akcji, może się jeszcze doczekam.
Co do metody isAuthorized() to trochę jednak mnie zastanawia, ponieważ czasem zanim dostaniem się do danych nam potrzebnych do autoryzacji musimy wydobyć inne, a wtedy metoda nie robi tylko tego co powinna.
  1. <?php
  2. if ($user->hasCredential('Admin.Post')) {
  3. return true;
  4. }
  5. ...
  6. $obj->getAuthor()->getId() == $user->getAttribute('user_id');
  7. ?>


Cytat(Sedziwoj @ 6.12.2007, 22:03:05 ) *
Do tego sprawdzenia powinny być nie na tym poziomie, tylko obiekt autoryzacji to powinien wiedzieć, przekazujemy autora i mamy boolean, nie obchodzi nas czy ma dostęp bo jest adminem czy dlatego że jest autorem.

Nie sądzę byś miał tutaj rację:
- Skąd SecurityManager ma wiedzieć, że ta akcja operuje na takich czy też innych obiektach?
- W jaki sposób ma odczytywać obiekty? W końcu to nie jest brocha SM.
- Co jeśli SecurityManager ma się zachowywać różnie w zależności od akcji? To znaczy są zmiany w sposobie dostępu do tego obiektu.

Osobiście pracuję w kodzie, w którym to SecurityManager ma wiedzieć czy ktoś ma dostęp do obiektu i wierz mi, jest sieczka. Są cztery klasy, każda gromada metod statycznych tylko po to by sprawdzić isUserAuthorizedForApprove(Application, Client, User). Do tego urosły wielkie utile, które są głównie używane w owych managerach.

Cytat(Sedziwoj @ 6.12.2007, 22:03:05 ) *
Do tego nie wiemy co mamy robić kiedy ktoś nie ma dostępu do treści, przy edycji to jest raczej oczywiste, przy podanym przeze mnie przykładzie dostępu do artykułu już nie. Ponieważ nie chcemy nic robić więcej niż wyświetlić inny szablon.

To co przemawia za tym by ten kod był właśnie w akcji to to, że akcja wie z czego będzie korzystać i z nikim innym nie musi się tym dzielić. Mylisz się twierdząc, że akcja nie wie jak zachować się w przypadku błędu z dostępem do obiektu. Może albo wywalić wyjątek, powiedzmy new UnauthorizedObjectAccessException(object $type) a jego obsługę zrzucić na kark FrontController-a albo przekierować na odpowiednią stronę (tu dodajemy metodę do interfejsu).

  1. <?php
  2. interface SecureAction extends Action {
  3. public function getCredentials();
  4. }
  5.  
  6. interface CustomAuthorizationAction extends SecureAction {
  7. public function isAuthorized(RequestDataHolder $rd);
  8. public function getAuthErrorView();
  9. }
  10. ?>


W skrajnym wypadku można wywalić CustomAuthorizationAction i pójść w abstrakcje:
  1. <?php
  2. abstract class CustomAuthorizationAction extends AbstractAction implements SecureAction {
  3.  
  4. public final function execute(RequestDataHolder $rd) {
  5. if ($this->isAuthorized($rd)) { // user ma prawa do potrzebnych obiektów
  6. return $this->doExecute($rd);
  7. }
  8. return $this->getAuthErrorView();
  9. }
  10.  
  11. protected abstract doExecute(RequestDataHolder $rd);
  12. protected abstract getAuthErrorView();
  13. }
  14. ?>


Oczywiście nie jest to jedyne rozwiązanie, fakt faktem zgłoszone uwagi wymagały rozbudowania przykładu podanego na początku. Inny pomysł jaki przyszedł mi do głowy to tworzenie delegatów, które by się przekazywało do SecureManagera.
Sedziwoj
Właśnie o taki komentarz mi chodziło smile.gif
Jeszcze muszę sobie pomyśleć nad tym, ale zauważ jedno, że druga propozycja klasy CustomAuthorizationAction niewiele różni się od tego co ja podałem. A to dlaczego? Bo w isAuthorized() pobierasz dane, potem w execute() sprawdzasz do nich dostęp, przy braku wywołujesz getAuthErrorView(), a przy powodzeniu doExecute().
Ale ogólnie jest ciekawe rozwiązanie, bo rozbijamy działanie na parę metod, które robią pewne fragmenty.
Mimo wszystko bym dodał metodę init() (czy jak jakkolwiek zwać) która by pobierała dane potrzebne do wywołania isAuthorized(), aby ta metoda nie robiła nic więcej niż sprawdzała czy ma dostęp.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.