Skip to content

Commit 436fff0

Browse files
committed
Add more functionality
- Types "perm_type" and "obj_type" to describe permissions and object types. - Table "permission_target" for the user to describe the desired state of the permissions. - Function "permission_diffs()" to find the differences between "permission_target" and the actual state. Add a README to document the extension.
1 parent 5731554 commit 436fff0

File tree

2 files changed

+272
-20
lines changed

2 files changed

+272
-20
lines changed

README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
PostgreSQL permission reports and checks
2+
========================================
3+
4+
This extension allows you to review object permissions on a PostgreSQL database.
5+
6+
Usage
7+
-----
8+
9+
### Views ###
10+
11+
The extension provides a number of views:
12+
13+
- `database_permissions`: permissions granted on the current database
14+
15+
- `schema_permissions`: permissions granted on schemas
16+
17+
- `table_permissions`: permissions granted on tables
18+
19+
- `view_permissions`: permissions granted on views
20+
21+
- `column_permissions`: permissions granted on table and view columns
22+
23+
- `function_permissions`: permissions granted on functions
24+
25+
- `sequence_permissions`: permissions granted on sequences
26+
27+
- `all_permissions`: permissions on all objects (`UNION` of the above)
28+
29+
All views have the same columns; a column is NULL if it has no meaning
30+
for the current view.
31+
32+
The `subobject_name` column only has a meaning in `column_permissions`, where
33+
it denotes the column name.
34+
35+
These views can be used to examine the currently granted permissions on
36+
database objects.
37+
38+
**Note:** Superusers are not show in the view, as they automatically have
39+
all permissions.
40+
41+
### Tables ###
42+
43+
The extension provides a table `permission_target` with which you can describe
44+
the permissions that *should* be granted on database objects.
45+
46+
If you set a relevant column in `permission_target` to NULL (e.g., the
47+
`object_name` and `subobject_name` columns in a `TABLE` entry), the meaning is
48+
that the entry refers to *all* possible objects (in the example above, all
49+
tables in the schema).
50+
51+
### Functions ###
52+
53+
The table function `permission_diffs()` checks the desired permissions in
54+
`permission_target` against the actually granted permissions in the views
55+
of the extension and returns a table of differences.
56+
57+
If the first column `missing` is `TRUE`, the result is a permission that should
58+
be there but isn't; if `missing` is `FALSE`, the result row is a permission that
59+
is there even though it is not defined in `permission_target` (an extra
60+
permission).
61+
62+
Installation
63+
------------
64+
65+
Make sure the PostgreSQL extension building infrastructure is installed.
66+
If you installed PostgreSQL with installation packages, you usually need to
67+
install the "development"-Package.
68+
69+
Make sure that `pg_config` is on your `PATH`. Then type
70+
71+
make install
72+
73+
Then connect to the database where you want to run `pg_permissions` and use
74+
75+
CREATE EXTENSION pg_permissions;
76+
77+
You need `CREATE` privileges on the schema where you install the extension.
78+
79+
### Installation without the extension building infrastructure ###
80+
81+
This is also what Windows users will have to dom because there is no extension
82+
building infrastructure for Windows.
83+
84+
Find out where your PostgreSQL share directory is:
85+
86+
pg_config --sharedir
87+
88+
Then copy `pg_permissions.control` and the SQL files to the `extension`
89+
subdirectory of that directory, e.g.
90+
91+
copy pg_permissions.control *.sql "C:\Program Files\PostgreSQL\10\share\extension"
92+
93+
You still have to run `CREATE EXTENSION` as described above.

pg_permissions--1.0.sql

Lines changed: 179 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,78 @@
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+
433
CREATE 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
1241
FROM 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)
1544
WHERE 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

1951
CREATE 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
2959
FROM 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)
3262
WHERE 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

3669
CREATE 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
4477
FROM 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
4881
WHERE 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

53107
CREATE 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
61115
FROM pg_catalog.pg_proc f
62116
CROSS JOIN pg_catalog.pg_roles AS r
63117
WHERE 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

66123
CREATE 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
74131
FROM pg_catalog.pg_namespace AS n
75132
CROSS JOIN pg_catalog.pg_roles AS r
76133
CROSS JOIN (VALUES ('USAGE'), ('CREATE')) AS p(perm)
77134
WHERE 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

80140
CREATE 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
90150
FROM 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

95158
CREATE VIEW all_permissions AS
96159
SELECT * FROM table_permissions
@@ -99,8 +162,104 @@ SELECT * FROM view_permissions
99162
UNION ALL
100163
SELECT * FROM column_permissions
101164
UNION ALL
165+
SELECT * FROM sequence_permissions
166+
UNION ALL
102167
SELECT * FROM function_permissions
103168
UNION ALL
104169
SELECT * FROM schema_permissions
105170
UNION ALL
106171
SELECT * 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

Comments
 (0)