Como obter todas as funções das quais um usuário é membro (incluindo funções herdadas)?

23

Digamos que eu tenha dois grupos de banco de dados do Postgresql, "autores" e "editores", e dois usuários, "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

Eu gostaria de escrever uma função de desempenho que retorne uma lista dos papéis (de preferência de seus oids) aos quais o maxwell pertence, algo como isto:

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

Ele deve retornar os oids para maxwell, autores e editores (mas não para ernest).

Mas não sei como fazê-lo quando houver herança.

Neil McGuigan
fonte

Respostas:

23

Você pode consultar o catálogo do sistema com uma consulta recursiva , em 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;

BTW, INHERITé o comportamento padrão CREATE ROLEe não precisa ser explicitado.

BTW2: dependências circulares não são possíveis. O Postgres não permite isso. Portanto, não precisamos verificar isso.

Erwin Brandstetter
fonte
18

Versão curta:

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

Aqui, usamos uma versão pg_has_roleque assume um nome de função como assunto e função oid para testar a associação , passando no membermodo para testar as associações herdadas.

A vantagem de usar pg_has_roleé que ele usa caches internos de informações de função do PostgreSQL para satisfazer rapidamente as consultas de associação.

Você pode agrupar isso em uma SECURITY DEFINERfunção, uma vez que pg_authidpossui acesso restrito. 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...;

Você pode usar pg_get_userbyid(oid)para obter o nome da função do oid sem a necessidade 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
fonte
2
+1 de qualquer maneira, pois pg_has_role()provavelmente é um pouco mais rápido que minha consulta recursiva, mesmo que isso dificilmente importe. Uma última coisa, porém: ele retorna todas as funções para os superusuários, o que pode ou não ser um efeito colateral bem-vindo. É aí que o resultado difere da minha consulta.
Erwin Brandstetter
16

Esta é uma versão simplificada da resposta de Craig Ringer que um não superusuário pode usar diretamente:

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

pg_rolesé essencialmente uma visão pg_authidacessível ao público, pois não revela senhas, ao contrário de pg_authid. A base oidé exportada até para a exibição. Quando não precisar de senhas, não há sentido em criar a função dedicada ao superusuário.

Daniel Vérité
fonte
3

Aqui está a minha opinião. Funciona para um usuário específico ou para todos os usuários.

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
fonte
1
Isso seria muito melhorado se você explicasse como diferia e melhorou as respostas anteriores. Você pode editar as informações adicionais diretamente na resposta.
Michael Green
1

Eu acredito que isso fará isso

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 você preferir obter os nomes das funções, substitua a primeira oidpor rolname.

SureShotUK
fonte
0

se você quiser conhecer todas as funções da sua função ativa no momento:

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
fonte