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


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'

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.


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;
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.