Как получить все роли, в которые входит пользователь (включая унаследованные роли)?

23

Допустим, у меня есть две группы баз данных Postgresql, «авторы» и «редакторы», и два пользователя, «maxwell» и «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

Я хотел бы написать функцию-исполнитель, которая возвращает список ролей (предпочтительно их oid), к которым принадлежит maxwell, что-то вроде этого:

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

Он должен возвращать oids для maxwell, авторов и редакторов (но не ernest).

Но я не уверен, как это сделать, когда есть наследство.

Нил Макгиган
источник

Ответы:

23

Вы можете запросить системный каталог с помощью рекурсивного запроса , в частности 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;

Кстати, INHERITэто поведение по умолчанию CREATE ROLEи не должно быть прописано.

Кстати: циклические зависимости невозможны. Postgres запрещает это. Так что нам не нужно проверять это.

Эрвин Брандштеттер
источник
18

Укороченная версия:

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

Здесь мы используем версию, pg_has_roleкоторая принимает имя роли в качестве субъекта и роли роли для проверки на членство , проходной memberрежим, поэтому мы проверяем на унаследованные членства.

Преимущество использования pg_has_roleсостоит в том, что он использует внутренние кэши PostgreSQL информации о ролях для быстрого удовлетворения запросов на членство.

Возможно, вы захотите обернуть это в SECURITY DEFINERфункцию, поскольку pg_authidимеет ограниченный доступ. Что-то типа:

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

Вы можете использовать pg_get_userbyid(oid)для получения имени роли из oid без запроса 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 в любом случае, так pg_has_role()как, вероятно, немного быстрее, чем мой рекурсивный запрос, даже если это вряд ли имеет значение. И последнее: хотя он возвращает все роли для суперпользователей, что может быть или не быть желанным побочным эффектом. Вот где результат отличается от моего запроса.
Эрвин Брандштеттер
16

Это упрощенная версия ответа Крейга Рингера, которую может использовать не суперпользователь:

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

pg_rolesЭто, по сути, взгляд на pg_authidдоступность для общественности, поскольку он не раскрывает пароли, в отличие от pg_authid. База oidдаже экспортируется в вид. Когда не нужны пароли, нет смысла создавать отдельную функцию, принадлежащую суперпользователю.

Даниэль Верите
источник
3

Вот мой взгляд на это. Это работает для одного конкретного пользователя или всех пользователей.

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
источник
1
Это было бы намного лучше, если бы вы объяснили, как отличаться от предыдущих ответов и улучшать их. Вы можете редактировать дополнительную информацию прямо в ответ.
Майкл Грин
1

Я верю, что это сделает

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

Если вы предпочитаете получать имена ролей, замените первое oidна rolname.

SureShotUK
источник
0

если вы хотите знать все роли вашей текущей активной роли:

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;
Михаил Гершкович
источник