Wie erhalte ich alle Rollen, in denen ein Benutzer Mitglied ist (einschließlich geerbter Rollen)?

23

Angenommen, ich habe zwei Postgresql-Datenbankgruppen, "Autoren" und "Redakteure", und zwei Benutzer, "Maxwell" und "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

Ich möchte eine performante Funktion schreiben, die eine Liste der Rollen (vorzugsweise deren Oids) zurückgibt, zu denen Maxwell gehört.

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

Es sollte die oids für Maxwell, Autoren und Herausgeber (aber nicht ernest) zurückgeben.

Aber ich bin nicht sicher, wie ich es tun soll, wenn es Vererbung gibt.

Neil McGuigan
quelle

Antworten:

23

Sie können den Systemkatalog mit einer rekursiven Abfrage abfragen , insbesondere 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;

Übrigens INHERITist das Standardverhalten von CREATE ROLEund muss nicht ausgeschrieben werden.

BTW2: Kreisförmige Abhängigkeiten sind nicht möglich. Postgres lässt das nicht zu. Wir müssen das also nicht überprüfen.

Erwin Brandstetter
quelle
18

Kurzfassung:

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

Hier verwenden wir eine Version pg_has_role, die einen Rollennamen als Betreff und eine Rollen-ID zum Testen der Mitgliedschaft und zum Bestehen des memberModus verwendet, um auf geerbte Mitgliedschaften zu testen.

Der Vorteil der Verwendung pg_has_rolebesteht darin, dass die internen Caches von PostgreSQL mit Rolleninformationen verwendet werden, um Mitgliedschaftsabfragen schnell zu beantworten.

Möglicherweise möchten Sie dies in eine SECURITY DEFINERFunktion einschließen , da pg_authidder Zugriff eingeschränkt ist. So etwas wie:

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

Mit können Sie pg_get_userbyid(oid)den Rollennamen aus dem OID abrufen, ohne Folgendes abfragen zu müssen 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
quelle
2
+1 sowieso, da pg_has_role()ist wohl etwas schneller als meine rekursive Abfrage, auch wenn das kaum eine Rolle spielt. Eine letzte Sache jedoch: Es gibt alle Rollen für Supernutzer zurück, was ein willkommener Nebeneffekt sein kann oder nicht. Hier unterscheidet sich das Ergebnis von meiner Anfrage.
Erwin Brandstetter
16

Dies ist eine vereinfachte Version der Antwort von Craig Ringer, die ein Nicht-Superuser direkt verwenden kann:

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

pg_rolesist im Wesentlichen eine Sichtweise auf pg_authidöffentlich zugängliche, da sie im Gegensatz zu Passwörtern nicht offenbart pg_authid. Die Basis oidwird sogar in die Ansicht exportiert. Wenn Sie keine Passwörter benötigen, macht es keinen Sinn, die dedizierte Superuser-eigene Funktion zu erstellen.

Daniel Vérité
quelle
3

Hier ist meine Meinung dazu. Es funktioniert für einen bestimmten Benutzer oder für alle Benutzer.

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
quelle
1
Dies würde sich erheblich verbessern, wenn Sie erläutern würden, inwiefern sich die vorherigen Antworten von den vorherigen unterscheiden und diese verbessern. Sie können die zusätzlichen Informationen direkt in der Antwort bearbeiten.
Michael Green
1

Ich glaube, das wird es schaffen

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

Wenn Sie die Rollennamen erhalten möchten, ersetzen Sie den ersten oiddurch rolname.

SureShotUK
quelle
0

Wenn Sie alle Rollen Ihrer derzeit aktiven Rolle kennen möchten:

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
quelle