Skip to content

Commit fe42742

Browse files
committed
Add update triggers to the permission views and fix column names
"schema_permissions" and "database_permissions" have a column "permissions" while all other views have the column "permission". This makes no sense. Add INSTEAD OF UPDATE triggers for all views. These triggers will issue GRANT and REVOKE statements as appropriate. Patch by Antonin Houska, although I omitted his eliminate_permission_diffs() function.
1 parent 7d38dcf commit fe42742

File tree

7 files changed

+711
-3
lines changed

7 files changed

+711
-3
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
EXTENSION = pg_permissions
2-
DATA = pg_permissions--1.0.sql
2+
DATA = pg_permissions--*.sql
33
DOCS = README.pg_permissions
44
REGRESS = sample
55

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ for the current view.
106106
These views can be used to examine the currently granted permissions on
107107
database objects.
108108

109-
**Note:** Superusers are not shown in the view, as they automatically have all
109+
The `granted` column of these views can be updated, which causes the
110+
appropriate `GRANT` or `REVOKE` command to be executed.
111+
112+
**Note:** Superusers are not shown in the views, as they automatically have all
110113
permissions.
111114

112115
### Tables ###
@@ -145,6 +148,10 @@ Then connect to the database where you want to run `pg_permissions` and use
145148

146149
CREATE EXTENSION pg_permissions;
147150

151+
To upgrade from an older version of the extension, run
152+
153+
ALTER EXTENSION pg_permissions UPDATE;
154+
148155
You need `CREATE` privileges on the schema where you install the extension.
149156

150157
### Installation without the extension building infrastructure ###

expected/sample.out

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,47 @@ ORDER BY object_type, schema_name, object_name, column_name, role_name, permissi
182182
f | user2 | DATABASE | | | | CREATE
183183
(20 rows)
184184

185+
/* fix some of the differences */
186+
UPDATE column_permissions SET
187+
granted = TRUE
188+
WHERE role_name = 'user1'
189+
AND schema_name = 'appschema'
190+
AND object_name = 'apptable2'
191+
AND column_name = 'val'
192+
AND permission = 'REFERENCES';
193+
UPDATE all_permissions SET
194+
granted = FALSE
195+
WHERE object_type = 'TABLE'
196+
AND role_name = 'user2'
197+
AND schema_name = 'appschema'
198+
AND object_name = 'apptable'
199+
AND permission = 'INSERT';
200+
/* check the fixed permissions */
201+
SELECT * FROM permission_diffs()
202+
WHERE role_name IN ('users', 'user1', 'user2')
203+
ORDER BY object_type, schema_name, object_name, column_name, role_name, permission, missing;
204+
missing | role_name | object_type | schema_name | object_name | column_name | permission
205+
---------+-----------+-------------+-------------+-----------------+-------------+------------
206+
t | user1 | TABLE | appschema | apptable | | DELETE
207+
t | user1 | TABLE | appschema | apptable2 | | SELECT
208+
t | user1 | TABLE | appschema | apptable2 | | INSERT
209+
t | user1 | TABLE | appschema | apptable2 | | UPDATE
210+
t | user1 | TABLE | appschema | apptable2 | | DELETE
211+
t | user2 | TABLE | appschema | apptable2 | | SELECT
212+
t | user1 | VIEW | appschema | appview | | UPDATE
213+
f | users | VIEW | appschema | appview | | SELECT
214+
t | user1 | COLUMN | appschema | apptable2 | val | SELECT
215+
t | user1 | COLUMN | appschema | apptable2 | val | INSERT
216+
t | user1 | COLUMN | appschema | apptable2 | val | UPDATE
217+
f | user2 | COLUMN | appschema | apptable2 | val | UPDATE
218+
t | user1 | SEQUENCE | appschema | appseq | | SELECT
219+
f | user2 | SEQUENCE | appschema | appseq | | UPDATE
220+
f | users | FUNCTION | appschema | appfun(integer) | | EXECUTE
221+
t | user1 | SCHEMA | appschema | | | CREATE
222+
f | user2 | SCHEMA | appschema | | | CREATE
223+
f | user2 | DATABASE | | | | CREATE
224+
(18 rows)
225+
185226
/* clean up */
186227
DROP FUNCTION appschema.appfun(integer);
187228
DROP VIEW appschema.appview;

pg_permissions--1.0--1.1.sql

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
2+
\echo Use "ALTER EXTENSION pg_permissions UPDATE" to load this file. \quit
3+
4+
/* rename some view columns to match the other views */
5+
6+
DROP VIEW all_permissions;
7+
DROP VIEW schema_permissions;
8+
DROP VIEW database_permissions;
9+
10+
CREATE VIEW schema_permissions AS
11+
SELECT obj_type 'SCHEMA' AS object_type,
12+
r.rolname AS role_name,
13+
n.nspname AS schema_name,
14+
NULL::text AS object_name,
15+
NULL::name AS column_name,
16+
p.perm::perm_type AS permission,
17+
has_schema_privilege(r.oid, n.oid, p.perm) AS granted
18+
FROM pg_catalog.pg_namespace AS n
19+
CROSS JOIN pg_catalog.pg_roles AS r
20+
CROSS JOIN (VALUES ('USAGE'), ('CREATE')) AS p(perm)
21+
WHERE n.nspname <> 'information_schema'
22+
AND n.nspname NOT LIKE 'pg_%'
23+
AND NOT r.rolsuper;
24+
25+
GRANT SELECT ON schema_permissions TO PUBLIC;
26+
27+
CREATE VIEW database_permissions AS
28+
WITH list AS (SELECT unnest AS perm
29+
FROM unnest ('{"CREATE", "CONNECT", "TEMPORARY"}'::text[]))
30+
SELECT obj_type 'DATABASE' AS object_type,
31+
r.rolname AS role_name,
32+
NULL::name AS schema_name,
33+
NULL::text AS object_name,
34+
NULL::name AS column_name,
35+
p.perm::perm_type AS permission,
36+
has_database_privilege(r.oid, d.oid, p.perm) AS granted
37+
FROM pg_catalog.pg_database AS d
38+
CROSS JOIN pg_catalog.pg_roles AS r
39+
CROSS JOIN (VALUES ('CREATE'), ('CONNECT'), ('TEMPORARY')) AS p(perm)
40+
WHERE d.datname = current_database()
41+
AND NOT r.rolsuper;
42+
43+
GRANT SELECT ON database_permissions TO PUBLIC;
44+
45+
CREATE VIEW all_permissions AS
46+
SELECT * FROM table_permissions
47+
UNION ALL
48+
SELECT * FROM view_permissions
49+
UNION ALL
50+
SELECT * FROM column_permissions
51+
UNION ALL
52+
SELECT * FROM sequence_permissions
53+
UNION ALL
54+
SELECT * FROM function_permissions
55+
UNION ALL
56+
SELECT * FROM schema_permissions
57+
UNION ALL
58+
SELECT * FROM database_permissions;
59+
60+
GRANT SELECT ON all_permissions TO PUBLIC;
61+
62+
/* update trigers for the views */
63+
64+
CREATE FUNCTION permissions_trigger_func()
65+
RETURNS TRIGGER
66+
LANGUAGE plpgsql
67+
AS $$
68+
DECLARE
69+
db_name text;
70+
cmd text;
71+
BEGIN
72+
IF NEW.object_type <> OLD.object_type OR
73+
NEW.role_name <> OLD.role_name OR
74+
NEW.schema_name <> OLD.schema_name OR
75+
NEW.object_name <> OLD.object_name OR
76+
NEW.column_name <> OLD.column_name OR
77+
NEW.permission <> OLD.permission
78+
THEN
79+
RAISE 'Only the "granted" column may be updated';
80+
END IF;
81+
82+
-- Is there anything to do at all?
83+
IF NEW.granted = OLD.granted
84+
THEN
85+
RETURN NEW;
86+
END IF;
87+
88+
IF OLD.object_type IN ('TABLE', 'VIEW')
89+
THEN
90+
IF NOT OLD.granted
91+
THEN
92+
cmd := format('GRANT %s ON %s.%s TO %s',
93+
OLD.permission, OLD.schema_name,
94+
OLD.object_name, OLD.role_name);
95+
ELSE
96+
cmd := format('REVOKE %s ON %s.%s FROM %s',
97+
OLD.permission, OLD.schema_name,
98+
OLD.object_name, OLD.role_name);
99+
END IF;
100+
ELSIF OLD.object_type = 'COLUMN'
101+
THEN
102+
IF NOT OLD.granted
103+
THEN
104+
cmd := format('GRANT %s(%s) ON %s.%s TO %s',
105+
OLD.permission, OLD.column_name,
106+
OLD.schema_name, OLD.object_name,
107+
OLD.role_name);
108+
ELSE
109+
cmd := format('REVOKE %s(%s) ON %s.%s FROM %s',
110+
OLD.permission, OLD.column_name,
111+
OLD.schema_name, OLD.object_name,
112+
OLD.role_name);
113+
END IF;
114+
ELSIF OLD.object_type = 'SEQUENCE'
115+
THEN
116+
IF NOT OLD.granted
117+
THEN
118+
cmd := format('GRANT %s ON SEQUENCE %s.%s TO %s',
119+
OLD.permission, OLD.schema_name,
120+
OLD.object_name, OLD.role_name);
121+
ELSE
122+
cmd := format('REVOKE %s ON SEQUENCE %s.%s FROM %s',
123+
OLD.permission, OLD.schema_name,
124+
OLD.object_name, OLD.role_name);
125+
END IF;
126+
ELSIF OLD.object_type = 'FUNCTION'
127+
THEN
128+
IF NOT OLD.granted
129+
THEN
130+
cmd := format('GRANT %s ON FUNCTION %s.%s TO %s',
131+
OLD.permission, OLD.schema_name,
132+
OLD.object_name, OLD.role_name);
133+
ELSE
134+
cmd := format('REVOKE %s ON FUNCTION %s.%s FROM %s',
135+
OLD.permission, OLD.schema_name,
136+
OLD.object_name, OLD.role_name);
137+
END IF;
138+
ELSIF OLD.object_type = 'SCHEMA'
139+
THEN
140+
IF NOT OLD.granted
141+
THEN
142+
cmd := format('GRANT %s ON SCHEMA %s TO %s',
143+
OLD.permission, OLD.schema_name,
144+
OLD.role_name);
145+
ELSE
146+
cmd := format('REVOKE %s ON SCHEMA %s FROM %s',
147+
OLD.permission, OLD.schema_name,
148+
OLD.role_name);
149+
END IF;
150+
ELSIF OLD.object_type = 'DATABASE'
151+
THEN
152+
db_name := pg_catalog.current_database();
153+
154+
IF NOT OLD.granted
155+
THEN
156+
cmd := format('GRANT %s ON DATABASE %s TO %s',
157+
OLD.permission, db_name, OLD.role_name);
158+
ELSE
159+
cmd := format('REVOKE %s ON DATABASE %s FROM %s',
160+
OLD.permission, db_name, OLD.role_name);
161+
END IF;
162+
ELSE
163+
RAISE 'Unrecognized object type: %',
164+
OLD.object_type;
165+
END IF;
166+
167+
EXECUTE cmd;
168+
RETURN NEW;
169+
END;
170+
$$;
171+
172+
CREATE TRIGGER permissions_trigger
173+
INSTEAD OF UPDATE ON table_permissions
174+
FOR EACH ROW EXECUTE PROCEDURE permissions_trigger_func();
175+
176+
CREATE TRIGGER permissions_trigger
177+
INSTEAD OF UPDATE ON column_permissions
178+
FOR EACH ROW EXECUTE PROCEDURE permissions_trigger_func();
179+
180+
CREATE TRIGGER permissions_trigger
181+
INSTEAD OF UPDATE ON view_permissions
182+
FOR EACH ROW EXECUTE PROCEDURE permissions_trigger_func();
183+
184+
CREATE TRIGGER permissions_trigger
185+
INSTEAD OF UPDATE ON sequence_permissions
186+
FOR EACH ROW EXECUTE PROCEDURE permissions_trigger_func();
187+
188+
CREATE TRIGGER permissions_trigger
189+
INSTEAD OF UPDATE ON function_permissions
190+
FOR EACH ROW EXECUTE PROCEDURE permissions_trigger_func();
191+
192+
CREATE TRIGGER permissions_trigger
193+
INSTEAD OF UPDATE ON schema_permissions
194+
FOR EACH ROW EXECUTE PROCEDURE permissions_trigger_func();
195+
196+
CREATE TRIGGER permissions_trigger
197+
INSTEAD OF UPDATE ON database_permissions
198+
FOR EACH ROW EXECUTE PROCEDURE permissions_trigger_func();
199+
200+
CREATE TRIGGER permissions_trigger
201+
INSTEAD OF UPDATE ON all_permissions
202+
FOR EACH ROW EXECUTE PROCEDURE permissions_trigger_func();

0 commit comments

Comments
 (0)