¿Cómo obtener todos los roles de los que es miembro un usuario (incluidos los roles heredados)?

23

Digamos que tengo dos grupos de bases de datos Postgresql, "autores" y "editores", y dos usuarios, "maxwell" y "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

Me gustaría escribir una función performante que devuelva una lista de los roles (preferiblemente sus oid) a los que pertenece maxwell, algo como esto:

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

Debería devolver los oids para maxwell, autores y editores (pero no ernest).

Pero no estoy seguro de cómo hacerlo cuando hay herencia.

Neil McGuigan
fuente

Respuestas:

23

Puede consultar el catálogo del sistema con una consulta recursiva , en particular 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;

Por cierto, INHERITes el comportamiento predeterminado de CREATE ROLEy no tiene que especificarse.

Por cierto: las dependencias circulares no son posibles. Postgres no lo permite. Entonces no tenemos que verificar eso.

Erwin Brandstetter
fuente
18

Version corta:

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

Aquí usamos una versión pg_has_roleque toma un nombre de rol como sujeto y oid de rol para probar la membresía , pasando el membermodo, así que probamos las membresías heredadas.

La ventaja de usar pg_has_rolees que utiliza las memorias caché internas de PostgreSQL de información de roles para satisfacer rápidamente las consultas de membresía.

Es posible que desee incluir esto en una SECURITY DEFINERfunción, ya que pg_authidtiene acceso restringido. Algo como:

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

Puede usar pg_get_userbyid(oid)para obtener el nombre del rol del oid sin la necesidad de consultar 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
fuente
2
+1 de todos modos, ya que pg_has_role()es probablemente un poco más rápido que mi consulta recursiva, incluso si eso apenas importa. Sin embargo, una última cosa: devuelve todos los roles para los superusuarios, lo que puede o no ser un efecto secundario bienvenido. Ahí es donde el resultado difiere de mi consulta.
Erwin Brandstetter
16

Esta es una versión simplificada de la respuesta de Craig Ringer que un no superusuario puede usar directamente:

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

pg_roleses esencialmente una vista pg_authidaccesible al público, ya que no revela contraseñas, al contrario de pg_authid. La base oidincluso se exporta a la vista. Cuando no se necesitan contraseñas, no tiene sentido crear la función dedicada propiedad del superusuario.

Daniel Vérité
fuente
3

Aquí está mi opinión al respecto. Funciona para un usuario específico o para todos los usuarios.

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
fuente
1
Esto mejoraría mucho si explicara cómo difiere de las respuestas anteriores y mejora las respuestas anteriores. Puede editar la información adicional directamente en la respuesta.
Michael Green
1

Creo que esto lo hará

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

Si prefiere obtener los nombres de los roles, reemplace el primero oidcon rolname.

SureShotUK
fuente
0

si desea conocer todos los roles de su rol actualmente activo:

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
fuente