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.

Neil McGuigan
źródło

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ć.

Erwin Brandstetter
źródło
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');
Craig Ringer
źródło
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.

Daniel Vérité
źródło
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'
Alexis.Rolland
źródło
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.

SureShotUK
źródło
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;
Mihail Gershkovich
źródło