Jak uzyskać wszystkie role, do których należy użytkownik (w tym role odziedziczone)?


23

Załóżmy, że mam dwie grupy baz danych Postgresql, „autorów” i „redaktorów” oraz dwóch użytkowników, „maxwell” i „ernest”.

create role authors;

create role editors;

create user maxwell;

create user ernest;

grant authors to editors; --editors can do what authors can do

grant editors to maxwell; --maxwell is an editor

grant authors to ernest; --ernest is an author

Chciałbym napisać funkcję wykonawczą, która zwraca listę ról (najlepiej ich identyfikatorów), do których należy maxwell, mniej więcej tak:

create or replace function get_all_roles() returns oid[] ...

Powinien zwrócić Oids dla Maxwella, autorów i redaktorów (ale nie Ernest).

Ale nie jestem pewien, jak to zrobić, gdy istnieje dziedzictwo.

Odpowiedzi:


23

Możesz przeszukać katalog systemowy za pomocą zapytania rekurencyjnego , w szczególności pg_auth_members:

WITH RECURSIVE cte AS (
   SELECT oid FROM pg_roles WHERE rolname = 'maxwell'

   UNION ALL
   SELECT m.roleid
   FROM   cte
   JOIN   pg_auth_members m ON m.member = cte.oid
)
SELECT oid FROM cte;

BTW, INHERITjest domyślnym zachowaniem CREATE ROLEi nie musi być pisane.

BTW2: zależności cykliczne nie są możliwe. Postgres nie zezwala na to. Więc nie musimy tego sprawdzać.


18

Krótka wersja:

SELECT a.oid 
FROM pg_authid a 
WHERE pg_has_role('maxwell', a.oid, 'member');

W tym przypadku używamy wersji, pg_has_rolektóra przyjmuje nazwę roli jako podmiot i rolę do przetestowania pod kątem członkostwa , w membertrybie zdanym , więc testujemy pod kątem dziedziczonego członkostwa.

Zaletą używania pg_has_rolejest to, że używa wewnętrznych pamięci podręcznych informacji o rolach PostgreSQL, aby szybko zaspokoić zapytania o członkostwo.

Możesz zawinąć to w SECURITY DEFINERfunkcję, ponieważ pg_authidma ograniczony dostęp. Coś jak:

CREATE OR REPLACE FUNCTION user_role_memberships(text)
RETURNS SETOF oid
LANGUAGE sql
SECURITY DEFINER
SET search_path = pg_catalog, pg_temp
AS $$
SELECT a.oid 
FROM pg_authid a 
WHERE pg_has_role($1, a.oid, 'member');
$$;

REVOKE EXECUTE ON FUNCTION user_role_memberships(text) FROM public;

GRANT EXECUTE ON FUNCTION user_role_memberships(text) TO ...whoever...;

Możesz użyć, pg_get_userbyid(oid)aby uzyskać nazwę roli z OID bez konieczności zapytania pg_authid:

SELECT a.oid AS member_oid, pg_get_userbyid(oid) AS member_name
FROM pg_authid a 
WHERE pg_has_role('maxwell', a.oid, 'member');

2
W każdym razie +1, ponieważ pg_has_role()jest to prawdopodobnie nieco szybsze niż moje zapytanie rekurencyjne, nawet jeśli nie ma to większego znaczenia. I ostatnia rzecz: zwraca wszystkie role dla superużytkowników, co może, ale nie musi, być pożądanym efektem ubocznym. Właśnie tam wynik różni się od mojego zapytania.
— Erwin Brandstetter

16

To jest uproszczona wersja odpowiedzi Craiga Ringera, z której nieużytkownik może bezpośrednio korzystać:

 SELECT oid, rolname FROM pg_roles WHERE
   pg_has_role( 'maxwell', oid, 'member');

pg_rolesjest zasadniczo poglądem na temat pg_authidpublicznie dostępnego, ponieważ nie ujawnia haseł, w przeciwieństwie do pg_authid. Baza oidjest nawet eksportowana do widoku. Gdy nie potrzebujesz haseł, nie ma sensu tworzyć dedykowanej funkcji administratora.


3

Oto moje zdanie na ten temat. Działa dla jednego określonego użytkownika lub wszystkich użytkowników.

select a.oid as user_role_id
, a.rolname as user_role_name
, b.roleid as other_role_id
, c.rolname as other_role_name
from pg_roles a
inner join pg_auth_members b on a.oid=b.member
inner join pg_roles c on b.roleid=c.oid 
where a.rolname = 'user_1'

1
Byłoby to znacznie poprawione, gdybyś wyjaśnił, w jaki sposób różni się od poprzednich odpowiedzi i ulepsza je. Możesz edytować dodatkowe informacje bezpośrednio w odpowiedzi.
— Michael Green

1

Wierzę, że to zrobi

SELECT 
    oid 
FROM 
    pg_roles 
WHERE 
    oid IN (SELECT 
                roleid 
            FROM 
                pg_auth_members 
            WHERE 
                member=(SELECT oid FROM pg_roles WHERE rolname='maxwell'));

Jeśli wolisz, aby uzyskać nazwy ról następnie zastąpić pierwszy oidz rolname.


0

jeśli chcesz poznać wszystkie role swojej obecnie aktywnej roli:

CREATE OR REPLACE VIEW public.my_roles
AS WITH RECURSIVE cte AS (
         SELECT pg_roles.oid,
            pg_roles.rolname
           FROM pg_roles
          WHERE pg_roles.rolname = CURRENT_USER
        UNION ALL
         SELECT m.roleid,
            pgr.rolname
           FROM cte cte_1
             JOIN pg_auth_members m ON m.member = cte_1.oid
             JOIN pg_roles pgr ON pgr.oid = m.roleid
        )
 SELECT array_agg(cte.rolname) AS my_roles
   FROM cte;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.