Simplify permissions for LOCK TABLE.

The prior behavior was confusing and hard to document. For instance,
if you had UPDATE privileges, you could lock a table in any lock mode
except ACCESS SHARE mode.

Now, if granted a privilege to lock at a given mode, one also has
privileges to lock at a less-conflicting mode. MAINTAIN, UPDATE,
DELETE, and TRUNCATE privileges allow any lock mode. INSERT privileges
allow ROW EXCLUSIVE (or below). SELECT privileges allow ACCESS SHARE.

Reviewed-by: Nathan Bossart
Discussion: https://postgr.es/m/9550c76535404a83156252b25a11babb4792ea1e.camel%40j-davis.com
This commit is contained in:
Jeff Davis 2023-01-13 14:14:54 -08:00
parent b7ae039536
commit c44f6334ca
4 changed files with 81 additions and 93 deletions

View File

@ -165,18 +165,19 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
<title>Notes</title> <title>Notes</title>
<para> <para>
To lock a table, one must ordinarily have the <literal>MAINTAIN</literal> To lock a table, the user must have the right privilege for the specified
privilege on the table or be the table's owner, a superuser, or a role <replaceable class="parameter">lockmode</replaceable>, or be the table's
with privileges of the owner, a superuser, or a role with privileges of the <link
<link linkend="predefined-roles-table"><literal>pg_maintain</literal></link> linkend="predefined-roles-table"><literal>pg_maintain</literal></link>
role. <literal>LOCK TABLE ... IN ACCESS SHARE MODE</literal> is allowed role. If the user has <literal>MAINTAIN</literal>,
with <literal>SELECT</literal> privileges on the target <literal>UPDATE</literal>, <literal>DELETE</literal>, or
table. <literal>LOCK TABLE ... IN ROW EXCLUSIVE MODE</literal> is allowed <literal>TRUNCATE</literal> privileges on the table, any <replaceable
with <literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>, class="parameter">lockmode</replaceable> is permitted. If the user has
or <literal>TRUNCATE</literal> privileges on the target table. All other <literal>INSERT</literal> privileges on the table, <literal>ROW EXCLUSIVE
forms of <command>LOCK</command> are allowed with MODE</literal> (or a less-conflicting mode as described in <xref
table-level <literal>UPDATE</literal>, <literal>DELETE</literal>, linkend="explicit-locking"/>) is permitted. If a user has
or <literal>TRUNCATE</literal> privileges. <literal>SELECT</literal> privileges on the table, <literal>ACCESS SHARE
MODE</literal> is permitted.
</para> </para>
<para> <para>

View File

@ -292,16 +292,16 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
AclResult aclresult; AclResult aclresult;
AclMode aclmask; AclMode aclmask;
/* Verify adequate privilege */ /* any of these privileges permit any lock mode */
if (lockmode == AccessShareLock) aclmask = ACL_MAINTAIN | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
aclmask = ACL_SELECT;
else if (lockmode == RowExclusiveLock)
aclmask = ACL_INSERT | ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
else
aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
/* MAINTAIN privilege allows all lock modes */ /* SELECT privileges also permit ACCESS SHARE and below */
aclmask |= ACL_MAINTAIN; if (lockmode <= AccessShareLock)
aclmask |= ACL_SELECT;
/* INSERT privileges also permit ROW EXCLUSIVE and below */
if (lockmode <= RowExclusiveLock)
aclmask |= ACL_INSERT;
aclresult = pg_class_aclcheck(reloid, userid, aclmask); aclresult = pg_class_aclcheck(reloid, userid, aclmask);

View File

@ -2706,13 +2706,13 @@ CREATE TABLE lock_table (a int);
GRANT SELECT ON lock_table TO regress_locktable_user; GRANT SELECT ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
COMMIT;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should fail LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should fail
ERROR: permission denied for table lock_table ERROR: permission denied for table lock_table
ROLLBACK; ROLLBACK;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
COMMIT;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail
ERROR: permission denied for table lock_table ERROR: permission denied for table lock_table
ROLLBACK; ROLLBACK;
@ -2722,13 +2722,12 @@ REVOKE SELECT ON lock_table FROM regress_locktable_user;
GRANT INSERT ON lock_table TO regress_locktable_user; GRANT INSERT ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail
ERROR: permission denied for table lock_table
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail
ERROR: permission denied for table lock_table ERROR: permission denied for table lock_table
ROLLBACK; ROLLBACK;
@ -2738,13 +2737,12 @@ REVOKE INSERT ON lock_table FROM regress_locktable_user;
GRANT UPDATE ON lock_table TO regress_locktable_user; GRANT UPDATE ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail
ERROR: permission denied for table lock_table
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
\c \c
@ -2753,13 +2751,12 @@ REVOKE UPDATE ON lock_table FROM regress_locktable_user;
GRANT DELETE ON lock_table TO regress_locktable_user; GRANT DELETE ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail
ERROR: permission denied for table lock_table
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
\c \c
@ -2768,17 +2765,30 @@ REVOKE DELETE ON lock_table FROM regress_locktable_user;
GRANT TRUNCATE ON lock_table TO regress_locktable_user; GRANT TRUNCATE ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail
ERROR: permission denied for table lock_table
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
\c \c
REVOKE TRUNCATE ON lock_table FROM regress_locktable_user; REVOKE TRUNCATE ON lock_table FROM regress_locktable_user;
-- LOCK TABLE and MAINTAIN permission
GRANT MAINTAIN ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT;
\c
REVOKE MAINTAIN ON lock_table FROM regress_locktable_user;
-- clean up -- clean up
DROP TABLE lock_table; DROP TABLE lock_table;
DROP USER regress_locktable_user; DROP USER regress_locktable_user;
@ -2920,14 +2930,6 @@ REINDEX INDEX maintain_test_a_idx;
ERROR: must be owner of index maintain_test_a_idx ERROR: must be owner of index maintain_test_a_idx
REINDEX SCHEMA reindex_test; REINDEX SCHEMA reindex_test;
ERROR: must be owner of schema reindex_test ERROR: must be owner of schema reindex_test
BEGIN;
LOCK TABLE maintain_test IN ACCESS SHARE MODE;
ERROR: permission denied for table maintain_test
COMMIT;
BEGIN;
LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
ERROR: permission denied for table maintain_test
COMMIT;
RESET ROLE; RESET ROLE;
SET ROLE regress_maintain; SET ROLE regress_maintain;
VACUUM maintain_test; VACUUM maintain_test;
@ -2939,12 +2941,6 @@ REINDEX TABLE maintain_test;
REINDEX INDEX maintain_test_a_idx; REINDEX INDEX maintain_test_a_idx;
REINDEX SCHEMA reindex_test; REINDEX SCHEMA reindex_test;
ERROR: must be owner of schema reindex_test ERROR: must be owner of schema reindex_test
BEGIN;
LOCK TABLE maintain_test IN ACCESS SHARE MODE;
COMMIT;
BEGIN;
LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
COMMIT;
RESET ROLE; RESET ROLE;
SET ROLE regress_maintain_all; SET ROLE regress_maintain_all;
VACUUM maintain_test; VACUUM maintain_test;
@ -2955,12 +2951,6 @@ REFRESH MATERIALIZED VIEW refresh_test;
REINDEX TABLE maintain_test; REINDEX TABLE maintain_test;
REINDEX INDEX maintain_test_a_idx; REINDEX INDEX maintain_test_a_idx;
REINDEX SCHEMA reindex_test; REINDEX SCHEMA reindex_test;
BEGIN;
LOCK TABLE maintain_test IN ACCESS SHARE MODE;
COMMIT;
BEGIN;
LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
COMMIT;
RESET ROLE; RESET ROLE;
DROP TABLE maintain_test; DROP TABLE maintain_test;
DROP MATERIALIZED VIEW refresh_test; DROP MATERIALIZED VIEW refresh_test;

View File

@ -1707,12 +1707,12 @@ CREATE TABLE lock_table (a int);
GRANT SELECT ON lock_table TO regress_locktable_user; GRANT SELECT ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should fail
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should fail
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail
ROLLBACK; ROLLBACK;
\c \c
@ -1722,12 +1722,12 @@ REVOKE SELECT ON lock_table FROM regress_locktable_user;
GRANT INSERT ON lock_table TO regress_locktable_user; GRANT INSERT ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should fail
ROLLBACK; ROLLBACK;
\c \c
@ -1737,12 +1737,12 @@ REVOKE INSERT ON lock_table FROM regress_locktable_user;
GRANT UPDATE ON lock_table TO regress_locktable_user; GRANT UPDATE ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
\c \c
@ -1752,12 +1752,12 @@ REVOKE UPDATE ON lock_table FROM regress_locktable_user;
GRANT DELETE ON lock_table TO regress_locktable_user; GRANT DELETE ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
\c \c
@ -1767,17 +1767,32 @@ REVOKE DELETE ON lock_table FROM regress_locktable_user;
GRANT TRUNCATE ON lock_table TO regress_locktable_user; GRANT TRUNCATE ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user; SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
BEGIN; BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should fail
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT; COMMIT;
\c \c
REVOKE TRUNCATE ON lock_table FROM regress_locktable_user; REVOKE TRUNCATE ON lock_table FROM regress_locktable_user;
-- LOCK TABLE and MAINTAIN permission
GRANT MAINTAIN ON lock_table TO regress_locktable_user;
SET SESSION AUTHORIZATION regress_locktable_user;
BEGIN;
LOCK TABLE lock_table IN ACCESS SHARE MODE; -- should pass
ROLLBACK;
BEGIN;
LOCK TABLE lock_table IN ROW EXCLUSIVE MODE; -- should pass
COMMIT;
BEGIN;
LOCK TABLE lock_table IN ACCESS EXCLUSIVE MODE; -- should pass
COMMIT;
\c
REVOKE MAINTAIN ON lock_table FROM regress_locktable_user;
-- clean up -- clean up
DROP TABLE lock_table; DROP TABLE lock_table;
DROP USER regress_locktable_user; DROP USER regress_locktable_user;
@ -1884,12 +1899,6 @@ REFRESH MATERIALIZED VIEW refresh_test;
REINDEX TABLE maintain_test; REINDEX TABLE maintain_test;
REINDEX INDEX maintain_test_a_idx; REINDEX INDEX maintain_test_a_idx;
REINDEX SCHEMA reindex_test; REINDEX SCHEMA reindex_test;
BEGIN;
LOCK TABLE maintain_test IN ACCESS SHARE MODE;
COMMIT;
BEGIN;
LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
COMMIT;
RESET ROLE; RESET ROLE;
SET ROLE regress_maintain; SET ROLE regress_maintain;
@ -1901,12 +1910,6 @@ REFRESH MATERIALIZED VIEW refresh_test;
REINDEX TABLE maintain_test; REINDEX TABLE maintain_test;
REINDEX INDEX maintain_test_a_idx; REINDEX INDEX maintain_test_a_idx;
REINDEX SCHEMA reindex_test; REINDEX SCHEMA reindex_test;
BEGIN;
LOCK TABLE maintain_test IN ACCESS SHARE MODE;
COMMIT;
BEGIN;
LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
COMMIT;
RESET ROLE; RESET ROLE;
SET ROLE regress_maintain_all; SET ROLE regress_maintain_all;
@ -1918,12 +1921,6 @@ REFRESH MATERIALIZED VIEW refresh_test;
REINDEX TABLE maintain_test; REINDEX TABLE maintain_test;
REINDEX INDEX maintain_test_a_idx; REINDEX INDEX maintain_test_a_idx;
REINDEX SCHEMA reindex_test; REINDEX SCHEMA reindex_test;
BEGIN;
LOCK TABLE maintain_test IN ACCESS SHARE MODE;
COMMIT;
BEGIN;
LOCK TABLE maintain_test IN ACCESS EXCLUSIVE MODE;
COMMIT;
RESET ROLE; RESET ROLE;
DROP TABLE maintain_test; DROP TABLE maintain_test;