Come ottenere tutti i ruoli di cui un utente è membro (inclusi i ruoli ereditati)?


23

Diciamo che ho due gruppi di database Postgresql, "autori" e "editori", e due utenti, "maxwell" e "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

Vorrei scrivere una funzione performante che restituisce un elenco dei ruoli (preferibilmente della loro oid) a cui appartiene maxwell, qualcosa del genere:

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

Dovrebbe restituire gli oid per maxwell, autori ed editori (ma non ernest).

Ma non sono sicuro di come farlo in caso di eredità.

Risposte:


23

È possibile eseguire una query sul catalogo di sistema con una query ricorsiva , in particolare 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;

A proposito, INHERITè il comportamento predefinito di CREATE ROLEe non deve essere precisato.

BTW2: dipendenze circolari non sono possibili. Postgres non lo consente. Quindi non dobbiamo verificarlo.


18

Versione breve:

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

Qui usiamo una versione pg_has_roleche assume un nome di ruolo come soggetto e ruolo da testare per l'appartenenza , passando la membermodalità in modo da testare le appartenenze ereditate.

Il vantaggio dell'utilizzo pg_has_roleè che utilizza le cache interne di PostgreSQL delle informazioni sul ruolo per soddisfare rapidamente le domande di appartenenza.

Potresti voler racchiuderlo in una SECURITY DEFINERfunzione, poiché pg_authidha un accesso limitato. Qualcosa di simile a:

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

È possibile utilizzare pg_get_userbyid(oid)per ottenere il nome del ruolo dall'oid senza la necessità di eseguire una query 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
+1 comunque, dato che pg_has_role()probabilmente è un po 'più veloce della mia query ricorsiva, anche se non ha importanza. Un'ultima cosa però: restituisce tutti i ruoli per i super utenti, che possono essere o meno un effetto collaterale positivo. Ecco dove il risultato differisce dalla mia domanda.
Erwin Brandstetter,

16

Questa è una versione semplificata della risposta di Craig Ringer che un non superutente può usare direttamente:

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

pg_rolesè essenzialmente una visione pg_authidaccessibile al pubblico, in quanto non rivela password, contrariamente a pg_authid. La base oidviene persino esportata nella vista. Quando non sono necessarie password, non ha senso creare la funzione di proprietà del superutente dedicata.


3

Ecco la mia opinione a riguardo. Funziona per un utente specifico o per tutti gli utenti.

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
Ciò sarebbe molto migliorato se spiegassi in che modo differire e migliorare le risposte precedenti. È possibile modificare le informazioni aggiuntive direttamente nella risposta.
Michael Green,

1

Credo che questo lo farà

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

Se si preferisce ottenere i nomi dei ruoli, sostituire il primo oidcon rolname.


0

se vuoi conoscere tutti i ruoli del tuo ruolo attualmente attivo:

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.