Załóżmy, że mam tabelę przedmiotów:
CREATE TABLE items
(
item serial PRIMARY KEY,
...
);
Teraz chcę wprowadzić pojęcie „uprawnień” dla każdego elementu (proszę zauważyć, że jestem nie mówię tu o uprawnieniach dostępu do bazy danych, ale o uprawnieniach logiki biznesowej dla tego elementu). Każdy element ma uprawnienia domyślne, a także uprawnienia na użytkownika, które mogą zastąpić uprawnienia domyślne.
Próbowałem wymyślić kilka sposobów na wdrożenie tego i wymyśliłem następujące rozwiązania, ale nie jestem pewien, który jest najlepszy i dlaczego:
1) Rozwiązanie logiczne
Użyj kolumny boolowskiej dla każdego pozwolenia:
CREATE TABLE items
(
item serial PRIMARY KEY,
can_change_description boolean NOT NULL,
can_change_price boolean NOT NULL,
can_delete_item_from_store boolean NOT NULL,
...
);
CREATE TABLE item_per_user_permissions
(
item int NOT NULL REFERENCES items(item),
user int NOT NULL REFERENCES users(user),
PRIMARY KEY(item, user),
can_change_description boolean NOT NULL,
can_change_price boolean NOT NULL,
can_delete_item_from_store boolean NOT NULL,
...
);
Zalety : Każde uprawnienie ma nazwę.
Niedogodności : Istnieją dziesiątki uprawnień, które znacznie zwiększają liczbę kolumn i trzeba je zdefiniować dwukrotnie (raz w każdej tabeli).
2) Rozwiązanie Integer
Użyj liczby całkowitej i traktuj ją jak pole bitowe (tj. Bit 0 oznacza can_change_description
, bit 1 oznacza can_change_price
itd. I użyj operacji bitowych, aby ustawić lub odczytać uprawnienia).
CREATE DOMAIN permissions AS integer;
Zalety : bardzo szybko.
Wady : Musisz śledzić, który bit oznacza, które uprawnienie zarówno w bazie danych, jak i interfejsie użytkownika.
3) Rozwiązanie Bitfield
Taki sam jak 2), ale użyj bit(n)
. Najprawdopodobniej te same zalety i wady, może nieco wolniejsze.
4) Rozwiązanie Enum
Użyj typu wyliczenia dla uprawnień:
CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);
a następnie utwórz dodatkową tabelę dla domyślnych uprawnień:
CREATE TABLE item_default_permissions
(
item int NOT NULL REFERENCES items(item),
perm permission NOT NULL,
PRIMARY KEY(item, perm)
);
i zmień tabelę definicji użytkownika na:
CREATE TABLE item_per_user_permissions
(
item int NOT NULL REFERENCES items(item),
user int NOT NULL REFERENCES users(user),
perm permission NOT NULL,
PRIMARY KEY(item, user, perm)
);
Zalety : Łatwo nazwać poszczególne uprawnienia (nie musisz obsługiwać pozycji bitów).
Niedogodności : Nawet przy pobieraniu domyślnych uprawnień wymaga dostępu do dwóch dodatkowych tabel: po pierwsze, do domyślnej tabeli uprawnień, a po drugie, do katalogu systemowego przechowującego wartości wyliczeniowe.
Zwłaszcza, że należy przywrócić domyślne uprawnienia dla każdego pojedynczego wyświetlenia strony tego elementu , wpływ ostatniej alternatywy na wydajność może być znaczący.
5) Rozwiązanie Enum Array
Taki sam jak 4), ale użyj tablicy do przechowywania wszystkich (domyślnych) uprawnień:
CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);
CREATE TABLE items
(
item serial PRIMARY KEY,
granted_permissions permission ARRAY,
...
);
Zalety : Łatwo nazwać poszczególne uprawnienia (nie musisz obsługiwać pozycji bitów).
Wady : Łamie 1. normalną formę i jest trochę brzydka. Zajmuje znaczną liczbę bajtów z rzędu, jeśli liczba uprawnień jest duża (około 50).
Czy możesz wymyślić inne alternatywy?
Jakie podejście należy zastosować i dlaczego?
Uwaga: jest to zmodyfikowana wersja pytania opublikowanego wcześniej na Stackoverflow .
źródło
bigint
pól (każde dobre dla 64 bitów) lub ciąg znaków. Napisałem kilka powiązanych odpowiedzi na temat SO, które mogą być pomocne.Odpowiedzi:
Wiem, że nie pytasz o bezpieczeństwo bazy danych per se , ale możesz robić, co chcesz, korzystając z zabezpieczeń bazy danych. Możesz nawet użyć tego w aplikacji internetowej. Jeśli nie chcesz używać zabezpieczeń bazy danych, schematy nadal obowiązują.
Potrzebujesz zabezpieczeń na poziomie kolumny, zabezpieczeń na poziomie wierszy i prawdopodobnie hierarchicznego zarządzania rolami. Bezpieczeństwo oparte na rolach jest znacznie łatwiejsze do zarządzania niż bezpieczeństwo oparte na użytkownikach.
Ten przykładowy kod dotyczy PostgreSQL 9.4, który wkrótce się pojawi. Możesz to zrobić za pomocą 9.3, ale potrzeba więcej pracy fizycznej.
Chcesz, aby wszystko było indeksowane, jeśli martwisz się wydajnością †, którą powinieneś być. Oznacza to, że pola maski bitowej i tablicy prawdopodobnie nie będą dobrym pomysłem.
W tym przykładzie trzymamy główne tabele danych w
data
schemacie, a odpowiednie widoki wpublic
.Ustaw wyzwalacz dla data.thing dla wstawek i aktualizacji wymuszających, że kolumna właściciela jest bieżącym użytkownikiem. Być może tylko właściciel może usunąć własne rekordy (inny wyzwalacz).
Utwórz
WITH CHECK OPTION
widok, z którego będą korzystać użytkownicy. Naprawdę staraj się, aby można go było aktualizować, w przeciwnym razie będziesz potrzebować wyzwalaczy / reguł, co jest więcej pracy.Następnie utwórz tabelę listy kontroli dostępu:
Zmień widok na konta ACL:
Utwórz domyślną tabelę uprawnień do wiersza:
Ustaw wyzwalacz wstawiania na data.thing, aby skopiował domyślne uprawnienia do wiersza do security.thing_acl.
grantor
iadmin_option
kolumnami do tabel acl, aby śledzić, kto przyznał uprawnienie i czy beneficjent może zarządzać uprawnieniami w tym wierszu.† W tym przypadku pg_has_role prawdopodobnie nie jest indeksowalny. Będziesz musiał uzyskać listę wszystkich ról nadrzędnych względem bieżącego_użytkownika i porównać z wartością właściciela / beneficjenta.
źródło
Czy rozważałeś użycie rozszerzenia PostgreSQL na liście kontroli dostępu ?
Zawiera natywny typ danych PostgreSQL ACE oraz zestaw funkcji, które pozwalają sprawdzić, czy użytkownik ma uprawnienia do dostępu do danych. Działa z systemem ról PostgreSQL lub z liczbami abstrakcyjnymi (lub UUID) reprezentującymi identyfikatory użytkownika / roli aplikacji.
W twoim przypadku po prostu dodajesz kolumnę ACL do swoich tabel danych i używasz jednej z
acl_check_access
funkcji, aby sprawdzić użytkownika na liście ACL.Korzystanie z list ACL jest niezwykle elastycznym sposobem radzenia sobie z uprawnieniami logiki biznesowej. Ponadto jest niesamowicie szybki - przeciętny koszt to tylko 25% czasu potrzebnego na odczytanie zapisu. Jedynym ograniczeniem jest to, że obsługuje maksymalnie 16 uprawnień niestandardowych na typ obiektu.
źródło
Mogę wymyślić inną możliwość zakodowania tego, relacyjną
Jeśli nie potrzebne
permission_per_item
może tableyou pominąć go i podłączPermissions
iItems
bezpośrednio doitem_per_user_permissions
stołu.schemat legendy
źródło