Przechowywanie pozycji menu z uprawnieniami użytkownika

11

Tworzę system menu w PHP i MySQL. Będę miał kilka różnych menu i do każdego menu będzie podłączony zestaw elementów menu.

Na stronie mam również różne uprawnienia użytkowników, niektórzy użytkownicy mogą zobaczyć wszystkie elementy menu, a niektóre elementy są ukryte przed niektórymi użytkownikami. Jestem ciekawy, jak mogę obsłużyć uprawnienia w czysty sposób, który pozwoli na łatwe dodawanie większej liczby użytkowników w przyszłości.

Do tej pory mam coś takiego:

-------------------
|Menus
-------------------
|id| |display name|
-------------------

----------------------------------------------------------
|Menu items
----------------------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort| |permission|
----------------------------------------------------------

Myślę, że permissionkolumna może być ciągiem oddzielonym przecinkami, który mogę dopasować do identyfikatora bieżącego użytkownika. Może to być również odniesienie do innej tabeli, która definiuje wszystkie możliwe kombinacje obecnie istniejących uprawnień.

Jednym rozwiązaniem może być po prostu przechowywanie wielu pozycji menu, w których jedyną różnicą jest pozwolenie, chociaż prowadziłoby to do podwójnego przechowywania i być może uciążliwe.

Bardzo chciałbym usłyszeć, jak to ustrukturyzować i co można uznać za czyste, dynamiczne i kiepskie.

Dzięki.

Zakres
źródło
Czy użytkownicy mają coś wspólnego? Zazwyczaj grupujesz pozycje menu w grupy funkcjonalne i przypisujesz użytkowników do tych grup (na przykład - użytkownicy administracyjni, użytkownicy DB, handlowcy itp.). Następnie po prostu zarządzasz grupami, w zależności od wyboru technologii - można to zarządzać za pomocą czegoś takiego jak Active Directory.
Michael
1
Otrzymujesz tutaj wiele dobrych odpowiedzi, ale możesz chcieć przeczytać o ACL. en.wikipedia.org/wiki/Access_control_list
Reactgular

Odpowiedzi:

18

Będę go modelować za pomocą schematu ER.

  • A PERMISSIONto dostęp przyznany dla ROLEdanego MENU_ITEM.
  • ROLA to zestaw predefiniowanych uprawnień, któremu nadano nazwę
  • USERMoże mieć wiele ról przyznane jej.
  • Przypisanie uprawnień do ról zamiast do użytkowników znacznie ułatwia administrowanie uprawnieniami.

wprowadź opis zdjęcia tutaj

Następnie możesz utworzyć widok, abyś nie musiał zapisywać złączeń za każdym razem:

create or replace view v_user_permissions
select
    distinct mi.id, mi.menu_id. mi.label. mi.link, mi.parent, mi.sort, u.user_id
from
    user u
    join user_role ur on (u.user_id = ur.user_id)
    join role ro on (ur.role_id = role.role_id)
    join permission per on (ro.role_id = per.role_id)
    join menu_item mi on (per.metu_item_id = mi.metu_item_id)

Następnie za każdym razem, gdy chcesz wiedzieć, do jakich elementów menu ma dostęp użytkownik, możesz zapytać:

select * from v_user_permissions up where up.user_id = 12736 order by up.sort

EDYTOWAĆ:

Ponieważ użytkownik może mieć przydzielone wiele ról, uprawnienia ról mogą się nakładać, tzn. Dwie różne role mogą mieć dostęp do tego samego elementu menu. Kiedy definiujesz rolę, nie wiesz wcześniej, czy będzie ona miała jakieś uprawnienia wspólne z innymi rolami. Ale ponieważ chodzi o połączenie zbiorów, ważne jest tylko to, czy dane pozwolenie jest częścią zestawu, a nie ile razy się pojawia, stąd distinctklauzula w widoku.

Tulains Córdova
źródło
Wielkie dzieki. Nie rozumiem, dlaczego sam tego nie wymyśliłem. Zgaduję, że zostałem zablokowany i nie mam doświadczenia :)
od
Ack, myślałem, że rozumiem, ale oczywiście nie rozumiem. Jak mogę mieć wiele uprawnień do jednego elementu menu?
rozpiętość
1
@span Ponieważ użytkownik może mieć przyznane wiele ról, a uprawnienia ról mogą się nakładać, tzn. dwie różne role mogą mieć dostęp do tego samego elementu menu. Kiedy definiujesz rolę, nie wiesz z góry, czy rola zostanie przyznana wraz z innymi, które mają z nią pewne uprawnienia. Ale ponieważ ten problem dotyczy łączenia zbiorów, ważne jest tylko to, czy dane pozwolenie jest częścią zestawu, czy nie, nie ile razy się pojawia.
Tulains Córdova
Dzięki, będę czytał twoją odpowiedź, dopóki jej nie otrzymam;). Myślę, że mój błąd polegał na tym, że można użyć jednego pozwolenia wraz z rolą do rozdzielenia pozycji menu. Wygląda na to, że potrzebuję pozwolenia na każdy „typ” pozycji menu. Jeszcze raz dziękuję za pomoc! Narysuję kilka diagramów Venna i zobaczę, czy uda mi się to właściwie obrócić. \ O /
od
5

Posiadanie listy oddzielonej przecinkami oznacza wykonanie porównania podciągów za każdym razem, gdy wykonujesz zapytanie względem menu. To mniej niż idealne.

Musisz znormalizować tabelę:

---------------------------------------------
|Menu items
---------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort|
---------------------------------------------

+---------------------------+
| menu_permission           |
|---------------------------|
| |menu_permission_id| (pk) |
| |menu_id| (fk)            |
| |permission|              |
+---------------------------+

Jeśli nadal chcesz oddzielonej przecinkami listę (z jakiegoś powodu), można wyciągnąć go z rzeczy, takich jak group_concatw MySQL wm_concat w oracle lub podobne funkcje w innych językach.

Zaletą tego jest wielokrotność.

Po pierwsze, praktyczność połączenia. Wykonanie podłańcucha przeciwko dowolnie dużemu ciągowi (jeśli poprawisz jego rozmiar, możesz później mieć problemy z wypełnieniem łańcucha, aby zacząć uzyskiwać uprawnienia jak azamiast another_permission), oznacza skanowanie łańcucha w każdym rzędzie. Nie jest to opcja, dla której bazy danych są zoptymalizowane.

Po drugie, zapytanie, które piszesz, staje się znacznie prostsze. Aby ustalić, czy uprawnienie „foo” istnieje na liście oddzielonej przecinkami, musisz sprawdzić, czy występuje „foo”.

... permission like "%foo%" ...

Daje to jednak fałszywie pozytywny wynik, jeśli masz również pozwolenie „foobar”. Więc teraz musisz mieć taki test

... permission like "%,foo,%" ...

ale da to fałszywy wynik ujemny, jeśli „foo” znajduje się na początku lub na końcu łańcucha. To prowadzi do czegoś takiego

... (permission like "foo,%" 
  or permission like "%,foo,%" 
  or permission like "%,foo" ) ...

Zauważysz, że jest prawdopodobne, że będziesz musiał wykonać wiele skanów ciągu. Ta droga prowadzi do szaleństwa.

Zauważ, że w przypadku wszystkich tych elementów brakuje praktycznej zdolności do wiązania parametrów (nadal jest to możliwe, staje się jeszcze bardziej brzydkie).

Normalizacja pola daje znacznie większą elastyczność i czytelność w bazie danych. Nie pożałujesz.


źródło
Dziękuję za wspaniałą odpowiedź, dała mi więcej wiedzy i jestem za to wdzięczny, chociaż myślę, że rozwiązanie user61852 będzie na razie najlepsze.
rozpiętość
3

To klasyczne podejście do tego jest User -> UserGroupnastępnie powiązane Menu -> MenuItem -> UserGroup. Użycie wartości całkowitej do ważenia poziomu uprawnień.

-------------------
|Menu
-------------------
|id| |display name|
-------------------

-------------------------------------------------------------
|Menu Item
-------------------------------------------------------------
|id| |menu_id| |label| |link| |parent| |sort| | min_option1
-------------------------------------------------------------

-------------------------------------------
| User
-------------------------------------------
|id| | username | password | user_group_id
-------------------------------------------

-------------------------------------------
| User Group
-------------------------------------------
|id| option1 | option2 | option2
-------------------------------------------

Kiedy musisz wyświetlić menu dla bieżącego użytkownika. Możesz wykonać zapytanie do bazy danych w ten sposób.

SELECT * FROM `MenuItem`
    LEFT JOIN `UserGroup` ON (`MenuItem`.`user_group_id` = `UserGroup`.`id`)
    LEFT JOIN `User` ON (`UserGroup`.`id` = `User`.`user_group_id` AND `User`.`id` = $current_user_id)
        WHERE `MenuItem`.`menu_id` = $menu_id AND `UserGroup`.`option1` >= `MenuItem`.`min_option1`;

Spowoduje to wybranie tylko menu widocznych dla bieżącego użytkownika na podstawie warunku dla option1.

Alternatywnie, jeśli przechowujesz dane grupy bieżącego użytkownika w bieżącej sesji, wówczas nie jest wymagane łączenie.

SELECT * FROM `MenuItem` WHERE `MenuItem`.`menu_id` = $menu_id AND `MenuItem`.`min_option1` >= $user_group_option1;

Gdy mówisz o chęci przechowywania wielu uprawnień na element menu. Uważam, aby nie pomylić ról użytkowników i logiki biznesowej.

Reactgular
źródło
2
Ten projekt pozwoliłby tylko na powiązanie każdego elementu menu z jedną grupą użytkowników, co oznacza albo ograniczone menu, albo powielanie danych. W rzeczywistości najlepiej jest mieć tabelę linków. Również wybór nazewnictwa tabeli DB jako liczby mnogiej mnie zasmuca;)
Ed James
@EdWoodcock oh bardzo dobry punkt. Powinienem był przejść z poziomem uprawnień (int), a następnie porównać go z poziomem grupy użytkownika. Zmienię to. Uwaga, przyzwyczajenie liczby mnogiej spowodowane przez moje użycie CakePHP. To dziwne, ponieważ środowisko używa pojedynczych aliasów dla tabel w zapytaniach.
Reactgular
@MatthewFoscarini Nie martw się, tak naprawdę nie przejmuję się, dopóki baza kodowa jest spójna;)
Ed James
1
Świetna odpowiedź. Będę o tym pamiętać następnym razem, gdy zrobię coś podobnego. Na razie myślę, że rozwiązanie user61852 będzie najlepiej pasować, ponieważ nie wymaga tylu zmian w istniejącym kodzie. Dzięki!
rozpiętość