11-- complain if script is sourced in psql, rather than via CREATE EXTENSION
22\echo Use " CREATE EXTENSION pg_permissions" to load this file. \quit
33
4+ /* types */
5+
6+ CREATE TYPE perm_type AS ENUM (
7+ ' SELECT' ,
8+ ' INSERT' ,
9+ ' UPDATE' ,
10+ ' DELETE' ,
11+ ' TRUNCATE' ,
12+ ' REFERENCES' ,
13+ ' TRIGGER' ,
14+ ' USAGE' ,
15+ ' CREATE' ,
16+ ' EXECUTE' ,
17+ ' CONNECT' ,
18+ ' TEMPORARY'
19+ );
20+
21+ CREATE TYPE obj_type AS ENUM (
22+ ' TABLE' ,
23+ ' VIEW' ,
24+ ' COLUMN' ,
25+ ' SEQUENCE' ,
26+ ' FUNCTION' ,
27+ ' SCHEMA' ,
28+ ' DATABASE'
29+ );
30+
31+ /* views for the actual permissions */
32+
433CREATE VIEW table_permissions AS
5- SELECT TEXT ' table ' AS object_type,
34+ SELECT obj_type ' TABLE ' AS object_type,
635 r .rolname AS role_name,
736 t .relnamespace ::regnamespace::name AS schema_name,
837 t .relname ::text AS object_name,
938 NULL ::name AS subobject_name,
10- p .perm AS permission,
39+ p .perm ::perm_type AS permission,
1140 has_table_privilege(r .oid , t .oid , p .perm ) AS granted
1241FROM pg_catalog .pg_class AS t
1342 CROSS JOIN pg_catalog .pg_roles AS r
1443 CROSS JOIN (VALUES (TEXT ' INSERT' ), (' UPDATE' ), (' DELETE' ), (' TRUNCATE' ), (' REFERENCES' ), (' TRIGGER' )) AS p(perm)
1544WHERE t .relnamespace ::regnamespace::name <> ' information_schema'
1645 AND t .relnamespace ::regnamespace::name NOT LIKE ' pg_%'
17- AND t .relkind = ' r' ;
46+ AND t .relkind = ' r'
47+ AND NOT r .rolsuper ;
48+
49+ GRANT SELECT ON table_permissions TO PUBLIC;
1850
1951CREATE VIEW view_permissions AS
20- WITH list AS (SELECT unnest AS perm
21- FROM unnest (' {"INSERT", "UPDATE", "DELETE", "TRIGGER"}' ::text []))
22- SELECT TEXT ' view' AS object_type,
52+ SELECT obj_type ' VIEW' AS object_type,
2353 r .rolname AS role_name,
2454 t .relnamespace ::regnamespace::name AS schema_name,
2555 t .relname ::text AS object_name,
2656 NULL ::name AS subobject_name,
27- p .perm AS permission,
57+ p .perm ::perm_type AS permission,
2858 has_table_privilege(r .oid , t .oid , p .perm ) AS granted
2959FROM pg_catalog .pg_class AS t
3060 CROSS JOIN pg_catalog .pg_roles AS r
3161 CROSS JOIN (VALUES (' INSERT' ), (' UPDATE' ), (' DELETE' ), (' TRIGGER' )) AS p(perm)
3262WHERE t .relnamespace ::regnamespace::name <> ' information_schema'
3363 AND t .relnamespace ::regnamespace::name NOT LIKE ' pg_%'
34- AND t .relkind = ' v' ;
64+ AND t .relkind = ' v'
65+ AND NOT r .rolsuper ;
66+
67+ GRANT SELECT ON view_permissions TO PUBLIC;
3568
3669CREATE VIEW column_permissions AS
37- SELECT TEXT ' column ' AS object_type,
70+ SELECT obj_type ' COLUMN ' AS object_type,
3871 r .rolname AS role_name,
3972 t .relnamespace ::regnamespace::name AS schema_name,
4073 t .relname ::text AS object_name,
4174 c .attname AS subobject_name,
42- p .perm AS permission,
75+ p .perm ::perm_type AS permission,
4376 has_column_privilege(r .oid , t .oid , c .attnum , p .perm ) AS granted
4477FROM pg_catalog .pg_class AS t
4578 JOIN pg_catalog .pg_attribute AS c ON t .oid = c .attrelid
@@ -48,49 +81,79 @@ FROM pg_catalog.pg_class AS t
4881WHERE t .relnamespace ::regnamespace::name <> ' information_schema'
4982 AND t .relnamespace ::regnamespace::name NOT LIKE ' pg_%'
5083 AND c .attnum > 0 AND NOT c .attisdropped
51- AND t .relkind IN (' r' , ' v' );
84+ AND t .relkind IN (' r' , ' v' )
85+ AND NOT r .rolsuper ;
86+
87+ GRANT SELECT ON column_permissions TO PUBLIC;
88+
89+ CREATE VIEW sequence_permissions AS
90+ SELECT obj_type ' SEQUENCE' AS object_type,
91+ r .rolname AS role_name,
92+ t .relnamespace ::regnamespace::name AS schema_name,
93+ t .relname ::text AS object_name,
94+ NULL ::name AS subobject_name,
95+ p .perm ::perm_type AS permission,
96+ has_sequence_privilege(r .oid , t .oid , p .perm ) AS granted
97+ FROM pg_catalog .pg_class AS t
98+ CROSS JOIN pg_catalog .pg_roles AS r
99+ CROSS JOIN (VALUES (' SELECT' ), (' USAGE' ), (' UPDATE' )) AS p(perm)
100+ WHERE t .relnamespace ::regnamespace::name <> ' information_schema'
101+ AND t .relnamespace ::regnamespace::name NOT LIKE ' pg_%'
102+ AND t .relkind = ' S'
103+ AND NOT r .rolsuper ;
104+
105+ GRANT SELECT ON sequence_permissions TO PUBLIC;
52106
53107CREATE VIEW function_permissions AS
54- SELECT TEXT ' function ' AS object_type,
108+ SELECT obj_type ' FUNCTION ' AS object_type,
55109 r .rolname AS role_name,
56110 f .pronamespace ::regnamespace::name AS schema_name,
57111 f .oid ::regprocedure::text AS object_name,
58112 NULL ::name AS subobject_name,
59- TEXT ' EXECUTE' AS permission,
113+ perm_type ' EXECUTE' AS permission,
60114 has_function_privilege(r .oid , f .oid , ' EXECUTE' ) AS granted
61115FROM pg_catalog .pg_proc f
62116 CROSS JOIN pg_catalog .pg_roles AS r
63117WHERE f .pronamespace ::regnamespace::name <> ' information_schema'
64- AND f .pronamespace ::regnamespace::name NOT LIKE ' pg_%' ;
118+ AND f .pronamespace ::regnamespace::name NOT LIKE ' pg_%'
119+ AND NOT r .rolsuper ;
120+
121+ GRANT SELECT ON function_permissions TO PUBLIC;
65122
66123CREATE VIEW schema_permissions AS
67- SELECT TEXT ' schema ' AS object_type,
124+ SELECT obj_type ' SCHEMA ' AS object_type,
68125 r .rolname AS role_name,
69126 n .nspname AS schema_name,
70127 NULL ::text AS object_name,
71128 NULL ::name AS subobject_name,
72- p .perm AS permissions,
129+ p .perm ::perm_type AS permissions,
73130 has_schema_privilege(r .oid , n .oid , p .perm ) AS granted
74131FROM pg_catalog .pg_namespace AS n
75132 CROSS JOIN pg_catalog .pg_roles AS r
76133 CROSS JOIN (VALUES (' USAGE' ), (' CREATE' )) AS p(perm)
77134WHERE n .nspname <> ' information_schema'
78- AND n .nspname NOT LIKE ' pg_%' ;
135+ AND n .nspname NOT LIKE ' pg_%'
136+ AND NOT r .rolsuper ;
137+
138+ GRANT SELECT ON schema_permissions TO PUBLIC;
79139
80140CREATE VIEW database_permissions AS
81141 WITH list AS (SELECT unnest AS perm
82142 FROM unnest (' {"CREATE", "CONNECT", "TEMPORARY"}' ::text []))
83- SELECT TEXT ' database ' AS object_type,
143+ SELECT obj_type ' DATABASE ' AS object_type,
84144 r .rolname AS role_name,
85145 NULL ::name AS schema_name,
86146 NULL ::text AS object_name,
87147 NULL ::name AS subobject_name,
88- p .perm AS permissions,
148+ p .perm ::perm_type AS permissions,
89149 has_database_privilege(r .oid , d .oid , p .perm ) AS granted
90150FROM pg_catalog .pg_database AS d
91151 CROSS JOIN pg_catalog .pg_roles AS r
92152 CROSS JOIN (VALUES (' CREATE' ), (' CONNECT' ), (' TEMPORARY' )) AS p(perm)
93- WHERE d .datname = current_database();
153+ WHERE d .datname = current_database()
154+ AND NOT r .rolsuper ;
155+
156+ GRANT SELECT ON database_permissions TO PUBLIC;
94157
95158CREATE VIEW all_permissions AS
96159SELECT * FROM table_permissions
@@ -99,8 +162,104 @@ SELECT * FROM view_permissions
99162UNION ALL
100163SELECT * FROM column_permissions
101164UNION ALL
165+ SELECT * FROM sequence_permissions
166+ UNION ALL
102167SELECT * FROM function_permissions
103168UNION ALL
104169SELECT * FROM schema_permissions
105170UNION ALL
106171SELECT * FROM database_permissions;
172+
173+ GRANT SELECT ON all_permissions TO PUBLIC;
174+
175+ /* table for the targeted permissions */
176+
177+ CREATE TABLE permission_target (
178+ id int4 PRIMARY KEY ,
179+ role_name name NOT NULL ,
180+ permissions perm_type[] NOT NULL ,
181+ object_type obj_type NOT NULL ,
182+ schema_name name,
183+ object_name text ,
184+ subobject_name name
185+ );
186+
187+ GRANT SELECT ON permission_target TO PUBLIC;
188+
189+ SELECT pg_catalog .pg_extension_config_dump (' permission_target' , ' ' );
190+
191+ CREATE FUNCTION permission_diffs ()
192+ RETURNS TABLE (
193+ missing boolean ,
194+ role_name name,
195+ object_type obj_type,
196+ schema_name name,
197+ object_name text ,
198+ subobject_name name,
199+ permission perm_type
200+ )
201+ LANGUAGE plpgsql SET search_path FROM CURRENT STABLE AS
202+ $$DECLARE
203+ typ obj_type;
204+ r name;
205+ ar name;
206+ s name;
207+ a_s name;
208+ o text ;
209+ ao text ;
210+ so name;
211+ aso name;
212+ p perm_type;
213+ g boolean ;
214+ ag boolean ;
215+ BEGIN
216+ FOR r, p, typ, s, o, so IN
217+ SELECT pt .role_name , p .permission , pt .object_type , pt .schema_name , pt .object_name , pt .subobject_name
218+ FROM permission_target AS pt
219+ CROSS JOIN LATERAL unnest(pt .permissions ) AS p(permission)
220+ LOOP
221+ FOR ar, a_s, ao, aso, ag IN
222+ SELECT ap .role_name , ap .schema_name , ap .object_name , ap .subobject_name , ap .granted
223+ FROM all_permissions AS ap
224+ WHERE ap .object_type = typ
225+ AND ap .permission = p
226+ AND (ap .schema_name = s OR s IS NULL )
227+ AND (ap .object_name = o OR o IS NULL )
228+ AND (ap .subobject_name = so OR so IS NULL )
229+ LOOP
230+ IF ar = r AND NOT ag THEN
231+ /* permission not granted that should be */
232+ permission_diffs .missing := TRUE;
233+ permission_diffs .role_name := r;
234+ permission_diffs .object_type := typ;
235+ permission_diffs .schema_name := a_s;
236+ permission_diffs .object_name := ao;
237+ permission_diffs .subobject_name := aso;
238+ permission_diffs .permission := p;
239+ RETURN NEXT;
240+ END IF;
241+ IF ar <> r AND ag THEN
242+ /* permission granted to a different role, check if there is a rule */
243+ IF NOT EXISTS (
244+ SELECT 1
245+ FROM permission_target AS pt
246+ WHERE pt .role_name = ar
247+ AND (pt .schema_name IS NULL OR pt .schema_name = a_s)
248+ AND (pt .object_name IS NULL OR pt .object_name = ao)
249+ AND (pt .subobject_name IS NULL OR pt .subobject_name = aso)
250+ )
251+ THEN
252+ /* extra permission found, report */
253+ permission_diffs .missing := FALSE;
254+ permission_diffs .role_name := ar;
255+ permission_diffs .object_type := typ;
256+ permission_diffs .schema_name := a_s;
257+ permission_diffs .object_name := ao;
258+ permission_diffs .subobject_name := aso;
259+ permission_diffs .permission := p;
260+ RETURN NEXT;
261+ END IF;
262+ END IF;
263+ END LOOP;
264+ END LOOP;
265+ END;$$;
0 commit comments