[PATCH v2] use has_privs_for_role for predefined roles
Attached is an updated version of the patch to replace
is_member_of_role with has_privs_for_role for predefined roles. It
does not remove is_member_of_role from acl.h but it does add a warning
not to use that function for privilege checking.
Please consider this for the upcoming commitfest.
Attachments:
0001-use-has_privs_for_roles-for-predefined-role-checks.patchapplication/octet-stream; name=0001-use-has_privs_for_roles-for-predefined-role-checks.patchDownload
From 05ad7d8dd2217392fe74ebd1dbaca2410b2dc241 Mon Sep 17 00:00:00 2001
From: Joshua Brindle <joshua.brindle@crunchydata.com>
Date: Tue, 26 Oct 2021 14:19:44 -0700
Subject: [PATCH] use has_privs_for_roles for predefined role checks
Generally if a role is granted membership to another role with NOINHERIT
they must use SET ROLE to access the privileges of that role, however
with predefined roles the membership and privilege is conflated, as
demonstrated by:
CREATE ROLE readrole;
CREATE ROLE role2 NOINHERIT;
CREATE ROLE brindle LOGIN;
GRANT role2 TO brindle;
CREATE TABLE foo(i INT);
GRANT readrole TO role2;
GRANT ALL ON TABLE foo TO readrole;
GRANT pg_read_all_stats,pg_read_all_settings,pg_read_server_files,pg_write_server_files,pg_execute_server_program TO role2;
Log in as brindle:
postgres=> select current_user;
current_user
--------------
brindle
(1 row)
postgres=> SELECT * FROM foo;
ERROR: permission denied for table foo
postgres=> SELECT DISTINCT query FROM pg_stat_activity;
query
----------------------------------------------
SELECT DISTINCT query FROM pg_stat_activity;
(2 rows)
postgres=> SET ROLE readrole;
SET
postgres=> SELECT * FROM foo;
i
---
(0 rows)
After this patch:
postgres=> SELECT DISTINCT query FROM pg_stat_activity;
query
--------------------------
<insufficient privilege>
(1 row)
postgres=> SET ROLE pg_read_all_stats;
SET
postgres=> SELECT DISTINCT query FROM pg_stat_activity;
query
----------------------------------------------
SELECT DISTINCT query FROM pg_stat_activity;
(2 rows)
postgres=> SHOW config_file;
ERROR: must be superuser or have privileges of pg_read_all_settings to examine "config_file"
postgres=> SET ROLE pg_read_all_settings;
SET
postgres=> SHOW config_file;
config_file
-----------------------------------
/var/lib/pgsql/15/postgresql.conf
(1 row)
With inheritance it works as expected:
ALTER ROLE role2 INHERIT;
postgres=> SELECT current_user;
current_user
--------------
brindle
(1 row)
postgres=> SHOW config_file;
config_file
-----------------------------------
/var/lib/pgsql/15/postgresql.conf
(1 row)
Signed-off-by: Joshua Brindle <joshua.brindle@crunchydata.com>
---
contrib/adminpack/adminpack.c | 2 +-
contrib/file_fdw/file_fdw.c | 8 ++++----
contrib/file_fdw/output/file_fdw.source | 2 +-
.../pg_stat_statements/pg_stat_statements.c | 2 +-
contrib/pgrowlocks/pgrowlocks.c | 2 +-
src/backend/commands/copy.c | 12 +++++------
src/backend/replication/walreceiver.c | 8 ++++----
src/backend/replication/walsender.c | 8 ++++----
src/backend/utils/adt/acl.c | 4 ++++
src/backend/utils/adt/dbsize.c | 8 ++++----
src/backend/utils/adt/genfile.c | 6 +++---
src/backend/utils/adt/pgstatfuncs.c | 2 +-
src/backend/utils/misc/guc.c | 20 +++++++++----------
.../unsafe_tests/expected/rolenames.out | 2 +-
14 files changed, 45 insertions(+), 41 deletions(-)
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index 48c17469104..0457d7b591d 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -79,7 +79,7 @@ convert_and_check_filename(text *arg)
* files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
return filename;
/*
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2c2f149fb01..a769b2ef7b9 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -269,16 +269,16 @@ file_fdw_validator(PG_FUNCTION_ARGS)
* otherwise there'd still be a security hole.
*/
if (strcmp(def->defname, "filename") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
if (strcmp(def->defname, "program") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ !has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
filename = defGetString(def);
}
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index 52b4d5f1df7..a1d44ed665a 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -436,7 +436,7 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 07fe0e7cdad..b2a4f056478 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1512,7 +1512,7 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
pgssEntry *entry;
/* Superusers or members of pg_read_all_stats members are allowed */
- is_allowed_role = is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS);
+ is_allowed_role = has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS);
/* hash table must exist already */
if (!pgss || !pgss_hash)
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 669a7d7730b..b9b724a554d 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -130,7 +130,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_SELECT);
if (aclresult != ACLCHECK_OK)
- aclresult = is_member_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
+ aclresult = has_privs_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..e26ff42fd82 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -80,26 +80,26 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
{
if (stmt->is_program)
{
- if (!is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
else
{
- if (is_from && !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_read_server_files role to COPY from a file"),
+ errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
- if (!is_from && !is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_write_server_files role to COPY to a file"),
+ errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index b90e5ca98ea..c8ddb6fc323 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1392,12 +1392,12 @@ pg_stat_get_wal_receiver(PG_FUNCTION_ARGS)
/* Fetch values */
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see details.
- * Other users only get the pid value to know whether it is a WAL
- * receiver, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know whether
+ * it is a WAL receiver, but no details.
*/
MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
}
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index d9ab6d6de24..4daf1581bc6 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3486,12 +3486,12 @@ pg_stat_get_wal_senders(PG_FUNCTION_ARGS)
memset(nulls, 0, sizeof(nulls));
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see
- * details. Other users only get the pid value to know it's a
- * walsender, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know
+ * it's a walsender, but no details.
*/
MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 67f8b29434a..e2ce4582f19 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4864,6 +4864,8 @@ has_privs_of_role(Oid member, Oid role)
* Is member a member of role (directly or indirectly)?
*
* This is defined to recurse through roles regardless of rolinherit.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role(Oid member, Oid role)
@@ -4904,6 +4906,8 @@ check_is_member_of_role(Oid member, Oid role)
*
* This is identical to is_member_of_role except we ignore superuser
* status.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role_nosuper(Oid member, Oid role)
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..91ec2615cd5 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -112,12 +112,12 @@ calculate_database_size(Oid dbOid)
AclResult aclresult;
/*
- * User must have connect privilege for target database or be a member of
+ * User must have connect privilege for target database or have privileges of
* pg_read_all_stats
*/
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
if (aclresult != ACLCHECK_OK &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(dbOid));
@@ -196,12 +196,12 @@ calculate_tablespace_size(Oid tblspcOid)
AclResult aclresult;
/*
- * User must be a member of pg_read_all_stats or have CREATE privilege for
+ * User must have privileges of pg_read_all_stats or have CREATE privilege for
* target tablespace, either explicitly granted or implicitly because it
* is default for current database.
*/
if (tblspcOid != MyDatabaseTableSpace &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c436d9318b6..f87f77093a6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -58,11 +58,11 @@ convert_and_check_filename(text *arg)
canonicalize_path(filename); /* filename can change length here */
/*
- * Members of the 'pg_read_server_files' role are allowed to access any
- * files on the server as the PG user, so no need to do any further checks
+ * Roles with privleges of the 'pg_read_server_files' role are allowed to access
+ * any files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
return filename;
/*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index ff5aedc99cb..56762a7d98d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -34,7 +34,7 @@
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
-#define HAS_PGSTAT_PERMISSIONS(role) (is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
+#define HAS_PGSTAT_PERMISSIONS(role) (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
Datum
pg_stat_get_numscans(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfda..e400bfdea13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8154,10 +8154,10 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
return NULL;
if (restrict_privileged &&
(record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -8201,10 +8201,10 @@ GetConfigOptionResetString(const char *name)
record = find_option(name, false, false, ERROR);
Assert(record != NULL);
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -9448,7 +9448,7 @@ ShowAllGUCConfig(DestReceiver *dest)
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* assign to the values array */
@@ -9515,7 +9515,7 @@ get_explain_guc_options(int *num)
/* return only options visible to the current user */
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* return only options that are different from their boot values */
@@ -9597,10 +9597,10 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
}
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
if (varname)
@@ -9628,7 +9628,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
{
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
*noshow = true;
else
*noshow = false;
@@ -9823,7 +9823,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
* insufficiently-privileged users.
*/
if (conf->source == PGC_S_FILE &&
- is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
{
values[14] = conf->sourcefile;
snprintf(buffer, sizeof(buffer), "%d", conf->sourceline);
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index eb608fdc2ea..88b1ff843be 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,7 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or a member of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
--
2.31.1
On Wed, Oct 27, 2021 at 5:14 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
Attached is an updated version of the patch to replace
is_member_of_role with has_privs_for_role for predefined roles. It
does not remove is_member_of_role from acl.h but it does add a warning
not to use that function for privilege checking.Please consider this for the upcoming commitfest.
I am not sure I understand what the advantage of this is supposed to be.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Wed, Oct 27, 2021 at 5:16 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Oct 27, 2021 at 5:14 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:Attached is an updated version of the patch to replace
is_member_of_role with has_privs_for_role for predefined roles. It
does not remove is_member_of_role from acl.h but it does add a warning
not to use that function for privilege checking.Please consider this for the upcoming commitfest.
I am not sure I understand what the advantage of this is supposed to be.
Pre-defined roles currently do not operate the same way other roles do
with respect to inheritance. The patchfile has an explanation and
examples, I wasn't sure if that needed to be repeated in the email or
not.
On Wed, Oct 27, 2021 at 5:20 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
On Wed, Oct 27, 2021 at 5:16 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Oct 27, 2021 at 5:14 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:Attached is an updated version of the patch to replace
is_member_of_role with has_privs_for_role for predefined roles. It
does not remove is_member_of_role from acl.h but it does add a warning
not to use that function for privilege checking.Please consider this for the upcoming commitfest.
I am not sure I understand what the advantage of this is supposed to be.
Pre-defined roles currently do not operate the same way other roles do
with respect to inheritance. The patchfile has an explanation and
examples, I wasn't sure if that needed to be repeated in the email or
not.
And FWIW several predefined role patches on the list currently
correctly use has_privs_for_role rather than is_memver_of_role so this
brings the older roles to be consistent with the newer ones being
proposed.
On 10/27/21, 2:19 PM, "Robert Haas" <robertmhaas@gmail.com> wrote:
I am not sure I understand what the advantage of this is supposed to be.
There are a couple of other related threads with some additional
context:
/messages/by-id/20211026224731.115337-1-joshua.brindle@crunchydata.com
/messages/by-id/CAGB+Vh4enxvLBM_BJweWEO12Q0ySLMBWK9iOLaM7e=V1Y0YadA@mail.gmail.com
Nathan
Greetings,
* Joshua Brindle (joshua.brindle@crunchydata.com) wrote:
On Wed, Oct 27, 2021 at 5:20 PM Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
On Wed, Oct 27, 2021 at 5:16 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Oct 27, 2021 at 5:14 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:Attached is an updated version of the patch to replace
is_member_of_role with has_privs_for_role for predefined roles. It
does not remove is_member_of_role from acl.h but it does add a warning
not to use that function for privilege checking.Please consider this for the upcoming commitfest.
I am not sure I understand what the advantage of this is supposed to be.
Pre-defined roles currently do not operate the same way other roles do
with respect to inheritance. The patchfile has an explanation and
examples, I wasn't sure if that needed to be repeated in the email or
not.And FWIW several predefined role patches on the list currently
correctly use has_privs_for_role rather than is_memver_of_role so this
brings the older roles to be consistent with the newer ones being
proposed.
Right, we really should be consistent here and we're not and that's not
a good thing. Further, if you're logged in as an unprivileged role and
expect to not see things that you shouldn't, then it's not good for that
to end up happening because you've been GRANT'd a more privileged, but
noinherit, role that was given some predefined roles. This doesn't end
up being an actual security issue as you could just SET ROLE to that
more privileged role, so it's not that you couldn't see that data, just
that you should have had to SET ROLE to do so.
Reviewing the actual patch, it generally looks good to me except that
you missed updating this comment:
Superusers or members of pg_read_all_stats members are allowed
Thanks,
Stephen
On Mon, Nov 8, 2021 at 3:44 PM Stephen Frost <sfrost@snowman.net> wrote:
Greetings,
* Joshua Brindle (joshua.brindle@crunchydata.com) wrote:
On Wed, Oct 27, 2021 at 5:20 PM Joshua Brindle <joshua.brindle@crunchydata.com> wrote:
On Wed, Oct 27, 2021 at 5:16 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Oct 27, 2021 at 5:14 PM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:Attached is an updated version of the patch to replace
is_member_of_role with has_privs_for_role for predefined roles. It
does not remove is_member_of_role from acl.h but it does add a warning
not to use that function for privilege checking.Please consider this for the upcoming commitfest.
I am not sure I understand what the advantage of this is supposed to be.
Pre-defined roles currently do not operate the same way other roles do
with respect to inheritance. The patchfile has an explanation and
examples, I wasn't sure if that needed to be repeated in the email or
not.And FWIW several predefined role patches on the list currently
correctly use has_privs_for_role rather than is_memver_of_role so this
brings the older roles to be consistent with the newer ones being
proposed.Right, we really should be consistent here and we're not and that's not
a good thing. Further, if you're logged in as an unprivileged role and
expect to not see things that you shouldn't, then it's not good for that
to end up happening because you've been GRANT'd a more privileged, but
noinherit, role that was given some predefined roles. This doesn't end
up being an actual security issue as you could just SET ROLE to that
more privileged role, so it's not that you couldn't see that data, just
that you should have had to SET ROLE to do so.Reviewing the actual patch, it generally looks good to me except that
you missed updating this comment:Superusers or members of pg_read_all_stats members are allowed
Thanks for the review, attached is an update with that comment fixed
and also sgml documentation changes that I missed earlier.
Attachments:
0001-use-has_privs_for_roles-for-predefined-role-checks.patchapplication/octet-stream; name=0001-use-has_privs_for_roles-for-predefined-role-checks.patchDownload
From b0b1effeaaab81db65857d792a343c0079f87c79 Mon Sep 17 00:00:00 2001
From: Joshua Brindle <joshua.brindle@crunchydata.com>
Date: Tue, 26 Oct 2021 14:19:44 -0700
Subject: [PATCH] use has_privs_for_roles for predefined role checks
Generally if a role is granted membership to another role with NOINHERIT
they must use SET ROLE to access the privileges of that role, however
with predefined roles the membership and privilege is conflated, as
demonstrated by:
CREATE ROLE readrole;
CREATE ROLE role2 NOINHERIT;
CREATE ROLE brindle LOGIN;
GRANT role2 TO brindle;
CREATE TABLE foo(i INT);
GRANT readrole TO role2;
GRANT ALL ON TABLE foo TO readrole;
GRANT pg_read_all_stats,pg_read_all_settings,pg_read_server_files,pg_write_server_files,pg_execute_server_program TO role2;
Log in as brindle:
postgres=> select current_user;
current_user
--------------
brindle
(1 row)
postgres=> SELECT * FROM foo;
ERROR: permission denied for table foo
postgres=> SELECT DISTINCT query FROM pg_stat_activity;
query
----------------------------------------------
SELECT DISTINCT query FROM pg_stat_activity;
(2 rows)
postgres=> SET ROLE readrole;
SET
postgres=> SELECT * FROM foo;
i
Signed-off-by: Joshua Brindle <joshua.brindle@crunchydata.com>
---
contrib/adminpack/adminpack.c | 2 +-
contrib/file_fdw/file_fdw.c | 8 ++++----
contrib/file_fdw/output/file_fdw.source | 2 +-
.../pg_stat_statements/pg_stat_statements.c | 4 ++--
contrib/pgrowlocks/pgrowlocks.c | 2 +-
doc/src/sgml/catalogs.sgml | 12 +++++------
src/backend/commands/copy.c | 12 +++++------
src/backend/replication/walreceiver.c | 8 ++++----
src/backend/replication/walsender.c | 8 ++++----
src/backend/utils/adt/acl.c | 4 ++++
src/backend/utils/adt/dbsize.c | 8 ++++----
src/backend/utils/adt/genfile.c | 6 +++---
src/backend/utils/adt/pgstatfuncs.c | 2 +-
src/backend/utils/misc/guc.c | 20 +++++++++----------
.../unsafe_tests/expected/rolenames.out | 2 +-
15 files changed, 52 insertions(+), 48 deletions(-)
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index 48c17469104..0457d7b591d 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -79,7 +79,7 @@ convert_and_check_filename(text *arg)
* files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
return filename;
/*
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2c2f149fb01..a769b2ef7b9 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -269,16 +269,16 @@ file_fdw_validator(PG_FUNCTION_ARGS)
* otherwise there'd still be a security hole.
*/
if (strcmp(def->defname, "filename") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
if (strcmp(def->defname, "program") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ !has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
filename = defGetString(def);
}
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index 52b4d5f1df7..a1d44ed665a 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -436,7 +436,7 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 726ba59e2bf..f6120f5c48b 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1511,8 +1511,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
HASH_SEQ_STATUS hash_seq;
pgssEntry *entry;
- /* Superusers or members of pg_read_all_stats members are allowed */
- is_allowed_role = is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS);
+ /* Superusers or roles with the privileges of pg_read_all_stats members are allowed */
+ is_allowed_role = has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS);
/* hash table must exist already */
if (!pgss || !pgss_hash)
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 669a7d7730b..b9b724a554d 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -130,7 +130,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_SELECT);
if (aclresult != ACLCHECK_OK)
- aclresult = is_member_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
+ aclresult = has_privs_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1d11be73f7..87254c4b8bd 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9916,8 +9916,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<para>
By default, the <structname>pg_backend_memory_contexts</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with the privileges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
@@ -12358,7 +12358,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
Configuration file the current value was set in (null for
values set from sources other than configuration files, or when
- examined by a user who is neither a superuser or a member of
+ examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>); helpful when using
<literal>include</literal> directives in configuration files
</para></entry>
@@ -12371,7 +12371,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
Line number within the configuration file the current value was
set at (null for values set from sources other than configuration files,
- or when examined by a user who is neither a superuser or a member of
+ or when examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>).
</para></entry>
</row>
@@ -12747,8 +12747,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
By default, the <structname>pg_shmem_allocations</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with privilges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..e26ff42fd82 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -80,26 +80,26 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
{
if (stmt->is_program)
{
- if (!is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
else
{
- if (is_from && !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_read_server_files role to COPY from a file"),
+ errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
- if (!is_from && !is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_write_server_files role to COPY to a file"),
+ errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 7a7eb3784e7..9de72962322 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1402,12 +1402,12 @@ pg_stat_get_wal_receiver(PG_FUNCTION_ARGS)
/* Fetch values */
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see details.
- * Other users only get the pid value to know whether it is a WAL
- * receiver, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know whether
+ * it is a WAL receiver, but no details.
*/
MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
}
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index fff7dfc6409..c9b57f2ff36 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3487,12 +3487,12 @@ pg_stat_get_wal_senders(PG_FUNCTION_ARGS)
memset(nulls, 0, sizeof(nulls));
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see
- * details. Other users only get the pid value to know it's a
- * walsender, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know
+ * it's a walsender, but no details.
*/
MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 67f8b29434a..e2ce4582f19 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4864,6 +4864,8 @@ has_privs_of_role(Oid member, Oid role)
* Is member a member of role (directly or indirectly)?
*
* This is defined to recurse through roles regardless of rolinherit.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role(Oid member, Oid role)
@@ -4904,6 +4906,8 @@ check_is_member_of_role(Oid member, Oid role)
*
* This is identical to is_member_of_role except we ignore superuser
* status.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role_nosuper(Oid member, Oid role)
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..91ec2615cd5 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -112,12 +112,12 @@ calculate_database_size(Oid dbOid)
AclResult aclresult;
/*
- * User must have connect privilege for target database or be a member of
+ * User must have connect privilege for target database or have privileges of
* pg_read_all_stats
*/
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
if (aclresult != ACLCHECK_OK &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(dbOid));
@@ -196,12 +196,12 @@ calculate_tablespace_size(Oid tblspcOid)
AclResult aclresult;
/*
- * User must be a member of pg_read_all_stats or have CREATE privilege for
+ * User must have privileges of pg_read_all_stats or have CREATE privilege for
* target tablespace, either explicitly granted or implicitly because it
* is default for current database.
*/
if (tblspcOid != MyDatabaseTableSpace &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c436d9318b6..f87f77093a6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -58,11 +58,11 @@ convert_and_check_filename(text *arg)
canonicalize_path(filename); /* filename can change length here */
/*
- * Members of the 'pg_read_server_files' role are allowed to access any
- * files on the server as the PG user, so no need to do any further checks
+ * Roles with privleges of the 'pg_read_server_files' role are allowed to access
+ * any files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
return filename;
/*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index ff5aedc99cb..56762a7d98d 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -34,7 +34,7 @@
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
-#define HAS_PGSTAT_PERMISSIONS(role) (is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
+#define HAS_PGSTAT_PERMISSIONS(role) (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
Datum
pg_stat_get_numscans(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfda..e400bfdea13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8154,10 +8154,10 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
return NULL;
if (restrict_privileged &&
(record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -8201,10 +8201,10 @@ GetConfigOptionResetString(const char *name)
record = find_option(name, false, false, ERROR);
Assert(record != NULL);
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -9448,7 +9448,7 @@ ShowAllGUCConfig(DestReceiver *dest)
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* assign to the values array */
@@ -9515,7 +9515,7 @@ get_explain_guc_options(int *num)
/* return only options visible to the current user */
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* return only options that are different from their boot values */
@@ -9597,10 +9597,10 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
}
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
if (varname)
@@ -9628,7 +9628,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
{
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
*noshow = true;
else
*noshow = false;
@@ -9823,7 +9823,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
* insufficiently-privileged users.
*/
if (conf->source == PGC_S_FILE &&
- is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
{
values[14] = conf->sourcefile;
snprintf(buffer, sizeof(buffer), "%d", conf->sourceline);
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index eb608fdc2ea..88b1ff843be 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,7 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or a member of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
--
2.31.1
On 11/8/21, 2:19 PM, "Joshua Brindle" <joshua.brindle@crunchydata.com> wrote:
Thanks for the review, attached is an update with that comment fixed
and also sgml documentation changes that I missed earlier.
I think there are a number of documentation changes that are still
missing. I did a quick scan and saw the "is member of" language in
func.sgml, monitoring.sgml, pgbuffercache.sgml, pgfreespacemap.sgml,
pgrowlocks.sgml, pgstatstatements.sgml, and pgvisibility.sgml.
<para>
By default, the <structname>pg_shmem_allocations</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with privilges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
nitpick: "privileges" is misspelled.
Nathan
On Wed, Nov 10, 2021 at 12:45 PM Bossart, Nathan <bossartn@amazon.com> wrote:
On 11/8/21, 2:19 PM, "Joshua Brindle" <joshua.brindle@crunchydata.com> wrote:
Thanks for the review, attached is an update with that comment fixed
and also sgml documentation changes that I missed earlier.I think there are a number of documentation changes that are still
missing. I did a quick scan and saw the "is member of" language in
func.sgml, monitoring.sgml, pgbuffercache.sgml, pgfreespacemap.sgml,
pgrowlocks.sgml, pgstatstatements.sgml, and pgvisibility.sgml.
All of these and also adminpack.sgml updated. I think that is all of
them but docs broken across lines and irregular wording makes it
difficult.
<para> By default, the <structname>pg_shmem_allocations</structname> view can be - read only by superusers or members of the <literal>pg_read_all_stats</literal> - role. + read only by superusers or roles with privilges of the + <literal>pg_read_all_stats</literal> role. </para> </sect1>nitpick: "privileges" is misspelled.
Fixed, thanks for reviewing.
Attachments:
0001-use-has_privs_for_roles-for-predefined-role-checks.patchapplication/octet-stream; name=0001-use-has_privs_for_roles-for-predefined-role-checks.patchDownload
From 752a3dfb0094dc45a7614553f4fcec15aeaa1a6c Mon Sep 17 00:00:00 2001
From: Joshua Brindle <joshua.brindle@crunchydata.com>
Date: Tue, 26 Oct 2021 14:19:44 -0700
Subject: [PATCH] use has_privs_for_roles for predefined role checks
Generally if a role is granted membership to another role with NOINHERIT
they must use SET ROLE to access the privileges of that role, however
with predefined roles the membership and privilege is conflated, as
demonstrated by:
CREATE ROLE readrole;
CREATE ROLE role2 NOINHERIT;
CREATE ROLE brindle LOGIN;
GRANT role2 TO brindle;
CREATE TABLE foo(i INT);
GRANT readrole TO role2;
GRANT ALL ON TABLE foo TO readrole;
GRANT pg_read_all_stats,pg_read_all_settings,pg_read_server_files,pg_write_server_files,pg_execute_server_program TO role2;
Log in as brindle:
postgres=> select current_user;
current_user
--------------
brindle
(1 row)
postgres=> SELECT * FROM foo;
ERROR: permission denied for table foo
postgres=> SELECT DISTINCT query FROM pg_stat_activity;
query
----------------------------------------------
SELECT DISTINCT query FROM pg_stat_activity;
(2 rows)
postgres=> SET ROLE readrole;
SET
postgres=> SELECT * FROM foo;
i
Signed-off-by: Joshua Brindle <joshua.brindle@crunchydata.com>
---
contrib/adminpack/adminpack.c | 2 +-
contrib/file_fdw/file_fdw.c | 8 ++++----
contrib/file_fdw/output/file_fdw.source | 2 +-
.../pg_stat_statements/pg_stat_statements.c | 4 ++--
contrib/pgrowlocks/pgrowlocks.c | 2 +-
doc/src/sgml/adminpack.sgml | 6 +++---
doc/src/sgml/catalogs.sgml | 12 +++++------
doc/src/sgml/func.sgml | 12 +++++------
doc/src/sgml/monitoring.sgml | 2 +-
doc/src/sgml/pgbuffercache.sgml | 2 +-
doc/src/sgml/pgfreespacemap.sgml | 2 +-
doc/src/sgml/pgrowlocks.sgml | 2 +-
doc/src/sgml/pgstatstatements.sgml | 2 +-
doc/src/sgml/pgvisibility.sgml | 4 ++--
src/backend/commands/copy.c | 12 +++++------
src/backend/replication/walreceiver.c | 8 ++++----
src/backend/replication/walsender.c | 8 ++++----
src/backend/utils/adt/acl.c | 4 ++++
src/backend/utils/adt/dbsize.c | 8 ++++----
src/backend/utils/adt/genfile.c | 6 +++---
src/backend/utils/adt/pgstatfuncs.c | 2 +-
src/backend/utils/misc/guc.c | 20 +++++++++----------
.../unsafe_tests/expected/rolenames.out | 2 +-
23 files changed, 68 insertions(+), 64 deletions(-)
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index 48c17469104..0457d7b591d 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -79,7 +79,7 @@ convert_and_check_filename(text *arg)
* files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
return filename;
/*
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 2c2f149fb01..a769b2ef7b9 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -269,16 +269,16 @@ file_fdw_validator(PG_FUNCTION_ARGS)
* otherwise there'd still be a security hole.
*/
if (strcmp(def->defname, "filename") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
if (strcmp(def->defname, "program") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ !has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
filename = defGetString(def);
}
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index 52b4d5f1df7..a1d44ed665a 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -436,7 +436,7 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 726ba59e2bf..f6120f5c48b 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1511,8 +1511,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
HASH_SEQ_STATUS hash_seq;
pgssEntry *entry;
- /* Superusers or members of pg_read_all_stats members are allowed */
- is_allowed_role = is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS);
+ /* Superusers or roles with the privileges of pg_read_all_stats members are allowed */
+ is_allowed_role = has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS);
/* hash table must exist already */
if (!pgss || !pgss_hash)
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 669a7d7730b..b9b724a554d 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -130,7 +130,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_SELECT);
if (aclresult != ACLCHECK_OK)
- aclresult = is_member_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
+ aclresult = has_privs_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
diff --git a/doc/src/sgml/adminpack.sgml b/doc/src/sgml/adminpack.sgml
index 0dd89be3534..5702456cd25 100644
--- a/doc/src/sgml/adminpack.sgml
+++ b/doc/src/sgml/adminpack.sgml
@@ -22,9 +22,9 @@
functions in <xref linkend="functions-admin-genfile-table"/>, which
provide read-only access.)
Only files within the database cluster directory can be accessed, unless the
- user is a superuser or given one of the pg_read_server_files, or pg_write_server_files
- roles, as appropriate for the function, but either a relative or absolute path is
- allowable.
+ user is a superuser or given privileges of one of the pg_read_server_files,
+ or pg_write_server_files roles, as appropriate for the function, but either a
+ relative or absolute path is allowable.
</para>
<table id="functions-adminpack-table">
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c1d11be73f7..8cfff1da3be 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9916,8 +9916,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<para>
By default, the <structname>pg_backend_memory_contexts</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with the privileges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
@@ -12358,7 +12358,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
Configuration file the current value was set in (null for
values set from sources other than configuration files, or when
- examined by a user who is neither a superuser or a member of
+ examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>); helpful when using
<literal>include</literal> directives in configuration files
</para></entry>
@@ -12371,7 +12371,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
Line number within the configuration file the current value was
set at (null for values set from sources other than configuration files,
- or when examined by a user who is neither a superuser or a member of
+ or when examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>).
</para></entry>
</row>
@@ -12747,8 +12747,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
By default, the <structname>pg_shmem_allocations</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with privileges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24447c00177..3bad6b0d08e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25321,7 +25321,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
Cancels the current query of the session whose backend process has the
specified process ID. This is also allowed if the
calling role is a member of the role whose backend is being canceled or
- the calling role has been granted <literal>pg_signal_backend</literal>,
+ the calling role has privileges of <literal>pg_signal_backend</literal>,
however only superusers can cancel superuser backends.
</para></entry>
</row>
@@ -25392,7 +25392,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
Terminates the session whose backend process has the
specified process ID. This is also allowed if the calling role
is a member of the role whose backend is being terminated or the
- calling role has been granted <literal>pg_signal_backend</literal>,
+ calling role has privileges of <literal>pg_signal_backend</literal>,
however only superusers can terminate superuser backends.
</para>
<para>
@@ -26666,7 +26666,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
Computes the total disk space used by the database with the specified
name or OID. To use this function, you must
have <literal>CONNECT</literal> privilege on the specified database
- (which is granted by default) or be a member of
+ (which is granted by default) or have privileges of
the <literal>pg_read_all_stats</literal> role.
</para></entry>
</row>
@@ -26796,7 +26796,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
Computes the total disk space used in the tablespace with the
specified name or OID. To use this function, you must
have <literal>CREATE</literal> privilege on the specified tablespace
- or be a member of the <literal>pg_read_all_stats</literal> role,
+ or have privileges of the <literal>pg_read_all_stats</literal> role,
unless it is the default tablespace for the current database.
</para></entry>
</row>
@@ -27257,7 +27257,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
a dot, directories, and other special files are excluded.
</para>
<para>
- This function is restricted to superusers and members of
+ This function is restricted to superusers and roles with privileges of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
@@ -27281,7 +27281,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
are excluded.
</para>
<para>
- This function is restricted to superusers and members of
+ This function is restricted to superusers and roles with privileges of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 3173ec25660..5e603db45aa 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -280,7 +280,7 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
(sessions belonging to a role that they are a member of). In rows about
other sessions, many columns will be null. Note, however, that the
existence of a session and its general properties such as its sessions user
- and database are visible to all users. Superusers and members of the
+ and database are visible to all users. Superusers and roles with privileges of
built-in role <literal>pg_read_all_stats</literal> (see also <xref
linkend="predefined-roles"/>) can see all the information about all sessions.
</para>
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index e68d159d30f..a06fd3e26de 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -24,7 +24,7 @@
</para>
<para>
- By default, use is restricted to superusers and members of the
+ By default, use is restricted to superusers and roles with privileges of the
<literal>pg_monitor</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
diff --git a/doc/src/sgml/pgfreespacemap.sgml b/doc/src/sgml/pgfreespacemap.sgml
index 5025498249d..0f3aebd27ef 100644
--- a/doc/src/sgml/pgfreespacemap.sgml
+++ b/doc/src/sgml/pgfreespacemap.sgml
@@ -16,7 +16,7 @@
</para>
<para>
- By default use is restricted to superusers and members of the
+ By default use is restricted to superusers and roles with privileges of the
<literal>pg_stat_scan_tables</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
diff --git a/doc/src/sgml/pgrowlocks.sgml b/doc/src/sgml/pgrowlocks.sgml
index 392d5f1f9a7..2914bf6e6d6 100644
--- a/doc/src/sgml/pgrowlocks.sgml
+++ b/doc/src/sgml/pgrowlocks.sgml
@@ -13,7 +13,7 @@
</para>
<para>
- By default use is restricted to superusers, members of the
+ By default use is restricted to superusers, roles with privileges of the
<literal>pg_stat_scan_tables</literal> role, and users with
<literal>SELECT</literal> permissions on the table.
</para>
diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml
index bc9d5bdbe3b..3a7e36bd13c 100644
--- a/doc/src/sgml/pgstatstatements.sgml
+++ b/doc/src/sgml/pgstatstatements.sgml
@@ -384,7 +384,7 @@
</table>
<para>
- For security reasons, only superusers and members of the
+ For security reasons, only superusers and roles with privileges of the
<literal>pg_read_all_stats</literal> role are allowed to see the SQL text and
<structfield>queryid</structfield> of queries executed by other users.
Other users can see the statistics, however, if the view has been installed
diff --git a/doc/src/sgml/pgvisibility.sgml b/doc/src/sgml/pgvisibility.sgml
index 75336946a61..8090aa5207e 100644
--- a/doc/src/sgml/pgvisibility.sgml
+++ b/doc/src/sgml/pgvisibility.sgml
@@ -140,8 +140,8 @@
</variablelist>
<para>
- By default, these functions are executable only by superusers and members of the
- <literal>pg_stat_scan_tables</literal> role, with the exception of
+ By default, these functions are executable only by superusers and roles with privileges
+ of the <literal>pg_stat_scan_tables</literal> role, with the exception of
<function>pg_truncate_visibility_map(relation regclass)</function> which can only
be executed by superusers.
</para>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f48531419..e26ff42fd82 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -80,26 +80,26 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
{
if (stmt->is_program)
{
- if (!is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
else
{
- if (is_from && !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_read_server_files role to COPY from a file"),
+ errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
- if (!is_from && !is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_write_server_files role to COPY to a file"),
+ errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 7a7eb3784e7..9de72962322 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1402,12 +1402,12 @@ pg_stat_get_wal_receiver(PG_FUNCTION_ARGS)
/* Fetch values */
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see details.
- * Other users only get the pid value to know whether it is a WAL
- * receiver, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know whether
+ * it is a WAL receiver, but no details.
*/
MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
}
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 7950afb173c..5e2435a8563 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3488,12 +3488,12 @@ pg_stat_get_wal_senders(PG_FUNCTION_ARGS)
memset(nulls, 0, sizeof(nulls));
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see
- * details. Other users only get the pid value to know it's a
- * walsender, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know
+ * it's a walsender, but no details.
*/
MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 67f8b29434a..e2ce4582f19 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4864,6 +4864,8 @@ has_privs_of_role(Oid member, Oid role)
* Is member a member of role (directly or indirectly)?
*
* This is defined to recurse through roles regardless of rolinherit.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role(Oid member, Oid role)
@@ -4904,6 +4906,8 @@ check_is_member_of_role(Oid member, Oid role)
*
* This is identical to is_member_of_role except we ignore superuser
* status.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role_nosuper(Oid member, Oid role)
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3c..91ec2615cd5 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -112,12 +112,12 @@ calculate_database_size(Oid dbOid)
AclResult aclresult;
/*
- * User must have connect privilege for target database or be a member of
+ * User must have connect privilege for target database or have privileges of
* pg_read_all_stats
*/
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
if (aclresult != ACLCHECK_OK &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(dbOid));
@@ -196,12 +196,12 @@ calculate_tablespace_size(Oid tblspcOid)
AclResult aclresult;
/*
- * User must be a member of pg_read_all_stats or have CREATE privilege for
+ * User must have privileges of pg_read_all_stats or have CREATE privilege for
* target tablespace, either explicitly granted or implicitly because it
* is default for current database.
*/
if (tblspcOid != MyDatabaseTableSpace &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index c436d9318b6..f87f77093a6 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -58,11 +58,11 @@ convert_and_check_filename(text *arg)
canonicalize_path(filename); /* filename can change length here */
/*
- * Members of the 'pg_read_server_files' role are allowed to access any
- * files on the server as the PG user, so no need to do any further checks
+ * Roles with privleges of the 'pg_read_server_files' role are allowed to access
+ * any files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
return filename;
/*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index e64857e5409..8965208af18 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -34,7 +34,7 @@
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
-#define HAS_PGSTAT_PERMISSIONS(role) (is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
+#define HAS_PGSTAT_PERMISSIONS(role) (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
Datum
pg_stat_get_numscans(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e91d5a3cfda..e400bfdea13 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8154,10 +8154,10 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
return NULL;
if (restrict_privileged &&
(record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -8201,10 +8201,10 @@ GetConfigOptionResetString(const char *name)
record = find_option(name, false, false, ERROR);
Assert(record != NULL);
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -9448,7 +9448,7 @@ ShowAllGUCConfig(DestReceiver *dest)
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* assign to the values array */
@@ -9515,7 +9515,7 @@ get_explain_guc_options(int *num)
/* return only options visible to the current user */
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* return only options that are different from their boot values */
@@ -9597,10 +9597,10 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
}
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
if (varname)
@@ -9628,7 +9628,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
{
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
*noshow = true;
else
*noshow = false;
@@ -9823,7 +9823,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
* insufficiently-privileged users.
*/
if (conf->source == PGC_S_FILE &&
- is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
{
values[14] = conf->sourcefile;
snprintf(buffer, sizeof(buffer), "%d", conf->sourceline);
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index eb608fdc2ea..88b1ff843be 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,7 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or a member of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
--
2.31.1
On 11/12/21, 12:34 PM, "Joshua Brindle" <joshua.brindle@crunchydata.com> wrote:
All of these and also adminpack.sgml updated. I think that is all of
them but docs broken across lines and irregular wording makes it
difficult.
LGTM. I've marked this as ready-for-committer.
Nathan
"Bossart, Nathan" <bossartn@amazon.com> writes:
On 11/12/21, 12:34 PM, "Joshua Brindle" <joshua.brindle@crunchydata.com> wrote:
All of these and also adminpack.sgml updated. I think that is all of
them but docs broken across lines and irregular wording makes it
difficult.
LGTM. I've marked this as ready-for-committer.
This needs another rebase --- it's trying to adjust
file_fdw/output/file_fdw.source, which no longer exists
(fix the regular file_fdw.out, instead).
regards, tom lane
On Tue, Jan 4, 2022 at 3:56 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
On 11/12/21, 12:34 PM, "Joshua Brindle" <joshua.brindle@crunchydata.com> wrote:
All of these and also adminpack.sgml updated. I think that is all of
them but docs broken across lines and irregular wording makes it
difficult.LGTM. I've marked this as ready-for-committer.
This needs another rebase --- it's trying to adjust
file_fdw/output/file_fdw.source, which no longer exists
(fix the regular file_fdw.out, instead).regards, tom lane
Attached, thanks
Attachments:
v4-0001-use-has_privs_for_roles-for-predefined-role-checks.patchapplication/octet-stream; name=v4-0001-use-has_privs_for_roles-for-predefined-role-checks.patchDownload
From 9cf228081f90ffd7c7447907cca629c1c0ab365f Mon Sep 17 00:00:00 2001
From: Joshua Brindle <joshua.brindle@crunchydata.com>
Date: Tue, 26 Oct 2021 14:19:44 -0700
Subject: [PATCH] use has_privs_for_roles for predefined role checks
Generally if a role is granted membership to another role with NOINHERIT
they must use SET ROLE to access the privileges of that role, however
with predefined roles the membership and privilege is conflated, as
demonstrated by:
CREATE ROLE readrole;
CREATE ROLE role2 NOINHERIT;
CREATE ROLE brindle LOGIN;
GRANT role2 TO brindle;
CREATE TABLE foo(i INT);
GRANT readrole TO role2;
GRANT ALL ON TABLE foo TO readrole;
GRANT pg_read_all_stats,pg_read_all_settings,pg_read_server_files,pg_write_server_files,pg_execute_server_program TO role2;
Log in as brindle:
postgres=> select current_user;
current_user
--------------
brindle
(1 row)
postgres=> SELECT * FROM foo;
ERROR: permission denied for table foo
postgres=> SELECT DISTINCT query FROM pg_stat_activity;
query
----------------------------------------------
SELECT DISTINCT query FROM pg_stat_activity;
(2 rows)
postgres=> SET ROLE readrole;
SET
postgres=> SELECT * FROM foo;
i
Signed-off-by: Joshua Brindle <joshua.brindle@crunchydata.com>
---
contrib/adminpack/adminpack.c | 2 +-
contrib/file_fdw/expected/file_fdw.out | 2 +-
contrib/file_fdw/file_fdw.c | 8 ++++----
.../pg_stat_statements/pg_stat_statements.c | 4 ++--
contrib/pgrowlocks/pgrowlocks.c | 2 +-
doc/src/sgml/adminpack.sgml | 6 +++---
doc/src/sgml/catalogs.sgml | 12 +++++------
doc/src/sgml/func.sgml | 12 +++++------
doc/src/sgml/monitoring.sgml | 2 +-
doc/src/sgml/pgbuffercache.sgml | 2 +-
doc/src/sgml/pgfreespacemap.sgml | 2 +-
doc/src/sgml/pgrowlocks.sgml | 2 +-
doc/src/sgml/pgstatstatements.sgml | 2 +-
doc/src/sgml/pgvisibility.sgml | 4 ++--
src/backend/commands/copy.c | 12 +++++------
src/backend/replication/walreceiver.c | 8 ++++----
src/backend/replication/walsender.c | 8 ++++----
src/backend/utils/adt/acl.c | 4 ++++
src/backend/utils/adt/dbsize.c | 8 ++++----
src/backend/utils/adt/genfile.c | 6 +++---
src/backend/utils/adt/pgstatfuncs.c | 2 +-
src/backend/utils/misc/guc.c | 20 +++++++++----------
.../unsafe_tests/expected/rolenames.out | 2 +-
23 files changed, 68 insertions(+), 64 deletions(-)
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index 48c1746910..0457d7b591 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -79,7 +79,7 @@ convert_and_check_filename(text *arg)
* files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
return filename;
/*
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 891146fef3..c13b7ca307 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -461,7 +461,7 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 146b524076..2c55318d9e 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -269,16 +269,16 @@ file_fdw_validator(PG_FUNCTION_ARGS)
* otherwise there'd still be a security hole.
*/
if (strcmp(def->defname, "filename") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
if (strcmp(def->defname, "program") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ !has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
filename = defGetString(def);
}
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index bede662907..5f48be1f36 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1507,8 +1507,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
HASH_SEQ_STATUS hash_seq;
pgssEntry *entry;
- /* Superusers or members of pg_read_all_stats members are allowed */
- is_allowed_role = is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS);
+ /* Superusers or roles with the privileges of pg_read_all_stats members are allowed */
+ is_allowed_role = has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS);
/* hash table must exist already */
if (!pgss || !pgss_hash)
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index d8946dc510..3dd8aa7bbe 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -130,7 +130,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_SELECT);
if (aclresult != ACLCHECK_OK)
- aclresult = is_member_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
+ aclresult = has_privs_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
diff --git a/doc/src/sgml/adminpack.sgml b/doc/src/sgml/adminpack.sgml
index 0dd89be353..5702456cd2 100644
--- a/doc/src/sgml/adminpack.sgml
+++ b/doc/src/sgml/adminpack.sgml
@@ -22,9 +22,9 @@
functions in <xref linkend="functions-admin-genfile-table"/>, which
provide read-only access.)
Only files within the database cluster directory can be accessed, unless the
- user is a superuser or given one of the pg_read_server_files, or pg_write_server_files
- roles, as appropriate for the function, but either a relative or absolute path is
- allowable.
+ user is a superuser or given privileges of one of the pg_read_server_files,
+ or pg_write_server_files roles, as appropriate for the function, but either a
+ relative or absolute path is allowable.
</para>
<table id="functions-adminpack-table">
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 03e2537b07..5f5a034240 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9932,8 +9932,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<para>
By default, the <structname>pg_backend_memory_contexts</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with the privileges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
@@ -12374,7 +12374,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
Configuration file the current value was set in (null for
values set from sources other than configuration files, or when
- examined by a user who is neither a superuser or a member of
+ examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>); helpful when using
<literal>include</literal> directives in configuration files
</para></entry>
@@ -12387,7 +12387,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
Line number within the configuration file the current value was
set at (null for values set from sources other than configuration files,
- or when examined by a user who is neither a superuser or a member of
+ or when examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>).
</para></entry>
</row>
@@ -12763,8 +12763,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
By default, the <structname>pg_shmem_allocations</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with privileges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e58efce586..1f53f1e17d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25406,7 +25406,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
Cancels the current query of the session whose backend process has the
specified process ID. This is also allowed if the
calling role is a member of the role whose backend is being canceled or
- the calling role has been granted <literal>pg_signal_backend</literal>,
+ the calling role has privileges of <literal>pg_signal_backend</literal>,
however only superusers can cancel superuser backends.
</para></entry>
</row>
@@ -25477,7 +25477,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
Terminates the session whose backend process has the
specified process ID. This is also allowed if the calling role
is a member of the role whose backend is being terminated or the
- calling role has been granted <literal>pg_signal_backend</literal>,
+ calling role has privileges of <literal>pg_signal_backend</literal>,
however only superusers can terminate superuser backends.
</para>
<para>
@@ -26751,7 +26751,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
Computes the total disk space used by the database with the specified
name or OID. To use this function, you must
have <literal>CONNECT</literal> privilege on the specified database
- (which is granted by default) or be a member of
+ (which is granted by default) or have privileges of
the <literal>pg_read_all_stats</literal> role.
</para></entry>
</row>
@@ -26881,7 +26881,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
Computes the total disk space used in the tablespace with the
specified name or OID. To use this function, you must
have <literal>CREATE</literal> privilege on the specified tablespace
- or be a member of the <literal>pg_read_all_stats</literal> role,
+ or have privileges of the <literal>pg_read_all_stats</literal> role,
unless it is the default tablespace for the current database.
</para></entry>
</row>
@@ -27342,7 +27342,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
a dot, directories, and other special files are excluded.
</para>
<para>
- This function is restricted to superusers and members of
+ This function is restricted to superusers and roles with privileges of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
@@ -27366,7 +27366,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
are excluded.
</para>
<para>
- This function is restricted to superusers and members of
+ This function is restricted to superusers and roles with privileges of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 62f2a3332b..3f89ebe005 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -280,7 +280,7 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
(sessions belonging to a role that they are a member of). In rows about
other sessions, many columns will be null. Note, however, that the
existence of a session and its general properties such as its sessions user
- and database are visible to all users. Superusers and members of the
+ and database are visible to all users. Superusers and roles with privileges of
built-in role <literal>pg_read_all_stats</literal> (see also <xref
linkend="predefined-roles"/>) can see all the information about all sessions.
</para>
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index e68d159d30..a06fd3e26d 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -24,7 +24,7 @@
</para>
<para>
- By default, use is restricted to superusers and members of the
+ By default, use is restricted to superusers and roles with privileges of the
<literal>pg_monitor</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
diff --git a/doc/src/sgml/pgfreespacemap.sgml b/doc/src/sgml/pgfreespacemap.sgml
index 5025498249..0f3aebd27e 100644
--- a/doc/src/sgml/pgfreespacemap.sgml
+++ b/doc/src/sgml/pgfreespacemap.sgml
@@ -16,7 +16,7 @@
</para>
<para>
- By default use is restricted to superusers and members of the
+ By default use is restricted to superusers and roles with privileges of the
<literal>pg_stat_scan_tables</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
diff --git a/doc/src/sgml/pgrowlocks.sgml b/doc/src/sgml/pgrowlocks.sgml
index 392d5f1f9a..2914bf6e6d 100644
--- a/doc/src/sgml/pgrowlocks.sgml
+++ b/doc/src/sgml/pgrowlocks.sgml
@@ -13,7 +13,7 @@
</para>
<para>
- By default use is restricted to superusers, members of the
+ By default use is restricted to superusers, roles with privileges of the
<literal>pg_stat_scan_tables</literal> role, and users with
<literal>SELECT</literal> permissions on the table.
</para>
diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml
index bc9d5bdbe3..3a7e36bd13 100644
--- a/doc/src/sgml/pgstatstatements.sgml
+++ b/doc/src/sgml/pgstatstatements.sgml
@@ -384,7 +384,7 @@
</table>
<para>
- For security reasons, only superusers and members of the
+ For security reasons, only superusers and roles with privileges of the
<literal>pg_read_all_stats</literal> role are allowed to see the SQL text and
<structfield>queryid</structfield> of queries executed by other users.
Other users can see the statistics, however, if the view has been installed
diff --git a/doc/src/sgml/pgvisibility.sgml b/doc/src/sgml/pgvisibility.sgml
index 75336946a6..8090aa5207 100644
--- a/doc/src/sgml/pgvisibility.sgml
+++ b/doc/src/sgml/pgvisibility.sgml
@@ -140,8 +140,8 @@
</variablelist>
<para>
- By default, these functions are executable only by superusers and members of the
- <literal>pg_stat_scan_tables</literal> role, with the exception of
+ By default, these functions are executable only by superusers and roles with privileges
+ of the <literal>pg_stat_scan_tables</literal> role, with the exception of
<function>pg_truncate_visibility_map(relation regclass)</function> which can only
be executed by superusers.
</para>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 53f4853141..e26ff42fd8 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -80,26 +80,26 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
{
if (stmt->is_program)
{
- if (!is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
else
{
- if (is_from && !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_read_server_files role to COPY from a file"),
+ errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
- if (!is_from && !is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_write_server_files role to COPY to a file"),
+ errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 7a7eb3784e..9de7296232 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1402,12 +1402,12 @@ pg_stat_get_wal_receiver(PG_FUNCTION_ARGS)
/* Fetch values */
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see details.
- * Other users only get the pid value to know whether it is a WAL
- * receiver, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know whether
+ * it is a WAL receiver, but no details.
*/
MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
}
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 84915ed95b..c421b25a7b 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3489,12 +3489,12 @@ pg_stat_get_wal_senders(PG_FUNCTION_ARGS)
memset(nulls, 0, sizeof(nulls));
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see
- * details. Other users only get the pid value to know it's a
- * walsender, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know
+ * it's a walsender, but no details.
*/
MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 67f8b29434..e2ce4582f1 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4864,6 +4864,8 @@ has_privs_of_role(Oid member, Oid role)
* Is member a member of role (directly or indirectly)?
*
* This is defined to recurse through roles regardless of rolinherit.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role(Oid member, Oid role)
@@ -4904,6 +4906,8 @@ check_is_member_of_role(Oid member, Oid role)
*
* This is identical to is_member_of_role except we ignore superuser
* status.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role_nosuper(Oid member, Oid role)
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index d5a7fb13f3..91ec2615cd 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -112,12 +112,12 @@ calculate_database_size(Oid dbOid)
AclResult aclresult;
/*
- * User must have connect privilege for target database or be a member of
+ * User must have connect privilege for target database or have privileges of
* pg_read_all_stats
*/
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
if (aclresult != ACLCHECK_OK &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(dbOid));
@@ -196,12 +196,12 @@ calculate_tablespace_size(Oid tblspcOid)
AclResult aclresult;
/*
- * User must be a member of pg_read_all_stats or have CREATE privilege for
+ * User must have privileges of pg_read_all_stats or have CREATE privilege for
* target tablespace, either explicitly granted or implicitly because it
* is default for current database.
*/
if (tblspcOid != MyDatabaseTableSpace &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 027ed86400..6c538315ca 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -59,11 +59,11 @@ convert_and_check_filename(text *arg)
canonicalize_path(filename); /* filename can change length here */
/*
- * Members of the 'pg_read_server_files' role are allowed to access any
- * files on the server as the PG user, so no need to do any further checks
+ * Roles with privleges of the 'pg_read_server_files' role are allowed to access
+ * any files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
return filename;
/*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index f529c1561a..3e18a7dcc8 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -34,7 +34,7 @@
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
-#define HAS_PGSTAT_PERMISSIONS(role) (is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
+#define HAS_PGSTAT_PERMISSIONS(role) (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
Datum
pg_stat_get_numscans(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f9504d3aec..4f6774e049 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8160,10 +8160,10 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
return NULL;
if (restrict_privileged &&
(record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -8207,10 +8207,10 @@ GetConfigOptionResetString(const char *name)
record = find_option(name, false, false, ERROR);
Assert(record != NULL);
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -9461,7 +9461,7 @@ ShowAllGUCConfig(DestReceiver *dest)
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* assign to the values array */
@@ -9528,7 +9528,7 @@ get_explain_guc_options(int *num)
/* return only options visible to the current user */
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* return only options that are different from their boot values */
@@ -9610,10 +9610,10 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
}
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
if (varname)
@@ -9641,7 +9641,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
{
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
*noshow = true;
else
*noshow = false;
@@ -9836,7 +9836,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
* insufficiently-privileged users.
*/
if (conf->source == PGC_S_FILE &&
- is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
{
values[14] = conf->sourcefile;
snprintf(buffer, sizeof(buffer), "%d", conf->sourceline);
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index eb608fdc2e..88b1ff843b 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,7 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or a member of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
--
2.32.0 (Apple Git-132)
On 1/4/22 16:51, Joshua Brindle wrote:
On Tue, Jan 4, 2022 at 3:56 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Bossart, Nathan" <bossartn@amazon.com> writes:
On 11/12/21, 12:34 PM, "Joshua Brindle" <joshua.brindle@crunchydata.com> wrote:
All of these and also adminpack.sgml updated. I think that is all of
them but docs broken across lines and irregular wording makes it
difficult.LGTM. I've marked this as ready-for-committer.
This needs another rebase --- it's trying to adjust
file_fdw/output/file_fdw.source, which no longer exists
(fix the regular file_fdw.out, instead).regards, tom lane
Attached, thanks
I'd like to pick this patch up and see it through to commit/push.
Presumably that will include back-patching to all supported pg versions.
Before I go through the effort to back-patch, does anyone want to argue
that this should *not* be back-patched?
Thanks,
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
Joe Conway <mail@joeconway.com> writes:
I'd like to pick this patch up and see it through to commit/push.
Presumably that will include back-patching to all supported pg versions.
Before I go through the effort to back-patch, does anyone want to argue
that this should *not* be back-patched?
Hm, I'm -0.5 or so. I think changing security-related behaviors
in a stable branch is a hard sell unless you are closing a security
hole. This is a fine improvement for HEAD but I'm inclined to
leave the back branches alone.
regards, tom lane
On Sun, Feb 6, 2022 at 12:24 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Joe Conway <mail@joeconway.com> writes:
I'd like to pick this patch up and see it through to commit/push.
Presumably that will include back-patching to all supported pg versions.
Before I go through the effort to back-patch, does anyone want to argue
that this should *not* be back-patched?Hm, I'm -0.5 or so. I think changing security-related behaviors
in a stable branch is a hard sell unless you are closing a security
hole. This is a fine improvement for HEAD but I'm inclined to
leave the back branches alone.
I think the threshold to back-patch a clear behavior change is pretty
high, so I do not think it should be back-patched.
I am also not convinced that a sufficient argument has been made for
changing this in master. This isn't the only thread where NOINHERIT
has come up lately, and I have to admit that I'm not a fan. Let's
suppose that I have two users, let's say sunita and sri. In the real
world, Sunita is Sri's manager and needs to be able to perform actions
as Sri when Sri is out of the office, but it isn't desirable for
Sunita to have Sri's privileges in all situations all the time. So we
mark role sunita as NOINHERIT and grant sri to sunita. Then it turns
out that Sunita also needs to be COPY TO/FROM PROGRAM, so we give her
pg_execute_server_program. Now, if she can't exercise this privilege
without setting role to the prefined role, that's bad, isn't it? I
mean, we want her to be able to copy between *her* tables and various
shell commands, not the tables owned by pg_execute_server_program, of
which there are presumably none.
It seems to me that the INHERIT role flag isn't very well-considered.
Inheritance, or the lack of it, ought to be decided separately for
each inherited role. However, that would be a major architectural
change. But in the absence of that, it seems clearly better for
predefined roles to disregard INHERIT and just always grant the rights
they are intended to give. Because if we don't do that, then we end up
with people having to SET ROLE to the predefined role and perform
actions directly as that role, which seems like it can't be what we
want. I almost feel like we ought to be looking for ways of preventing
people from doing SET ROLE to a predefined role altogether, not
encouraging them to do it.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 2/7/22 10:35, Robert Haas wrote:
On Sun, Feb 6, 2022 at 12:24 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Joe Conway <mail@joeconway.com> writes:
I'd like to pick this patch up and see it through to commit/push.
Presumably that will include back-patching to all supported pg versions.
Before I go through the effort to back-patch, does anyone want to argue
that this should *not* be back-patched?Hm, I'm -0.5 or so. I think changing security-related behaviors
in a stable branch is a hard sell unless you are closing a security
hole. This is a fine improvement for HEAD but I'm inclined to
leave the back branches alone.I think the threshold to back-patch a clear behavior change is pretty
high, so I do not think it should be back-patched.
ok
I am also not convinced that a sufficient argument has been made for
changing this in master. This isn't the only thread where NOINHERIT
has come up lately, and I have to admit that I'm not a fan. Let's
suppose that I have two users, let's say sunita and sri. In the real
world, Sunita is Sri's manager and needs to be able to perform actions
as Sri when Sri is out of the office, but it isn't desirable for
Sunita to have Sri's privileges in all situations all the time. So we
mark role sunita as NOINHERIT and grant sri to sunita. Then it turns
out that Sunita also needs to be COPY TO/FROM PROGRAM, so we give her
pg_execute_server_program. Now, if she can't exercise this privilege
without setting role to the prefined role, that's bad, isn't it? I
mean, we want her to be able to copy between *her* tables and various
shell commands, not the tables owned by pg_execute_server_program, of
which there are presumably none.
Easily worked around with one additional level of role:
8<---------------------------------------
nmx=# create user sunita;
CREATE ROLE
nmx=# create user sri superuser;
CREATE ROLE
nmx=# create user sri_alt noinherit;
CREATE ROLE
nmx=# grant sri to sri_alt;
GRANT ROLE
nmx=# grant sri_alt to sunita;
GRANT ROLE
nmx=# grant pg_execute_server_program to sunita;
GRANT ROLE
nmx=# set session authorization sri;
SET
nmx=# create table foo(id int);
CREATE TABLE
nmx=# insert into foo values(42);
INSERT 0 1
nmx=# set session authorization sunita;
SET
nmx=> select * from foo;
ERROR: permission denied for table foo
nmx=> set role sri;
SET
nmx=# select * from foo;
id
----
42
(1 row)
nmx=# reset role;
RESET
nmx=> select current_user;
current_user
--------------
sunita
(1 row)
nmx=> create temp table sfoo(f1 text);
CREATE TABLE
nmx=> copy sfoo(f1) from program 'id';
COPY 1
nmx=> select f1 from sfoo;
f1
----------------------------------------------------------------------------------------
uid=1001(postgres) gid=1001(postgres)
groups=1001(postgres),108(ssl-cert),1002(pgconf)
(1 row)
8<---------------------------------------
It seems to me that the INHERIT role flag isn't very well-considered.
Inheritance, or the lack of it, ought to be decided separately for
each inherited role. However, that would be a major architectural
change.
Agreed -- that would be useful.
But in the absence of that, it seems clearly better for predefined
roles to disregard INHERIT and just always grant the rights they are
intended to give. Because if we don't do that, then we end up with
people having to SET ROLE to the predefined role and perform actions
directly as that role, which seems like it can't be what we want. I
almost feel like we ought to be looking for ways of preventing people
from doing SET ROLE to a predefined role altogether, not encouraging
them to do it.
I disagree with this though.
It is confusing and IMHO dangerous that the predefined roles currently
work differently than regular roles eith respect to privilege inheritance.
In fact, I would extend that argument to the pseudo-role PUBLIC.
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
On Mon, Feb 7, 2022 at 11:13 AM Joe Conway <mail@joeconway.com> wrote:
Easily worked around with one additional level of role:
Interesting.
But in the absence of that, it seems clearly better for predefined
roles to disregard INHERIT and just always grant the rights they are
intended to give. Because if we don't do that, then we end up with
people having to SET ROLE to the predefined role and perform actions
directly as that role, which seems like it can't be what we want. I
almost feel like we ought to be looking for ways of preventing people
from doing SET ROLE to a predefined role altogether, not encouraging
them to do it.I disagree with this though.
It is confusing and IMHO dangerous that the predefined roles currently
work differently than regular roles eith respect to privilege inheritance.
I feel like that's kind of a conclusory statement, as opposed to
making an argument. I mean that this tells me something about how you
feel, but it doesn't really help me understand why you feel that way.
I suppose one argument in favor of your position is that if it
happened to be sri who was granted a predefined role, sunita would
inherit the rest of sr's privileges only with SET ROLE, but the
predefined role either way (IIUC, which I might not). If that's so,
then I guess I see the point, but I'm still sort of inclined to think
we're just trading one set of problems in for a different set. I just
have such a hard time imaging anyone using NOINHERIT in anger and
being happy with the result....
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Feb 7, 2022 at 12:09 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Feb 7, 2022 at 11:13 AM Joe Conway <mail@joeconway.com> wrote:
Easily worked around with one additional level of role:
Interesting.
But in the absence of that, it seems clearly better for predefined
roles to disregard INHERIT and just always grant the rights they are
intended to give. Because if we don't do that, then we end up with
people having to SET ROLE to the predefined role and perform actions
directly as that role, which seems like it can't be what we want. I
almost feel like we ought to be looking for ways of preventing people
from doing SET ROLE to a predefined role altogether, not encouraging
them to do it.I disagree with this though.
It is confusing and IMHO dangerous that the predefined roles currently
work differently than regular roles eith respect to privilege inheritance.I feel like that's kind of a conclusory statement, as opposed to
making an argument. I mean that this tells me something about how you
feel, but it doesn't really help me understand why you feel that way.I suppose one argument in favor of your position is that if it
happened to be sri who was granted a predefined role, sunita would
inherit the rest of sr's privileges only with SET ROLE, but the
predefined role either way (IIUC, which I might not). If that's so,
then I guess I see the point, but I'm still sort of inclined to think
we're just trading one set of problems in for a different set. I just
have such a hard time imaging anyone using NOINHERIT in anger and
being happy with the result....
IMO this is inarguably a plain bug. The inheritance system works one
way for pre-defined roles and another way for other roles - and the
difference isn't even documented.
The question is whether there is a security issue warranting back
patching, which is a bit of a tossup I think. According to git history
it's always worked this way, and the possible breakage of pre-existing
clusters seems maybe not worth it.
On 2/7/22 12:09, Robert Haas wrote:
On Mon, Feb 7, 2022 at 11:13 AM Joe Conway <mail@joeconway.com> wrote:
It is confusing and IMHO dangerous that the predefined roles currently
work differently than regular roles eith respect to privilege inheritance.I feel like that's kind of a conclusory statement, as opposed to
making an argument. I mean that this tells me something about how you
feel, but it doesn't really help me understand why you feel that way.
The argument is that we call these things "predefined roles", but they
do not behave the same way normal "roles" behave.
Someone not intimately familiar with that fact could easily make bad
assumptions, and therefore wind up with misconfigured security settings.
In other words, it violates the principle of least astonishment (POLA).
As Joshua said nearby, it simply jumps out at me as a bug.
-------
After more thought, perhaps the real problem is that these things should
not have been called "predefined roles" at all. I know, the horse has
already left the barn on that, but in any case...
They are (to me at least) similar in concept to Linux capabilities in
that they allow roles other than superusers to do a certain subset of
the things historically reserved for superusers through a special
mechanism (hardcoded) rather than through the normal privilege system
(GRANTS/ACLs).
As an example, the predefined role pg_read_all_settings allows a
non-superuser to read GUC normally reserved for superuser access only.
If I create a new user "bob" with the default INHERIT attribute, and I
grant postgres to bob, bob must SET ROLE to postgres in order to access
the capability to read superuser settings.
This is similar to bob's access to the default superuser privilege to
read data in someone else's table (must SET ROLE to access that capability).
But it is different from bob's access to inherited privileges which are
GRANTed:
8<--------------------------
psql nmx
psql (15devel)
Type "help" for help.
nmx=# create user bob;
CREATE ROLE
nmx=# grant postgres to bob;
GRANT ROLE
nmx=# \q
8<--------------------------
-and-
8<--------------------------
psql -U bob nmx
psql (15devel)
Type "help" for help.
nmx=> select current_user;
current_user
--------------
bob
(1 row)
nmx=> show stats_temp_directory;
ERROR: must be superuser or have privileges of pg_read_all_settings to
examine "stats_temp_directory"
nmx=> set role postgres;
SET
nmx=# show stats_temp_directory;
stats_temp_directory
----------------------
pg_stat_tmp
(1 row)
nmx=# select current_user;
current_user
--------------
postgres
(1 row)
nmx=# select * from foo;
id
----
42
(1 row)
nmx=# reset role;
RESET
nmx=> select current_user;
current_user
--------------
bob
(1 row)
nmx=> select * from foo;
ERROR: permission denied for table foo
nmx=> set role postgres;
SET
nmx=# grant select on table foo to postgres;
GRANT
nmx=# reset role;
RESET
nmx=> select current_user;
current_user
--------------
bob
(1 row)
nmx=> select * from foo;
id
----
42
(1 row)
8<--------------------------
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
On Tue, Feb 8, 2022 at 6:59 AM Joe Conway <mail@joeconway.com> wrote:
This is similar to bob's access to the default superuser privilege to
read data in someone else's table (must SET ROLE to access that capability).But it is different from bob's access to inherited privileges which are
GRANTed:
Yeah. I think right here you've put your finger on what's been bugging
me about this: it's similar to one thing, and it's different from
another. To you and Joshua and Stephen, it seems 100% obvious that
these roles should work like grants of other roles. But I think of
them as capabilities derived from the superuser account, and so I'm
sort of tempted to think that they should work the way the superuser
bit does. And that's why I don't think the fact that they work the
other way is "just a bug" -- it's one of two possible ways that
someone could think that it ought to work based on how other things in
the system actually do work.
I'm not hard stuck on the idea that the current behavior is right, but
I don't think that we can really say that we've made things fully
consistent unless we make things like SUPERUSER and BYPASSRLS work the
same way that you want to make predefined roles work. And probably do
something about the INHERIT flag too because the current situation
seems like a hot mess.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Feb 8, 2022 at 8:46 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Feb 8, 2022 at 6:59 AM Joe Conway <mail@joeconway.com> wrote:
This is similar to bob's access to the default superuser privilege to
read data in someone else's table (must SET ROLE to access that capability).But it is different from bob's access to inherited privileges which are
GRANTed:Yeah. I think right here you've put your finger on what's been bugging
me about this: it's similar to one thing, and it's different from
another. To you and Joshua and Stephen, it seems 100% obvious that
these roles should work like grants of other roles. But I think of
them as capabilities derived from the superuser account, and so I'm
sort of tempted to think that they should work the way the superuser
bit does. And that's why I don't think the fact that they work the
other way is "just a bug" -- it's one of two possible ways that
someone could think that it ought to work based on how other things in
the system actually do work.I'm not hard stuck on the idea that the current behavior is right, but
I don't think that we can really say that we've made things fully
consistent unless we make things like SUPERUSER and BYPASSRLS work the
same way that you want to make predefined roles work. And probably do
something about the INHERIT flag too because the current situation
seems like a hot mess.
I think hot mess is an apt description of the current situation, for
example consider that:
src/backend/catalog/aclchk.c
3931: has_privs_of_role(roleid, ROLE_PG_READ_ALL_DATA))
3943: has_privs_of_role(roleid, ROLE_PG_WRITE_ALL_DATA))
4279: (has_privs_of_role(roleid, ROLE_PG_READ_ALL_DATA) ||
4280: has_privs_of_role(roleid, ROLE_PG_WRITE_ALL_DATA)))
src/backend/storage/ipc/signalfuncs.c
82: if (!has_privs_of_role(GetUserId(), proc->roleId) &&
83: !has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND))
src/backend/storage/ipc/procarray.c
3843: if (!has_privs_of_role(GetUserId(), proc->roleId) &&
3844: !has_privs_of_role(GetUserId(), ROLE_PG_SIGNAL_BACKEND))
src/backend/tcop/utility.c
943: if (!has_privs_of_role(GetUserId(), ROLE_PG_CHECKPOINTER))
4 predefined roles currently use has_privs_of_role in master.
Further, pg_monitor, as an SQL-only predefined role, also behaves
consistently with the INHERIT rules that other roles do.
In order for SQL-only predefined roles to ignore INHERIT we would need
to hardcode bypasses for them, which IMO seems like the worst possible
solution to the current inconsistency.
On Tue, Feb 8, 2022 at 10:00 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:
4 predefined roles currently use has_privs_of_role in master.
Further, pg_monitor, as an SQL-only predefined role, also behaves
consistently with the INHERIT rules that other roles do.In order for SQL-only predefined roles to ignore INHERIT we would need
to hardcode bypasses for them, which IMO seems like the worst possible
solution to the current inconsistency.
I agree we need to make the situation consistent. But if you think
there's exactly one problem here and this patch fixes it, I
emphatically disagree.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 2/8/22 10:07, Robert Haas wrote:
On Tue, Feb 8, 2022 at 10:00 AM Joshua Brindle
<joshua.brindle@crunchydata.com> wrote:4 predefined roles currently use has_privs_of_role in master.
Further, pg_monitor, as an SQL-only predefined role, also behaves
consistently with the INHERIT rules that other roles do.In order for SQL-only predefined roles to ignore INHERIT we would need
to hardcode bypasses for them, which IMO seems like the worst possible
solution to the current inconsistency.I agree we need to make the situation consistent. But if you think
there's exactly one problem here and this patch fixes it, I
emphatically disagree.
If we were to start all over again with this feature my vote would be to
do things differently than we have done. I would not have called them
predefined roles, and I would have used attributes of roles (e.g. make
rolsuper into a bitmap rather than a boolean) rather than role
membership to implement them. But I didn't find time to participate in
the original discussion or review/write the code, so I have little room
to complain.
However since we did call these things predefined roles, and used role
membership to implement them, I think they ought to behave both
self-consistently as a group, and like other real roles.
That is what this patch does unless I am missing something.
I guess an alternative is to discuss a "proper fix", but it seems to me
that would be a version 16 thing given how late we are in this
development cycle and how invasive it is likely to be. And doing nothing
for pg15 is not a very satisfying proposition :-/
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
On Tue, Feb 8, 2022 at 7:38 PM Joe Conway <mail@joeconway.com> wrote:
If we were to start all over again with this feature my vote would be to
do things differently than we have done. I would not have called them
predefined roles, and I would have used attributes of roles (e.g. make
rolsuper into a bitmap rather than a boolean) rather than role
membership to implement them. But I didn't find time to participate in
the original discussion or review/write the code, so I have little room
to complain.
Yep, fair. I kind of like the predefined role concept myself. I find
it sort of elegant, mostly because I think it scales better than a
bitmask, which can run out of bits surprisingly rapidly. But opinions
can vary, of course.
However since we did call these things predefined roles, and used role
membership to implement them, I think they ought to behave both
self-consistently as a group, and like other real roles.That is what this patch does unless I am missing something.
Yes, I see that.
I guess an alternative is to discuss a "proper fix", but it seems to me
that would be a version 16 thing given how late we are in this
development cycle and how invasive it is likely to be. And doing nothing
for pg15 is not a very satisfying proposition :-/
True. The fact that we don't have consistency among existing
predefined roles is IMHO the best argument for this patch. That surely
can't be right.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Feb 08, 2022 at 10:54:50PM -0500, Robert Haas wrote:
On Tue, Feb 8, 2022 at 7:38 PM Joe Conway <mail@joeconway.com> wrote:
If we were to start all over again with this feature my vote would be to
do things differently than we have done. I would not have called them
predefined roles, and I would have used attributes of roles (e.g. make
rolsuper into a bitmap rather than a boolean) rather than role
membership to implement them. But I didn't find time to participate in
the original discussion or review/write the code, so I have little room
to complain.Yep, fair. I kind of like the predefined role concept myself. I find
it sort of elegant, mostly because I think it scales better than a
bitmask, which can run out of bits surprisingly rapidly. But opinions
can vary, of course.
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Wed, Feb 9, 2022 at 1:13 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.
I couldn't agree more. Apparently it's even confusing to developers,
because otherwise (1) we wouldn't have the problem the patch proposes
to fix in the first place and (2) I would have immediately been
convinced of the value of the patch once it showed up. Since those
things didn't happen, this is apparently confusing to (1) whoever
wrote the code that this patch fixes and (2) me.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 2/9/22 13:13, Nathan Bossart wrote:
On Tue, Feb 08, 2022 at 10:54:50PM -0500, Robert Haas wrote:
On Tue, Feb 8, 2022 at 7:38 PM Joe Conway <mail@joeconway.com> wrote:
If we were to start all over again with this feature my vote would be to
do things differently than we have done. I would not have called them
predefined roles, and I would have used attributes of roles (e.g. make
rolsuper into a bitmap rather than a boolean) rather than role
membership to implement them. But I didn't find time to participate in
the original discussion or review/write the code, so I have little room
to complain.Yep, fair. I kind of like the predefined role concept myself. I find
it sort of elegant, mostly because I think it scales better than a
bitmask, which can run out of bits surprisingly rapidly. But opinions
can vary, of course.I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.
Yep, I was suggesting that the latter would have been preferable to me
while Robert seemed to prefer the former. Honestly I could be happy with
either of those solutions, but as I alluded to that is probably a
discussion for the next development cycle since I don't see us doing
that big a change in this one.
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
On Wed, Feb 9, 2022 at 3:58 PM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Feb 9, 2022 at 1:13 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.I couldn't agree more. Apparently it's even confusing to developers,
because otherwise (1) we wouldn't have the problem the patch proposes
to fix in the first place and (2) I would have immediately been
convinced of the value of the patch once it showed up. Since those
things didn't happen, this is apparently confusing to (1) whoever
wrote the code that this patch fixes and (2) me.
My original patch removed is_member_of to address #1 above, but that
was rejected[1]. There is now a warning in the header beside it to
hopefully dissuade improper usage going forward.
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.Yep, I was suggesting that the latter would have been preferable to me while
Robert seemed to prefer the former. Honestly I could be happy with either of
those solutions, but as I alluded to that is probably a discussion for the
next development cycle since I don't see us doing that big a change in this
one.
I agree. I still think Joshua's proposed patch is a worthwhile improvement
for v15.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.Yep, I was suggesting that the latter would have been preferable to me while
Robert seemed to prefer the former. Honestly I could be happy with either of
those solutions, but as I alluded to that is probably a discussion for the
next development cycle since I don't see us doing that big a change in this
one.I agree. I still think Joshua's proposed patch is a worthwhile improvement
for v15.
+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
On Thu, Feb 10, 2022 at 2:37 PM Joe Conway <mail@joeconway.com> wrote:
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.Yep, I was suggesting that the latter would have been preferable to me while
Robert seemed to prefer the former. Honestly I could be happy with either of
those solutions, but as I alluded to that is probably a discussion for the
next development cycle since I don't see us doing that big a change in this
one.I agree. I still think Joshua's proposed patch is a worthwhile improvement
for v15.+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.
Rebased patch to apply to master attached.
Attachments:
v5-0001-use-has_privs_for_roles-for-predefined-role-checks.patchapplication/octet-stream; name=v5-0001-use-has_privs_for_roles-for-predefined-role-checks.patchDownload
From 6e6595511c22736664c7bfa5983782e061303c26 Mon Sep 17 00:00:00 2001
From: Joshua Brindle <joshua.brindle@crunchydata.com>
Date: Tue, 26 Oct 2021 14:19:44 -0700
Subject: [PATCH] use has_privs_for_roles for predefined role checks
Generally if a role is granted membership to another role with NOINHERIT
they must use SET ROLE to access the privileges of that role, however
with predefined roles the membership and privilege is conflated, as
demonstrated by:
CREATE ROLE readrole;
CREATE ROLE role2 NOINHERIT;
CREATE ROLE brindle LOGIN;
GRANT role2 TO brindle;
CREATE TABLE foo(i INT);
GRANT readrole TO role2;
GRANT ALL ON TABLE foo TO readrole;
GRANT pg_read_all_stats,pg_read_all_settings,pg_read_server_files,pg_write_server_files,pg_execute_server_program TO role2;
Log in as brindle:
postgres=> select current_user;
current_user
--------------
brindle
(1 row)
postgres=> SELECT * FROM foo;
ERROR: permission denied for table foo
postgres=> SELECT DISTINCT query FROM pg_stat_activity;
query
----------------------------------------------
SELECT DISTINCT query FROM pg_stat_activity;
(2 rows)
postgres=> SET ROLE readrole;
SET
postgres=> SELECT * FROM foo;
i
Signed-off-by: Joshua Brindle <joshua.brindle@crunchydata.com>
---
contrib/adminpack/adminpack.c | 2 +-
contrib/file_fdw/expected/file_fdw.out | 2 +-
contrib/file_fdw/file_fdw.c | 8 ++++----
.../pg_stat_statements/pg_stat_statements.c | 4 ++--
contrib/pgrowlocks/pgrowlocks.c | 2 +-
doc/src/sgml/adminpack.sgml | 6 +++---
doc/src/sgml/catalogs.sgml | 12 +++++------
doc/src/sgml/func.sgml | 12 +++++------
doc/src/sgml/monitoring.sgml | 2 +-
doc/src/sgml/pgbuffercache.sgml | 2 +-
doc/src/sgml/pgfreespacemap.sgml | 2 +-
doc/src/sgml/pgrowlocks.sgml | 2 +-
doc/src/sgml/pgstatstatements.sgml | 2 +-
doc/src/sgml/pgvisibility.sgml | 4 ++--
src/backend/commands/copy.c | 12 +++++------
src/backend/replication/walreceiver.c | 8 ++++----
src/backend/replication/walsender.c | 8 ++++----
src/backend/utils/adt/acl.c | 4 ++++
src/backend/utils/adt/dbsize.c | 8 ++++----
src/backend/utils/adt/genfile.c | 6 +++---
src/backend/utils/adt/pgstatfuncs.c | 2 +-
src/backend/utils/misc/guc.c | 20 +++++++++----------
.../unsafe_tests/expected/rolenames.out | 2 +-
23 files changed, 68 insertions(+), 64 deletions(-)
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index d7d84d096f7..03addf1dc5f 100644
--- a/contrib/adminpack/adminpack.c
+++ b/contrib/adminpack/adminpack.c
@@ -79,7 +79,7 @@ convert_and_check_filename(text *arg)
* files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
return filename;
/*
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 0ac6e4e0d73..14acdb27e5b 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -459,7 +459,7 @@ ALTER FOREIGN TABLE agg_text OWNER TO regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
-ERROR: only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
+ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index db08593d97f..4773cadec09 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -269,16 +269,16 @@ file_fdw_validator(PG_FUNCTION_ARGS)
* otherwise there'd still be a security hole.
*/
if (strcmp(def->defname, "filename") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
if (strcmp(def->defname, "program") == 0 &&
- !is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ !has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("only superuser or a member of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
+ errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
filename = defGetString(def);
}
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index d803253ceaf..11dd673bdd5 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -1507,8 +1507,8 @@ pg_stat_statements_internal(FunctionCallInfo fcinfo,
HASH_SEQ_STATUS hash_seq;
pgssEntry *entry;
- /* Superusers or members of pg_read_all_stats members are allowed */
- is_allowed_role = is_member_of_role(userid, ROLE_PG_READ_ALL_STATS);
+ /* Superusers or roles with the privileges of pg_read_all_stats members are allowed */
+ is_allowed_role = has_privs_of_role(userid, ROLE_PG_READ_ALL_STATS);
/* hash table must exist already */
if (!pgss || !pgss_hash)
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index d8946dc5107..3dd8aa7bbe4 100644
--- a/contrib/pgrowlocks/pgrowlocks.c
+++ b/contrib/pgrowlocks/pgrowlocks.c
@@ -130,7 +130,7 @@ pgrowlocks(PG_FUNCTION_ARGS)
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_SELECT);
if (aclresult != ACLCHECK_OK)
- aclresult = is_member_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
+ aclresult = has_privs_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
diff --git a/doc/src/sgml/adminpack.sgml b/doc/src/sgml/adminpack.sgml
index 0dd89be3534..5702456cd25 100644
--- a/doc/src/sgml/adminpack.sgml
+++ b/doc/src/sgml/adminpack.sgml
@@ -22,9 +22,9 @@
functions in <xref linkend="functions-admin-genfile-table"/>, which
provide read-only access.)
Only files within the database cluster directory can be accessed, unless the
- user is a superuser or given one of the pg_read_server_files, or pg_write_server_files
- roles, as appropriate for the function, but either a relative or absolute path is
- allowable.
+ user is a superuser or given privileges of one of the pg_read_server_files,
+ or pg_write_server_files roles, as appropriate for the function, but either a
+ relative or absolute path is allowable.
</para>
<table id="functions-adminpack-table">
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 83987a99045..5e909536467 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9978,8 +9978,8 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<para>
By default, the <structname>pg_backend_memory_contexts</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with the privileges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
@@ -12420,7 +12420,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
Configuration file the current value was set in (null for
values set from sources other than configuration files, or when
- examined by a user who is neither a superuser or a member of
+ examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>); helpful when using
<literal>include</literal> directives in configuration files
</para></entry>
@@ -12433,7 +12433,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
Line number within the configuration file the current value was
set at (null for values set from sources other than configuration files,
- or when examined by a user who is neither a superuser or a member of
+ or when examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>).
</para></entry>
</row>
@@ -12809,8 +12809,8 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<para>
By default, the <structname>pg_shmem_allocations</structname> view can be
- read only by superusers or members of the <literal>pg_read_all_stats</literal>
- role.
+ read only by superusers or roles with privileges of the
+ <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index df3cd5987b7..17fc4e8bbb0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25435,7 +25435,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
Cancels the current query of the session whose backend process has the
specified process ID. This is also allowed if the
calling role is a member of the role whose backend is being canceled or
- the calling role has been granted <literal>pg_signal_backend</literal>,
+ the calling role has privileges of <literal>pg_signal_backend</literal>,
however only superusers can cancel superuser backends.
</para></entry>
</row>
@@ -25508,7 +25508,7 @@ SELECT collation for ('foo' COLLATE "de_DE");
Terminates the session whose backend process has the
specified process ID. This is also allowed if the calling role
is a member of the role whose backend is being terminated or the
- calling role has been granted <literal>pg_signal_backend</literal>,
+ calling role has privileges of <literal>pg_signal_backend</literal>,
however only superusers can terminate superuser backends.
</para>
<para>
@@ -26783,7 +26783,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
Computes the total disk space used by the database with the specified
name or OID. To use this function, you must
have <literal>CONNECT</literal> privilege on the specified database
- (which is granted by default) or be a member of
+ (which is granted by default) or have privileges of
the <literal>pg_read_all_stats</literal> role.
</para></entry>
</row>
@@ -26913,7 +26913,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
Computes the total disk space used in the tablespace with the
specified name or OID. To use this function, you must
have <literal>CREATE</literal> privilege on the specified tablespace
- or be a member of the <literal>pg_read_all_stats</literal> role,
+ or have privileges of the <literal>pg_read_all_stats</literal> role,
unless it is the default tablespace for the current database.
</para></entry>
</row>
@@ -27392,7 +27392,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
a dot, directories, and other special files are excluded.
</para>
<para>
- This function is restricted to superusers and members of
+ This function is restricted to superusers and roles with privileges of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
@@ -27416,7 +27416,7 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
are excluded.
</para>
<para>
- This function is restricted to superusers and members of
+ This function is restricted to superusers and roles with privileges of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 9fb62fec8ec..821d18c3e49 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -280,7 +280,7 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser
(sessions belonging to a role that they are a member of). In rows about
other sessions, many columns will be null. Note, however, that the
existence of a session and its general properties such as its sessions user
- and database are visible to all users. Superusers and members of the
+ and database are visible to all users. Superusers and roles with privileges of
built-in role <literal>pg_read_all_stats</literal> (see also <xref
linkend="predefined-roles"/>) can see all the information about all sessions.
</para>
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index e68d159d30f..a06fd3e26de 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -24,7 +24,7 @@
</para>
<para>
- By default, use is restricted to superusers and members of the
+ By default, use is restricted to superusers and roles with privileges of the
<literal>pg_monitor</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
diff --git a/doc/src/sgml/pgfreespacemap.sgml b/doc/src/sgml/pgfreespacemap.sgml
index 5025498249d..0f3aebd27ef 100644
--- a/doc/src/sgml/pgfreespacemap.sgml
+++ b/doc/src/sgml/pgfreespacemap.sgml
@@ -16,7 +16,7 @@
</para>
<para>
- By default use is restricted to superusers and members of the
+ By default use is restricted to superusers and roles with privileges of the
<literal>pg_stat_scan_tables</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
diff --git a/doc/src/sgml/pgrowlocks.sgml b/doc/src/sgml/pgrowlocks.sgml
index 392d5f1f9a7..2914bf6e6d6 100644
--- a/doc/src/sgml/pgrowlocks.sgml
+++ b/doc/src/sgml/pgrowlocks.sgml
@@ -13,7 +13,7 @@
</para>
<para>
- By default use is restricted to superusers, members of the
+ By default use is restricted to superusers, roles with privileges of the
<literal>pg_stat_scan_tables</literal> role, and users with
<literal>SELECT</literal> permissions on the table.
</para>
diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml
index bc9d5bdbe3b..3a7e36bd13c 100644
--- a/doc/src/sgml/pgstatstatements.sgml
+++ b/doc/src/sgml/pgstatstatements.sgml
@@ -384,7 +384,7 @@
</table>
<para>
- For security reasons, only superusers and members of the
+ For security reasons, only superusers and roles with privileges of the
<literal>pg_read_all_stats</literal> role are allowed to see the SQL text and
<structfield>queryid</structfield> of queries executed by other users.
Other users can see the statistics, however, if the view has been installed
diff --git a/doc/src/sgml/pgvisibility.sgml b/doc/src/sgml/pgvisibility.sgml
index 75336946a61..8090aa5207e 100644
--- a/doc/src/sgml/pgvisibility.sgml
+++ b/doc/src/sgml/pgvisibility.sgml
@@ -140,8 +140,8 @@
</variablelist>
<para>
- By default, these functions are executable only by superusers and members of the
- <literal>pg_stat_scan_tables</literal> role, with the exception of
+ By default, these functions are executable only by superusers and roles with privileges
+ of the <literal>pg_stat_scan_tables</literal> role, with the exception of
<function>pg_truncate_visibility_map(relation regclass)</function> which can only
be executed by superusers.
</para>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7da7105d44b..7a0c897cc97 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -80,26 +80,26 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
{
if (stmt->is_program)
{
- if (!is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_execute_server_program role to COPY to or from an external program"),
+ errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
else
{
- if (is_from && !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_read_server_files role to COPY from a file"),
+ errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
- if (!is_from && !is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
+ if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of the pg_write_server_files role to COPY to a file"),
+ errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ceaff097b97..3c9411e2213 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -1403,12 +1403,12 @@ pg_stat_get_wal_receiver(PG_FUNCTION_ARGS)
/* Fetch values */
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see details.
- * Other users only get the pid value to know whether it is a WAL
- * receiver, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know whether
+ * it is a WAL receiver, but no details.
*/
MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
}
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 5a718b1fe9b..98828e6e8c8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3499,12 +3499,12 @@ pg_stat_get_wal_senders(PG_FUNCTION_ARGS)
memset(nulls, 0, sizeof(nulls));
values[0] = Int32GetDatum(pid);
- if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
- * Only superusers and members of pg_read_all_stats can see
- * details. Other users only get the pid value to know it's a
- * walsender, but no details.
+ * Only superusers and roles with privileges of pg_read_all_stats
+ * can see details. Other users only get the pid value to know
+ * it's a walsender, but no details.
*/
MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 0a16f8156cb..1d3dd8978bc 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4864,6 +4864,8 @@ has_privs_of_role(Oid member, Oid role)
* Is member a member of role (directly or indirectly)?
*
* This is defined to recurse through roles regardless of rolinherit.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role(Oid member, Oid role)
@@ -4904,6 +4906,8 @@ check_is_member_of_role(Oid member, Oid role)
*
* This is identical to is_member_of_role except we ignore superuser
* status.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role_nosuper(Oid member, Oid role)
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3a2f2e1f99d..0576764ac4b 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -112,12 +112,12 @@ calculate_database_size(Oid dbOid)
AclResult aclresult;
/*
- * User must have connect privilege for target database or be a member of
+ * User must have connect privilege for target database or have privileges of
* pg_read_all_stats
*/
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
if (aclresult != ACLCHECK_OK &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(dbOid));
@@ -196,12 +196,12 @@ calculate_tablespace_size(Oid tblspcOid)
AclResult aclresult;
/*
- * User must be a member of pg_read_all_stats or have CREATE privilege for
+ * User must have privileges of pg_read_all_stats or have CREATE privilege for
* target tablespace, either explicitly granted or implicitly because it
* is default for current database.
*/
if (tblspcOid != MyDatabaseTableSpace &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index fe6863d8b44..7c89a861f82 100644
--- a/src/backend/utils/adt/genfile.c
+++ b/src/backend/utils/adt/genfile.c
@@ -59,11 +59,11 @@ convert_and_check_filename(text *arg)
canonicalize_path(filename); /* filename can change length here */
/*
- * Members of the 'pg_read_server_files' role are allowed to access any
- * files on the server as the PG user, so no need to do any further checks
+ * Roles with privleges of the 'pg_read_server_files' role are allowed to access
+ * any files on the server as the PG user, so no need to do any further checks
* here.
*/
- if (is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+ if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
return filename;
/*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index fd993d0d5fb..590739af467 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -34,7 +34,7 @@
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
-#define HAS_PGSTAT_PERMISSIONS(role) (is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
+#define HAS_PGSTAT_PERMISSIONS(role) (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
Datum
pg_stat_get_numscans(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1e3650184b1..26ac23d8efe 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8212,10 +8212,10 @@ GetConfigOption(const char *name, bool missing_ok, bool restrict_privileged)
return NULL;
if (restrict_privileged &&
(record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -8259,10 +8259,10 @@ GetConfigOptionResetString(const char *name)
record = find_option(name, false, false, ERROR);
Assert(record != NULL);
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
@@ -9534,7 +9534,7 @@ ShowAllGUCConfig(DestReceiver *dest)
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* assign to the values array */
@@ -9601,7 +9601,7 @@ get_explain_guc_options(int *num)
/* return only options visible to the current user */
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* return only options that are different from their boot values */
@@ -9683,10 +9683,10 @@ GetConfigOptionByName(const char *name, const char **varname, bool missing_ok)
}
if ((record->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
+ errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
if (varname)
@@ -9753,7 +9753,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
{
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
- !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
+ !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
*noshow = true;
else
*noshow = false;
@@ -9948,7 +9948,7 @@ GetConfigOptionByNum(int varnum, const char **values, bool *noshow)
* insufficiently-privileged users.
*/
if (conf->source == PGC_S_FILE &&
- is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
+ has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
{
values[14] = conf->sourcefile;
snprintf(buffer, sizeof(buffer), "%d", conf->sourceline);
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index eb608fdc2ea..88b1ff843be 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1077,7 +1077,7 @@ SHOW session_preload_libraries;
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
-ERROR: must be superuser or a member of pg_read_all_settings to examine "session_preload_libraries"
+ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
--
2.31.1
On 3/3/22 11:26, Joshua Brindle wrote:
On Thu, Feb 10, 2022 at 2:37 PM Joe Conway <mail@joeconway.com> wrote:
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.Yep, I was suggesting that the latter would have been preferable to me while
Robert seemed to prefer the former. Honestly I could be happy with either of
those solutions, but as I alluded to that is probably a discussion for the
next development cycle since I don't see us doing that big a change in this
one.I agree. I still think Joshua's proposed patch is a worthwhile improvement
for v15.+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.Rebased patch to apply to master attached.
Well longer than I planned, but finally took a closer look.
I made one minor editorial fix to Joshua's patch, rebased to current
master, and added two missing call sites that presumably are related to
recent commits for pg_basebackup.
On that last note, I did not find basebackup_to_shell.required_role
documented at all, and did not attempt to fix that.
When this thread petered out, it seemed that Robert was at least neutral
on the patch, and everyone else was +1 on applying it to master for pg15.
As such, if there are any other issues, complaints, etc., please speak
real soon now...
Thanks,
Joe
Attachments:
v6-0001-use-has_privs_for_roles-for-predefined-role-checks.patchtext/x-patch; charset=UTF-8; name=v6-0001-use-has_privs_for_roles-for-predefined-role-checks.patchDownload
Use has_privs_for_roles for predefined role checks
Generally if a role is granted membership to another role with NOINHERIT they must use SET ROLE to access the privileges of that role, however with predefined roles the membership and privilege is conflated. Fix that by replacing is_member_of_role with has_privs_for_role for predefined roles. Patch does not remove is_member_of_role from acl.h, but it does add a warning not to use that function for privilege checking. Not backpatched based on hackers list discussion.
Author: Joshua Brindle
Reviewed-by: Stephen Frost, Nathan Bossart, Joe Conway
Discussion: https://postgr.es/m/flat/CAGB+Vh4Zv_TvKt2tv3QNS6tUM_F_9icmuj0zjywwcgVi4PAhFA@mail.gmail.com
diff --git a/contrib/adminpack/adminpack.c b/contrib/adminpack/adminpack.c
index d7d84d0..03addf1 100644
*** a/contrib/adminpack/adminpack.c
--- b/contrib/adminpack/adminpack.c
*************** convert_and_check_filename(text *arg)
*** 79,85 ****
* files on the server as the PG user, so no need to do any further checks
* here.
*/
! if (is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
return filename;
/*
--- 79,85 ----
* files on the server as the PG user, so no need to do any further checks
* here.
*/
! if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
return filename;
/*
diff --git a/contrib/basebackup_to_shell/basebackup_to_shell.c b/contrib/basebackup_to_shell/basebackup_to_shell.c
index d82cb6d..f0ddef1 100644
*** a/contrib/basebackup_to_shell/basebackup_to_shell.c
--- b/contrib/basebackup_to_shell/basebackup_to_shell.c
*************** _PG_init(void)
*** 90,96 ****
}
/*
! * We choose to defer sanity sanity checking until shell_get_sink(), and so
* just pass the target detail through without doing anything. However, we do
* permissions checks here, before any real work has been done.
*/
--- 90,96 ----
}
/*
! * We choose to defer sanity checking until shell_get_sink(), and so
* just pass the target detail through without doing anything. However, we do
* permissions checks here, before any real work has been done.
*/
*************** shell_check_detail(char *target, char *t
*** 103,109 ****
StartTransactionCommand();
roleid = get_role_oid(shell_required_role, true);
! if (!is_member_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to use basebackup_to_shell")));
--- 103,109 ----
StartTransactionCommand();
roleid = get_role_oid(shell_required_role, true);
! if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to use basebackup_to_shell")));
diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 0ac6e4e..14acdb2 100644
*** a/contrib/file_fdw/expected/file_fdw.out
--- b/contrib/file_fdw/expected/file_fdw.out
*************** ALTER FOREIGN TABLE agg_text OWNER TO re
*** 459,465 ****
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
! ERROR: only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
--- 459,465 ----
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
SET ROLE regress_file_fdw_user;
ALTER FOREIGN TABLE agg_text OPTIONS (SET format 'text');
! ERROR: only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table
SET ROLE regress_file_fdw_superuser;
-- cleanup
RESET ROLE;
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index db08593..4773cad 100644
*** a/contrib/file_fdw/file_fdw.c
--- b/contrib/file_fdw/file_fdw.c
*************** file_fdw_validator(PG_FUNCTION_ARGS)
*** 269,284 ****
* otherwise there'd still be a security hole.
*/
if (strcmp(def->defname, "filename") == 0 &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("only superuser or a member of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
if (strcmp(def->defname, "program") == 0 &&
! !is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("only superuser or a member of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
filename = defGetString(def);
}
--- 269,284 ----
* otherwise there'd still be a security hole.
*/
if (strcmp(def->defname, "filename") == 0 &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("only superuser or a role with privileges of the pg_read_server_files role may specify the filename option of a file_fdw foreign table")));
if (strcmp(def->defname, "program") == 0 &&
! !has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("only superuser or a role with privileges of the pg_execute_server_program role may specify the program option of a file_fdw foreign table")));
filename = defGetString(def);
}
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 9e525a6..55786ae 100644
*** a/contrib/pg_stat_statements/pg_stat_statements.c
--- b/contrib/pg_stat_statements/pg_stat_statements.c
*************** pg_stat_statements_internal(FunctionCall
*** 1503,1510 ****
HASH_SEQ_STATUS hash_seq;
pgssEntry *entry;
! /* Superusers or members of pg_read_all_stats members are allowed */
! is_allowed_role = is_member_of_role(userid, ROLE_PG_READ_ALL_STATS);
/* hash table must exist already */
if (!pgss || !pgss_hash)
--- 1503,1510 ----
HASH_SEQ_STATUS hash_seq;
pgssEntry *entry;
! /* Superusers or roles with the privileges of pg_read_all_stats members are allowed */
! is_allowed_role = has_privs_of_role(userid, ROLE_PG_READ_ALL_STATS);
/* hash table must exist already */
if (!pgss || !pgss_hash)
diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c
index 713a165..1d4d496 100644
*** a/contrib/pgrowlocks/pgrowlocks.c
--- b/contrib/pgrowlocks/pgrowlocks.c
*************** pgrowlocks(PG_FUNCTION_ARGS)
*** 104,110 ****
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_SELECT);
if (aclresult != ACLCHECK_OK)
! aclresult = is_member_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
--- 104,110 ----
aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
ACL_SELECT);
if (aclresult != ACLCHECK_OK)
! aclresult = has_privs_of_role(GetUserId(), ROLE_PG_STAT_SCAN_TABLES) ? ACLCHECK_OK : ACLCHECK_NO_PRIV;
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, get_relkind_objtype(rel->rd_rel->relkind),
diff --git a/doc/src/sgml/adminpack.sgml b/doc/src/sgml/adminpack.sgml
index 0dd89be..5702456 100644
*** a/doc/src/sgml/adminpack.sgml
--- b/doc/src/sgml/adminpack.sgml
***************
*** 22,30 ****
functions in <xref linkend="functions-admin-genfile-table"/>, which
provide read-only access.)
Only files within the database cluster directory can be accessed, unless the
! user is a superuser or given one of the pg_read_server_files, or pg_write_server_files
! roles, as appropriate for the function, but either a relative or absolute path is
! allowable.
</para>
<table id="functions-adminpack-table">
--- 22,30 ----
functions in <xref linkend="functions-admin-genfile-table"/>, which
provide read-only access.)
Only files within the database cluster directory can be accessed, unless the
! user is a superuser or given privileges of one of the pg_read_server_files,
! or pg_write_server_files roles, as appropriate for the function, but either a
! relative or absolute path is allowable.
</para>
<table id="functions-adminpack-table">
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4dc5b34..5010c63 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
*************** SCRAM-SHA-256$<replaceable><iteration
*** 10006,10013 ****
<para>
By default, the <structname>pg_backend_memory_contexts</structname> view can be
! read only by superusers or members of the <literal>pg_read_all_stats</literal>
! role.
</para>
</sect1>
--- 10006,10013 ----
<para>
By default, the <structname>pg_backend_memory_contexts</structname> view can be
! read only by superusers or roles with the privileges of the
! <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
*************** SELECT * FROM pg_locks pl LEFT JOIN pg_p
*** 12448,12454 ****
<para>
Configuration file the current value was set in (null for
values set from sources other than configuration files, or when
! examined by a user who is neither a superuser or a member of
<literal>pg_read_all_settings</literal>); helpful when using
<literal>include</literal> directives in configuration files
</para></entry>
--- 12448,12454 ----
<para>
Configuration file the current value was set in (null for
values set from sources other than configuration files, or when
! examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>); helpful when using
<literal>include</literal> directives in configuration files
</para></entry>
*************** SELECT * FROM pg_locks pl LEFT JOIN pg_p
*** 12461,12467 ****
<para>
Line number within the configuration file the current value was
set at (null for values set from sources other than configuration files,
! or when examined by a user who is neither a superuser or a member of
<literal>pg_read_all_settings</literal>).
</para></entry>
</row>
--- 12461,12467 ----
<para>
Line number within the configuration file the current value was
set at (null for values set from sources other than configuration files,
! or when examined by a user who neither is a superuser nor has privileges of
<literal>pg_read_all_settings</literal>).
</para></entry>
</row>
*************** SELECT * FROM pg_locks pl LEFT JOIN pg_p
*** 12837,12844 ****
<para>
By default, the <structname>pg_shmem_allocations</structname> view can be
! read only by superusers or members of the <literal>pg_read_all_stats</literal>
! role.
</para>
</sect1>
--- 12837,12844 ----
<para>
By default, the <structname>pg_shmem_allocations</structname> view can be
! read only by superusers or roles with privileges of the
! <literal>pg_read_all_stats</literal> role.
</para>
</sect1>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8a802fb..3a9d62b 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT collation for ('foo' COLLATE "de_
*** 25435,25441 ****
Cancels the current query of the session whose backend process has the
specified process ID. This is also allowed if the
calling role is a member of the role whose backend is being canceled or
! the calling role has been granted <literal>pg_signal_backend</literal>,
however only superusers can cancel superuser backends.
</para></entry>
</row>
--- 25435,25441 ----
Cancels the current query of the session whose backend process has the
specified process ID. This is also allowed if the
calling role is a member of the role whose backend is being canceled or
! the calling role has privileges of <literal>pg_signal_backend</literal>,
however only superusers can cancel superuser backends.
</para></entry>
</row>
*************** SELECT collation for ('foo' COLLATE "de_
*** 25508,25514 ****
Terminates the session whose backend process has the
specified process ID. This is also allowed if the calling role
is a member of the role whose backend is being terminated or the
! calling role has been granted <literal>pg_signal_backend</literal>,
however only superusers can terminate superuser backends.
</para>
<para>
--- 25508,25514 ----
Terminates the session whose backend process has the
specified process ID. This is also allowed if the calling role
is a member of the role whose backend is being terminated or the
! calling role has privileges of <literal>pg_signal_backend</literal>,
however only superusers can terminate superuser backends.
</para>
<para>
*************** postgres=# SELECT * FROM pg_walfile_name
*** 26783,26789 ****
Computes the total disk space used by the database with the specified
name or OID. To use this function, you must
have <literal>CONNECT</literal> privilege on the specified database
! (which is granted by default) or be a member of
the <literal>pg_read_all_stats</literal> role.
</para></entry>
</row>
--- 26783,26789 ----
Computes the total disk space used by the database with the specified
name or OID. To use this function, you must
have <literal>CONNECT</literal> privilege on the specified database
! (which is granted by default) or have privileges of
the <literal>pg_read_all_stats</literal> role.
</para></entry>
</row>
*************** postgres=# SELECT * FROM pg_walfile_name
*** 26913,26919 ****
Computes the total disk space used in the tablespace with the
specified name or OID. To use this function, you must
have <literal>CREATE</literal> privilege on the specified tablespace
! or be a member of the <literal>pg_read_all_stats</literal> role,
unless it is the default tablespace for the current database.
</para></entry>
</row>
--- 26913,26919 ----
Computes the total disk space used in the tablespace with the
specified name or OID. To use this function, you must
have <literal>CREATE</literal> privilege on the specified tablespace
! or have privileges of the <literal>pg_read_all_stats</literal> role,
unless it is the default tablespace for the current database.
</para></entry>
</row>
*************** SELECT pg_size_pretty(sum(pg_relation_si
*** 27392,27398 ****
a dot, directories, and other special files are excluded.
</para>
<para>
! This function is restricted to superusers and members of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
--- 27392,27398 ----
a dot, directories, and other special files are excluded.
</para>
<para>
! This function is restricted to superusers and roles with privileges of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
*************** SELECT pg_size_pretty(sum(pg_relation_si
*** 27416,27422 ****
are excluded.
</para>
<para>
! This function is restricted to superusers and members of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
--- 27416,27422 ----
are excluded.
</para>
<para>
! This function is restricted to superusers and roles with privileges of
the <literal>pg_monitor</literal> role by default, but other users can
be granted EXECUTE to run the function.
</para></entry>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 35b2923..6a6b09d 100644
*** a/doc/src/sgml/monitoring.sgml
--- b/doc/src/sgml/monitoring.sgml
*************** postgres 27093 0.0 0.0 30096 2752
*** 280,286 ****
(sessions belonging to a role that they are a member of). In rows about
other sessions, many columns will be null. Note, however, that the
existence of a session and its general properties such as its sessions user
! and database are visible to all users. Superusers and members of the
built-in role <literal>pg_read_all_stats</literal> (see also <xref
linkend="predefined-roles"/>) can see all the information about all sessions.
</para>
--- 280,286 ----
(sessions belonging to a role that they are a member of). In rows about
other sessions, many columns will be null. Note, however, that the
existence of a session and its general properties such as its sessions user
! and database are visible to all users. Superusers and roles with privileges of
built-in role <literal>pg_read_all_stats</literal> (see also <xref
linkend="predefined-roles"/>) can see all the information about all sessions.
</para>
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index e68d159..a06fd3e 100644
*** a/doc/src/sgml/pgbuffercache.sgml
--- b/doc/src/sgml/pgbuffercache.sgml
***************
*** 24,30 ****
</para>
<para>
! By default, use is restricted to superusers and members of the
<literal>pg_monitor</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
--- 24,30 ----
</para>
<para>
! By default, use is restricted to superusers and roles with privileges of the
<literal>pg_monitor</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
diff --git a/doc/src/sgml/pgfreespacemap.sgml b/doc/src/sgml/pgfreespacemap.sgml
index 1f7867d..3063885 100644
*** a/doc/src/sgml/pgfreespacemap.sgml
--- b/doc/src/sgml/pgfreespacemap.sgml
***************
*** 16,22 ****
</para>
<para>
! By default use is restricted to superusers and members of the
<literal>pg_stat_scan_tables</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
--- 16,22 ----
</para>
<para>
! By default use is restricted to superusers and roles with privileges of the
<literal>pg_stat_scan_tables</literal> role. Access may be granted to others
using <command>GRANT</command>.
</para>
diff --git a/doc/src/sgml/pgrowlocks.sgml b/doc/src/sgml/pgrowlocks.sgml
index 392d5f1..2914bf6 100644
*** a/doc/src/sgml/pgrowlocks.sgml
--- b/doc/src/sgml/pgrowlocks.sgml
***************
*** 13,19 ****
</para>
<para>
! By default use is restricted to superusers, members of the
<literal>pg_stat_scan_tables</literal> role, and users with
<literal>SELECT</literal> permissions on the table.
</para>
--- 13,19 ----
</para>
<para>
! By default use is restricted to superusers, roles with privileges of the
<literal>pg_stat_scan_tables</literal> role, and users with
<literal>SELECT</literal> permissions on the table.
</para>
diff --git a/doc/src/sgml/pgstatstatements.sgml b/doc/src/sgml/pgstatstatements.sgml
index bc9d5bd..3a7e36b 100644
*** a/doc/src/sgml/pgstatstatements.sgml
--- b/doc/src/sgml/pgstatstatements.sgml
***************
*** 384,390 ****
</table>
<para>
! For security reasons, only superusers and members of the
<literal>pg_read_all_stats</literal> role are allowed to see the SQL text and
<structfield>queryid</structfield> of queries executed by other users.
Other users can see the statistics, however, if the view has been installed
--- 384,390 ----
</table>
<para>
! For security reasons, only superusers and roles with privileges of the
<literal>pg_read_all_stats</literal> role are allowed to see the SQL text and
<structfield>queryid</structfield> of queries executed by other users.
Other users can see the statistics, however, if the view has been installed
diff --git a/doc/src/sgml/pgvisibility.sgml b/doc/src/sgml/pgvisibility.sgml
index 7533694..8090aa5 100644
*** a/doc/src/sgml/pgvisibility.sgml
--- b/doc/src/sgml/pgvisibility.sgml
***************
*** 140,147 ****
</variablelist>
<para>
! By default, these functions are executable only by superusers and members of the
! <literal>pg_stat_scan_tables</literal> role, with the exception of
<function>pg_truncate_visibility_map(relation regclass)</function> which can only
be executed by superusers.
</para>
--- 140,147 ----
</variablelist>
<para>
! By default, these functions are executable only by superusers and roles with privileges
! of the <literal>pg_stat_scan_tables</literal> role, with the exception of
<function>pg_truncate_visibility_map(relation regclass)</function> which can only
be executed by superusers.
</para>
diff --git a/doc/src/sgml/ref/pg_basebackup.sgml b/doc/src/sgml/ref/pg_basebackup.sgml
index 4a630b5..36baafd 100644
*** a/doc/src/sgml/ref/pg_basebackup.sgml
--- b/doc/src/sgml/ref/pg_basebackup.sgml
*************** PostgreSQL documentation
*** 237,243 ****
<literal>server:/some/path</literal>, the backup will be stored on
the machine where the server is running in the
<literal>/some/path</literal> directory. Storing a backup on the
! server requires superuser privileges or being granted the
<literal>pg_write_server_files</literal> role. If the target is set to
<literal>blackhole</literal>, the contents are discarded and not
stored anywhere. This should only be used for testing purposes, as you
--- 237,243 ----
<literal>server:/some/path</literal>, the backup will be stored on
the machine where the server is running in the
<literal>/some/path</literal> directory. Storing a backup on the
! server requires superuser privileges or having privileges of the
<literal>pg_write_server_files</literal> role. If the target is set to
<literal>blackhole</literal>, the contents are discarded and not
stored anywhere. This should only be used for testing purposes, as you
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 7da7105..7a0c897 100644
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
*************** DoCopy(ParseState *pstate, const CopyStm
*** 80,105 ****
{
if (stmt->is_program)
{
! if (!is_member_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or a member of the pg_execute_server_program role to COPY to or from an external program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
else
{
! if (is_from && !is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or a member of the pg_read_server_files role to COPY from a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
! if (!is_from && !is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or a member of the pg_write_server_files role to COPY to a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
--- 80,105 ----
{
if (stmt->is_program)
{
! if (!has_privs_of_role(GetUserId(), ROLE_PG_EXECUTE_SERVER_PROGRAM))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or have privileges of the pg_execute_server_program role to COPY to or from an external program"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
else
{
! if (is_from && !has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or have privileges of the pg_read_server_files role to COPY from a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
! if (!is_from && !has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or have privileges of the pg_write_server_files role to COPY to a file"),
errhint("Anyone can COPY to stdout or from stdin. "
"psql's \\copy command also works for anyone.")));
}
diff --git a/src/backend/replication/basebackup_server.c b/src/backend/replication/basebackup_server.c
index a878629..bc16897 100644
*** a/src/backend/replication/basebackup_server.c
--- b/src/backend/replication/basebackup_server.c
*************** bbsink_server_new(bbsink *next, char *pa
*** 69,78 ****
/* Replication permission is not sufficient in this case. */
StartTransactionCommand();
! if (!is_member_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or a member of the pg_write_server_files role to create server backup")));
CommitTransactionCommand();
/*
--- 69,78 ----
/* Replication permission is not sufficient in this case. */
StartTransactionCommand();
! if (!has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or a role with privileges of the pg_write_server_files role to create server backup")));
CommitTransactionCommand();
/*
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index ceaff09..3c9411e 100644
*** a/src/backend/replication/walreceiver.c
--- b/src/backend/replication/walreceiver.c
*************** pg_stat_get_wal_receiver(PG_FUNCTION_ARG
*** 1403,1414 ****
/* Fetch values */
values[0] = Int32GetDatum(pid);
! if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
! * Only superusers and members of pg_read_all_stats can see details.
! * Other users only get the pid value to know whether it is a WAL
! * receiver, but no details.
*/
MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
}
--- 1403,1414 ----
/* Fetch values */
values[0] = Int32GetDatum(pid);
! if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
! * Only superusers and roles with privileges of pg_read_all_stats
! * can see details. Other users only get the pid value to know whether
! * it is a WAL receiver, but no details.
*/
MemSet(&nulls[1], true, sizeof(bool) * (tupdesc->natts - 1));
}
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 2d0292a..cffb348 100644
*** a/src/backend/replication/walsender.c
--- b/src/backend/replication/walsender.c
*************** pg_stat_get_wal_senders(PG_FUNCTION_ARGS
*** 3473,3484 ****
memset(nulls, 0, sizeof(nulls));
values[0] = Int32GetDatum(pid);
! if (!is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
! * Only superusers and members of pg_read_all_stats can see
! * details. Other users only get the pid value to know it's a
! * walsender, but no details.
*/
MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
}
--- 3473,3484 ----
memset(nulls, 0, sizeof(nulls));
values[0] = Int32GetDatum(pid);
! if (!has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
/*
! * Only superusers and roles with privileges of pg_read_all_stats
! * can see details. Other users only get the pid value to know
! * it's a walsender, but no details.
*/
MemSet(&nulls[1], true, PG_STAT_GET_WAL_SENDERS_COLS - 1);
}
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 0a16f81..1d3dd89 100644
*** a/src/backend/utils/adt/acl.c
--- b/src/backend/utils/adt/acl.c
*************** has_privs_of_role(Oid member, Oid role)
*** 4864,4869 ****
--- 4864,4871 ----
* Is member a member of role (directly or indirectly)?
*
* This is defined to recurse through roles regardless of rolinherit.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role(Oid member, Oid role)
*************** check_is_member_of_role(Oid member, Oid
*** 4904,4909 ****
--- 4906,4913 ----
*
* This is identical to is_member_of_role except we ignore superuser
* status.
+ *
+ * Do not use this for privilege checking, instead use has_privs_of_role()
*/
bool
is_member_of_role_nosuper(Oid member, Oid role)
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3a2f2e1..0576764 100644
*** a/src/backend/utils/adt/dbsize.c
--- b/src/backend/utils/adt/dbsize.c
*************** calculate_database_size(Oid dbOid)
*** 112,123 ****
AclResult aclresult;
/*
! * User must have connect privilege for target database or be a member of
* pg_read_all_stats
*/
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
if (aclresult != ACLCHECK_OK &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(dbOid));
--- 112,123 ----
AclResult aclresult;
/*
! * User must have connect privilege for target database or have privileges of
* pg_read_all_stats
*/
aclresult = pg_database_aclcheck(dbOid, GetUserId(), ACL_CONNECT);
if (aclresult != ACLCHECK_OK &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(dbOid));
*************** calculate_tablespace_size(Oid tblspcOid)
*** 196,207 ****
AclResult aclresult;
/*
! * User must be a member of pg_read_all_stats or have CREATE privilege for
* target tablespace, either explicitly granted or implicitly because it
* is default for current database.
*/
if (tblspcOid != MyDatabaseTableSpace &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
--- 196,207 ----
AclResult aclresult;
/*
! * User must have privileges of pg_read_all_stats or have CREATE privilege for
* target tablespace, either explicitly granted or implicitly because it
* is default for current database.
*/
if (tblspcOid != MyDatabaseTableSpace &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS))
{
aclresult = pg_tablespace_aclcheck(tblspcOid, GetUserId(), ACL_CREATE);
if (aclresult != ACLCHECK_OK)
diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c
index 1ed0162..88f279d 100644
*** a/src/backend/utils/adt/genfile.c
--- b/src/backend/utils/adt/genfile.c
*************** convert_and_check_filename(text *arg)
*** 59,69 ****
canonicalize_path(filename); /* filename can change length here */
/*
! * Members of the 'pg_read_server_files' role are allowed to access any
! * files on the server as the PG user, so no need to do any further checks
* here.
*/
! if (is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
return filename;
/*
--- 59,69 ----
canonicalize_path(filename); /* filename can change length here */
/*
! * Roles with privleges of the 'pg_read_server_files' role are allowed to access
! * any files on the server as the PG user, so no need to do any further checks
* here.
*/
! if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
return filename;
/*
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index eff45b1..ce84525 100644
*** a/src/backend/utils/adt/pgstatfuncs.c
--- b/src/backend/utils/adt/pgstatfuncs.c
***************
*** 34,40 ****
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
! #define HAS_PGSTAT_PERMISSIONS(role) (is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
Datum
pg_stat_get_numscans(PG_FUNCTION_ARGS)
--- 34,40 ----
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
! #define HAS_PGSTAT_PERMISSIONS(role) (has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_STATS) || has_privs_of_role(GetUserId(), role))
Datum
pg_stat_get_numscans(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index e7f0a38..c3ef798 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** GetConfigOption(const char *name, bool m
*** 8215,8224 ****
return NULL;
if (restrict_privileged &&
(record->flags & GUC_SUPERUSER_ONLY) &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
--- 8215,8224 ----
return NULL;
if (restrict_privileged &&
(record->flags & GUC_SUPERUSER_ONLY) &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
*************** GetConfigOptionResetString(const char *n
*** 8262,8271 ****
record = find_option(name, false, false, ERROR);
Assert(record != NULL);
if ((record->flags & GUC_SUPERUSER_ONLY) &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
--- 8262,8271 ----
record = find_option(name, false, false, ERROR);
Assert(record != NULL);
if ((record->flags & GUC_SUPERUSER_ONLY) &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
switch (record->vartype)
*************** ShowAllGUCConfig(DestReceiver *dest)
*** 9537,9543 ****
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* assign to the values array */
--- 9537,9543 ----
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* assign to the values array */
*************** get_explain_guc_options(int *num)
*** 9604,9610 ****
/* return only options visible to the current user */
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* return only options that are different from their boot values */
--- 9604,9610 ----
/* return only options visible to the current user */
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
continue;
/* return only options that are different from their boot values */
*************** GetConfigOptionByName(const char *name,
*** 9686,9695 ****
}
if ((record->flags & GUC_SUPERUSER_ONLY) &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or a member of pg_read_all_settings to examine \"%s\"",
name)));
if (varname)
--- 9686,9695 ----
}
if ((record->flags & GUC_SUPERUSER_ONLY) &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! errmsg("must be superuser or have privileges of pg_read_all_settings to examine \"%s\"",
name)));
if (varname)
*************** GetConfigOptionByNum(int varnum, const c
*** 9756,9762 ****
{
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
! !is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
*noshow = true;
else
*noshow = false;
--- 9756,9762 ----
{
if ((conf->flags & GUC_NO_SHOW_ALL) ||
((conf->flags & GUC_SUPERUSER_ONLY) &&
! !has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS)))
*noshow = true;
else
*noshow = false;
*************** GetConfigOptionByNum(int varnum, const c
*** 9951,9957 ****
* insufficiently-privileged users.
*/
if (conf->source == PGC_S_FILE &&
! is_member_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
{
values[14] = conf->sourcefile;
snprintf(buffer, sizeof(buffer), "%d", conf->sourceline);
--- 9951,9957 ----
* insufficiently-privileged users.
*/
if (conf->source == PGC_S_FILE &&
! has_privs_of_role(GetUserId(), ROLE_PG_READ_ALL_SETTINGS))
{
values[14] = conf->sourcefile;
snprintf(buffer, sizeof(buffer), "%d", conf->sourceline);
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index eb608fd..88b1ff8 100644
*** a/src/test/modules/unsafe_tests/expected/rolenames.out
--- b/src/test/modules/unsafe_tests/expected/rolenames.out
*************** SHOW session_preload_libraries;
*** 1077,1083 ****
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
! ERROR: must be superuser or a member of pg_read_all_settings to examine "session_preload_libraries"
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
--- 1077,1083 ----
SET SESSION AUTHORIZATION regress_role_nopriv;
-- fails with role not member of pg_read_all_settings
SHOW session_preload_libraries;
! ERROR: must be superuser or have privileges of pg_read_all_settings to examine "session_preload_libraries"
RESET SESSION AUTHORIZATION;
ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK;
On Sun, Mar 20, 2022 at 12:27 PM Joe Conway <mail@joeconway.com> wrote:
On 3/3/22 11:26, Joshua Brindle wrote:
On Thu, Feb 10, 2022 at 2:37 PM Joe Conway <mail@joeconway.com> wrote:
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.Yep, I was suggesting that the latter would have been preferable to me while
Robert seemed to prefer the former. Honestly I could be happy with either of
those solutions, but as I alluded to that is probably a discussion for the
next development cycle since I don't see us doing that big a change in this
one.I agree. I still think Joshua's proposed patch is a worthwhile improvement
for v15.+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.Rebased patch to apply to master attached.
Well longer than I planned, but finally took a closer look.
I made one minor editorial fix to Joshua's patch, rebased to current
master, and added two missing call sites that presumably are related to
recent commits for pg_basebackup.
FWIW I pinged Stephen when I saw the basebackup changes included
is_member_of and he didn't think they necessarily needed to be changed
since they aren't accessible to human and you can't SET ROLE on a
replication connection in order to access the role where inheritance
was blocked.
Show quoted text
On that last note, I did not find basebackup_to_shell.required_role
documented at all, and did not attempt to fix that.When this thread petered out, it seemed that Robert was at least neutral
on the patch, and everyone else was +1 on applying it to master for pg15.As such, if there are any other issues, complaints, etc., please speak
real soon now...Thanks,
Joe
On 3/20/22 12:31, Joshua Brindle wrote:
On Sun, Mar 20, 2022 at 12:27 PM Joe Conway <mail@joeconway.com> wrote:
On 3/3/22 11:26, Joshua Brindle wrote:
On Thu, Feb 10, 2022 at 2:37 PM Joe Conway <mail@joeconway.com> wrote:
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.Yep, I was suggesting that the latter would have been preferable to me while
Robert seemed to prefer the former. Honestly I could be happy with either of
those solutions, but as I alluded to that is probably a discussion for the
next development cycle since I don't see us doing that big a change in this
one.I agree. I still think Joshua's proposed patch is a worthwhile improvement
for v15.+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.Rebased patch to apply to master attached.
Well longer than I planned, but finally took a closer look.
I made one minor editorial fix to Joshua's patch, rebased to current
master, and added two missing call sites that presumably are related to
recent commits for pg_basebackup.FWIW I pinged Stephen when I saw the basebackup changes included
is_member_of and he didn't think they necessarily needed to be changed
since they aren't accessible to human and you can't SET ROLE on a
replication connection in order to access the role where inheritance
was blocked.
Maybe worth a discussion, but it seems strange to me to treat them
differently when the whole purpose of this patch is to make things
consistent ¯\_(ツ)_/¯
Joe
On Sun, Mar 20, 2022 at 12:34 PM Joe Conway <mail@joeconway.com> wrote:
On 3/20/22 12:31, Joshua Brindle wrote:
On Sun, Mar 20, 2022 at 12:27 PM Joe Conway <mail@joeconway.com> wrote:
On 3/3/22 11:26, Joshua Brindle wrote:
On Thu, Feb 10, 2022 at 2:37 PM Joe Conway <mail@joeconway.com> wrote:
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between predefined roles and role
attributes confusing. INHERIT doesn't govern role attributes, but it will
govern predefined roles when this patch is applied. Maybe the role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined roles should be
converted to role attributes.Yep, I was suggesting that the latter would have been preferable to me while
Robert seemed to prefer the former. Honestly I could be happy with either of
those solutions, but as I alluded to that is probably a discussion for the
next development cycle since I don't see us doing that big a change in this
one.I agree. I still think Joshua's proposed patch is a worthwhile improvement
for v15.+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.Rebased patch to apply to master attached.
Well longer than I planned, but finally took a closer look.
I made one minor editorial fix to Joshua's patch, rebased to current
master, and added two missing call sites that presumably are related to
recent commits for pg_basebackup.FWIW I pinged Stephen when I saw the basebackup changes included
is_member_of and he didn't think they necessarily needed to be changed
since they aren't accessible to human and you can't SET ROLE on a
replication connection in order to access the role where inheritance
was blocked.Maybe worth a discussion, but it seems strange to me to treat them
differently when the whole purpose of this patch is to make things
consistent ¯\_(ツ)_/¯
I don't have a strong opinion either way, just noting that it's a
possible exception area due to how it is used.
Thanks for committing this.
Greetings,
On Sun, Mar 20, 2022 at 18:31 Joshua Brindle <joshua.brindle@crunchydata.com>
wrote:
On Sun, Mar 20, 2022 at 12:27 PM Joe Conway <mail@joeconway.com> wrote:
On 3/3/22 11:26, Joshua Brindle wrote:
On Thu, Feb 10, 2022 at 2:37 PM Joe Conway <mail@joeconway.com> wrote:
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between predefined
roles and role
attributes confusing. INHERIT doesn't govern role attributes,
but it will
govern predefined roles when this patch is applied. Maybe the
role
attribute system should eventually be deprecated in favor of using
predefined roles for everything. Or perhaps the predefined rolesshould be
converted to role attributes.
Yep, I was suggesting that the latter would have been preferable
to me while
Robert seemed to prefer the former. Honestly I could be happy with
either of
those solutions, but as I alluded to that is probably a discussion
for the
next development cycle since I don't see us doing that big a
change in this
one.
I agree. I still think Joshua's proposed patch is a worthwhile
improvement
for v15.
+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.Rebased patch to apply to master attached.
Well longer than I planned, but finally took a closer look.
I made one minor editorial fix to Joshua's patch, rebased to current
master, and added two missing call sites that presumably are related to
recent commits for pg_basebackup.FWIW I pinged Stephen when I saw the basebackup changes included
is_member_of and he didn't think they necessarily needed to be changed
since they aren't accessible to human and you can't SET ROLE on a
replication connection in order to access the role where inheritance
was blocked.
Yeah, though that should really be very clearly explained in comments
around that code as anything that uses is_member should really be viewed as
an exception. I also wouldn’t be against using has_privs there anyway and
saying that you shouldn’t be trying to connect as one role on a replication
connection and using the privs of another when you don’t automatically
inherit those rights, but on the assumption that the committer intended
is_member there because SET ROLE isn’t something one does on replication
connections, I’m alright with it being as is.
Kind Regards,
Stephen P Frost
Show quoted text
On 3/20/22 12:38, Stephen Frost wrote:
Greetings,
On Sun, Mar 20, 2022 at 18:31 Joshua Brindle
<joshua.brindle@crunchydata.com <mailto:joshua.brindle@crunchydata.com>>
wrote:On Sun, Mar 20, 2022 at 12:27 PM Joe Conway <mail@joeconway.com
<mailto:mail@joeconway.com>> wrote:On 3/3/22 11:26, Joshua Brindle wrote:
On Thu, Feb 10, 2022 at 2:37 PM Joe Conway <mail@joeconway.com
<mailto:mail@joeconway.com>> wrote:
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between
predefined roles and role
attributes confusing. INHERIT doesn't govern role
attributes, but it will
govern predefined roles when this patch is applied. Maybe
the role
attribute system should eventually be deprecated in favor
of using
predefined roles for everything. Or perhaps the
predefined roles should be
converted to role attributes.
Yep, I was suggesting that the latter would have been
preferable to me while
Robert seemed to prefer the former. Honestly I could be
happy with either of
those solutions, but as I alluded to that is probably a
discussion for the
next development cycle since I don't see us doing that big
a change in this
one.
I agree. I still think Joshua's proposed patch is a
worthwhile improvement
for v15.
+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.Rebased patch to apply to master attached.
Well longer than I planned, but finally took a closer look.
I made one minor editorial fix to Joshua's patch, rebased to current
master, and added two missing call sites that presumably arerelated to
recent commits for pg_basebackup.
FWIW I pinged Stephen when I saw the basebackup changes included
is_member_of and he didn't think they necessarily needed to be changed
since they aren't accessible to human and you can't SET ROLE on a
replication connection in order to access the role where inheritance
was blocked.Yeah, though that should really be very clearly explained in comments
around that code as anything that uses is_member should really be viewed
as an exception. I also wouldn’t be against using has_privs there
anyway and saying that you shouldn’t be trying to connect as one role on
a replication connection and using the privs of another when you don’t
automatically inherit those rights, but on the assumption that the
committer intended is_member there because SET ROLE isn’t something one
does on replication connections, I’m alright with it being as is.
Robert -- any opinion on this? If I am not mistaken it is code that you
are actively working on.
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
On 3/21/22 16:15, Joe Conway wrote:
On 3/20/22 12:38, Stephen Frost wrote:
Greetings,
On Sun, Mar 20, 2022 at 18:31 Joshua Brindle
<joshua.brindle@crunchydata.com <mailto:joshua.brindle@crunchydata.com>>
wrote:On Sun, Mar 20, 2022 at 12:27 PM Joe Conway <mail@joeconway.com
<mailto:mail@joeconway.com>> wrote:On 3/3/22 11:26, Joshua Brindle wrote:
On Thu, Feb 10, 2022 at 2:37 PM Joe Conway <mail@joeconway.com
<mailto:mail@joeconway.com>> wrote:
On 2/10/22 14:28, Nathan Bossart wrote:
On Wed, Feb 09, 2022 at 04:39:11PM -0500, Joe Conway wrote:
On 2/9/22 13:13, Nathan Bossart wrote:
I do wonder if users find the differences between
predefined roles and role
attributes confusing. INHERIT doesn't govern role
attributes, but it will
govern predefined roles when this patch is applied. Maybe
the role
attribute system should eventually be deprecated in favor
of using
predefined roles for everything. Or perhaps the
predefined roles should be
converted to role attributes.
Yep, I was suggesting that the latter would have been
preferable to me while
Robert seemed to prefer the former. Honestly I could be
happy with either of
those solutions, but as I alluded to that is probably a
discussion for the
next development cycle since I don't see us doing that big
a change in this
one.
I agree. I still think Joshua's proposed patch is a
worthwhile improvement
for v15.
+1
I am planning to get into it in detail this weekend. So far I have
really just ensured it merges cleanly and passes make world.Rebased patch to apply to master attached.
Well longer than I planned, but finally took a closer look.
I made one minor editorial fix to Joshua's patch, rebased to current
master, and added two missing call sites that presumably arerelated to
recent commits for pg_basebackup.
FWIW I pinged Stephen when I saw the basebackup changes included
is_member_of and he didn't think they necessarily needed to be changed
since they aren't accessible to human and you can't SET ROLE on a
replication connection in order to access the role where inheritance
was blocked.Yeah, though that should really be very clearly explained in comments
around that code as anything that uses is_member should really be viewed
as an exception. I also wouldn’t be against using has_privs there
anyway and saying that you shouldn’t be trying to connect as one role on
a replication connection and using the privs of another when you don’t
automatically inherit those rights, but on the assumption that the
committer intended is_member there because SET ROLE isn’t something one
does on replication connections, I’m alright with it being as is.Robert -- any opinion on this? If I am not mistaken it is code that you
are actively working on.
Lacking any feedback from Robert, I removed my changes related to
basebackup and pushed Joshua's patch with one minor editorial fix. What
to do with the basebackup changes can go on the open items list for pg15
I guess.
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
On Mon, Mar 21, 2022 at 4:15 PM Joe Conway <mail@joeconway.com> wrote:
Robert -- any opinion on this? If I am not mistaken it is code that you
are actively working on.
Woops, I only just saw this. I don't mind if you want to change the
calls to is_member_of_role() in basebackup_server.c and
basebackup_to_shell.c to has_privs_of_role(). However, it's not clear
to me why it's different than the calls we have in other places, like
calculate_database_size() and the relatively widely-used
check_is_member_of_role(). As long as we have a bunch of different
practices in different parts of the code base I can't see people
getting this right consistently ... leaving aside any possible
disagreement about which way is "right".
--
Robert Haas
EDB: http://www.enterprisedb.com
On 3/28/22 15:56, Robert Haas wrote:
On Mon, Mar 21, 2022 at 4:15 PM Joe Conway <mail@joeconway.com> wrote:
Robert -- any opinion on this? If I am not mistaken it is code that you
are actively working on.Woops, I only just saw this. I don't mind if you want to change the
calls to is_member_of_role() in basebackup_server.c and
basebackup_to_shell.c to has_privs_of_role().
No worries -- I will take care of that shortly.
However, it's not clear to me why it's different than the calls we
have in other places, like calculate_database_size() and the
relatively widely-used check_is_member_of_role().
I will have to go refresh my memory, but when I looked at those sites
closely it all made sense to me.
I think most if not all of them were checking for the ability to switch
to the other role, not actually checking for privileges by virtue of
belonging to that role.
As long as we have a bunch of different practices in different parts
of the code base I can't see people getting this right consistently
... leaving aside any possible disagreement about which way is
"right".
When I take the next pass I can consider whether additional comments
will help and report back.
Joe
--
Crunchy Data - http://crunchydata.com
PostgreSQL Support for Secure Enterprises
Consulting, Training, & Open Source Development
On 3/28/22 15:56, Robert Haas wrote:
On Mon, Mar 21, 2022 at 4:15 PM Joe Conway <mail@joeconway.com> wrote:
Robert -- any opinion on this? If I am not mistaken it is code that you
are actively working on.Woops, I only just saw this. I don't mind if you want to change the
calls to is_member_of_role() in basebackup_server.c and
basebackup_to_shell.c to has_privs_of_role().
Done as originally posted.
On that last note, I did not find basebackup_to_shell.required_role
documented at all, and did not attempt to fix that.
I saw that Robert added documentation and it already reads correctly I
believe, except possibly an unrelated issue:
8<--------------
<para>
A role which replication whose privileges users are required to
possess
in order to make use of the <literal>shell</literal> backup target.
If this is not set, any replication user may make use of the
<literal>shell</literal> backup target.
</para>
8<--------------
Robert, should that actually be:
s/role which replication/role with replication/
?
Joe
On Sat, Apr 2, 2022 at 1:32 PM Joe Conway <mail@joeconway.com> wrote:
I saw that Robert added documentation and it already reads correctly I
believe, except possibly an unrelated issue:8<--------------
<para>
A role which replication whose privileges users are required to
possess
in order to make use of the <literal>shell</literal> backup target.
If this is not set, any replication user may make use of the
<literal>shell</literal> backup target.
</para>
8<--------------Robert, should that actually be:
s/role which replication/role with replication/
Oh, good catch. Actually shouldn't "with replication" just be deleted?
Or maybe it needs to be reworded more, for clarity: "Replication users
must possess the privileges of this role in order to ..."
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Feb 7, 2022 at 11:13 AM Joe Conway <mail@joeconway.com> wrote:
It seems to me that the INHERIT role flag isn't very well-considered.
Inheritance, or the lack of it, ought to be decided separately for
each inherited role. However, that would be a major architectural
change.Agreed -- that would be useful.
Is this a kind of change people would support? Here's a quick sketch:
1. Extend the GRANT role_name TO role_name [ WITH ADMIN OPTION ] with
a new, optional clause, something like WITH NO INHERIT or WITH
NOINHERIT or WITHOUT INHERIT.
2. Remove the INHERIT | NOINHERIT flag from CREATE ROLE and ALTER ROLE.
3. Replace pg_authid.rolinherit with pg_auth_members.inherit. Any
place where we would have considered rolinherit, instead consider the
inherit flag for the particular pg_auth_members entry at issue.
4. When dumping from an old version, dump all grants to NOINHERIT
roles as non-inheritable grants.
The idea here is that, today, a role must either inherit privileges
from all roles of which it is a member or none of them. With this
proposal, you could make a role inherit some of those privileges but
not others. I think it's not difficult to see how that might be
useful: for example, user A could be granted non-login role X with
inheritance and login role B without inheritance. That would allow
user A to exercise the privileges of a member of group X at all times,
and the privileges of user B only with an explicit SET ROLE operation.
That way, A is empowered to act for B when necessary, but won't
accidentally do so.
It's been proposed elsewhere by Stephen and others that we ought to
have the ability to grant ADMIN OPTION on a role without granting
membership in that role. There's some overlap between these two
proposals. If your concern is just about accident prevention, you will
be happy to use this instead of that. If you want real access
restrictions, you will want that, not this. I think that's OK. Doing
this first doesn't mean we can't do that later. What about the
reverse? Could we add ADMIN-without-membership first, and then decide
whether to do this later? Maybe so, but I have come to feel that
NOINHERIT is such an ugly wart that we'll be happier the sooner we
kill it. It seems to make every discussion that we have about any
other potential change in this area more difficult, and I venture that
ADMIN-without-membership wouldn't turn out to be an exception.
Based on previous discussions I think that, long term, we're probably
headed toward a future where role grants have a bunch of different
flags, each of which controls a single behavior: whether you can
implicitly use the privileges of the role, whether you can access them
via SET ROLE, whether you can grant the role to others, or revoke it
from them, etc. I don't know exactly what the final list will look
like, and hopefully it won't be so long that it makes us all wish we
were dead, but there doesn't seem to be any possibility that implicit
inheritance of permissions won't be in that list. I spent a few
minutes considering whether I ought to instead propose that we just
nuke NOINHERIT from orbit and replace it with absolutely nothing, and
concluded that such a proposal had no hope of attracting consensus.
Perhaps the idea of replacing it with that is more powerful and at
least IMHO cleaner will.
Thoughts?
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
Is this a kind of change people would support? Here's a quick sketch:
1. Extend the GRANT role_name TO role_name [ WITH ADMIN OPTION ] with
a new, optional clause, something like WITH NO INHERIT or WITH
NOINHERIT or WITHOUT INHERIT.
2. Remove the INHERIT | NOINHERIT flag from CREATE ROLE and ALTER ROLE.
3. Replace pg_authid.rolinherit with pg_auth_members.inherit. Any
place where we would have considered rolinherit, instead consider the
inherit flag for the particular pg_auth_members entry at issue.
4. When dumping from an old version, dump all grants to NOINHERIT
roles as non-inheritable grants.
Point 2 would cause every existing pg_dumpall script to fail, which
seems like kind of a large gotcha. Less unpleasant alternatives
could include
* Continue to accept the syntax, but ignore it, maybe with a WARNING
for the alternative that doesn't correspond to the new behavior.
* Keep pg_authid.rolinherit, and have it act as supplying the default
behavior for subsequent GRANTs to that role.
Perhaps there are other ways.
regards, tom lane
On Thu, Jun 2, 2022 at 1:17 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Point 2 would cause every existing pg_dumpall script to fail, which
seems like kind of a large gotcha. Less unpleasant alternatives
could include* Continue to accept the syntax, but ignore it, maybe with a WARNING
for the alternative that doesn't correspond to the new behavior.* Keep pg_authid.rolinherit, and have it act as supplying the default
behavior for subsequent GRANTs to that role.
Of those two alternatives, I would certainly prefer the first, because
the second doesn't actually get rid of the ugly wart. It just adds a
non-ugly thing that we have to maintain along with the ugly thing,
apparently in perpetuity. If we do the first of these, we can probably
remove the obsolete syntax at some point in the distant future, and in
the meantime, we don't have to figure out how it's supposed to
interact with existing features or new ones, since the actual feature
is already removed.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jun 02, 2022 at 12:26:48PM -0400, Robert Haas wrote:
On Mon, Feb 7, 2022 at 11:13 AM Joe Conway <mail@joeconway.com> wrote:
It seems to me that the INHERIT role flag isn't very well-considered.
Inheritance, or the lack of it, ought to be decided separately for
each inherited role. However, that would be a major architectural
change.Agreed -- that would be useful.
Is this a kind of change people would support? Here's a quick sketch:
Yes.
The idea here is that, today, a role must either inherit privileges
from all roles of which it is a member or none of them. With this
proposal, you could make a role inherit some of those privileges but
not others. I think it's not difficult to see how that might be
useful: for example, user A could be granted non-login role X with
inheritance and login role B without inheritance. That would allow
user A to exercise the privileges of a member of group X at all times,
and the privileges of user B only with an explicit SET ROLE operation.
That way, A is empowered to act for B when necessary, but won't
accidentally do so.
I think we should also consider replacing role attributes with predefined
roles. I'm not sure that this proposal totally prepares us for such a
change, given role attributes apply only to the specific role for which
they are set and aren't inherited. ISTM in order to support that, we'd
need even more enhanced functionality. For example, if I want 'robert' to
be a superuser, and I want 'joe' to inherit the privileges of 'robert' but
not 'pg_superuser', you'd need some way to specify inheriting only certain
privileges possessed by an intermediate role.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, Jun 2, 2022 at 2:07 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I think we should also consider replacing role attributes with predefined
roles. I'm not sure that this proposal totally prepares us for such a
change, given role attributes apply only to the specific role for which
they are set and aren't inherited. ISTM in order to support that, we'd
need even more enhanced functionality. For example, if I want 'robert' to
be a superuser, and I want 'joe' to inherit the privileges of 'robert' but
not 'pg_superuser', you'd need some way to specify inheriting only certain
privileges possessed by an intermediate role.
I guess we could think about adding something like an ONLY clause,
like GRANT ONLY robert TO joe. I feel a little bit uncomfortable about
that, though, because it assumes that robert is a superuser but his
own privileges are distinguishable from those of the superuser. Are
they really? If I can assume robert's identity, I can presumably
Trojan my way into the superuser account pretty easily. I'll just
define a little trigger on one of his tables. I don't really see a way
where we can ever make it safe to grant a non-superuser membership in
a superuser role.
But even if there is a way, I think that is a separate patch from what
I'm proposing here. [NO]INHERIT only has to do with what privileges
you can exercise without SET ROLE. To solve the problem you're talking
about here, you'd need a way to control what privileges are conferred
in any manner, which is related, but different.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jun 02, 2022 at 03:37:34PM -0400, Robert Haas wrote:
On Thu, Jun 2, 2022 at 2:07 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I think we should also consider replacing role attributes with predefined
roles. I'm not sure that this proposal totally prepares us for such a
change, given role attributes apply only to the specific role for which
they are set and aren't inherited. ISTM in order to support that, we'd
need even more enhanced functionality. For example, if I want 'robert' to
be a superuser, and I want 'joe' to inherit the privileges of 'robert' but
not 'pg_superuser', you'd need some way to specify inheriting only certain
privileges possessed by an intermediate role.I guess we could think about adding something like an ONLY clause,
like GRANT ONLY robert TO joe. I feel a little bit uncomfortable about
that, though, because it assumes that robert is a superuser but his
own privileges are distinguishable from those of the superuser. Are
they really? If I can assume robert's identity, I can presumably
Trojan my way into the superuser account pretty easily. I'll just
define a little trigger on one of his tables. I don't really see a way
where we can ever make it safe to grant a non-superuser membership in
a superuser role.
I was primarily looking at this from the angle of preserving current
behavior when upgrading from a version with role attributes to a version
without them. If it's alright that a role with privileges of a superuser
role begins being treated like a superuser after an upgrade, then we
probably don't need something like GRANT ONLY. I bet that's how a lot of
people expect role attributes to work, anyway. I'm sure I did at some
point.
But even if there is a way, I think that is a separate patch from what
I'm proposing here. [NO]INHERIT only has to do with what privileges
you can exercise without SET ROLE. To solve the problem you're talking
about here, you'd need a way to control what privileges are conferred
in any manner, which is related, but different.
I agree that the role-attribute-to-predefined-role stuff needs its own
thread. I just think it's worth designing this stuff with that in mind.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On 2022-06-02 Th 14:06, Robert Haas wrote:
On Thu, Jun 2, 2022 at 1:17 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Point 2 would cause every existing pg_dumpall script to fail, which
seems like kind of a large gotcha. Less unpleasant alternatives
could include* Continue to accept the syntax, but ignore it, maybe with a WARNING
for the alternative that doesn't correspond to the new behavior.* Keep pg_authid.rolinherit, and have it act as supplying the default
behavior for subsequent GRANTs to that role.Of those two alternatives, I would certainly prefer the first, because
the second doesn't actually get rid of the ugly wart. It just adds a
non-ugly thing that we have to maintain along with the ugly thing,
apparently in perpetuity. If we do the first of these, we can probably
remove the obsolete syntax at some point in the distant future, and in
the meantime, we don't have to figure out how it's supposed to
interact with existing features or new ones, since the actual feature
is already removed.
+1
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On Thu, Jun 2, 2022 at 12:26 PM Robert Haas <robertmhaas@gmail.com> wrote:
1. Extend the GRANT role_name TO role_name [ WITH ADMIN OPTION ] with
a new, optional clause, something like WITH NO INHERIT or WITH
NOINHERIT or WITHOUT INHERIT.
I just realized that, with ADMIN OPTION, you can change your mind
after the initial grant, and we probably would want to allow the same
kind of thing here. The existing syntax is:
1. GRANT foo TO bar [WITH ADMIN OPTION];
2. REVOKE foo FROM bar;
3. REVOKE ADMIN OPTION FOR foo FROM bar;
To grant the admin option later, you just use (1) again and this time
include WITH ADMIN OPTION. To revoke it without removing the grant
entirely, you use (3). This could easily be generalized to any number
of options all of which are Boolean properties and all of which have a
default value of false, but INHERIT is a Boolean property with a
default value of true, and calling the property NOINHERIT to dodge
that issue seems dumb. I'd like to find a way to extend the syntax
that can accommodate not only INHERIT as an option, but any other
options we might want to add in the future, and maybe even leave room
for non-Boolean properties, just in case. The question of which
options we think it valuable to add is separate from what the syntax
ought to be, so for syntax discussion purposes let's suppose we want
to add three new options: FRUIT, which can be strawberry, banana, or
kiwi; CHOCOLATE, a Boolean whose default value is true; and SARDINES,
another Boolean whose default value is false. Then:
GRANT foo TO bar WITH FRUIT STRAWBERRY;
GRANT foo TO bar WITH CHOCOLATE FALSE;
GRANT foo TO bar WITH SARDINES TRUE;
GRANT foo TO bar WITH SARDINES; -- same as previous
GRANT foo TO bar WITH SARDINES OPTION; -- also same as previous
GRANT foo TO bar WITH FRUIT KIWI, SARDINES; -- multiple options
GRANT foo TO bar WITH CHOCOLATE OPTION, SARDINES OPTION; -- dubious combination
In other words, you write a comma-separated list of option names. Each
option name can be followed by an option value or by the word OPTION.
If it is followed by the word OPTION or by nothing, the option value
is taken to be true. This would mean that, going forward, any of the
following would work: (a) GRANT foo TO bar WITH ADMIN OPTION, (b)
GRANT foo TO BAR WITH ADMIN, (c) GRANT foo TO BAR WITH ADMIN TRUE.
To revoke a grant entirely, you just say REVOKE foo FROM bar, as now.
To change an option for an existing grant, you can re-execute the
grant statement with a different WITH clause. Any options that are
explicitly mentioned will be changed to have the associated values;
unmentioned options will retain their existing values. If you want to
change the value of a Boolean option to false, you have a second
option, which is to write "REVOKE option_name OPTION FOR foo FROM
bar," which means exactly the same thing as "GRANT foo TO bar WITH
option_name FALSE".
In terms of what options to offer, the most obvious idea is to just
add INHERIT as a Boolean option which is true by default. We could go
further and also add a SET option, with the idea that INHERIT OPTION
controls whether you can exercise the privileges of the role without
SET ROLE, and SET OPTION controls whether you can switch to that role
using the SET ROLE command. Those two things together would give us a
way to get to the admin-without-membership concept that we have
previously discussed: GRANT foo TO BAR WITH ADMIN TRUE, INHERIT FALSE,
SET FALSE sounds like it should do the trick.
I briefly considered suggesting that the way to set a Boolean-valued
option to false ought to be to write "NO option_name" rather than
"option_name FALSE", since it reads more naturally, but I proposed
this instead because it's more like what we do for other options lists
(cf. EXPLAIN, VACUUM, COPY).
Thoughts?
--
Robert Haas
EDB: http://www.enterprisedb.com
Greetings,
* Robert Haas (robertmhaas@gmail.com) wrote:
On Mon, Feb 7, 2022 at 11:13 AM Joe Conway <mail@joeconway.com> wrote:
It seems to me that the INHERIT role flag isn't very well-considered.
Inheritance, or the lack of it, ought to be decided separately for
each inherited role. However, that would be a major architectural
change.Agreed -- that would be useful.
Is this a kind of change people would support? Here's a quick sketch:
On the whole, moving things to pg_auth_members when they're about
relationships between roles makes more sense to me too (ISTR mentioning
that somewhere or at least thinking about it but not sure where and it
doesn't really matter).
It's been proposed elsewhere by Stephen and others that we ought to
have the ability to grant ADMIN OPTION on a role without granting
membership in that role. There's some overlap between these two
proposals. If your concern is just about accident prevention, you will
be happy to use this instead of that. If you want real access
restrictions, you will want that, not this. I think that's OK. Doing
this first doesn't mean we can't do that later. What about the
reverse? Could we add ADMIN-without-membership first, and then decide
whether to do this later? Maybe so, but I have come to feel that
NOINHERIT is such an ugly wart that we'll be happier the sooner we
kill it. It seems to make every discussion that we have about any
other potential change in this area more difficult, and I venture that
ADMIN-without-membership wouldn't turn out to be an exception.
Being able to segregate the control over privileges from the control
over objects is definitely helpful in some environments. I don't see
any strong reason that one must happen before the other and am happy to
defer to whomever has cycles to work on these things to sort that out.
Based on previous discussions I think that, long term, we're probably
headed toward a future where role grants have a bunch of different
flags, each of which controls a single behavior: whether you can
implicitly use the privileges of the role, whether you can access them
via SET ROLE, whether you can grant the role to others, or revoke it
from them, etc. I don't know exactly what the final list will look
like, and hopefully it won't be so long that it makes us all wish we
were dead, but there doesn't seem to be any possibility that implicit
inheritance of permissions won't be in that list. I spent a few
minutes considering whether I ought to instead propose that we just
nuke NOINHERIT from orbit and replace it with absolutely nothing, and
concluded that such a proposal had no hope of attracting consensus.
I agree that a future where there's more granularity in terms of role
grants would be a better place than where we are now. I'd be -1 on just
removing 'NOINHERIT', to no one's surprise, I'm sure.
Perhaps the idea of replacing it with that is more powerful and at
least IMHO cleaner will.
-EPARSEFAIL (tho I'm guessing you were intending to say that replacing
the role-attribute noinherit with something more powerful would be a
generally agreeable way forward, which I would generally support)
* Robert Haas (robertmhaas@gmail.com) wrote:
On Thu, Jun 2, 2022 at 12:26 PM Robert Haas <robertmhaas@gmail.com> wrote:
1. Extend the GRANT role_name TO role_name [ WITH ADMIN OPTION ] with
a new, optional clause, something like WITH NO INHERIT or WITH
NOINHERIT or WITHOUT INHERIT.I just realized that, with ADMIN OPTION, you can change your mind
after the initial grant, and we probably would want to allow the same
kind of thing here. The existing syntax is:
Yes.
1. GRANT foo TO bar [WITH ADMIN OPTION];
2. REVOKE foo FROM bar;
3. REVOKE ADMIN OPTION FOR foo FROM bar;To grant the admin option later, you just use (1) again and this time
include WITH ADMIN OPTION. To revoke it without removing the grant
entirely, you use (3). This could easily be generalized to any number
of options all of which are Boolean properties and all of which have a
default value of false, but INHERIT is a Boolean property with a
default value of true, and calling the property NOINHERIT to dodge
that issue seems dumb. I'd like to find a way to extend the syntax
that can accommodate not only INHERIT as an option, but any other
options we might want to add in the future, and maybe even leave room
for non-Boolean properties, just in case. The question of which
options we think it valuable to add is separate from what the syntax
ought to be, so for syntax discussion purposes let's suppose we want
to add three new options: FRUIT, which can be strawberry, banana, or
kiwi; CHOCOLATE, a Boolean whose default value is true; and SARDINES,
another Boolean whose default value is false. Then:GRANT foo TO bar WITH FRUIT STRAWBERRY;
GRANT foo TO bar WITH CHOCOLATE FALSE;
GRANT foo TO bar WITH SARDINES TRUE;
GRANT foo TO bar WITH SARDINES; -- same as previous
GRANT foo TO bar WITH SARDINES OPTION; -- also same as previous
GRANT foo TO bar WITH FRUIT KIWI, SARDINES; -- multiple options
GRANT foo TO bar WITH CHOCOLATE OPTION, SARDINES OPTION; -- dubious combinationIn other words, you write a comma-separated list of option names. Each
option name can be followed by an option value or by the word OPTION.
If it is followed by the word OPTION or by nothing, the option value
is taken to be true. This would mean that, going forward, any of the
following would work: (a) GRANT foo TO bar WITH ADMIN OPTION, (b)
GRANT foo TO BAR WITH ADMIN, (c) GRANT foo TO BAR WITH ADMIN TRUE.
Seems generally reasonable... though I'm thinking that we should make
OPTION only be accepted for ADMIN as TRUE rather than having it
magically work for all the other things because ... why would we? What
if we decided later that we wanted a FRUIT OPTION (to use your example
of FRUIT being an enum). I don't feel very strongly on that point
though (but what if you wrote FRUIT OPTION and FRUIT isn't able to just
be TRUE? Would that blow up, or?).
To revoke a grant entirely, you just say REVOKE foo FROM bar, as now.
To change an option for an existing grant, you can re-execute the
grant statement with a different WITH clause. Any options that are
explicitly mentioned will be changed to have the associated values;
unmentioned options will retain their existing values. If you want to
change the value of a Boolean option to false, you have a second
option, which is to write "REVOKE option_name OPTION FOR foo FROM
bar," which means exactly the same thing as "GRANT foo TO bar WITH
option_name FALSE".
I'm a bit concerned about this because, iiuc, it would mean:
GRANT foo TO bar WITH FRUIT KIWI, SARDINES;
GRANT foo TO bar WITH FRUIT STRAWBERRY;
would mean that the GRANT of FRUIT would then *only* have STRAWBERRY,
right? The first GRANT in this example would essentially be entirely
undone by the second GRANT. Having GRANT remove things would be new, I
believe. An alternative would be to have GRANT continue to always be
'additive' and require using REVOKE to remove them, ala:
REVOKE FRUIT OPTION FOR foo FROM bar;
GRANT foo TO bar WITH FRUIT STRAWBERRY;
Another tricky item to consider here is the "implicitly includes role
membership" part of the GRANT. That is, the spec explicitly says:
GRANT foo TO bar WITH ADMIN OPTION;
Means that 'foo' is grant'd to 'bar' (plus the admin option thing is
done). In your proposal, does:
GRANT foo TO bar WITH FRUIT STRAWBERRY;
mean that 'foo' is grant'd to 'bar' too? Seems to be closest to current
usage and the spec's ideas on these things. I'm thinking that could be
dealt with by having a MEMBERSHIP option (which would be a separate
column in pg_auth_members and default would be 'true') but otherwise
using exactly what you have here, eg:
GRANT foo TO bar;
GRANT foo TO bar WITH MEMBERSHIP; -- identical to above
GRANT foo TO bar WITH MEMBERSHIP TRUE; -- identical to above
GRANT foo TO bar WITH MEMBERSHIP OPTION; -- identical to above
GRANT foo TO bar WITH MEMBERSHIP FALSE; -- no-op
GRANT foo TO bar WITH MEMBERSHIP FALSE, FRUIT STRAWBERRY; -- only fruit
GRANT foo TO bar WITH FRUIT STRAWBERRY; -- with membership and fruit
Note also that the spec defines a WITH HIERARCHY OPTION too (but it's
explicitly defined for table privileges not roles, but still part of the
overall GRANT syntax) and after those WITH's has a GRANTED BY syntax
(for both roles and object privileges, which are actually distinct
things in the spec), just when thinking about what will/won't work in
the grammar.
In terms of what options to offer, the most obvious idea is to just
add INHERIT as a Boolean option which is true by default. We could go
further and also add a SET option, with the idea that INHERIT OPTION
controls whether you can exercise the privileges of the role without
SET ROLE, and SET OPTION controls whether you can switch to that role
using the SET ROLE command. Those two things together would give us a
way to get to the admin-without-membership concept that we have
previously discussed: GRANT foo TO BAR WITH ADMIN TRUE, INHERIT FALSE,
SET FALSE sounds like it should do the trick.
Guess this is pretty close to what I just suggested above, but using
"SET" for that strikes me as quite generic while MEMBERSHIP is clearer.
Not really sure how I feel about what the results of:
GRANT foo TO bar;
GRANT foo TO bar WITH MEMBERSHIP FALSE;
should be with this but I think I'm leaning towards the "GRANT isn't for
removing stuff" side. A GRANT is for giving things, REVOKE is for
taking things away, meaning that the second statement above would,
again, be a no-op.
I briefly considered suggesting that the way to set a Boolean-valued
option to false ought to be to write "NO option_name" rather than
"option_name FALSE", since it reads more naturally, but I proposed
this instead because it's more like what we do for other options lists
(cf. EXPLAIN, VACUUM, COPY).
The spec has WITH ADMIN OPTION and WITH HIERARCHY OPTION, so going in
that general direction seems a bit better. Also, as mentioned, GRANT
doesn't really do 'subtractive' things, so having a 'NO' in there seems
to go against the grain.
Thanks!
Stephen
Import Notes
Reply to msg id not found: CA+TgmoYA-zo8usqGoPGaXRFzJbzJxLuvk7RzNyWKhe5dHUuRsA@mail.gmail.comCA+Tgmoax3J2D1RoqSqBRuLVifMjinZ5SPvR+rBqBT89u57zOJQ@mail.gmail.com | Resolved by subject fallback
[ trimming various comments that broadly make sense to me and which
don't seem to require further comment in this moment ]
On Mon, Jun 6, 2022 at 7:21 PM Stephen Frost <sfrost@snowman.net> wrote:
To revoke a grant entirely, you just say REVOKE foo FROM bar, as now.
To change an option for an existing grant, you can re-execute the
grant statement with a different WITH clause. Any options that are
explicitly mentioned will be changed to have the associated values;
unmentioned options will retain their existing values. If you want to
change the value of a Boolean option to false, you have a second
option, which is to write "REVOKE option_name OPTION FOR foo FROM
bar," which means exactly the same thing as "GRANT foo TO bar WITH
option_name FALSE".I'm a bit concerned about this because, iiuc, it would mean:
GRANT foo TO bar WITH FRUIT KIWI, SARDINES;
GRANT foo TO bar WITH FRUIT STRAWBERRY;would mean that the GRANT of FRUIT would then *only* have STRAWBERRY,
right?
I think that you are misunderstanding what kind of option I intended
FRUIT to be. Here, I was imagining FRUIT as a property that every
grant has. Any given grant is either strawberry, or it's kiwi, or it's
banana. It cannot be more than one of those things, nor can it be none
of those things. It follows that if you execute GRANT without
specifying a FRUIT, there's some default - hopefully banana, but
that's a matter of taste. Later, you can change the fruit associated
with a grant, but you cannot remove it, because there's no such thing
as a fruitless grant. Imagine that the catalog representation is a
"char" that is either 's', 'k', or 'b'.
Now you could certainly question whether it's a good idea for us to
have an option that works like this. I don't really know. For a while
I thought that it might make sense to propose something like ACCESS {
EXPLICIT | IMPLICIT | NONE }, where ACCESS IMPLICIT would mean that
the grantee implicitly has the permissions of the role, ACCESS
EXPLICIT means that they do not implicitly have those permissions but
can access them via SET ROLE, and ACCESS NONE means that they can't
even do that. The default would I suppose be ACCESS IMPLICIT but you
could change it to one of the other two. However, I then thought that
it made more sense to keep it as two separate Booleans because
actually all four combinations are sensible: you could want to have a
setup where you're allowed to implicitly access the permissions of the
role but you CANNOT SET ROLE to it. For instance, this might make
sense for a predefined role, so that you don't end up with tables
owned by pg_monitor or whatever.
Anyway, if the hypothetical FRUIT property works as I describe here -
there's always a single value - then the second GRANT leaves the
SARDINES property set, but changes the FRUIT property from strawberry
to kiwi. Since the property is single-valued, you cannot add a second
fruit, nor can you remove the fruit altogether, because those just
aren't sensible ideas with an option of this kind. As alonger example,
it's like the FORMAT property of EXPLAIN: it always has to be TEXT or
XML or JSON. You can choose not to explicitly specify the option, but
then you get a default. Your EXPLAIN output always has to have some
format.
In your proposal, does:
GRANT foo TO bar WITH FRUIT STRAWBERRY;
mean that 'foo' is grant'd to 'bar' too? Seems to be closest to current
usage and the spec's ideas on these things. I'm thinking that could be
dealt with by having a MEMBERSHIP option (which would be a separate
column in pg_auth_members and default would be 'true') but otherwise
using exactly what you have here, eg:
Currently, GRANT always creates an entry in pg_auth_members, or
modifies an existing one, or does nothing because the one that's there
is the same as the one it would have created. I think we should stick
with that idea.
That's why I proposed the name SET, not MEMBERSHIP. You would still
get a catalog entry in pg_auth_members, so you are still a member in
some loose sense even if your grant has INHERIT FALSE and SET FALSE,
but in such a case the fact that you are a member isn't really doing
anything for you in terms of getting you access to privileges because
you're neither allowed to exercise them implicitly nor SET ROLE to the
role.
I find that idea - that GRANT always grants membership but membership
by itself doesn't really do anything for you unless you've got some
other options enabled somewhere - more appealing than the design you
seem to have in mind, which seems to me that membership is the same
thing as the ability to SET ROLE and thus, if the ability to SET ROLE
has not been granted, you have a grant that didn't confirm membership
in any sense. I'm not saying we couldn't make that work, but I think
it's awkward to make that work. Among other problems, what happens
with the actual catalog representation? You could for example still
create a role in pg_auth_members and then have a Boolean column
membership = false, but that's a bit odd. Or you could add a new
catalog or you could rename the existing catalog, but that's more
complicated for not much benefit. I think there's some fuzziness at
the semantic level with this kind of thing too: if I do a GRANT with
MEMBERSHIP FALSE, what exactly is it that I am granting? I like the
conceptual simplicity of being able to say that a GRANT always confers
membership, but membership does not intrinsically include the ability
to SET ROLE -- that's a Boolean property of membership, not membership
itself.
--
Robert Haas
EDB: http://www.enterprisedb.com
Greetings,
* Robert Haas (robertmhaas@gmail.com) wrote:
On Mon, Jun 6, 2022 at 7:21 PM Stephen Frost <sfrost@snowman.net> wrote:
To revoke a grant entirely, you just say REVOKE foo FROM bar, as now.
To change an option for an existing grant, you can re-execute the
grant statement with a different WITH clause. Any options that are
explicitly mentioned will be changed to have the associated values;
unmentioned options will retain their existing values. If you want to
change the value of a Boolean option to false, you have a second
option, which is to write "REVOKE option_name OPTION FOR foo FROM
bar," which means exactly the same thing as "GRANT foo TO bar WITH
option_name FALSE".I'm a bit concerned about this because, iiuc, it would mean:
GRANT foo TO bar WITH FRUIT KIWI, SARDINES;
GRANT foo TO bar WITH FRUIT STRAWBERRY;would mean that the GRANT of FRUIT would then *only* have STRAWBERRY,
right?I think that you are misunderstanding what kind of option I intended
FRUIT to be. Here, I was imagining FRUIT as a property that every
grant has. Any given grant is either strawberry, or it's kiwi, or it's
banana. It cannot be more than one of those things, nor can it be none
of those things. It follows that if you execute GRANT without
specifying a FRUIT, there's some default - hopefully banana, but
that's a matter of taste. Later, you can change the fruit associated
with a grant, but you cannot remove it, because there's no such thing
as a fruitless grant. Imagine that the catalog representation is a
"char" that is either 's', 'k', or 'b'.
Ah, yeah, if it's always single-value then that seems reasonable to me
too. If we ever get to wanting to support multiple choices for a given
option then we could possibly require they be provided as an ARRAY or
using ()'s or something else, but we probably don't need to try and sort
that today.
In your proposal, does:
GRANT foo TO bar WITH FRUIT STRAWBERRY;
mean that 'foo' is grant'd to 'bar' too? Seems to be closest to current
usage and the spec's ideas on these things. I'm thinking that could be
dealt with by having a MEMBERSHIP option (which would be a separate
column in pg_auth_members and default would be 'true') but otherwise
using exactly what you have here, eg:Currently, GRANT always creates an entry in pg_auth_members, or
modifies an existing one, or does nothing because the one that's there
is the same as the one it would have created. I think we should stick
with that idea.
Alright.
That's why I proposed the name SET, not MEMBERSHIP. You would still
get a catalog entry in pg_auth_members, so you are still a member in
some loose sense even if your grant has INHERIT FALSE and SET FALSE,
but in such a case the fact that you are a member isn't really doing
anything for you in terms of getting you access to privileges because
you're neither allowed to exercise them implicitly nor SET ROLE to the
role.
Naming things is hard. :) I'm still not a fan of calling that option
'SET' and 'membership' feels like how it's typically described today
when someone has the rights of a group (implicitly or explicitly). As
for what to call "has a pg_auth_members row but no actual access", maybe
'associated'?
That does lead me down a bit of a rabbit hole because every role in the
entire system could be considered 'associated' with every other one and
if the case of "no pg_auth_members row" is identical to the case of
"pg_auth_members row with everything off/default" then it feels a bit
odd to have an entry for it- and is there any way to get rid of that
entry?
All that said ... we have a similar thing with GRANT today when it comes
to privileges on objects in that we go from NULL to owner-all+whatever,
and while it's a bit odd, it works well enough.
I find that idea - that GRANT always grants membership but membership
by itself doesn't really do anything for you unless you've got some
other options enabled somewhere - more appealing than the design you
seem to have in mind, which seems to me that membership is the same
thing as the ability to SET ROLE and thus, if the ability to SET ROLE
has not been granted, you have a grant that didn't confirm membership
in any sense. I'm not saying we couldn't make that work, but I think
it's awkward to make that work. Among other problems, what happens
with the actual catalog representation? You could for example still
create a role in pg_auth_members and then have a Boolean column
membership = false, but that's a bit odd. Or you could add a new
catalog or you could rename the existing catalog, but that's more
complicated for not much benefit. I think there's some fuzziness at
the semantic level with this kind of thing too: if I do a GRANT with
MEMBERSHIP FALSE, what exactly is it that I am granting? I like the
conceptual simplicity of being able to say that a GRANT always confers
membership, but membership does not intrinsically include the ability
to SET ROLE -- that's a Boolean property of membership, not membership
itself.
I agree with having the ability to have the SET ROLE privilege be
distinct and able to be given, or not. I don't think we need a new
catalog either, my thought was more along the lines of just renaming
what you proposed as being 'SET' to be 'MEMBERSHIP' while mostly keeping
the rest the same, but I did want to ask the question that didn't get
answered above:
In your proposal, does:
GRANT foo TO bar WITH FRUIT STRAWBERRY;
mean that 'foo' is grant'd to 'bar' too?
That is, regardless of how we track these things in the catalog or such,
we have to respect that:
GRANT foo TO bar;
is a SQL-defined thing that says that a 'role authorization descriptor'
is created. SET ROLE then checks if a role authorization descriptor
exists or not matching the current role to the new role and if it does
then the current role is changed to the new role. What I was really
trying to get at above is that:
GRANT foo TO bar WITH $anything-other-than-SET-false;
should probably also create a 'role authorization descriptor' that SET
ROLE will pick up on. In other words, the 'SET' thing, or if we call
that something else, should exist as a distinct column in
pg_auth_members, but the default value of it should be 'true', with the
ability for it to be turned to false either at GRANT time or with a
REVOKE.
Thanks,
Stephen
On Wed, Jun 8, 2022 at 10:16 AM Stephen Frost <sfrost@snowman.net> wrote:
That's why I proposed the name SET, not MEMBERSHIP. You would still
get a catalog entry in pg_auth_members, so you are still a member in
some loose sense even if your grant has INHERIT FALSE and SET FALSE,
but in such a case the fact that you are a member isn't really doing
anything for you in terms of getting you access to privileges because
you're neither allowed to exercise them implicitly nor SET ROLE to the
role.Naming things is hard. :) I'm still not a fan of calling that option
'SET' and 'membership' feels like how it's typically described today
when someone has the rights of a group (implicitly or explicitly). As
for what to call "has a pg_auth_members row but no actual access", maybe
'associated'?
What I want here is two Boolean flags, one of which controls whether
you implicitly have the privileges of the granted role and the other
of which controls whether you can access those privileges via SET
ROLE. In each case, I want TRUE to be the state where you have or can
get the privileges and FALSE the state where you can't. I'm not
especially stuck on the names INHERIT and SET for those things,
although in my opinion INHERIT is pretty good and SET is where there
is, perhaps, more likely to be room for improvement. However, I don't
think ASSOCIATED is an improvement because it reverses the sense: now,
TRUE means you don't have the privileges (because you're only
associated, not actually a member, I guess) and FALSE means you do
(because you are not merely associated, but are a member). Yick. That
seems super-unintuitive.
Other alternatives to SET:
- BECOME (can you become that user?)
- ASSUME (can you assume that roles's privileges?)
- EXPLICIT (perhaps also renaming the INHERIT permission to IMPLICIT)
Honestly the main thing I don't like about SET is that it sounds like
it ought to be the action verb in the command, rather than just an
option name. But we've already crossed that Rubicon by deciding that
you can GRANT SET on a GUC, and what's the difference between that and
granting a role WITH SET? I'm actually a bit afraid that if we get
creative here it will sound inconsistent.
That does lead me down a bit of a rabbit hole because every role in the
entire system could be considered 'associated' with every other one and
if the case of "no pg_auth_members row" is identical to the case of
"pg_auth_members row with everything off/default" then it feels a bit
odd to have an entry for it- and is there any way to get rid of that
entry?
"everything off" and "everything default" are two very different
things. If you just do "GRANT foo TO bar" without setting any options,
then you get all the options set to their default values, and that's
going to generate a pg_auth_members entry that we certainly can't
omit. However, let's say that you then alter that grant by removing
every single privilege bit, one by one:
GRANT foo TO bar; -- normal grant
GRANT foo TO bar WITH INHERIT OFF; -- implicit inheritance of privileges removed
GRANT foo TO bar WITH SET OFF; -- explicit SET ROLE privilege now also removed
GRANT foo TO bar WITH JOIE_DE_VIVRE OFF; -- now there's nothing left
One can certainly make an argument that the last GRANT ought to just
go ahead and nuke the pg_auth_members entry entirely, and I'm willing
to do that if that's what most people want. However, I think it might
be better to leave well enough alone. In my proposal, that last GRANT
command is just adjusting the options of the existing grant, not
removing it. True, it is a pretty worthless grant, lacking even joie
de vivre, but it is a grant all the same, and it can still be dumped
and restored just fine. With this change, that last GRANT command
becomes, in effect, a REVOKE, which is a little odd. Right now, the
pg_auth_members row has no "payload data" and perhaps it never will,
but if we were storing, I don't know, the number of times the grant
had ever been used, or when it got created, or whatever, that
information would be lost when that row is deleted, and that seems
like something that the user might want to have happen only when they
explicitly asked for it.
I don't think this is a big deal either way. I've designed systems for
other things both ways in the past, and my general experience has been
that the implicit-removal behavior tends to turn out to be more
annoying than you initially think that it will be, but if people want
that, it should be possible.
In your proposal, does:
GRANT foo TO bar WITH FRUIT STRAWBERRY;
mean that 'foo' is grant'd to 'bar' too?
That is, regardless of how we track these things in the catalog or such,
we have to respect that:GRANT foo TO bar;
is a SQL-defined thing that says that a 'role authorization descriptor'
is created. SET ROLE then checks if a role authorization descriptor
exists or not matching the current role to the new role and if it does
then the current role is changed to the new role. What I was really
trying to get at above is that:GRANT foo TO bar WITH $anything-other-than-SET-false;
should probably also create a 'role authorization descriptor' that SET
ROLE will pick up on. In other words, the 'SET' thing, or if we call
that something else, should exist as a distinct column in
pg_auth_members, but the default value of it should be 'true', with the
ability for it to be turned to false either at GRANT time or with a
REVOKE.
I think we're on the same page here. I have a strong desire not to get
caught up in a fruitless argument about SQL specification compliance,
but I believe everything after "in other words" matches my plan.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 02.06.22 18:26, Robert Haas wrote:
On Mon, Feb 7, 2022 at 11:13 AM Joe Conway<mail@joeconway.com> wrote:
It seems to me that the INHERIT role flag isn't very well-considered.
Inheritance, or the lack of it, ought to be decided separately for
each inherited role. However, that would be a major architectural
change.Agreed -- that would be useful.
Is this a kind of change people would support?
I think this would create a conflict with what role-based access control
normally means (outside of PostgreSQL specifically). A role is a
collection of privileges that you dynamically enable (e.g., with SET
ROLE). That corresponds to the NOINHERIT behavior. It's also what the
SQL standard specifies (which in term references other standards for
RBAC). The INHERIT behavior basically emulates the groups that used to
exist in PostgreSQL.
There are also possibly other properties you could derive from that
distinction. For example, you could argue that something like
pg_hba.conf should have group matching but not (noinherit-)role
matching, since those roles are not actually activated all the time.
There are also more advanced concepts like roles that are mutually
exclusive so that you can't activate them both at once.
Now, what PostgreSQL currently implements is a bit of a mess that's a
somewhat incompatible subset of all that. But you can basically get
those standard behaviors somehow. So I don't think we should break this
and go into a totally opposite direction.
Greetings,
On Fri, Jun 10, 2022 at 16:36 Peter Eisentraut <
peter.eisentraut@enterprisedb.com> wrote:
On 02.06.22 18:26, Robert Haas wrote:
On Mon, Feb 7, 2022 at 11:13 AM Joe Conway<mail@joeconway.com> wrote:
It seems to me that the INHERIT role flag isn't very well-considered.
Inheritance, or the lack of it, ought to be decided separately for
each inherited role. However, that would be a major architectural
change.Agreed -- that would be useful.
Is this a kind of change people would support?
I think this would create a conflict with what role-based access control
normally means (outside of PostgreSQL specifically). A role is a
collection of privileges that you dynamically enable (e.g., with SET
ROLE). That corresponds to the NOINHERIT behavior. It's also what the
SQL standard specifies (which in term references other standards for
RBAC). The INHERIT behavior basically emulates the groups that used to
exist in PostgreSQL.
Based on the later discussion, I don’t think anyone is really advocating to
move away from having a concept of immediately-inherited rights (inherit)
or to remove SET ROLE, or even to really change how they work all that
much. The idea is to give admins to control these with more granularity.
There are also possibly other properties you could derive from that
distinction. For example, you could argue that something like
pg_hba.conf should have group matching but not (noinherit-)role
matching, since those roles are not actually activated all the time.
This might be an interesting thing to consider separating out as a distinct
property, or we could just define it to depend on an existing other
property (which is essentially what is done today).
There are also more advanced concepts like roles that are mutually
exclusive so that you can't activate them both at once.
That’s an interesting one to consider and might be relevant to Robert’s
thoughts on how to extend GRANT.
Now, what PostgreSQL currently implements is a bit of a mess that's a
somewhat incompatible subset of all that. But you can basically get
those standard behaviors somehow. So I don't think we should break this
and go into a totally opposite direction.
Not sure how this is totally opposite.
Thanks,
Stephen
Show quoted text
On Fri, Jun 10, 2022 at 4:36 PM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
I think this would create a conflict with what role-based access control
normally means (outside of PostgreSQL specifically). A role is a
collection of privileges that you dynamically enable (e.g., with SET
ROLE). That corresponds to the NOINHERIT behavior. It's also what the
SQL standard specifies (which in term references other standards for
RBAC). The INHERIT behavior basically emulates the groups that used to
exist in PostgreSQL.
I don't think this creates any more of a conflict than we've already
got. In fact, I'd go so far as to say it resolves a problem that we
currently have. As far as I can see, we are stuck with a situation
where we have to support both the INHERIT behavior and the NOINHERIT
behavior. Removing either one would be a pretty major compatibility
break. And even if some people were willing to endorse that, it seems
clear from previous discussions that there are people who like the
NOINHERIT behavior and would object to its removal, and other people
(like me!) who like the INHERIT behavior and would object to removing
that. If you think it's feasible to get rid of either of these
behaviors, I'd be interested in hearing your thoughts on that, but to
me it looks like we are stuck with supporting both. From my point of
view, the question is how to make the best of that situation.
Consider a user who in general prefers the NOINHERIT behavior but also
wants to use predefined roles. Perhaps user 'peter' is to be granted
both 'paul' and 'pg_execute_server_programs'. If role 'peter' is set
to INHERIT, Peter will be sad, because his love for NOINHERIT probably
means that he doesn't want to exercise Paul's privileges
automatically. However, he needs to inherit the privileges of
'pg_execute_server_programs' or they are of no use to him. Peter
presumably wants to use COPY TO/FROM program to put data into a table
owned by 'peter', not a table owned by 'pg_execute_server_programs'.
If so, being able to SET ROLE to 'pg_execute_server_programs' is of no
use to him at all, but inheriting the privilege is useful. What is
really needed here is to have the grant of 'paul' be NOINHERIT and the
grant of 'pg_execute_server_programs' be INHERIT, but there's no way
to do that presently. My proposal fixes that problem.
If you don't agree with that analysis, I'd like to know which part of
it seems wrong to you. To me, it seems like an open and shut case: a
grant of a predefined role, or of a group, should have the INHERIT
behavior. A grant of a login role could plausibly have either one,
depending on user preferences. As long as the INHERIT property is a
property of the role to which the permission is granted, you have
trouble as soon as you try to do one grant that should have INHERIT
behavior and another that should have NOINHERIT behavior targeting the
same role.
There are also possibly other properties you could derive from that
distinction. For example, you could argue that something like
pg_hba.conf should have group matching but not (noinherit-)role
matching, since those roles are not actually activated all the time.There are also more advanced concepts like roles that are mutually
exclusive so that you can't activate them both at once.
I don't see how my proposal makes any of that any harder than it is
today. Or any easier, either. Seems pretty much orthogonal.
Now, what PostgreSQL currently implements is a bit of a mess that's a
somewhat incompatible subset of all that. But you can basically get
those standard behaviors somehow. So I don't think we should break this
and go into a totally opposite direction.
I don't think I'm proposing to break anything or go in a totally
opposite direction from anything, and to be honest I'm kind of
confused as to why you think that what I'm proposing would have that
effect. As far as I can see, the changes that I'm proposing are
upward-compatible and would permit easy migration to new releases via
either pg_dump or pg_upgrade with no behavior changes at all. Some
syntax would be a bit different on the new releases and that would
unlock some new options we don't currently have, but there's no
behavior that you can get today which you wouldn't be able to get any
more under this proposal.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Jun 13, 2022 at 11:01 AM Robert Haas <robertmhaas@gmail.com> wrote:
Some
syntax would be a bit different on the new releases and that would
unlock some new options we don't currently have, but there's no
behavior that you can get today which you wouldn't be able to get any
more under this proposal.
Agreed. Moving the inherit flag to the many-to-many join relation provides
flexibility, while representing the present behavior is trivial - every row
for a given member role has the same value for said flag.
One seemingly missing feature is to specify for a role that its privileges
cannot be inherited. In this case associations where it is the group role
mustn't be flagged inherit. Symmetrically, "inherit only" seems like a
plausible option for pre-defined group roles.
I agree that granting membership makes the pg_auth_members record appear
and revoking membership makes it disappear.
I dislike having GRANT do stuff when membership is already established.
ALTER MEMBER role IN group ALTER [SET | ASSUME] [TO | =] [TRUE | FALSE]
David J.
On Mon, Jun 13, 2022 at 2:42 PM David G. Johnston
<david.g.johnston@gmail.com> wrote:
Agreed. Moving the inherit flag to the many-to-many join relation provides flexibility, while representing the present behavior is trivial - every row for a given member role has the same value for said flag.
Precisely.
One seemingly missing feature is to specify for a role that its privileges cannot be inherited. In this case associations where it is the group role mustn't be flagged inherit. Symmetrically, "inherit only" seems like a plausible option for pre-defined group roles.
Yeah, I was kind of wondering about that, although I hadn't thought so
much of making it mandatory as having some kind of way of setting the
default. One could do either, but I think that can be left for a
future patch that builds on what I am proposing here. No sense trying
to do too many things all at once.
I agree that granting membership makes the pg_auth_members record appear and revoking membership makes it disappear.
Great.
I dislike having GRANT do stuff when membership is already established.
ALTER MEMBER role IN group ALTER [SET | ASSUME] [TO | =] [TRUE | FALSE]
I thought about this, too. We could definitely do something like that.
I imagine it would be called ALTER GRANT rather than ALTER MEMBER, but
other than that I don't see an issue. However, there's existing
precedent for the way I proposed it: if you repeat the same GRANT
command but write WITH ADMIN OPTION only the second time, it modifies
the existing grant and adds the admin option to it. If you repeat it
verbatim the second time, it instead gives you a warning that your
command was redundant. That to me establishes the precedent that the
way you modify the options associated with a GRANT is to issue a new
GRANT command. I don't find changing that existing behavior very
appealing, even if we might not pick the same thing if we were doing
it over. We could add something else alongside that to provide another
way of accomplishing the same thing, but that seemed more complicated
for not much benefit.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 13.06.22 20:00, Robert Haas wrote:
I don't think this creates any more of a conflict than we've already
got. In fact, I'd go so far as to say it resolves a problem that we
currently have. As far as I can see, we are stuck with a situation
where we have to support both the INHERIT behavior and the NOINHERIT
behavior. Removing either one would be a pretty major compatibility
break. And even if some people were willing to endorse that, it seems
clear from previous discussions that there are people who like the
NOINHERIT behavior and would object to its removal, and other people
(like me!) who like the INHERIT behavior and would object to removing
that. If you think it's feasible to get rid of either of these
behaviors, I'd be interested in hearing your thoughts on that, but to
me it looks like we are stuck with supporting both. From my point of
view, the question is how to make the best of that situation.
I think we want to keep both.
Consider a user who in general prefers the NOINHERIT behavior but also
wants to use predefined roles. Perhaps user 'peter' is to be granted
both 'paul' and 'pg_execute_server_programs'. If role 'peter' is set
to INHERIT, Peter will be sad, because his love for NOINHERIT probably
means that he doesn't want to exercise Paul's privileges
automatically. However, he needs to inherit the privileges of
'pg_execute_server_programs' or they are of no use to him. Peter
presumably wants to use COPY TO/FROM program to put data into a table
owned by 'peter', not a table owned by 'pg_execute_server_programs'.
If so, being able to SET ROLE to 'pg_execute_server_programs' is of no
use to him at all, but inheriting the privilege is useful.
That's because our implementation of SET ROLE is bogus. We should have
a SET ROLE that is separate from SET SESSION AUTHORIZATION, where the
current user can keep their current user-ness and additionally enable
(non-inherited) roles.
I don't think I'm proposing to break anything or go in a totally
opposite direction from anything, and to be honest I'm kind of
confused as to why you think that what I'm proposing would have that
effect. As far as I can see, the changes that I'm proposing are
upward-compatible and would permit easy migration to new releases via
either pg_dump or pg_upgrade with no behavior changes at all. Some
syntax would be a bit different on the new releases and that would
unlock some new options we don't currently have, but there's no
behavior that you can get today which you wouldn't be able to get any
more under this proposal.
I'm mainly concerned that (AAIU), you propose to remove the current
INHERIT/NOINHERIT attribute of roles. I wouldn't like that. If you
want a feature that allows overriding that per-grant, maybe that's okay.
On Wed, Jun 15, 2022 at 5:23 AM Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
Consider a user who in general prefers the NOINHERIT behavior but also
wants to use predefined roles. Perhaps user 'peter' is to be granted
both 'paul' and 'pg_execute_server_programs'. If role 'peter' is set
to INHERIT, Peter will be sad, because his love for NOINHERIT probably
means that he doesn't want to exercise Paul's privileges
automatically. However, he needs to inherit the privileges of
'pg_execute_server_programs' or they are of no use to him. Peter
presumably wants to use COPY TO/FROM program to put data into a table
owned by 'peter', not a table owned by 'pg_execute_server_programs'.
If so, being able to SET ROLE to 'pg_execute_server_programs' is of no
use to him at all, but inheriting the privilege is useful.That's because our implementation of SET ROLE is bogus. We should have
a SET ROLE that is separate from SET SESSION AUTHORIZATION, where the
current user can keep their current user-ness and additionally enable
(non-inherited) roles.
It would help me to have a better description of what you think the
behavior ought to be. I've always thought there was something funny
about SET ROLE and SET SESSION AUTHORIZATION, because it seems like
they are too similar to each other. But it would surprise me if SET
ROLE added additional privileges to my session while leaving the old
ones intact, too, much as I'd be surprised if SET work_mem = '8MB'
followed by SET work_mem = '1GB' somehow left both values partly in
effect at the same time. It feels to me like SET is describing an
action that changes the session state, rather than adding to it.
I'm mainly concerned that (AAIU), you propose to remove the current
INHERIT/NOINHERIT attribute of roles. I wouldn't like that. If you
want a feature that allows overriding that per-grant, maybe that's okay.
Yeah, I want to remove it and replace it with something more
fine-grained. I don't yet understand why that's a problem for anything
you want to do.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jun 2, 2022 at 1:17 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Point 2 would cause every existing pg_dumpall script to fail, which
seems like kind of a large gotcha. Less unpleasant alternatives
could include* Continue to accept the syntax, but ignore it, maybe with a WARNING
for the alternative that doesn't correspond to the new behavior.* Keep pg_authid.rolinherit, and have it act as supplying the default
behavior for subsequent GRANTs to that role.
Here's a rather small patch that does it the first way.
I know that Peter Eisentraut didn't like the idea of removing the
role-level option, but I don't know why, so I don't know whether doing
this the second way instead would address his objection.
I suppose if we did it the second way, we could make the syntax GRANT
granted_role TO recipient_role WITH INHERIT { TRUE | FALSE | DEFAULT
}, and DEFAULT would copy the current value of the rolinherit
property, so that changing the rolinherit property later would not
affect previous grants. The reverse is also possible: with the same
syntax, the rolinherit column could be changed from bool to "char",
storing t/f/d, and 'd' could mean the value of the rolinherit property
at time of use; thus, changing rolinherit would affect previous grants
performed using WITH INHERIT DEFAULT but not those that specified WITH
INHERIT TRUE or WITH INHERIT FALSE.
In some sense, this whole scheme seems backwards to me: wouldn't it be
more natural if the default were based on the role being granted
rather than the role to which it is granted? If I decide to GRANT
some_predefined_role TO some_user, it seems like I am really likely to
want the INHERIT behavior, whereas if I GRANT some_other_user TO
some_user, it could go either way depending on the use case. But this
may be water under the bridge: introducing a way to set the default
that is backwards from the way that the current role-level property
works may be too confusing to be worthy of any consideration at all.
Thoughts?
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v1-0002-Replace-NO-INHERIT-property-for-roles-with-a-gran.patchapplication/octet-stream; name=v1-0002-Replace-NO-INHERIT-property-for-roles-with-a-gran.patchDownload
From 499994967d116f8ba56c69d40225d98f761982b1 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 10 Jun 2022 16:29:10 -0400
Subject: [PATCH v1 2/2] Replace [NO]INHERIT property for roles with a
grant-level option.
---
doc/src/sgml/catalogs.sgml | 30 +--
src/backend/catalog/system_views.sql | 1 -
src/backend/commands/user.c | 210 ++++++++++-----
src/backend/nodes/copyfuncs.c | 2 +-
src/backend/nodes/equalfuncs.c | 2 +-
src/backend/parser/gram.y | 47 +++-
src/backend/utils/adt/acl.c | 47 +---
src/bin/pg_dump/pg_dumpall.c | 21 +-
src/bin/pg_dump/t/002_pg_dump.pl | 2 +-
src/bin/psql/describe.c | 23 +-
src/include/catalog/pg_auth_members.h | 1 +
src/include/catalog/pg_authid.dat | 26 +-
src/include/catalog/pg_authid.h | 1 -
src/include/nodes/parsenodes.h | 2 +-
src/test/regress/expected/create_role.out | 4 +
src/test/regress/expected/privileges.out | 2 +-
src/test/regress/expected/roleattributes.out | 255 +++++++++----------
src/test/regress/expected/rules.out | 1 -
src/test/regress/sql/privileges.sql | 2 +-
src/test/regress/sql/roleattributes.sql | 66 ++---
20 files changed, 393 insertions(+), 352 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index c00c93dd7b..8f03778dc9 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1517,16 +1517,6 @@
</para></entry>
</row>
- <row>
- <entry role="catalog_table_entry"><para role="column_definition">
- <structfield>rolinherit</structfield> <type>bool</type>
- </para>
- <para>
- Role automatically inherits privileges of roles it is a
- member of
- </para></entry>
- </row>
-
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>rolcreaterole</structfield> <type>bool</type>
@@ -1708,6 +1698,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>roleid</structfield> to others
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>inherit_option</structfield> <type>bool</type>
+ </para>
+ <para>
+ True if <structfield>member</structfield> automatically inherits the
+ privileges of <structfield>roleid</structfield>
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -12071,16 +12071,6 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</para></entry>
</row>
- <row>
- <entry role="catalog_table_entry"><para role="column_definition">
- <structfield>rolinherit</structfield> <type>bool</type>
- </para>
- <para>
- Role automatically inherits privileges of roles it is a
- member of
- </para></entry>
- </row>
-
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>rolcreaterole</structfield> <type>bool</type>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index fedaed533b..79f6cfb0fd 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -18,7 +18,6 @@ CREATE VIEW pg_roles AS
SELECT
rolname,
rolsuper,
- rolinherit,
rolcreaterole,
rolcreatedb,
rolcanlogin,
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 984305ba31..b5ade0905a 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -42,6 +42,15 @@
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
+typedef struct
+{
+ unsigned specified;
+ bool admin;
+ bool inherit;
+} GrantRoleOptions;
+
+#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001
+#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
@@ -51,10 +60,11 @@ check_password_hook_type check_password_hook = NULL;
static void AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt);
+ Oid grantorId, GrantRoleOptions *popt);
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt);
+ GrantRoleOptions *popt);
+static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */
@@ -81,7 +91,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ListCell *option;
char *password = NULL; /* user password */
bool issuper = false; /* Make the user a superuser? */
- bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
bool createdb = false; /* Can the user create databases? */
bool canlogin = false; /* Can this user login? */
@@ -96,7 +105,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
bool validUntil_null;
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
- DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
@@ -107,6 +115,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ GrantRoleOptions popt;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -115,7 +124,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
break;
case ROLESTMT_USER:
canlogin = true;
- /* may eventually want inherit to default to false here */
break;
case ROLESTMT_GROUP:
break;
@@ -145,9 +153,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
}
else if (strcmp(defel->defname, "inherit") == 0)
{
- if (dinherit)
- errorConflictingDefElem(defel, pstate);
- dinherit = defel;
+ ereport(WARNING,
+ errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
+ errmsg("role options INHERIT and NOINHERIT are deprecated"),
+ errhint("grant role membership WITH INHERIT TRUE or WITH INHERIT FALSE instead"));
}
else if (strcmp(defel->defname, "createrole") == 0)
{
@@ -218,8 +227,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
password = strVal(dpassword->arg);
if (dissuper)
issuper = boolVal(dissuper->arg);
- if (dinherit)
- inherit = boolVal(dinherit->arg);
if (dcreaterole)
createrole = boolVal(dcreaterole->arg);
if (dcreatedb)
@@ -345,7 +352,6 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
- new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
@@ -429,6 +435,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
+ /* Default grant. */
+ InitGrantRoleOptions(&popt);
+
/*
* Add the new role to the specified existing roles.
*/
@@ -453,7 +462,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
- GetUserId(), false);
+ GetUserId(), &popt);
ReleaseSysCache(oldroletup);
}
@@ -463,12 +472,14 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
- AddRoleMems(stmt->role, roleid,
- adminmembers, roleSpecsToIds(adminmembers),
- GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+ popt.admin = true;
+ AddRoleMems(stmt->role, roleid,
+ adminmembers, roleSpecsToIds(adminmembers),
+ GetUserId(), &popt);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
@@ -509,7 +520,6 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
bool validUntil_null;
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
- DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
@@ -519,6 +529,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
+ GrantRoleOptions popt;
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
@@ -542,9 +553,10 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
}
else if (strcmp(defel->defname, "inherit") == 0)
{
- if (dinherit)
- errorConflictingDefElem(defel, pstate);
- dinherit = defel;
+ ereport(WARNING,
+ errcode(ERRCODE_WARNING_DEPRECATED_FEATURE),
+ errmsg("role options INHERIT and NOINHERIT are deprecated"),
+ errhint("grant role membership WITH INHERIT TRUE or WITH INHERIT FALSE instead"));
}
else if (strcmp(defel->defname, "createrole") == 0)
{
@@ -654,7 +666,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
else if (!have_createrole_privilege())
{
/* check the rest */
- if (dinherit || dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
+ if (dcreaterole || dcreatedb || dcanlogin || dconnlimit ||
drolemembers || dvalidUntil || !dpassword || roleid != GetUserId())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -704,12 +716,6 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
new_record_repl[Anum_pg_authid_rolsuper - 1] = true;
}
- if (dinherit)
- {
- new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(boolVal(dinherit->arg));
- new_record_repl[Anum_pg_authid_rolinherit - 1] = true;
- }
-
if (dcreaterole)
{
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(boolVal(dcreaterole->arg));
@@ -792,6 +798,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ InitGrantRoleOptions(&popt);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -805,11 +813,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (stmt->action == +1) /* add members to role */
AddRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- false);
+ &popt);
}
/*
@@ -1230,7 +1238,31 @@ GrantRole(GrantRoleStmt *stmt)
Oid grantor;
List *grantee_ids;
ListCell *item;
+ GrantRoleOptions popt;
+
+ /* Parse options list. */
+ InitGrantRoleOptions(&popt);
+ foreach(item, stmt->opt)
+ {
+ DefElem *opt = (DefElem *) lfirst(item);
+ if (strcmp(opt->defname, "admin") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+ popt.admin = defGetBoolean(opt);
+ }
+ else if (strcmp(opt->defname, "inherit") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT;
+ popt.inherit = defGetBoolean(opt);
+ }
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized role option \"%s\"", opt->defname));
+ }
+
+ /* Determine grantor. */
if (stmt->grantor)
grantor = get_rolespec_oid(stmt->grantor, false);
else
@@ -1243,8 +1275,7 @@ GrantRole(GrantRoleStmt *stmt)
/*
* Step through all of the granted roles and add/remove entries for the
- * grantees, or, if admin_opt is set, then just add/remove the admin
- * option.
+ * grantees, or, if opt != NIL, then just add/remove the named option(s).
*
* Note: Permissions checking is done by AddRoleMems/DelRoleMems
*/
@@ -1264,11 +1295,11 @@ GrantRole(GrantRoleStmt *stmt)
if (stmt->is_grant)
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- grantor, stmt->admin_opt);
+ grantor, &popt);
else
DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- stmt->admin_opt);
+ &popt);
}
/*
@@ -1375,7 +1406,7 @@ roleSpecsToIds(List *memberNames)
static void
AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt)
+ Oid grantorId, GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1481,38 +1512,63 @@ AddRoleMems(const char *rolename, Oid roleid,
errmsg("role \"%s\" is a member of role \"%s\"",
rolename, get_rolespec_name(memberRole))));
- /*
- * Check if entry for this role/member already exists; if so, give
- * warning unless we are adding admin option.
- */
- authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
- ObjectIdGetDatum(roleid),
- ObjectIdGetDatum(memberid));
- if (HeapTupleIsValid(authmem_tuple) &&
- (!admin_opt ||
- ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
- {
- ereport(NOTICE,
- (errmsg("role \"%s\" is already a member of role \"%s\"",
- get_rolespec_name(memberRole), rolename)));
- ReleaseSysCache(authmem_tuple);
- continue;
- }
-
- /* Build a tuple to insert or update */
+ /* Initialize bookkeeping for possible insert or update */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
+ new_record[Anum_pg_auth_members_roleid - 1] =
+ ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_members_member - 1] =
+ ObjectIdGetDatum(memberid);
+ new_record[Anum_pg_auth_members_grantor - 1] =
+ ObjectIdGetDatum(grantorId);
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(popt->admin);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(popt->inherit);
+
+ /* Find any existing tuple */
+ authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
+ ObjectIdGetDatum(roleid),
+ ObjectIdGetDatum(memberid));
- new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
- new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
- new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
+ /*
+ * If we found a tuple, update it with new option values, unless
+ * there are no changes, in which case issue a WARNING.
+ *
+ * If we didn't find a tuple, just insert one.
+ */
if (HeapTupleIsValid(authmem_tuple))
{
- new_record_repl[Anum_pg_auth_members_grantor - 1] = true;
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ Form_pg_auth_members form;
+ bool at_least_one_change = false;
+
+ form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
+
+ if (form->admin_option != popt->admin)
+ {
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ (popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0;
+ at_least_one_change = true;
+ }
+
+ if (form->inherit_option != popt->inherit)
+ {
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ (popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0;
+ at_least_one_change = true;
+ }
+
+ if (!at_least_one_change)
+ {
+ ereport(NOTICE,
+ (errmsg("role \"%s\" is already a member of role \"%s\"",
+ get_rolespec_name(memberRole), rolename)));
+ ReleaseSysCache(authmem_tuple);
+ continue;
+ }
+
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
new_record_nulls, new_record_repl);
@@ -1521,6 +1577,7 @@ AddRoleMems(const char *rolename, Oid roleid,
}
else
{
+
tuple = heap_form_tuple(pg_authmem_dsc,
new_record, new_record_nulls);
CatalogTupleInsert(pg_authmem_rel, tuple);
@@ -1548,7 +1605,7 @@ AddRoleMems(const char *rolename, Oid roleid,
static void
DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt)
+ GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1605,14 +1662,14 @@ DelRoleMems(const char *rolename, Oid roleid,
continue;
}
- if (!admin_opt)
+ if (popt->specified == 0)
{
/* Remove the entry altogether */
CatalogTupleDelete(pg_authmem_rel, &authmem_tuple->t_self);
}
else
{
- /* Just turn off the admin option */
+ /* Just turn off the specified option */
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members];
bool new_record_nulls[Natts_pg_auth_members];
@@ -1623,8 +1680,22 @@ DelRoleMems(const char *rolename, Oid roleid,
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0)
+ {
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ }
+ else if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+ {
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ }
+ else
+ elog(ERROR, "no role option to revoke?");
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
@@ -1643,3 +1714,14 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
table_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * Initialize a GrantRoleOptions object with default values.
+ */
+static void
+InitGrantRoleOptions(GrantRoleOptions *popt)
+{
+ popt->specified = 0;
+ popt->admin = false;
+ popt->inherit = true;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 51d630fa89..135da1a8a8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3971,7 +3971,7 @@ _copyGrantRoleStmt(const GrantRoleStmt *from)
COPY_NODE_FIELD(granted_roles);
COPY_NODE_FIELD(grantee_roles);
COPY_SCALAR_FIELD(is_grant);
- COPY_SCALAR_FIELD(admin_opt);
+ COPY_NODE_FIELD(opt);
COPY_NODE_FIELD(grantor);
COPY_SCALAR_FIELD(behavior);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e747e1667d..e7f72a3c11 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1572,7 +1572,7 @@ _equalGrantRoleStmt(const GrantRoleStmt *a, const GrantRoleStmt *b)
COMPARE_NODE_FIELD(granted_roles);
COMPARE_NODE_FIELD(grantee_roles);
COMPARE_SCALAR_FIELD(is_grant);
- COMPARE_SCALAR_FIELD(admin_opt);
+ COMPARE_NODE_FIELD(opt);
COMPARE_NODE_FIELD(grantor);
COMPARE_SCALAR_FIELD(behavior);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..7df5a60055 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -359,9 +359,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> utility_option_arg
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
- opt_grant_grant_option opt_grant_admin_option
+ opt_grant_grant_option
opt_nowait opt_if_exists opt_with_data
opt_transaction_chain
+%type <list> grant_role_opt_list
+%type <defelt> grant_role_opt
+%type <node> grant_role_opt_value
%type <ival> opt_nowait_or_skip
%type <list> OptRoleList AlterOptRoleList
@@ -7818,15 +7821,26 @@ opt_grant_grant_option:
*****************************************************************************/
GrantRoleStmt:
- GRANT privilege_list TO role_list opt_grant_admin_option opt_granted_by
+ GRANT privilege_list TO role_list opt_granted_by
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = true;
n->granted_roles = $2;
n->grantee_roles = $4;
- n->admin_opt = $5;
- n->grantor = $6;
+ n->opt = NIL;
+ n->grantor = $5;
+ $$ = (Node *) n;
+ }
+ | GRANT privilege_list TO role_list WITH grant_role_opt_list opt_granted_by
+ {
+ GrantRoleStmt *n = makeNode(GrantRoleStmt);
+
+ n->is_grant = true;
+ n->granted_roles = $2;
+ n->grantee_roles = $4;
+ n->opt = $6;
+ n->grantor = $7;
$$ = (Node *) n;
}
;
@@ -7837,7 +7851,7 @@ RevokeRoleStmt:
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false;
- n->admin_opt = false;
+ n->opt = NIL;
n->granted_roles = $2;
n->grantee_roles = $4;
n->behavior = $6;
@@ -7846,9 +7860,12 @@ RevokeRoleStmt:
| REVOKE ADMIN OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
+ DefElem *opt;
+ opt = makeDefElem(pstrdup($2),
+ (Node *) makeBoolean(false), @2);
n->is_grant = false;
- n->admin_opt = true;
+ n->opt = list_make1(opt);
n->granted_roles = $5;
n->grantee_roles = $7;
n->behavior = $9;
@@ -7856,8 +7873,22 @@ RevokeRoleStmt:
}
;
-opt_grant_admin_option: WITH ADMIN OPTION { $$ = true; }
- | /*EMPTY*/ { $$ = false; }
+grant_role_opt_list:
+ grant_role_opt_list grant_role_opt { $$ = lappend($1, $2); }
+ | grant_role_opt { $$ = list_make1($1); }
+ ;
+
+grant_role_opt:
+ ColLabel grant_role_opt_value
+ {
+ $$ = makeDefElem(pstrdup($1), $2, @2);
+ }
+ ;
+
+grant_role_opt_value:
+ OPTION { $$ = (Node *) makeBoolean(true); }
+ | TRUE_P { $$ = (Node *) makeBoolean(true); }
+ | FALSE_P { $$ = (Node *) makeBoolean(false); }
;
opt_granted_by: GRANTED BY RoleSpec { $$ = $3; }
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 772c04155c..393b3bba0e 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -66,7 +66,7 @@ typedef struct
*/
enum RoleRecurseType
{
- ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */
+ ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */
ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */
};
static Oid cached_role[] = {InvalidOid, InvalidOid};
@@ -4763,15 +4763,12 @@ initialize_acl(void)
/*
* In normal mode, set a callback on any syscache invalidation of rows
- * of pg_auth_members (for roles_is_member_of()), pg_authid (for
- * has_rolinherit()), or pg_database (for roles_is_member_of())
+ * of pg_auth_members and pg_database, since changes to these catalogs
+ * can affect the return value of roles_is_member_of().
*/
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
RoleMembershipCacheCallback,
(Datum) 0);
- CacheRegisterSyscacheCallback(AUTHOID,
- RoleMembershipCacheCallback,
- (Datum) 0);
CacheRegisterSyscacheCallback(DATABASEOID,
RoleMembershipCacheCallback,
(Datum) 0);
@@ -4797,29 +4794,11 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
}
-
-/* Check if specified role has rolinherit set */
-static bool
-has_rolinherit(Oid roleid)
-{
- bool result = false;
- HeapTuple utup;
-
- utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- if (HeapTupleIsValid(utup))
- {
- result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- ReleaseSysCache(utup);
- }
- return result;
-}
-
-
/*
* Get a list of roles that the specified roleid is a member of
*
- * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit
- * set, while ROLERECURSE_MEMBERS recurses through all roles. This sets
+ * Type ROLERECURSE_PRIVS recurses only through grants that have 'inherit'
+ * set, while ROLERECURSE_MEMBERS recurses through all grants. This sets
* *is_admin==true if and only if role "roleid" has an ADMIN OPTION membership
* in role "admin_of".
*
@@ -4884,23 +4863,23 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
CatCList *memlist;
int i;
- if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
- continue; /* ignore non-inheriting roles */
-
/* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
ObjectIdGetDatum(memberid));
for (i = 0; i < memlist->n_members; i++)
{
HeapTuple tup = &memlist->members[i]->tuple;
- Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+ Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
+ Oid otherid = form->roleid;
+
+ if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+ continue; /* ignore non-heritable grants */
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
*/
- if (otherid == admin_of &&
- ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
+ if (otherid == admin_of && form->admin_option &&
OidIsValid(admin_of))
*is_admin = true;
@@ -4943,7 +4922,7 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
/*
* Does member have the privileges of role (directly or indirectly)?
*
- * This is defined not to recurse through roles that don't have rolinherit
+ * This is defined not to recurse through grants that don't have inherit_option
* set; for such roles, membership implies the ability to do SET ROLE, but
* the privileges are not available until you've done so.
*/
@@ -4971,7 +4950,7 @@ has_privs_of_role(Oid member, Oid role)
/*
* Is member a member of role (directly or indirectly)?
*
- * This is defined to recurse through roles regardless of rolinherit.
+ * This is defined to recurse through grants regardless of inherit_option.
*
* Do not use this for privilege checking, instead use has_privs_of_role()
*/
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index ae41a652d7..3820866d19 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -723,7 +723,6 @@ dumpRoles(PGconn *conn)
int i_oid,
i_rolname,
i_rolsuper,
- i_rolinherit,
i_rolcreaterole,
i_rolcreatedb,
i_rolcanlogin,
@@ -739,7 +738,7 @@ dumpRoles(PGconn *conn)
/* note: rolconfig is dumped later */
if (server_version >= 90600)
printfPQExpBuffer(buf,
- "SELECT oid, rolname, rolsuper, rolinherit, "
+ "SELECT oid, rolname, rolsuper, "
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, rolreplication, rolbypassrls, "
@@ -750,7 +749,7 @@ dumpRoles(PGconn *conn)
"ORDER BY 2", role_catalog, role_catalog);
else if (server_version >= 90500)
printfPQExpBuffer(buf,
- "SELECT oid, rolname, rolsuper, rolinherit, "
+ "SELECT oid, rolname, rolsuper, "
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, rolreplication, rolbypassrls, "
@@ -760,7 +759,7 @@ dumpRoles(PGconn *conn)
"ORDER BY 2", role_catalog, role_catalog);
else
printfPQExpBuffer(buf,
- "SELECT oid, rolname, rolsuper, rolinherit, "
+ "SELECT oid, rolname, rolsuper, "
"rolcreaterole, rolcreatedb, "
"rolcanlogin, rolconnlimit, rolpassword, "
"rolvaliduntil, rolreplication, "
@@ -775,7 +774,6 @@ dumpRoles(PGconn *conn)
i_oid = PQfnumber(res, "oid");
i_rolname = PQfnumber(res, "rolname");
i_rolsuper = PQfnumber(res, "rolsuper");
- i_rolinherit = PQfnumber(res, "rolinherit");
i_rolcreaterole = PQfnumber(res, "rolcreaterole");
i_rolcreatedb = PQfnumber(res, "rolcreatedb");
i_rolcanlogin = PQfnumber(res, "rolcanlogin");
@@ -833,11 +831,6 @@ dumpRoles(PGconn *conn)
else
appendPQExpBufferStr(buf, " NOSUPERUSER");
- if (strcmp(PQgetvalue(res, i, i_rolinherit), "t") == 0)
- appendPQExpBufferStr(buf, " INHERIT");
- else
- appendPQExpBufferStr(buf, " NOINHERIT");
-
if (strcmp(PQgetvalue(res, i, i_rolcreaterole), "t") == 0)
appendPQExpBufferStr(buf, " CREATEROLE");
else
@@ -930,8 +923,12 @@ dumpRoleMembership(PGconn *conn)
printfPQExpBuffer(buf, "SELECT ur.rolname AS roleid, "
"um.rolname AS member, "
"a.admin_option, "
- "ug.rolname AS grantor "
- "FROM pg_auth_members a "
+ "ug.rolname AS grantor, ");
+ if (server_version >= 160000)
+ appendPQExpBuffer(buf, "a.inherit_option ");
+ else
+ appendPQExpBuffer(buf, "um.rolinherit AS inherit_option ");
+ appendPQExpBuffer(buf, "FROM pg_auth_members a "
"LEFT JOIN %s ur on ur.oid = a.roleid "
"LEFT JOIN %s um on um.oid = a.member "
"LEFT JOIN %s ug on ug.oid = a.grantor "
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 1f08716f69..99894b9914 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -575,7 +575,7 @@ my %tests = (
'ALTER ROLE regress_dump_test_role' => {
regexp => qr/^
\QALTER ROLE regress_dump_test_role WITH \E
- \QNOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN \E
+ \QNOSUPERUSER NOCREATEROLE NOCREATEDB NOLOGIN \E
\QNOREPLICATION NOBYPASSRLS;\E
/xm,
like => {
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index d1ae699171..7355b8cbbf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3589,7 +3589,7 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf,
- "SELECT r.rolname, r.rolsuper, r.rolinherit,\n"
+ "SELECT r.rolname, r.rolsuper,\n"
" r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,\n"
" r.rolconnlimit, r.rolvaliduntil,\n"
" ARRAY(SELECT b.rolname\n"
@@ -3646,26 +3646,23 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
if (strcmp(PQgetvalue(res, i, 1), "t") == 0)
add_role_attribute(&buf, _("Superuser"));
- if (strcmp(PQgetvalue(res, i, 2), "t") != 0)
- add_role_attribute(&buf, _("No inheritance"));
-
- if (strcmp(PQgetvalue(res, i, 3), "t") == 0)
+ if (strcmp(PQgetvalue(res, i, 2), "t") == 0)
add_role_attribute(&buf, _("Create role"));
- if (strcmp(PQgetvalue(res, i, 4), "t") == 0)
+ if (strcmp(PQgetvalue(res, i, 3), "t") == 0)
add_role_attribute(&buf, _("Create DB"));
- if (strcmp(PQgetvalue(res, i, 5), "t") != 0)
+ if (strcmp(PQgetvalue(res, i, 4), "t") != 0)
add_role_attribute(&buf, _("Cannot login"));
- if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
+ if (strcmp(PQgetvalue(res, i, (verbose ? 9 : 8)), "t") == 0)
add_role_attribute(&buf, _("Replication"));
if (pset.sversion >= 90500)
- if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
+ if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
add_role_attribute(&buf, _("Bypass RLS"));
- conns = atoi(PQgetvalue(res, i, 6));
+ conns = atoi(PQgetvalue(res, i, 5));
if (conns >= 0)
{
if (buf.len > 0)
@@ -3680,7 +3677,7 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
conns);
}
- if (strcmp(PQgetvalue(res, i, 7), "") != 0)
+ if (strcmp(PQgetvalue(res, i, 6), "") != 0)
{
if (buf.len > 0)
appendPQExpBufferChar(&buf, '\n');
@@ -3692,10 +3689,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
printTableAddCell(&cont, attr[i], false, false);
- printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+ printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
if (verbose)
- printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
+ printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
}
termPQExpBuffer(&buf);
diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h
index 1bc027f133..1e144a75a8 100644
--- a/src/include/catalog/pg_auth_members.h
+++ b/src/include/catalog/pg_auth_members.h
@@ -33,6 +33,7 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_
Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */
Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */
bool admin_option; /* granted with admin option? */
+ bool inherit_option; /* exercise privileges without SET ROLE? */
} FormData_pg_auth_members;
/* ----------------
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 6c28119fa1..a0b1520e74 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -20,67 +20,67 @@
# will replace that at database initialization time.
{ oid => '10', oid_symbol => 'BOOTSTRAP_SUPERUSERID',
- rolname => 'POSTGRES', rolsuper => 't', rolinherit => 't',
+ rolname => 'POSTGRES', rolsuper => 't',
rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '6171', oid_symbol => 'ROLE_PG_DATABASE_OWNER',
- rolname => 'pg_database_owner', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_database_owner', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '6181', oid_symbol => 'ROLE_PG_READ_ALL_DATA',
- rolname => 'pg_read_all_data', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_read_all_data', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '6182', oid_symbol => 'ROLE_PG_WRITE_ALL_DATA',
- rolname => 'pg_write_all_data', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_write_all_data', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '3373', oid_symbol => 'ROLE_PG_MONITOR',
- rolname => 'pg_monitor', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_monitor', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '3374', oid_symbol => 'ROLE_PG_READ_ALL_SETTINGS',
- rolname => 'pg_read_all_settings', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_read_all_settings', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '3375', oid_symbol => 'ROLE_PG_READ_ALL_STATS',
- rolname => 'pg_read_all_stats', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_read_all_stats', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '3377', oid_symbol => 'ROLE_PG_STAT_SCAN_TABLES',
- rolname => 'pg_stat_scan_tables', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_stat_scan_tables', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4569', oid_symbol => 'ROLE_PG_READ_SERVER_FILES',
- rolname => 'pg_read_server_files', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_read_server_files', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4570', oid_symbol => 'ROLE_PG_WRITE_SERVER_FILES',
- rolname => 'pg_write_server_files', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_write_server_files', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4571', oid_symbol => 'ROLE_PG_EXECUTE_SERVER_PROGRAM',
- rolname => 'pg_execute_server_program', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_execute_server_program', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4200', oid_symbol => 'ROLE_PG_SIGNAL_BACKEND',
- rolname => 'pg_signal_backend', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_signal_backend', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
{ oid => '4544', oid_symbol => 'ROLE_PG_CHECKPOINTER',
- rolname => 'pg_checkpointer', rolsuper => 'f', rolinherit => 't',
+ rolname => 'pg_checkpointer', rolsuper => 'f',
rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
rolpassword => '_null_', rolvaliduntil => '_null_' },
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 3512601c80..27e1f72844 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -33,7 +33,6 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
Oid oid; /* oid */
NameData rolname; /* name of role */
bool rolsuper; /* read this field via superuser() only! */
- bool rolinherit; /* inherit privileges from other roles? */
bool rolcreaterole; /* allowed to create more roles? */
bool rolcreatedb; /* allowed to create databases? */
bool rolcanlogin; /* allowed to log in as session user? */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 73f635b455..3a1d21d9ff 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2439,7 +2439,7 @@ typedef struct GrantRoleStmt
List *granted_roles; /* list of roles to be granted/revoked */
List *grantee_roles; /* list of member roles to add/delete */
bool is_grant; /* true = GRANT, false = REVOKE */
- bool admin_opt; /* with admin option */
+ List *opt; /* options e.g. WITH GRANT OPTION */
RoleSpec *grantor; /* set grantor to other than current role */
DropBehavior behavior; /* drop behavior (for REVOKE) */
} GrantRoleStmt;
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 4e67d72760..cc3abdc10d 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -16,6 +16,8 @@ CREATE ROLE regress_createdb CREATEDB;
CREATE ROLE regress_createrole CREATEROLE;
CREATE ROLE regress_login LOGIN;
CREATE ROLE regress_inherit INHERIT;
+WARNING: role options INHERIT and NOINHERIT are deprecated
+HINT: grant role membership WITH INHERIT TRUE or WITH INHERIT FALSE instead
CREATE ROLE regress_connection_limit CONNECTION LIMIT 5;
CREATE ROLE regress_encrypted_password ENCRYPTED PASSWORD 'foo';
CREATE ROLE regress_password_null PASSWORD NULL;
@@ -52,6 +54,8 @@ CREATE ROLE regress_plainrole;
CREATE ROLE regress_rolecreator CREATEROLE;
-- ok, roles with CREATEROLE can create new roles with privilege they lack
CREATE ROLE regress_tenant CREATEDB CREATEROLE LOGIN INHERIT CONNECTION LIMIT 5;
+WARNING: role options INHERIT and NOINHERIT are deprecated
+HINT: grant role membership WITH INHERIT TRUE or WITH INHERIT FALSE instead
-- ok, regress_tenant can create objects within the database
SET SESSION AUTHORIZATION regress_tenant;
CREATE TABLE tenant_table (i integer);
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 03df567d50..a7e02e1b02 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -420,7 +420,7 @@ ERROR: permission denied for table atest3
DELETE FROM atest3; -- ok
BEGIN;
RESET SESSION AUTHORIZATION;
-ALTER ROLE regress_priv_user1 NOINHERIT;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
SET SESSION AUTHORIZATION regress_priv_user1;
DELETE FROM atest3;
ERROR: permission denied for table atest3
diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out
index 5e6969b173..345bb33286 100644
--- a/src/test/regress/expected/roleattributes.out
+++ b/src/test/regress/expected/roleattributes.out
@@ -1,240 +1,213 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_superuser | f | f | f | f | f | f | -1 | |
(1 row)
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_superuser | t | f | f | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_superuser | f | f | f | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_superuser | t | t | f | f | f | f | f | -1 | |
-(1 row)
-
--- default for inherit is true
-CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_inherit | f | t | f | f | f | f | f | -1 | |
-(1 row)
-
-CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
-(1 row)
-
-ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | t | f | f | f | f | f | -1 | |
-(1 row)
-
-ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_inherit | f | f | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_superuser | t | f | f | f | f | f | -1 | |
(1 row)
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_createrole | f | f | f | f | f | f | -1 | |
(1 row)
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createrole | f | t | f | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createrole | f | f | f | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
--------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createrole | f | t | t | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createrole | f | t | f | f | f | f | -1 | |
(1 row)
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+---------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_createdb | f | f | f | f | f | f | -1 | |
(1 row)
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-----------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createdb | f | f | t | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-----------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createdb | f | f | f | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_createdb | f | t | f | t | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+-----------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_createdb | f | f | t | f | f | f | -1 | |
(1 row)
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_role_canlogin | f | f | f | f | f | f | -1 | |
(1 row)
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_role_canlogin | f | f | f | t | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_role_canlogin | f | f | f | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_role_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_role_canlogin | f | f | f | t | f | f | -1 | |
(1 row)
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_user_canlogin | f | f | f | t | f | f | -1 | |
(1 row)
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_user_canlogin | f | f | f | f | f | f | -1 | |
(1 row)
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | t | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_user_canlogin | f | f | f | t | f | f | -1 | |
(1 row)
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_user_canlogin | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_user_canlogin | f | f | f | f | f | f | -1 | |
(1 row)
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_replication | f | f | f | f | f | f | -1 | |
(1 row)
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_replication | f | f | f | f | t | f | -1 | |
(1 row)
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_replication | f | f | f | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_replication | f | t | f | f | f | t | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+--------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_replication | f | f | f | f | t | f | -1 | |
(1 row)
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-----------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_def_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+----------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_def_bypassrls | f | f | f | f | f | f | -1 | |
(1 row)
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_bypassrls | f | f | f | f | f | t | -1 | |
(1 row)
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | f | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_bypassrls | f | f | f | f | f | f | -1 | |
(1 row)
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
- rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
-------------------------+----------+------------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
- regress_test_bypassrls | f | t | f | f | f | f | t | -1 | |
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+ rolname | rolsuper | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
+------------------------+----------+---------------+-------------+-------------+----------------+--------------+--------------+-------------+---------------
+ regress_test_bypassrls | f | f | f | f | f | t | -1 | |
(1 row)
-- clean up roles
DROP ROLE regress_test_def_superuser;
DROP ROLE regress_test_superuser;
DROP ROLE regress_test_def_inherit;
+ERROR: role "regress_test_def_inherit" does not exist
DROP ROLE regress_test_inherit;
+ERROR: role "regress_test_inherit" does not exist
DROP ROLE regress_test_def_createrole;
DROP ROLE regress_test_createrole;
DROP ROLE regress_test_def_createdb;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index fc3cde3226..5c4da3817a 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1476,7 +1476,6 @@ pg_replication_slots| SELECT l.slot_name,
LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
pg_roles| SELECT pg_authid.rolname,
pg_authid.rolsuper,
- pg_authid.rolinherit,
pg_authid.rolcreaterole,
pg_authid.rolcreatedb,
pg_authid.rolcanlogin,
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 2a6ba38e52..f768815c2c 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -286,7 +286,7 @@ DELETE FROM atest3; -- ok
BEGIN;
RESET SESSION AUTHORIZATION;
-ALTER ROLE regress_priv_user1 NOINHERIT;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
SET SESSION AUTHORIZATION regress_priv_user1;
DELETE FROM atest3;
ROLLBACK;
diff --git a/src/test/regress/sql/roleattributes.sql b/src/test/regress/sql/roleattributes.sql
index c961b2d730..ccfb7487cc 100644
--- a/src/test/regress/sql/roleattributes.sql
+++ b/src/test/regress/sql/roleattributes.sql
@@ -1,83 +1,73 @@
-- default for superuser is false
CREATE ROLE regress_test_def_superuser;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser';
CREATE ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH NOSUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
ALTER ROLE regress_test_superuser WITH SUPERUSER;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
-
--- default for inherit is true
-CREATE ROLE regress_test_def_inherit;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_inherit';
-CREATE ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
-ALTER ROLE regress_test_inherit WITH INHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
-ALTER ROLE regress_test_inherit WITH NOINHERIT;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_inherit';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_superuser';
-- default for create role is false
CREATE ROLE regress_test_def_createrole;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createrole';
CREATE ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH NOCREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
ALTER ROLE regress_test_createrole WITH CREATEROLE;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createrole';
-- default for create database is false
CREATE ROLE regress_test_def_createdb;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_createdb';
CREATE ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH NOCREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
ALTER ROLE regress_test_createdb WITH CREATEDB;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_createdb';
-- default for can login is false for role
CREATE ROLE regress_test_def_role_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_role_canlogin';
CREATE ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
ALTER ROLE regress_test_role_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_role_canlogin';
-- default for can login is true for user
CREATE USER regress_test_def_user_canlogin;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_user_canlogin';
CREATE USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH LOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
ALTER USER regress_test_user_canlogin WITH NOLOGIN;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_user_canlogin';
-- default for replication is false
CREATE ROLE regress_test_def_replication;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_replication';
CREATE ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH NOREPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
ALTER ROLE regress_test_replication WITH REPLICATION;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_replication';
-- default for bypassrls is false
CREATE ROLE regress_test_def_bypassrls;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_bypassrls';
CREATE ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH NOBYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
ALTER ROLE regress_test_bypassrls WITH BYPASSRLS;
-SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
+SELECT rolname, rolsuper, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_bypassrls';
-- clean up roles
DROP ROLE regress_test_def_superuser;
--
2.24.3 (Apple Git-128)
v1-0001-Fake-version-stamp.patchapplication/octet-stream; name=v1-0001-Fake-version-stamp.patchDownload
From cf0275fed01363b03d1d0c27bc074d1ce478107b Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Wed, 22 Jun 2022 15:57:54 -0400
Subject: [PATCH v1 1/2] Fake version stamp.
---
configure | 18 +++++++++---------
configure.ac | 2 +-
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/configure b/configure
index 7dec6b7bf9..fb07cd27d9 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for PostgreSQL 15beta1.
+# Generated by GNU Autoconf 2.69 for PostgreSQL 16devel.
#
# Report bugs to <pgsql-bugs@lists.postgresql.org>.
#
@@ -582,8 +582,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='PostgreSQL'
PACKAGE_TARNAME='postgresql'
-PACKAGE_VERSION='15beta1'
-PACKAGE_STRING='PostgreSQL 15beta1'
+PACKAGE_VERSION='16devel'
+PACKAGE_STRING='PostgreSQL 16devel'
PACKAGE_BUGREPORT='pgsql-bugs@lists.postgresql.org'
PACKAGE_URL='https://www.postgresql.org/'
@@ -1452,7 +1452,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures PostgreSQL 15beta1 to adapt to many kinds of systems.
+\`configure' configures PostgreSQL 16devel to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1517,7 +1517,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of PostgreSQL 15beta1:";;
+ short | recursive ) echo "Configuration of PostgreSQL 16devel:";;
esac
cat <<\_ACEOF
@@ -1691,7 +1691,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-PostgreSQL configure 15beta1
+PostgreSQL configure 16devel
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2444,7 +2444,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by PostgreSQL $as_me 15beta1, which was
+It was created by PostgreSQL $as_me 16devel, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -20534,7 +20534,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by PostgreSQL $as_me 15beta1, which was
+This file was extended by PostgreSQL $as_me 16devel, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -20605,7 +20605,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-PostgreSQL config.status 15beta1
+PostgreSQL config.status 16devel
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index d093fb88dd..6c6f997ee3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -17,7 +17,7 @@ dnl Read the Autoconf manual for details.
dnl
m4_pattern_forbid(^PGAC_)dnl to catch undefined macros
-AC_INIT([PostgreSQL], [15beta1], [pgsql-bugs@lists.postgresql.org], [], [https://www.postgresql.org/])
+AC_INIT([PostgreSQL], [16devel], [pgsql-bugs@lists.postgresql.org], [], [https://www.postgresql.org/])
m4_if(m4_defn([m4_PACKAGE_VERSION]), [2.69], [], [m4_fatal([Autoconf version 2.69 is required.
Untested combinations of 'autoconf' and PostgreSQL versions are not
--
2.24.3 (Apple Git-128)
On Wed, Jun 22, 2022 at 04:30:36PM -0400, Robert Haas wrote:
On Thu, Jun 2, 2022 at 1:17 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Point 2 would cause every existing pg_dumpall script to fail, which
seems like kind of a large gotcha. Less unpleasant alternatives
could include* Continue to accept the syntax, but ignore it, maybe with a WARNING
for the alternative that doesn't correspond to the new behavior.* Keep pg_authid.rolinherit, and have it act as supplying the default
behavior for subsequent GRANTs to that role.Here's a rather small patch that does it the first way.
I've been thinking about whether we ought to WARNING or ERROR with the
deprecated syntax. WARNING has the advantage of not breaking existing
scripts, but users might not catch that NOINHERIT roles will effectively
become INHERIT roles, which IMO is just a different type of breakage.
However, I don't think I can defend ERROR-ing and breaking all existing
pg_dumpall scripts completely, so perhaps the best we can do is emit a
WARNING until we feel comfortable removing the option completely 5-10 years
from now.
I'm guessing we'll also need a new pg_dumpall option for generating pre-v16
style role commands versus the v16+ style ones. When run on v16 and later,
you'd have to require the latter option, as you won't always be able to
convert grant-level inheritance options into role-level options. However,
you can always do the reverse. I'm thinking that by default, pg_dumpall
would output the style of commands for the current server version.
pg_upgrade would make use of this option when upgrading from <v16 to v16
and above. Is this close to what you are thinking?
I suppose if we did it the second way, we could make the syntax GRANT
granted_role TO recipient_role WITH INHERIT { TRUE | FALSE | DEFAULT
}, and DEFAULT would copy the current value of the rolinherit
property, so that changing the rolinherit property later would not
affect previous grants. The reverse is also possible: with the same
syntax, the rolinherit column could be changed from bool to "char",
storing t/f/d, and 'd' could mean the value of the rolinherit property
at time of use; thus, changing rolinherit would affect previous grants
performed using WITH INHERIT DEFAULT but not those that specified WITH
INHERIT TRUE or WITH INHERIT FALSE.
Yeah, something like this might be a nice way to sidestep the issue. I was
imagining something more like your second option, but instead of continuing
to allow grant-level options to take effect when rolinherit was true or
false, I was thinking we would ignore them or even disallow them. By
disallowing grant-level options when a role-level option was set, we might
be able to avoid the confusion about what takes effect when. That being
said, the syntax for this sort of thing might not be the cleanest.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Wed, Jun 29, 2022 at 7:19 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
Here's a rather small patch that does it the first way.
I've been thinking about whether we ought to WARNING or ERROR with the
deprecated syntax. WARNING has the advantage of not breaking existing
scripts, but users might not catch that NOINHERIT roles will effectively
become INHERIT roles, which IMO is just a different type of breakage.
However, I don't think I can defend ERROR-ing and breaking all existing
pg_dumpall scripts completely, so perhaps the best we can do is emit a
WARNING until we feel comfortable removing the option completely 5-10 years
from now.
Yeah, I think if we're going to ERROR we should just remove the syntax
support entirely. A syntax error by any other name is still a syntax
error. If we're going to go with a WARNING there's some value in that
to allow existing dumps to be sorta-restored on new versions, but this
is also moot if we don't end up deprecating this syntax after all.
I'm guessing we'll also need a new pg_dumpall option for generating pre-v16
style role commands versus the v16+ style ones. When run on v16 and later,
you'd have to require the latter option, as you won't always be able to
convert grant-level inheritance options into role-level options. However,
you can always do the reverse. I'm thinking that by default, pg_dumpall
would output the style of commands for the current server version.
pg_upgrade would make use of this option when upgrading from <v16 to v16
and above. Is this close to what you are thinking?
I don't see why we need an option. pg_dump's charter is to produce
output suitable for the server version that matches the pg_dump
version. Existing pg_dump versions will do the right thing for the
server versions they support, and in the v16, we can just straight up
change the behavior to produce the syntax that v16 wants.
I suppose if we did it the second way, we could make the syntax GRANT
granted_role TO recipient_role WITH INHERIT { TRUE | FALSE | DEFAULT
}, and DEFAULT would copy the current value of the rolinherit
property, so that changing the rolinherit property later would not
affect previous grants. The reverse is also possible: with the same
syntax, the rolinherit column could be changed from bool to "char",
storing t/f/d, and 'd' could mean the value of the rolinherit property
at time of use; thus, changing rolinherit would affect previous grants
performed using WITH INHERIT DEFAULT but not those that specified WITH
INHERIT TRUE or WITH INHERIT FALSE.Yeah, something like this might be a nice way to sidestep the issue. I was
imagining something more like your second option, but instead of continuing
to allow grant-level options to take effect when rolinherit was true or
false, I was thinking we would ignore them or even disallow them. By
disallowing grant-level options when a role-level option was set, we might
be able to avoid the confusion about what takes effect when. That being
said, the syntax for this sort of thing might not be the cleanest.
I don't think that it's a good idea to disallow grant-level options
when a role-level option is set, for two reasons. First, it would
necessitate restricting the ability to ALTER the role-level option;
otherwise no invariant could be maintained. Second, I'm interested in
this feature because I want to be able to perform a role grant that
will have a well-defined behavior without knowing what role-level
options are set. I thought initially that I wouldn't be able to
accomplish my goals if we kept the role-level options around, but now
I think that's not true. However, I think I need the grant-level
option to work regardless of how the role-level option is set. The
problem with the role-level option is essentially that you can't
reason about what a new grant will do.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jun 30, 2022 at 09:42:11AM -0400, Robert Haas wrote:
On Wed, Jun 29, 2022 at 7:19 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I'm guessing we'll also need a new pg_dumpall option for generating pre-v16
style role commands versus the v16+ style ones. When run on v16 and later,
you'd have to require the latter option, as you won't always be able to
convert grant-level inheritance options into role-level options. However,
you can always do the reverse. I'm thinking that by default, pg_dumpall
would output the style of commands for the current server version.
pg_upgrade would make use of this option when upgrading from <v16 to v16
and above. Is this close to what you are thinking?I don't see why we need an option. pg_dump's charter is to produce
output suitable for the server version that matches the pg_dump
version. Existing pg_dump versions will do the right thing for the
server versions they support, and in the v16, we can just straight up
change the behavior to produce the syntax that v16 wants.
Got it. Makes sense.
I suppose if we did it the second way, we could make the syntax GRANT
granted_role TO recipient_role WITH INHERIT { TRUE | FALSE | DEFAULT
}, and DEFAULT would copy the current value of the rolinherit
property, so that changing the rolinherit property later would not
affect previous grants. The reverse is also possible: with the same
syntax, the rolinherit column could be changed from bool to "char",
storing t/f/d, and 'd' could mean the value of the rolinherit property
at time of use; thus, changing rolinherit would affect previous grants
performed using WITH INHERIT DEFAULT but not those that specified WITH
INHERIT TRUE or WITH INHERIT FALSE.Yeah, something like this might be a nice way to sidestep the issue. I was
imagining something more like your second option, but instead of continuing
to allow grant-level options to take effect when rolinherit was true or
false, I was thinking we would ignore them or even disallow them. By
disallowing grant-level options when a role-level option was set, we might
be able to avoid the confusion about what takes effect when. That being
said, the syntax for this sort of thing might not be the cleanest.I don't think that it's a good idea to disallow grant-level options
when a role-level option is set, for two reasons. First, it would
necessitate restricting the ability to ALTER the role-level option;
otherwise no invariant could be maintained. Second, I'm interested in
this feature because I want to be able to perform a role grant that
will have a well-defined behavior without knowing what role-level
options are set. I thought initially that I wouldn't be able to
accomplish my goals if we kept the role-level options around, but now
I think that's not true. However, I think I need the grant-level
option to work regardless of how the role-level option is set. The
problem with the role-level option is essentially that you can't
reason about what a new grant will do.
IIUC you are suggesting that we'd leave rolinherit in pg_authid alone, but
we'd add the ability to specify a grant-level option that would always take
precedence. The default (WITH INHERIT DEFAULT) would cause things to work
exactly as they do today (i.e., use rolinherit). Does this sound right?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, Jun 30, 2022 at 7:29 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
IIUC you are suggesting that we'd leave rolinherit in pg_authid alone, but
we'd add the ability to specify a grant-level option that would always take
precedence. The default (WITH INHERIT DEFAULT) would cause things to work
exactly as they do today (i.e., use rolinherit). Does this sound right?
Yeah, that could be an alternative to the patch I proposed previously.
What do you (and others) think of that idea?
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Jun 30, 2022 at 10:21:53PM -0400, Robert Haas wrote:
On Thu, Jun 30, 2022 at 7:29 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
IIUC you are suggesting that we'd leave rolinherit in pg_authid alone, but
we'd add the ability to specify a grant-level option that would always take
precedence. The default (WITH INHERIT DEFAULT) would cause things to work
exactly as they do today (i.e., use rolinherit). Does this sound right?Yeah, that could be an alternative to the patch I proposed previously.
What do you (and others) think of that idea?
I like it. If rolinherit is left in place, existing pg_dumpall scripts
will continue to work, and folks can continue to use the role-level option
exactly as they do today. However, we'd be adding the ability to use a
grant-level option if desired, and it would be relatively easy to reason
about (i.e., the grant-level option always takes precedence over the
role-level option). Also, AFAICT this strategy still provides the full set
of behavior that would be possible if only the grant-level option existed.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On 6/30/22 22:58, Nathan Bossart wrote:
On Thu, Jun 30, 2022 at 10:21:53PM -0400, Robert Haas wrote:
On Thu, Jun 30, 2022 at 7:29 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
IIUC you are suggesting that we'd leave rolinherit in pg_authid alone, but
we'd add the ability to specify a grant-level option that would always take
precedence. The default (WITH INHERIT DEFAULT) would cause things to work
exactly as they do today (i.e., use rolinherit). Does this sound right?Yeah, that could be an alternative to the patch I proposed previously.
What do you (and others) think of that idea?I like it. If rolinherit is left in place, existing pg_dumpall scripts
will continue to work, and folks can continue to use the role-level option
exactly as they do today. However, we'd be adding the ability to use a
grant-level option if desired, and it would be relatively easy to reason
about (i.e., the grant-level option always takes precedence over the
role-level option). Also, AFAICT this strategy still provides the full set
of behavior that would be possible if only the grant-level option existed.
Would this allow for an explicit REVOKE to override a default INHERIT
along a specific path?
--
Joe Conway
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Fri, Jul 1, 2022 at 6:17 AM Joe Conway <mail@joeconway.com> wrote:
Would this allow for an explicit REVOKE to override a default INHERIT
along a specific path?
Can you give an example?
If you mean that A is granted to B which is granted to C which is
granted to D and you now want NOINHERIT behavior for the B->C link in
the chain, this would allow that. You could modify the existing grant
by saying either "REVOKE INHERIT OPTION FOR B FROM C" or "GRANT B TO C
WITH INHERIT FALSE".
--
Robert Haas
EDB: http://www.enterprisedb.com
On 7/1/22 07:48, Robert Haas wrote:
On Fri, Jul 1, 2022 at 6:17 AM Joe Conway <mail@joeconway.com> wrote:
Would this allow for an explicit REVOKE to override a default INHERIT
along a specific path?Can you give an example?
If you mean that A is granted to B which is granted to C which is
granted to D and you now want NOINHERIT behavior for the B->C link in
the chain, this would allow that. You could modify the existing grant
by saying either "REVOKE INHERIT OPTION FOR B FROM C" or "GRANT B TO C
WITH INHERIT FALSE".
Hmm, maybe I am misunderstanding something, but what I mean is something
like:
8<----------------
CREATE TABLE t1(f1 int);
CREATE TABLE t2(f1 int);
CREATE USER A; --defaults to INHERIT
CREATE USER B;
CREATE USER C;
GRANT select ON TABLE t1 TO B;
GRANT select ON TABLE t2 TO C;
GRANT B TO A;
GRANT C TO A;
SET SESSION AUTHORIZATION A;
-- works
SELECT * FROM t1;
-- works
SELECT * FROM t2;
RESET SESSION AUTHORIZATION;
REVOKE INHERIT OPTION FOR C FROM A;
SET SESSION AUTHORIZATION A;
-- works
SELECT * FROM t1;
-- fails
SELECT * FROM t2;
8<----------------
So now A has implicit inherited privs for t1 but not for t2.
--
Joe Conway
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Fri, Jul 1, 2022 at 8:22 AM Joe Conway <mail@joeconway.com> wrote:
Hmm, maybe I am misunderstanding something, but what I mean is something
like:8<----------------
CREATE TABLE t1(f1 int);
CREATE TABLE t2(f1 int);CREATE USER A; --defaults to INHERIT
CREATE USER B;
CREATE USER C;GRANT select ON TABLE t1 TO B;
GRANT select ON TABLE t2 TO C;GRANT B TO A;
GRANT C TO A;SET SESSION AUTHORIZATION A;
-- works
SELECT * FROM t1;
-- works
SELECT * FROM t2;RESET SESSION AUTHORIZATION;
REVOKE INHERIT OPTION FOR C FROM A;
SET SESSION AUTHORIZATION A;-- works
SELECT * FROM t1;
-- fails
SELECT * FROM t2;
8<----------------So now A has implicit inherited privs for t1 but not for t2.
Yeah, I anticipate that this would work in the way that you postulate here.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Fri, Jul 1, 2022 at 9:05 AM Robert Haas <robertmhaas@gmail.com> wrote:
So now A has implicit inherited privs for t1 but not for t2.
Yeah, I anticipate that this would work in the way that you postulate here.
And here is a patch that makes it work that way. In this version, you
can GRANT foo TO bar WITH INHERIT { TRUE | FALSE | DEFAULT }, and
DEFAULT means that role-level [NO]INHERIT property is controlling.
Otherwise, the grant-level option overrides the role-level option.
I have some hesitations about this approach. On the positive side, I
believe it's fully backward compatible, and that's something to like.
On the negative side, I've always felt that the role-level properties
- [NO]INHERIT, [NO]CREATEROLE, [NO]SUPERUSER, [NO]CREATEDB were hacky
kludges, and I'd be happier they all went away in favor of more
principled ways of controlling those behaviors. I think that the
previous design moves us in the direction of being able to eventually
remove [NO]INHERIT, whereas this, if anything, entrenches it.
It might even encourage people to add *more* role-level Boolean flags.
For instance, say we add another grant-level property that controls
whether or not you can SET ROLE to the granted role. Well, it somebody
going to then say that, for symmetry with CREATE ROLE .. [NO]INHERIT
we need CREATE ROLE .. [NO]SETROLE? I think such additions would be
actively harmful, not only because they add complexity, but also
because I think they are fundamentally the wrong way to think about
these kinds of properties.
To me, setting a default for whether or not role grants have INHERIT
default by default is a bit like setting a default for the size of
your future credit card transactions. "If I use my credit card and
don't say how much I want to charge, make it $6.82!" This is obviously
nonsense, at least in normal scenarios, because the amount of a credit
card transaction depends fundamentally on the purpose of the credit
card transaction, and you cannot know what the right amount to charge
in future transactions will be unless you already know the purpose of
those transactions. So here: we probably cannot say anything about the
purpose of a future GRANT based on the identity of the role that is
receiving the privileges. You could have a special purpose role that
receives many grants but always with the same general purpose, just as
you could have a credit card holder that only ever buys things that
cost $6.82, and either case, a default could I guess be useful. But
those seem to be corner cases, and even then the benefit of being able
to set the default seems to be modest.
Thoughts?
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v2-0001-Add-a-grant-level-INHERIT-option-to-override-the-.patchapplication/octet-stream; name=v2-0001-Add-a-grant-level-INHERIT-option-to-override-the-.patchDownload
From e94327703cb0b4c9ce08da123588a66bfb876146 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 10 Jun 2022 16:29:10 -0400
Subject: [PATCH v2] Add a grant-level INHERIT option to override the
role-level property.
---
doc/src/sgml/catalogs.sgml | 11 ++
doc/src/sgml/ref/create_role.sgml | 20 +--
doc/src/sgml/ref/grant.sgml | 28 +++-
doc/src/sgml/ref/revoke.sgml | 5 +-
src/backend/commands/user.c | 204 ++++++++++++++++++-----
src/backend/nodes/copyfuncs.c | 2 +-
src/backend/nodes/equalfuncs.c | 2 +-
src/backend/parser/gram.y | 50 +++++-
src/backend/tcop/utility.c | 2 +-
src/backend/utils/adt/acl.c | 49 ++++--
src/bin/pg_dump/pg_dumpall.c | 25 ++-
src/include/catalog/pg_auth_members.h | 13 ++
src/include/commands/user.h | 2 +-
src/include/nodes/parsenodes.h | 2 +-
src/test/regress/expected/privileges.out | 22 ++-
src/test/regress/sql/privileges.sql | 22 ++-
16 files changed, 367 insertions(+), 92 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 25b02c4e37..0eee9fd692 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1708,6 +1708,17 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>roleid</structfield> to others
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>inherit_option</structfield> <type>bool</type>
+ </para>
+ <para>
+ <literal>t</literal> = privileges are automatically inherited,
+ <literal>f</literal> = privileges are not automatically inherited,
+ <literal>d</literal> = privileges are automatically inherited if the member has <literal>rolinherit = true</literal>
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index b6a4ea1f72..6ce6c9481b 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -133,16 +133,16 @@ in sync when changing the above synopsis!
<term><literal>NOINHERIT</literal></term>
<listitem>
<para>
- These clauses determine whether a role <quote>inherits</quote> the
- privileges of roles it is a member of.
- A role with the <literal>INHERIT</literal> attribute can automatically
- use whatever database privileges have been granted to all roles
- it is directly or indirectly a member of.
- Without <literal>INHERIT</literal>, membership in another role
- only grants the ability to <command>SET ROLE</command> to that other role;
- the privileges of the other role are only available after having
- done so.
- If not specified,
+ These clauses determine whether a role will by default
+ <quote>inherit</quote> the privileges of roles it is a member of.
+ Regardless of the value of this setting, privileges for a
+ <literal>GRANT</literal> statement that specifies
+ <literal>WITH INHERIT TRUE</literal> will be inherited, and privileges
+ for a <literal>GRANT</literal> statement that specifies
+ <literal>WITH INHERIT FALSE</literal> will not be inherited.
+ For a <literal>GRANT</literal> that uses
+ <literal>WITH INHERIT DEFAULT</literal>, privileges will be inherited
+ according to how this property is set for the member role.
<literal>INHERIT</literal> is the default.
</para>
</listitem>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index f744b05b55..c3193c8227 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -98,7 +98,7 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...]
- [ WITH ADMIN OPTION ]
+ [ WITH { ADMIN | INHERIT } { OPTION | TRUE | FALSE | DEFUALT } ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
<phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
@@ -255,7 +255,18 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
</para>
<para>
- If <literal>WITH ADMIN OPTION</literal> is specified, the member can
+ The effect of membership in a role can be modified by specifying the
+ <literal>ADMIN</literal> or <literal>INHERIT</literal> option.
+ Either of these options can be set to <literal>TRUE</literal> or
+ <literal>FALSE</literal>; the <literal>INHERIT</literal> option can
+ also be set to <literal>DEFAULT</literal>. The keyword
+ <literal>OPTION</literal> is accepted as a synonym for
+ <literal>TRUE</literal>, so that <literal>WITH ADMIN OPTION</literal>
+ is a synonym for <literal>WITH ADMIN TRUE</literal>.
+ </para>
+
+ <para>
+ The <literal>ADMIN</literal> option allows the member to
in turn grant membership in the role to others, and revoke membership
in the role as well. Without the admin option, ordinary users cannot
do that. A role is not considered to hold <literal>WITH ADMIN
@@ -265,6 +276,19 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
in any role that is not a superuser.
</para>
+ <para>
+ The <literal>INHERIT</literal> option, if it is set to
+ <literal>TRUE</literal>, causes the member to inherit the privileges of
+ the granted role. That is, it can automatically use whatever database
+ privileges have been granted to that role. If set to
+ <literal>FALSE</literal>, the member does not inherit the privileges
+ of the granted role. If set to <literal>DEFAULT</literal>, the behavior
+ depends on the attributes of the member role: privileges are inherited
+ if the member role is set to <literal>INHERIT</literal> but not if the
+ member role is set to <literal>NOINHERIT</literal>.
+ See <link linkend="sql-createrole"><command>CREATE ROLE</command></link>.
+ </para>
+
<para>
If <literal>GRANTED BY</literal> is specified, the grant is recorded as
having been done by the specified role. Only database superusers may
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 62f1971036..7a83a087c3 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -125,7 +125,7 @@ REVOKE [ GRANT OPTION FOR ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
-REVOKE [ ADMIN OPTION FOR ]
+REVOKE [ { ADMIN | INHERIT } OPTION FOR ]
<replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
@@ -198,6 +198,9 @@ REVOKE [ ADMIN OPTION FOR ]
<para>
When revoking membership in a role, <literal>GRANT OPTION</literal> is instead
called <literal>ADMIN OPTION</literal>, but the behavior is similar.
+ It is also possible to revoke the <literal>INHERIT OPTION</literal>,
+ which is equivalent to setting the value of that option to
+ <literal>FALSE</literal>.
This form of the command also allows a <literal>GRANTED BY</literal>
option, but that option is currently ignored (except for checking
the existence of the named role).
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 984305ba31..b92e9a8481 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -42,6 +42,15 @@
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
+typedef struct
+{
+ unsigned specified;
+ bool admin;
+ char inherit;
+} GrantRoleOptions;
+
+#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001
+#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
@@ -51,10 +60,11 @@ check_password_hook_type check_password_hook = NULL;
static void AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt);
+ Oid grantorId, GrantRoleOptions *popt);
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt);
+ GrantRoleOptions *popt);
+static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */
@@ -107,6 +117,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ GrantRoleOptions popt;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -429,6 +440,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
+ /* Default grant. */
+ InitGrantRoleOptions(&popt);
+
/*
* Add the new role to the specified existing roles.
*/
@@ -453,7 +467,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
- GetUserId(), false);
+ GetUserId(), &popt);
ReleaseSysCache(oldroletup);
}
@@ -463,12 +477,14 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
- AddRoleMems(stmt->role, roleid,
- adminmembers, roleSpecsToIds(adminmembers),
- GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+ popt.admin = true;
+ AddRoleMems(stmt->role, roleid,
+ adminmembers, roleSpecsToIds(adminmembers),
+ GetUserId(), &popt);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
@@ -519,6 +535,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
+ GrantRoleOptions popt;
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
@@ -792,6 +809,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ InitGrantRoleOptions(&popt);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -805,11 +824,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (stmt->action == +1) /* add members to role */
AddRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- false);
+ &popt);
}
/*
@@ -1224,13 +1243,59 @@ RenameRole(const char *oldname, const char *newname)
* Grant/Revoke roles to/from roles
*/
void
-GrantRole(GrantRoleStmt *stmt)
+GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
{
Relation pg_authid_rel;
Oid grantor;
List *grantee_ids;
ListCell *item;
+ GrantRoleOptions popt;
+
+ /* Parse options list. */
+ InitGrantRoleOptions(&popt);
+ foreach(item, stmt->opt)
+ {
+ DefElem *opt = (DefElem *) lfirst(item);
+ char *optval = defGetString(opt);
+
+ if (strcmp(opt->defname, "admin") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+
+ if (parse_bool(optval, &popt.admin))
+ continue;
+ }
+ else if (strcmp(opt->defname, "inherit") == 0)
+ {
+ bool boolval;
+
+ popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT;
+ if (parse_bool(optval, &boolval))
+ {
+ popt.inherit = boolval ? ROLE_GRANT_INHERIT_TRUE
+ : ROLE_GRANT_INHERIT_FALSE;
+ continue;
+ }
+ else if (strcmp(optval, "default") == 0)
+ {
+ popt.inherit = ROLE_GRANT_INHERIT_DEFAULT;
+ continue;
+ }
+ }
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized role option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location));
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized value for role option \"%s\": \"%s\"",
+ opt->defname, optval),
+ parser_errposition(pstate, opt->location)));
+ }
+ /* Determine grantor. */
if (stmt->grantor)
grantor = get_rolespec_oid(stmt->grantor, false);
else
@@ -1243,8 +1308,7 @@ GrantRole(GrantRoleStmt *stmt)
/*
* Step through all of the granted roles and add/remove entries for the
- * grantees, or, if admin_opt is set, then just add/remove the admin
- * option.
+ * grantees, or, if opt != NIL, then just add/remove the named option(s).
*
* Note: Permissions checking is done by AddRoleMems/DelRoleMems
*/
@@ -1264,11 +1328,11 @@ GrantRole(GrantRoleStmt *stmt)
if (stmt->is_grant)
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- grantor, stmt->admin_opt);
+ grantor, &popt);
else
DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- stmt->admin_opt);
+ &popt);
}
/*
@@ -1375,7 +1439,7 @@ roleSpecsToIds(List *memberNames)
static void
AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt)
+ Oid grantorId, GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1481,38 +1545,63 @@ AddRoleMems(const char *rolename, Oid roleid,
errmsg("role \"%s\" is a member of role \"%s\"",
rolename, get_rolespec_name(memberRole))));
- /*
- * Check if entry for this role/member already exists; if so, give
- * warning unless we are adding admin option.
- */
- authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
- ObjectIdGetDatum(roleid),
- ObjectIdGetDatum(memberid));
- if (HeapTupleIsValid(authmem_tuple) &&
- (!admin_opt ||
- ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
- {
- ereport(NOTICE,
- (errmsg("role \"%s\" is already a member of role \"%s\"",
- get_rolespec_name(memberRole), rolename)));
- ReleaseSysCache(authmem_tuple);
- continue;
- }
-
- /* Build a tuple to insert or update */
+ /* Initialize bookkeeping for possible insert or update */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
+ new_record[Anum_pg_auth_members_roleid - 1] =
+ ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_members_member - 1] =
+ ObjectIdGetDatum(memberid);
+ new_record[Anum_pg_auth_members_grantor - 1] =
+ ObjectIdGetDatum(grantorId);
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(popt->admin);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ CharGetDatum(popt->inherit);
+
+ /* Find any existing tuple */
+ authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
+ ObjectIdGetDatum(roleid),
+ ObjectIdGetDatum(memberid));
- new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
- new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
- new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
+ /*
+ * If we found a tuple, update it with new option values, unless
+ * there are no changes, in which case issue a WARNING.
+ *
+ * If we didn't find a tuple, just insert one.
+ */
if (HeapTupleIsValid(authmem_tuple))
{
- new_record_repl[Anum_pg_auth_members_grantor - 1] = true;
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ Form_pg_auth_members form;
+ bool at_least_one_change = false;
+
+ form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
+
+ if (form->admin_option != popt->admin)
+ {
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ (popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0;
+ at_least_one_change = true;
+ }
+
+ if (form->inherit_option != popt->inherit)
+ {
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ (popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0;
+ at_least_one_change = true;
+ }
+
+ if (!at_least_one_change)
+ {
+ ereport(NOTICE,
+ (errmsg("role \"%s\" is already a member of role \"%s\"",
+ get_rolespec_name(memberRole), rolename)));
+ ReleaseSysCache(authmem_tuple);
+ continue;
+ }
+
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
new_record_nulls, new_record_repl);
@@ -1548,7 +1637,7 @@ AddRoleMems(const char *rolename, Oid roleid,
static void
DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt)
+ GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1605,14 +1694,14 @@ DelRoleMems(const char *rolename, Oid roleid,
continue;
}
- if (!admin_opt)
+ if (popt->specified == 0)
{
/* Remove the entry altogether */
CatalogTupleDelete(pg_authmem_rel, &authmem_tuple->t_self);
}
else
{
- /* Just turn off the admin option */
+ /* Just turn off the specified option */
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members];
bool new_record_nulls[Natts_pg_auth_members];
@@ -1623,8 +1712,22 @@ DelRoleMems(const char *rolename, Oid roleid,
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0)
+ {
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ }
+ else if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+ {
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ CharGetDatum(ROLE_GRANT_INHERIT_FALSE);
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ }
+ else
+ elog(ERROR, "no role option to revoke?");
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
@@ -1643,3 +1746,14 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
table_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * Initialize a GrantRoleOptions object with default values.
+ */
+static void
+InitGrantRoleOptions(GrantRoleOptions *popt)
+{
+ popt->specified = 0;
+ popt->admin = false;
+ popt->inherit = ROLE_GRANT_INHERIT_DEFAULT;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 51d630fa89..135da1a8a8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3971,7 +3971,7 @@ _copyGrantRoleStmt(const GrantRoleStmt *from)
COPY_NODE_FIELD(granted_roles);
COPY_NODE_FIELD(grantee_roles);
COPY_SCALAR_FIELD(is_grant);
- COPY_SCALAR_FIELD(admin_opt);
+ COPY_NODE_FIELD(opt);
COPY_NODE_FIELD(grantor);
COPY_SCALAR_FIELD(behavior);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e747e1667d..e7f72a3c11 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1572,7 +1572,7 @@ _equalGrantRoleStmt(const GrantRoleStmt *a, const GrantRoleStmt *b)
COMPARE_NODE_FIELD(granted_roles);
COMPARE_NODE_FIELD(grantee_roles);
COMPARE_SCALAR_FIELD(is_grant);
- COMPARE_SCALAR_FIELD(admin_opt);
+ COMPARE_NODE_FIELD(opt);
COMPARE_NODE_FIELD(grantor);
COMPARE_SCALAR_FIELD(behavior);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..18e02f5bf1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -359,9 +359,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> utility_option_arg
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
- opt_grant_grant_option opt_grant_admin_option
+ opt_grant_grant_option
opt_nowait opt_if_exists opt_with_data
opt_transaction_chain
+%type <list> grant_role_opt_list
+%type <defelt> grant_role_opt
+%type <node> grant_role_opt_value
%type <ival> opt_nowait_or_skip
%type <list> OptRoleList AlterOptRoleList
@@ -7818,15 +7821,26 @@ opt_grant_grant_option:
*****************************************************************************/
GrantRoleStmt:
- GRANT privilege_list TO role_list opt_grant_admin_option opt_granted_by
+ GRANT privilege_list TO role_list opt_granted_by
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = true;
n->granted_roles = $2;
n->grantee_roles = $4;
- n->admin_opt = $5;
- n->grantor = $6;
+ n->opt = NIL;
+ n->grantor = $5;
+ $$ = (Node *) n;
+ }
+ | GRANT privilege_list TO role_list WITH grant_role_opt_list opt_granted_by
+ {
+ GrantRoleStmt *n = makeNode(GrantRoleStmt);
+
+ n->is_grant = true;
+ n->granted_roles = $2;
+ n->grantee_roles = $4;
+ n->opt = $6;
+ n->grantor = $7;
$$ = (Node *) n;
}
;
@@ -7837,18 +7851,21 @@ RevokeRoleStmt:
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false;
- n->admin_opt = false;
+ n->opt = NIL;
n->granted_roles = $2;
n->grantee_roles = $4;
n->behavior = $6;
$$ = (Node *) n;
}
- | REVOKE ADMIN OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
+ | REVOKE ColId OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
+ DefElem *opt;
+ opt = makeDefElem(pstrdup($2),
+ (Node *) makeBoolean(false), @2);
n->is_grant = false;
- n->admin_opt = true;
+ n->opt = list_make1(opt);
n->granted_roles = $5;
n->grantee_roles = $7;
n->behavior = $9;
@@ -7856,8 +7873,23 @@ RevokeRoleStmt:
}
;
-opt_grant_admin_option: WITH ADMIN OPTION { $$ = true; }
- | /*EMPTY*/ { $$ = false; }
+grant_role_opt_list:
+ grant_role_opt_list ',' grant_role_opt { $$ = lappend($1, $3); }
+ | grant_role_opt { $$ = list_make1($1); }
+ ;
+
+grant_role_opt:
+ ColLabel grant_role_opt_value
+ {
+ $$ = makeDefElem(pstrdup($1), $2, @1);
+ }
+ ;
+
+grant_role_opt_value:
+ OPTION { $$ = (Node *) makeBoolean(true); }
+ | TRUE_P { $$ = (Node *) makeBoolean(true); }
+ | FALSE_P { $$ = (Node *) makeBoolean(false); }
+ | DEFAULT { $$ = (Node *) makeString(pstrdup($1)); }
;
opt_granted_by: GRANTED BY RoleSpec { $$ = $3; }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6a5bcded55..d61645ddbb 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -767,7 +767,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_GrantRoleStmt:
/* no event triggers for global objects */
- GrantRole((GrantRoleStmt *) parsetree);
+ GrantRole(pstate, (GrantRoleStmt *) parsetree);
break;
case T_CreatedbStmt:
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 772c04155c..f3c844443c 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -66,7 +66,7 @@ typedef struct
*/
enum RoleRecurseType
{
- ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */
+ ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */
ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */
};
static Oid cached_role[] = {InvalidOid, InvalidOid};
@@ -4818,8 +4818,8 @@ has_rolinherit(Oid roleid)
/*
* Get a list of roles that the specified roleid is a member of
*
- * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit
- * set, while ROLERECURSE_MEMBERS recurses through all roles. This sets
+ * Type ROLERECURSE_PRIVS recurses only through grants that have 'inherit'
+ * set, while ROLERECURSE_MEMBERS recurses through all grants. This sets
* *is_admin==true if and only if role "roleid" has an ADMIN OPTION membership
* in role "admin_of".
*
@@ -4883,9 +4883,8 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
Oid memberid = lfirst_oid(l);
CatCList *memlist;
int i;
-
- if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
- continue; /* ignore non-inheriting roles */
+ bool rolinherit = false; /* placate compiler */
+ bool rolinherit_is_set = false;
/* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
@@ -4893,14 +4892,40 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
for (i = 0; i < memlist->n_members; i++)
{
HeapTuple tup = &memlist->members[i]->tuple;
- Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+ Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
+ Oid otherid = form->roleid;
+
+ /*
+ * If we're supposed to ignore non-heritable grants, do so.
+ *
+ * Any given GRANT might be WITH INHERIT FALSE, in which case it
+ * is not inherited regardless of anything else, or it might be
+ * WITH INHERIT TRUE, in which case it definitely is inherited,
+ * or it might be WITH INHERIT DEFAULT, in which case it depends
+ * on whether the role is INHERIT or NOINHERIT.
+ */
+ if (type == ROLERECURSE_PRIVS)
+ {
+ if (form->inherit_option == ROLE_GRANT_INHERIT_FALSE)
+ continue;
+ if (form->inherit_option == ROLE_GRANT_INHERIT_DEFAULT)
+ {
+ /* Avoid repeated catalog lookups. */
+ if (!rolinherit_is_set)
+ {
+ rolinherit = has_rolinherit(memberid);
+ rolinherit_is_set = true;
+ }
+ if (!rolinherit)
+ continue;
+ }
+ }
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
*/
- if (otherid == admin_of &&
- ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
+ if (otherid == admin_of && form->admin_option &&
OidIsValid(admin_of))
*is_admin = true;
@@ -4943,8 +4968,8 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
/*
* Does member have the privileges of role (directly or indirectly)?
*
- * This is defined not to recurse through roles that don't have rolinherit
- * set; for such roles, membership implies the ability to do SET ROLE, but
+ * This is defined not to recurse through grants that are not inherited;
+ * in such cases, membership implies the ability to do SET ROLE, but
* the privileges are not available until you've done so.
*/
bool
@@ -4971,7 +4996,7 @@ has_privs_of_role(Oid member, Oid role)
/*
* Is member a member of role (directly or indirectly)?
*
- * This is defined to recurse through roles regardless of rolinherit.
+ * This is defined to recurse through grants whether they are inherited or not.
*
* Do not use this for privilege checking, instead use has_privs_of_role()
*/
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index ae41a652d7..5914165038 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -926,11 +926,16 @@ dumpRoleMembership(PGconn *conn)
PQExpBuffer buf = createPQExpBuffer();
PGresult *res;
int i;
+ bool server_has_inherit_option;
+
+ server_has_inherit_option = (server_version >= 160000);
printfPQExpBuffer(buf, "SELECT ur.rolname AS roleid, "
"um.rolname AS member, "
- "a.admin_option, "
- "ug.rolname AS grantor "
+ "a.admin_option");
+ if (server_has_inherit_option)
+ appendPQExpBuffer(buf, ", a.inherit_option");
+ appendPQExpBuffer(buf, ", ug.rolname AS grantor "
"FROM pg_auth_members a "
"LEFT JOIN %s ur on ur.oid = a.roleid "
"LEFT JOIN %s um on um.oid = a.member "
@@ -946,20 +951,28 @@ dumpRoleMembership(PGconn *conn)
{
char *roleid = PQgetvalue(res, i, 0);
char *member = PQgetvalue(res, i, 1);
- char *option = PQgetvalue(res, i, 2);
+ char *admin_option = PQgetvalue(res, i, 2);
fprintf(OPF, "GRANT %s", fmtId(roleid));
fprintf(OPF, " TO %s", fmtId(member));
- if (*option == 't')
+ if (*admin_option == 't')
fprintf(OPF, " WITH ADMIN OPTION");
+ if (server_has_inherit_option)
+ {
+ char *inherit_option = PQgetvalue(res, i, 3);
+ if (*inherit_option == 't')
+ fprintf(OPF, " WITH INHERIT TRUE");
+ else if (*inherit_option == 'f')
+ fprintf(OPF, " WITH INHERIT FALSE");
+ }
/*
* We don't track the grantor very carefully in the backend, so cope
* with the possibility that it has been dropped.
*/
- if (!PQgetisnull(res, i, 3))
+ if (!PQgetisnull(res, i, 4))
{
- char *grantor = PQgetvalue(res, i, 3);
+ char *grantor = PQgetvalue(res, i, 4);
fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
}
diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h
index 1bc027f133..27a99aa4b6 100644
--- a/src/include/catalog/pg_auth_members.h
+++ b/src/include/catalog/pg_auth_members.h
@@ -33,8 +33,21 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_
Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */
Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */
bool admin_option; /* granted with admin option? */
+ char inherit_option; /* exercise privileges without SET ROLE? */
} FormData_pg_auth_members;
+/*
+ * Does this grant allow the granted role's privileges to be exercised without
+ * executing SET ROLE?
+ *
+ * If ROLE_GRANT_INHERIT_DEFAULT is used, then the answer will be yes if the
+ * role is marked INHERIT and no if the role is marked NOINHERIT. If either
+ * of the other values is used, it takes precedence over the role-level flag.
+ */
+#define ROLE_GRANT_INHERIT_TRUE 't'
+#define ROLE_GRANT_INHERIT_FALSE 'f'
+#define ROLE_GRANT_INHERIT_DEFAULT 'd'
+
/* ----------------
* Form_pg_auth_members corresponds to a pointer to a tuple with
* the format of pg_auth_members relation.
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d3dd8303d2..54c720d880 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -28,7 +28,7 @@ extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
extern void DropRole(DropRoleStmt *stmt);
-extern void GrantRole(GrantRoleStmt *stmt);
+extern void GrantRole(ParseState *pstate, GrantRoleStmt *stmt);
extern ObjectAddress RenameRole(const char *oldname, const char *newname);
extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 73f635b455..3a1d21d9ff 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2439,7 +2439,7 @@ typedef struct GrantRoleStmt
List *granted_roles; /* list of roles to be granted/revoked */
List *grantee_roles; /* list of member roles to add/delete */
bool is_grant; /* true = GRANT, false = REVOKE */
- bool admin_opt; /* with admin option */
+ List *opt; /* options e.g. WITH GRANT OPTION */
RoleSpec *grantor; /* set grantor to other than current role */
DropBehavior behavior; /* drop behavior (for REVOKE) */
} GrantRoleStmt;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 03df567d50..a24ce12f1a 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -422,7 +422,27 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- fail because NOINHERIT
+ERROR: permission denied for table atest3
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT TRUE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- ok because grant option overrides noinherit
+ROLLBACK;
+BEGIN;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail because grant option overrides inherit
+ERROR: permission denied for table atest3
+ROLLBACK;
+BEGIN;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- as above, but change grant option using other syntax
ERROR: permission denied for table atest3
ROLLBACK;
-- views
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 2a6ba38e52..83353953a0 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -288,7 +288,27 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- fail because NOINHERIT
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT TRUE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- ok because grant option overrides noinherit
+ROLLBACK;
+
+BEGIN;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail because grant option overrides inherit
+ROLLBACK;
+
+BEGIN;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- as above, but change grant option using other syntax
ROLLBACK;
-- views
--
2.24.3 (Apple Git-128)
On Fri, Jul 01, 2022 at 02:59:53PM -0400, Robert Haas wrote:
And here is a patch that makes it work that way. In this version, you
can GRANT foo TO bar WITH INHERIT { TRUE | FALSE | DEFAULT }, and
DEFAULT means that role-level [NO]INHERIT property is controlling.
Otherwise, the grant-level option overrides the role-level option.
I haven't spent a ton of time reviewing the patch yet, but I did read
through it and thought it looked pretty good.
I have some hesitations about this approach. On the positive side, I
believe it's fully backward compatible, and that's something to like.
On the negative side, I've always felt that the role-level properties
- [NO]INHERIT, [NO]CREATEROLE, [NO]SUPERUSER, [NO]CREATEDB were hacky
kludges, and I'd be happier they all went away in favor of more
principled ways of controlling those behaviors. I think that the
previous design moves us in the direction of being able to eventually
remove [NO]INHERIT, whereas this, if anything, entrenches it.
+1. As mentioned upthread [0]/messages/by-id/20220602180755.GA2402942@nathanxps13, I think attributes like CREATEROLE,
SUPERUSER, and CREATEDB could be replaced with predefined roles. However,
since role attributes aren't inherited, that would result in a behavior
change. I'm curious what you have in mind. It might be worth spinning off
a new thread for this sooner than later.
It might even encourage people to add *more* role-level Boolean flags.
For instance, say we add another grant-level property that controls
whether or not you can SET ROLE to the granted role. Well, it somebody
going to then say that, for symmetry with CREATE ROLE .. [NO]INHERIT
we need CREATE ROLE .. [NO]SETROLE? I think such additions would be
actively harmful, not only because they add complexity, but also
because I think they are fundamentally the wrong way to think about
these kinds of properties.
Perhaps there should just be a policy against adding new role-level
options. Instead, new functionality should be provided via predefined
roles or grant-level options. There is some precedent for this. For
example, VACUUM has several options that are only usable via the
parenthesized syntax, and the unparenthesized syntax is marked deprecated
in the documentation. (Speaking of which, is it finally time to remove the
unparenthesized syntax or add WARNINGs when it is used?) For [NO]INHERIT
and WITH INHERIT DEFAULT, presumably we could do something similar. Down
the road, those would be removed in favor of only using grant-level
options.
[0]: /messages/by-id/20220602180755.GA2402942@nathanxps13
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jul 1, 2022 at 5:12 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I have some hesitations about this approach. On the positive side, I
believe it's fully backward compatible, and that's something to like.
On the negative side, I've always felt that the role-level properties
- [NO]INHERIT, [NO]CREATEROLE, [NO]SUPERUSER, [NO]CREATEDB were hacky
kludges, and I'd be happier they all went away in favor of more
principled ways of controlling those behaviors. I think that the
previous design moves us in the direction of being able to eventually
remove [NO]INHERIT, whereas this, if anything, entrenches it.+1. As mentioned upthread [0], I think attributes like CREATEROLE,
SUPERUSER, and CREATEDB could be replaced with predefined roles. However,
since role attributes aren't inherited, that would result in a behavior
change. I'm curious what you have in mind. It might be worth spinning off
a new thread for this sooner than later.
I don't think there is a whole lot of point in replacing role-level
flags with predefined roles that work exactly the same way. Maybe
there is some point, but I'm not excited about it. The problem with
these settings in my opinion is that they are too monolithic. Either
you can create any role with basically any privileges or you can
create no roles at all. Either you are a superuser and can bypass all
permissions checks or you are not and can't bypass any permissions
checks.
unparenthesized syntax or add WARNINGs when it is used?) For [NO]INHERIT
and WITH INHERIT DEFAULT, presumably we could do something similar. Down
the road, those would be removed in favor of only using grant-level
options.
I think it'd be hard to do that if WITH INHERIT DEFAULT is actually
state stored in the catalog. Maybe I should revise this again so that
the catalog column is just true or false, and the role-level property
only sets the default for future grants. That might be more like what
Tom had in mind originally.
More clear sketch: Remove WITH INHERIT DEFAULT and just have WITH
INHERIT { TRUE | FALSE }. If neither is specified, the default for a
new GRANT is set based on the role-level property. I want more control
than that.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Sat, Jul 02, 2022 at 08:45:50AM -0400, Robert Haas wrote:
I don't think there is a whole lot of point in replacing role-level
flags with predefined roles that work exactly the same way. Maybe
there is some point, but I'm not excited about it. The problem with
these settings in my opinion is that they are too monolithic. Either
you can create any role with basically any privileges or you can
create no roles at all. Either you are a superuser and can bypass all
permissions checks or you are not and can't bypass any permissions
checks.
Okay. I see.
unparenthesized syntax or add WARNINGs when it is used?) For [NO]INHERIT
and WITH INHERIT DEFAULT, presumably we could do something similar. Down
the road, those would be removed in favor of only using grant-level
options.I think it'd be hard to do that if WITH INHERIT DEFAULT is actually
state stored in the catalog. Maybe I should revise this again so that
the catalog column is just true or false, and the role-level property
only sets the default for future grants. That might be more like what
Tom had in mind originally.
I was thinking that when DEFAULT was removed, pg_dump would just need to
generate WITH INHERIT TRUE/FALSE based on the value of rolinherit for older
versions. Using the role-level property as the default for future grants
seems a viable strategy, although it would break backward compatibility.
For example, if I create a NOINHERIT role, grant a bunch of roles to it,
and then change it to INHERIT, the role won't begin inheriting the
privileges of the roles it is a member of. Right now, it does.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Sat, Jul 2, 2022 at 6:16 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I was thinking that when DEFAULT was removed, pg_dump would just need to
generate WITH INHERIT TRUE/FALSE based on the value of rolinherit for older
versions. Using the role-level property as the default for future grants
seems a viable strategy, although it would break backward compatibility.
For example, if I create a NOINHERIT role, grant a bunch of roles to it,
and then change it to INHERIT, the role won't begin inheriting the
privileges of the roles it is a member of. Right now, it does.
I think the idea you propose here is interesting, because I think it
proves that committing v2 or something like it doesn't really lock us
into the role-level property any more than we already are, which at
least makes me feel slightly less bad about that option. However, if
there's implacable opposition to any compatibility break at any point,
then maybe this plan would never actually be implemented in practice.
And if there's not, maybe we can be bolder now.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Sat, Jul 02, 2022 at 11:04:28PM -0400, Robert Haas wrote:
On Sat, Jul 2, 2022 at 6:16 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I was thinking that when DEFAULT was removed, pg_dump would just need to
generate WITH INHERIT TRUE/FALSE based on the value of rolinherit for older
versions. Using the role-level property as the default for future grants
seems a viable strategy, although it would break backward compatibility.
For example, if I create a NOINHERIT role, grant a bunch of roles to it,
and then change it to INHERIT, the role won't begin inheriting the
privileges of the roles it is a member of. Right now, it does.I think the idea you propose here is interesting, because I think it
proves that committing v2 or something like it doesn't really lock us
into the role-level property any more than we already are, which at
least makes me feel slightly less bad about that option. However, if
there's implacable opposition to any compatibility break at any point,
then maybe this plan would never actually be implemented in practice.
And if there's not, maybe we can be bolder now.
If by "bolder" you mean "mark [NO]INHERIT as deprecated-and-to-be-removed
and begin emitting WARNINGs when it and WITH INHERIT DEFAULT are used," I
think it's worth consideration. I suspect it will be hard to sell removing
[NO]INHERIT in v16 because it would introduce a compatibility break without
giving users much time to migrate. I could be wrong, though.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Sun, Jul 3, 2022 at 1:17 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
If by "bolder" you mean "mark [NO]INHERIT as deprecated-and-to-be-removed
and begin emitting WARNINGs when it and WITH INHERIT DEFAULT are used," I
think it's worth consideration. I suspect it will be hard to sell removing
[NO]INHERIT in v16 because it would introduce a compatibility break without
giving users much time to migrate. I could be wrong, though.
It's a fair point. But, if our goal for v16 is to do something that
could lead to an eventual deprecation of [NO]INHERIT, I still think
removing WITH INHERIT DEFAULT from the patch set is probably a good
idea. Perhaps then we could document that we recommend using the
grant-level option rather than setting NOINHERIT on the role, and if
users were to follow that advice, eventually no one would be relying
on the role-level property anymore. It might be optimistic to assume
that users will read the documentation, let alone follow it, but it's
something. Now, that does mean accepting a compatibility break now, in
that flipping the role-level setting would no longer affect existing
grants, but it's less of a compatibility break than I proposed
originally, so maybe it's acceptable.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Jul 5, 2022 at 8:04 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Sun, Jul 3, 2022 at 1:17 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
If by "bolder" you mean "mark [NO]INHERIT as deprecated-and-to-be-removed
and begin emitting WARNINGs when it and WITH INHERIT DEFAULT are used," I
think it's worth consideration. I suspect it will be hard to sell removing
[NO]INHERIT in v16 because it would introduce a compatibility break without
giving users much time to migrate. I could be wrong, though.It's a fair point. But, if our goal for v16 is to do something that
could lead to an eventual deprecation of [NO]INHERIT, I still think
removing WITH INHERIT DEFAULT from the patch set is probably a good
idea.
So here is an updated patch with that change.
For those who may not have read the entire thread, the current patch
does not actually remove the role-level option as the subject line
suggests, but rather makes it set the default for future grants as
suggested by Tom in
/messages/by-id/1066202.1654190251@sss.pgh.pa.us
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v3-0001-Allow-grant-level-control-of-role-inheritance-beh.patchapplication/octet-stream; name=v3-0001-Allow-grant-level-control-of-role-inheritance-beh.patchDownload
From 6de439cb3568d2c7798171a8459968ba4d2b57f1 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 8 Jul 2022 15:34:23 -0400
Subject: [PATCH v3] Allow grant-level control of role inheritance behavior.
The GRANT statement can now specify WITH INHERIT TRUE or WITH
INHERIT FALSE to control whether the member inherits the granted
role's permissions. For symmetry, you can now likewise write
WITH ADMIN TRUE or WITH ADMIN FALSE to turn ADMIN OPTION on or off.
If a GRANT does not specify WITH INHERIT, the behavior based on
whether the member role is marked INHERIT or NOINHERIT. This means
that if all roles are marked INHERIT or NOINHERIT before any role
grants are performed, the behavior is identical to what we had before;
otherwise, it's different, because ALTER ROLE [NO]INHERIT now only
changes the default behavior of future grants, and has no effect on
existing ones.
---
doc/src/sgml/catalogs.sgml | 10 ++
doc/src/sgml/ref/create_role.sgml | 29 ++--
doc/src/sgml/ref/grant.sgml | 26 ++-
doc/src/sgml/ref/revoke.sgml | 5 +-
src/backend/commands/user.c | 210 ++++++++++++++++++-----
src/backend/nodes/copyfuncs.c | 2 +-
src/backend/nodes/equalfuncs.c | 2 +-
src/backend/parser/gram.y | 49 +++++-
src/backend/tcop/utility.c | 2 +-
src/backend/utils/adt/acl.c | 55 +++---
src/bin/pg_dump/pg_dumpall.c | 29 +++-
src/include/catalog/pg_auth_members.h | 1 +
src/include/commands/user.h | 2 +-
src/include/nodes/parsenodes.h | 2 +-
src/test/regress/expected/privileges.out | 14 +-
src/test/regress/sql/privileges.sql | 13 +-
16 files changed, 338 insertions(+), 113 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4f3f375a84..1d2eadac18 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1708,6 +1708,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>roleid</structfield> to others
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>inherit_option</structfield> <type>bool</type>
+ </para>
+ <para>
+ True if the member automatically inherits the privileges of the
+ granted role
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index b6a4ea1f72..029a193361 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -133,17 +133,24 @@ in sync when changing the above synopsis!
<term><literal>NOINHERIT</literal></term>
<listitem>
<para>
- These clauses determine whether a role <quote>inherits</quote> the
- privileges of roles it is a member of.
- A role with the <literal>INHERIT</literal> attribute can automatically
- use whatever database privileges have been granted to all roles
- it is directly or indirectly a member of.
- Without <literal>INHERIT</literal>, membership in another role
- only grants the ability to <command>SET ROLE</command> to that other role;
- the privileges of the other role are only available after having
- done so.
- If not specified,
- <literal>INHERIT</literal> is the default.
+ When the <literal>GRANT</literal> statement is used to confer
+ membership in one role to another role, the <literal>GRANT</literal>
+ may use the <literal>WITH INHERIT</literal> clause to specify whether
+ the privileges of the granted role should be <quote>inherited</quote>
+ by the new member. If the <literal>GRANT</literal> statement does not
+ specify either inheritance behavior, the new <literal>GRANT</literal>
+ will be created <literal>WITH INHERIT TRUE</literal> if the member
+ role is set to <literal>INHERIT</literal> and to
+ <literal>WITH INHERIT FALSE</literal> if it is set to
+ <literal>NOINHERIT</literal>.
+ </para>
+
+ <para>
+ In <productname>PostgreSQL</productname> versions before 16,
+ the <literal>GRANT</literal> statement did not support
+ <literal>WITH INHERIT</literal>. Therefore, changing this role-level
+ property would also change the behavior of already-existing grants.
+ This is no longer the case.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index f744b05b55..6ef0393431 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -98,7 +98,7 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...]
- [ WITH ADMIN OPTION ]
+ [ WITH { ADMIN | INHERIT } { OPTION | TRUE | FALSE } ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
<phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
@@ -255,7 +255,17 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
</para>
<para>
- If <literal>WITH ADMIN OPTION</literal> is specified, the member can
+ The effect of membership in a role can be modified by specifying the
+ <literal>ADMIN</literal> or <literal>INHERIT</literal> option, each
+ of which can be set to either <literal>TRUE</literal> or
+ <literal>FALSE</literal>. The keyword <literal>OPTION</literal> is accepted
+ as a synonym for <literal>TRUE</literal>, so that
+ <literal>WITH ADMIN OPTION</literal>
+ is a synonym for <literal>WITH ADMIN TRUE</literal>.
+ </para>
+
+ <para>
+ The <literal>ADMIN</literal> option allows the member to
in turn grant membership in the role to others, and revoke membership
in the role as well. Without the admin option, ordinary users cannot
do that. A role is not considered to hold <literal>WITH ADMIN
@@ -265,6 +275,18 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
in any role that is not a superuser.
</para>
+ <para>
+ The <literal>INHERIT</literal> option, if it is set to
+ <literal>TRUE</literal>, causes the member to inherit the privileges of
+ the granted role. That is, it can automatically use whatever database
+ privileges have been granted to that role. If set to
+ <literal>FALSE</literal>, the member does not inherit the privileges
+ of the granted role. If this clause is not specified, it defaults to
+ true if the member role is set to <literal>INHERIT</literal> and to false
+ if the member role is set to <literal>NOINHERIT</literal>.
+ See <link linkend="sql-createrole"><command>CREATE ROLE</command></link>.
+ </para>
+
<para>
If <literal>GRANTED BY</literal> is specified, the grant is recorded as
having been done by the specified role. Only database superusers may
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 62f1971036..7a83a087c3 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -125,7 +125,7 @@ REVOKE [ GRANT OPTION FOR ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
-REVOKE [ ADMIN OPTION FOR ]
+REVOKE [ { ADMIN | INHERIT } OPTION FOR ]
<replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
@@ -198,6 +198,9 @@ REVOKE [ ADMIN OPTION FOR ]
<para>
When revoking membership in a role, <literal>GRANT OPTION</literal> is instead
called <literal>ADMIN OPTION</literal>, but the behavior is similar.
+ It is also possible to revoke the <literal>INHERIT OPTION</literal>,
+ which is equivalent to setting the value of that option to
+ <literal>FALSE</literal>.
This form of the command also allows a <literal>GRANTED BY</literal>
option, but that option is currently ignored (except for checking
the existence of the named role).
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 984305ba31..359be3bd07 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -42,6 +42,15 @@
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
+typedef struct
+{
+ unsigned specified;
+ bool admin;
+ bool inherit;
+} GrantRoleOptions;
+
+#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001
+#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
@@ -51,10 +60,11 @@ check_password_hook_type check_password_hook = NULL;
static void AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt);
+ Oid grantorId, GrantRoleOptions *popt);
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt);
+ GrantRoleOptions *popt);
+static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */
@@ -107,6 +117,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ GrantRoleOptions popt;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -429,6 +440,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
+ /* Default grant. */
+ InitGrantRoleOptions(&popt);
+
/*
* Add the new role to the specified existing roles.
*/
@@ -453,7 +467,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
- GetUserId(), false);
+ GetUserId(), &popt);
ReleaseSysCache(oldroletup);
}
@@ -463,12 +477,14 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
- AddRoleMems(stmt->role, roleid,
- adminmembers, roleSpecsToIds(adminmembers),
- GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+ popt.admin = true;
+ AddRoleMems(stmt->role, roleid,
+ adminmembers, roleSpecsToIds(adminmembers),
+ GetUserId(), &popt);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
@@ -519,6 +535,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
+ GrantRoleOptions popt;
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
@@ -792,6 +809,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ InitGrantRoleOptions(&popt);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -805,11 +824,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (stmt->action == +1) /* add members to role */
AddRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- false);
+ &popt);
}
/*
@@ -1224,13 +1243,48 @@ RenameRole(const char *oldname, const char *newname)
* Grant/Revoke roles to/from roles
*/
void
-GrantRole(GrantRoleStmt *stmt)
+GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
{
Relation pg_authid_rel;
Oid grantor;
List *grantee_ids;
ListCell *item;
+ GrantRoleOptions popt;
+ /* Parse options list. */
+ InitGrantRoleOptions(&popt);
+ foreach(item, stmt->opt)
+ {
+ DefElem *opt = (DefElem *) lfirst(item);
+ char *optval = defGetString(opt);
+
+ if (strcmp(opt->defname, "admin") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+
+ if (parse_bool(optval, &popt.admin))
+ continue;
+ }
+ else if (strcmp(opt->defname, "inherit") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT;
+ if (parse_bool(optval, &popt.inherit))
+ continue;
+ }
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized role option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location));
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized value for role option \"%s\": \"%s\"",
+ opt->defname, optval),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ /* Determine grantor. */
if (stmt->grantor)
grantor = get_rolespec_oid(stmt->grantor, false);
else
@@ -1243,8 +1297,7 @@ GrantRole(GrantRoleStmt *stmt)
/*
* Step through all of the granted roles and add/remove entries for the
- * grantees, or, if admin_opt is set, then just add/remove the admin
- * option.
+ * grantees, or, if opt != NIL, then just add/remove the named option(s).
*
* Note: Permissions checking is done by AddRoleMems/DelRoleMems
*/
@@ -1264,11 +1317,11 @@ GrantRole(GrantRoleStmt *stmt)
if (stmt->is_grant)
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- grantor, stmt->admin_opt);
+ grantor, &popt);
else
DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- stmt->admin_opt);
+ &popt);
}
/*
@@ -1375,7 +1428,7 @@ roleSpecsToIds(List *memberNames)
static void
AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt)
+ Oid grantorId, GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1481,38 +1534,65 @@ AddRoleMems(const char *rolename, Oid roleid,
errmsg("role \"%s\" is a member of role \"%s\"",
rolename, get_rolespec_name(memberRole))));
- /*
- * Check if entry for this role/member already exists; if so, give
- * warning unless we are adding admin option.
- */
- authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
- ObjectIdGetDatum(roleid),
- ObjectIdGetDatum(memberid));
- if (HeapTupleIsValid(authmem_tuple) &&
- (!admin_opt ||
- ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
- {
- ereport(NOTICE,
- (errmsg("role \"%s\" is already a member of role \"%s\"",
- get_rolespec_name(memberRole), rolename)));
- ReleaseSysCache(authmem_tuple);
- continue;
- }
-
- /* Build a tuple to insert or update */
+ /* Initialize bookkeeping for possible insert or update */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
+ new_record[Anum_pg_auth_members_roleid - 1] =
+ ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_members_member - 1] =
+ ObjectIdGetDatum(memberid);
+ new_record[Anum_pg_auth_members_grantor - 1] =
+ ObjectIdGetDatum(grantorId);
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(popt->admin);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(popt->inherit);
+
+ /* Find any existing tuple */
+ authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
+ ObjectIdGetDatum(roleid),
+ ObjectIdGetDatum(memberid));
- new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
- new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
- new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
+ /*
+ * If we found a tuple, update it with new option values, unless
+ * there are no changes, in which case issue a WARNING.
+ *
+ * If we didn't find a tuple, just insert one.
+ */
if (HeapTupleIsValid(authmem_tuple))
{
- new_record_repl[Anum_pg_auth_members_grantor - 1] = true;
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ Form_pg_auth_members form;
+ bool at_least_one_change = false;
+
+ form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
+
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0
+ && form->admin_option != popt->admin)
+ {
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0
+ && form->inherit_option != popt->inherit)
+ {
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
+ if (!at_least_one_change)
+ {
+ ereport(NOTICE,
+ (errmsg("role \"%s\" is already a member of role \"%s\"",
+ get_rolespec_name(memberRole), rolename)));
+ ReleaseSysCache(authmem_tuple);
+ continue;
+ }
+
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
new_record_nulls, new_record_repl);
@@ -1521,6 +1601,21 @@ AddRoleMems(const char *rolename, Oid roleid,
}
else
{
+ /* If WITH INHERIT not specified, look up role-level property. */
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) == 0)
+ {
+ HeapTuple mrtup;
+ Form_pg_authid mrform;
+
+ mrtup = SearchSysCache1(AUTHOID, memberid);
+ if (!HeapTupleIsValid(mrtup))
+ elog(ERROR, "cache lookup failed for role %u", memberid);
+ mrform = (Form_pg_authid) GETSTRUCT(mrtup);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ mrform->rolinherit;
+ ReleaseSysCache(mrtup);
+ }
+
tuple = heap_form_tuple(pg_authmem_dsc,
new_record, new_record_nulls);
CatalogTupleInsert(pg_authmem_rel, tuple);
@@ -1548,7 +1643,7 @@ AddRoleMems(const char *rolename, Oid roleid,
static void
DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt)
+ GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1605,14 +1700,14 @@ DelRoleMems(const char *rolename, Oid roleid,
continue;
}
- if (!admin_opt)
+ if (popt->specified == 0)
{
/* Remove the entry altogether */
CatalogTupleDelete(pg_authmem_rel, &authmem_tuple->t_self);
}
else
{
- /* Just turn off the admin option */
+ /* Just turn off the specified option */
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members];
bool new_record_nulls[Natts_pg_auth_members];
@@ -1623,8 +1718,22 @@ DelRoleMems(const char *rolename, Oid roleid,
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0)
+ {
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ }
+ else if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+ {
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ }
+ else
+ elog(ERROR, "no role option to revoke?");
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
@@ -1643,3 +1752,14 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
table_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * Initialize a GrantRoleOptions object with default values.
+ */
+static void
+InitGrantRoleOptions(GrantRoleOptions *popt)
+{
+ popt->specified = 0;
+ popt->admin = false;
+ popt->inherit = false;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2c834e4d0d..dd3d3b8ae8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3938,7 +3938,7 @@ _copyGrantRoleStmt(const GrantRoleStmt *from)
COPY_NODE_FIELD(granted_roles);
COPY_NODE_FIELD(grantee_roles);
COPY_SCALAR_FIELD(is_grant);
- COPY_SCALAR_FIELD(admin_opt);
+ COPY_NODE_FIELD(opt);
COPY_NODE_FIELD(grantor);
COPY_SCALAR_FIELD(behavior);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 449352639f..32b4fca549 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1588,7 +1588,7 @@ _equalGrantRoleStmt(const GrantRoleStmt *a, const GrantRoleStmt *b)
COMPARE_NODE_FIELD(granted_roles);
COMPARE_NODE_FIELD(grantee_roles);
COMPARE_SCALAR_FIELD(is_grant);
- COMPARE_SCALAR_FIELD(admin_opt);
+ COMPARE_NODE_FIELD(opt);
COMPARE_NODE_FIELD(grantor);
COMPARE_SCALAR_FIELD(behavior);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0523013f53..a5fcc61736 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -359,9 +359,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> utility_option_arg
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
- opt_grant_grant_option opt_grant_admin_option
+ opt_grant_grant_option
opt_nowait opt_if_exists opt_with_data
opt_transaction_chain
+%type <list> grant_role_opt_list
+%type <defelt> grant_role_opt
+%type <node> grant_role_opt_value
%type <ival> opt_nowait_or_skip
%type <list> OptRoleList AlterOptRoleList
@@ -7818,15 +7821,26 @@ opt_grant_grant_option:
*****************************************************************************/
GrantRoleStmt:
- GRANT privilege_list TO role_list opt_grant_admin_option opt_granted_by
+ GRANT privilege_list TO role_list opt_granted_by
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = true;
n->granted_roles = $2;
n->grantee_roles = $4;
- n->admin_opt = $5;
- n->grantor = $6;
+ n->opt = NIL;
+ n->grantor = $5;
+ $$ = (Node *) n;
+ }
+ | GRANT privilege_list TO role_list WITH grant_role_opt_list opt_granted_by
+ {
+ GrantRoleStmt *n = makeNode(GrantRoleStmt);
+
+ n->is_grant = true;
+ n->granted_roles = $2;
+ n->grantee_roles = $4;
+ n->opt = $6;
+ n->grantor = $7;
$$ = (Node *) n;
}
;
@@ -7837,18 +7851,21 @@ RevokeRoleStmt:
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false;
- n->admin_opt = false;
+ n->opt = NIL;
n->granted_roles = $2;
n->grantee_roles = $4;
n->behavior = $6;
$$ = (Node *) n;
}
- | REVOKE ADMIN OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
+ | REVOKE ColId OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
+ DefElem *opt;
+ opt = makeDefElem(pstrdup($2),
+ (Node *) makeBoolean(false), @2);
n->is_grant = false;
- n->admin_opt = true;
+ n->opt = list_make1(opt);
n->granted_roles = $5;
n->grantee_roles = $7;
n->behavior = $9;
@@ -7856,8 +7873,22 @@ RevokeRoleStmt:
}
;
-opt_grant_admin_option: WITH ADMIN OPTION { $$ = true; }
- | /*EMPTY*/ { $$ = false; }
+grant_role_opt_list:
+ grant_role_opt_list ',' grant_role_opt { $$ = lappend($1, $3); }
+ | grant_role_opt { $$ = list_make1($1); }
+ ;
+
+grant_role_opt:
+ ColLabel grant_role_opt_value
+ {
+ $$ = makeDefElem(pstrdup($1), $2, @1);
+ }
+ ;
+
+grant_role_opt_value:
+ OPTION { $$ = (Node *) makeBoolean(true); }
+ | TRUE_P { $$ = (Node *) makeBoolean(true); }
+ | FALSE_P { $$ = (Node *) makeBoolean(false); }
;
opt_granted_by: GRANTED BY RoleSpec { $$ = $3; }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6b0a865262..aa00815787 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -767,7 +767,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_GrantRoleStmt:
/* no event triggers for global objects */
- GrantRole((GrantRoleStmt *) parsetree);
+ GrantRole(pstate, (GrantRoleStmt *) parsetree);
break;
case T_CreatedbStmt:
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index b7fd3bcf05..b118482afe 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -66,7 +66,7 @@ typedef struct
*/
enum RoleRecurseType
{
- ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */
+ ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */
ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */
};
static Oid cached_role[] = {InvalidOid, InvalidOid};
@@ -4737,8 +4737,8 @@ initialize_acl(void)
/*
* In normal mode, set a callback on any syscache invalidation of rows
- * of pg_auth_members (for roles_is_member_of()), pg_authid (for
- * has_rolinherit()), or pg_database (for roles_is_member_of())
+ * of pg_auth_members (for roles_is_member_of()) pg_database (for
+ * roles_is_member_of())
*/
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
RoleMembershipCacheCallback,
@@ -4771,29 +4771,11 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
}
-
-/* Check if specified role has rolinherit set */
-static bool
-has_rolinherit(Oid roleid)
-{
- bool result = false;
- HeapTuple utup;
-
- utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- if (HeapTupleIsValid(utup))
- {
- result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- ReleaseSysCache(utup);
- }
- return result;
-}
-
-
/*
* Get a list of roles that the specified roleid is a member of
*
- * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit
- * set, while ROLERECURSE_MEMBERS recurses through all roles. This sets
+ * Type ROLERECURSE_PRIVS recurses only through inheritable grants,
+ * while ROLERECURSE_MEMBERS recurses through all grants. This sets
* *is_admin==true if and only if role "roleid" has an ADMIN OPTION membership
* in role "admin_of".
*
@@ -4858,23 +4840,32 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
CatCList *memlist;
int i;
- if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
- continue; /* ignore non-inheriting roles */
-
/* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
ObjectIdGetDatum(memberid));
for (i = 0; i < memlist->n_members; i++)
{
HeapTuple tup = &memlist->members[i]->tuple;
- Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+ Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
+ Oid otherid = form->roleid;
+
+ /*
+ * If we're supposed to ignore non-heritable grants, do so.
+ *
+ * Any given GRANT might be WITH INHERIT FALSE, in which case it
+ * is not inherited regardless of anything else, or it might be
+ * WITH INHERIT TRUE, in which case it definitely is inherited,
+ * or it might be WITH INHERIT DEFAULT, in which case it depends
+ * on whether the role is INHERIT or NOINHERIT.
+ */
+ if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+ continue;
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
*/
- if (otherid == admin_of &&
- ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
+ if (otherid == admin_of && form->admin_option &&
OidIsValid(admin_of))
*is_admin = true;
@@ -4917,8 +4908,8 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
/*
* Does member have the privileges of role (directly or indirectly)?
*
- * This is defined not to recurse through roles that don't have rolinherit
- * set; for such roles, membership implies the ability to do SET ROLE, but
+ * This is defined not to recurse through grants that are not inherited;
+ * in such cases, membership implies the ability to do SET ROLE, but
* the privileges are not available until you've done so.
*/
bool
@@ -4945,7 +4936,7 @@ has_privs_of_role(Oid member, Oid role)
/*
* Is member a member of role (directly or indirectly)?
*
- * This is defined to recurse through roles regardless of rolinherit.
+ * This is defined to recurse through grants whether they are inherited or not.
*
* Do not use this for privilege checking, instead use has_privs_of_role()
*/
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 26d3d53809..a92746db15 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -926,11 +926,18 @@ dumpRoleMembership(PGconn *conn)
PQExpBuffer buf = createPQExpBuffer();
PGresult *res;
int i;
+ bool server_has_inherit_option;
+ int i_inherit_option;
+ int i_grantor;
+
+ server_has_inherit_option = (server_version >= 160000);
printfPQExpBuffer(buf, "SELECT ur.rolname AS roleid, "
"um.rolname AS member, "
- "a.admin_option, "
- "ug.rolname AS grantor "
+ "a.admin_option");
+ if (server_has_inherit_option)
+ appendPQExpBuffer(buf, ", a.inherit_option");
+ appendPQExpBuffer(buf, ", ug.rolname AS grantor "
"FROM pg_auth_members a "
"LEFT JOIN %s ur on ur.oid = a.roleid "
"LEFT JOIN %s um on um.oid = a.member "
@@ -938,6 +945,8 @@ dumpRoleMembership(PGconn *conn)
"WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')"
"ORDER BY 1,2,3", role_catalog, role_catalog, role_catalog);
res = executeQuery(conn, buf->data);
+ i_inherit_option = PQfnumber(res, "inherit_option");
+ i_grantor = PQfnumber(res, "grantor");
if (PQntuples(res) > 0)
fprintf(OPF, "--\n-- Role memberships\n--\n\n");
@@ -946,20 +955,28 @@ dumpRoleMembership(PGconn *conn)
{
char *roleid = PQgetvalue(res, i, 0);
char *member = PQgetvalue(res, i, 1);
- char *option = PQgetvalue(res, i, 2);
+ char *admin_option = PQgetvalue(res, i, 2);
fprintf(OPF, "GRANT %s", fmtId(roleid));
fprintf(OPF, " TO %s", fmtId(member));
- if (*option == 't')
+ if (*admin_option == 't')
fprintf(OPF, " WITH ADMIN OPTION");
+ if (server_has_inherit_option)
+ {
+ char *inherit_option = PQgetvalue(res, i, i_inherit_option);
+ if (*inherit_option == 't')
+ fprintf(OPF, " WITH INHERIT TRUE");
+ else if (*inherit_option == 'f')
+ fprintf(OPF, " WITH INHERIT FALSE");
+ }
/*
* We don't track the grantor very carefully in the backend, so cope
* with the possibility that it has been dropped.
*/
- if (!PQgetisnull(res, i, 3))
+ if (!PQgetisnull(res, i, i_grantor))
{
- char *grantor = PQgetvalue(res, i, 3);
+ char *grantor = PQgetvalue(res, i, i_grantor);
fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
}
diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h
index 1bc027f133..1e144a75a8 100644
--- a/src/include/catalog/pg_auth_members.h
+++ b/src/include/catalog/pg_auth_members.h
@@ -33,6 +33,7 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_
Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */
Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */
bool admin_option; /* granted with admin option? */
+ bool inherit_option; /* exercise privileges without SET ROLE? */
} FormData_pg_auth_members;
/* ----------------
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d3dd8303d2..54c720d880 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -28,7 +28,7 @@ extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
extern void DropRole(DropRoleStmt *stmt);
-extern void GrantRole(GrantRoleStmt *stmt);
+extern void GrantRole(ParseState *pstate, GrantRoleStmt *stmt);
extern ObjectAddress RenameRole(const char *oldname, const char *newname);
extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5f6d65b5c4..15edae1f20 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2440,7 +2440,7 @@ typedef struct GrantRoleStmt
List *granted_roles; /* list of roles to be granted/revoked */
List *grantee_roles; /* list of member roles to add/delete */
bool is_grant; /* true = GRANT, false = REVOKE */
- bool admin_opt; /* with admin option */
+ List *opt; /* options e.g. WITH GRANT OPTION */
RoleSpec *grantor; /* set grantor to other than current role */
DropBehavior behavior; /* drop behavior (for REVOKE) */
} GrantRoleStmt;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index e10dd6f9ae..9bef770ac9 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -422,7 +422,19 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- ok because grant-level option is unchanged
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail
+ERROR: permission denied for table atest3
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- also fail
ERROR: permission denied for table atest3
ROLLBACK;
-- views
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 6d1fd3391a..53a035d0c2 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -288,7 +288,18 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- ok because grant-level option is unchanged
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- also fail
ROLLBACK;
-- views
--
2.24.3 (Apple Git-128)
On Fri, Jul 08, 2022 at 03:56:56PM -0400, Robert Haas wrote:
For those who may not have read the entire thread, the current patch
does not actually remove the role-level option as the subject line
suggests, but rather makes it set the default for future grants as
suggested by Tom in
/messages/by-id/1066202.1654190251@sss.pgh.pa.us
I think this is an interesting approach, as it seems to move things closer
to the end goal (i.e., removing [NO]INHERIT), but it also introduces a
pretty significant compatibility break. With this change, you cannot keep
using [NO]INHERIT like you do today. You will also need to individually
update each GRANT. The advantage of using [NO]INHERIT as the fallback
value in the absence of a grant-level option is that users don't need to
adjust behavior right away. They can essentially ignore the new
grant-level options for now, although presumably they'd need to adjust at
some point.
IMO we should either retain compatibility for existing scripts for now, or
we should remove [NO]INHERIT completely in v16. I'm worried that keeping
[NO]INHERIT around while changing its behavior somewhat subtly is only
going to create confusion.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jul 8, 2022 at 5:02 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I think this is an interesting approach, as it seems to move things closer
to the end goal (i.e., removing [NO]INHERIT), but it also introduces a
pretty significant compatibility break. With this change, you cannot keep
using [NO]INHERIT like you do today. You will also need to individually
update each GRANT.
True. But it may not be very common for people to ALTER ROLE
[NO]INHERIT after having previously used GRANT, so I'm not sure that
it would be a big problem in practice.
The advantage of using [NO]INHERIT as the fallback
value in the absence of a grant-level option is that users don't need to
adjust behavior right away. They can essentially ignore the new
grant-level options for now, although presumably they'd need to adjust at
some point.
Also true.
IMO we should either retain compatibility for existing scripts for now, or
we should remove [NO]INHERIT completely in v16. I'm worried that keeping
[NO]INHERIT around while changing its behavior somewhat subtly is only
going to create confusion.
Could be. It's just a little hard to know what to do here, because
we've gotten opinions from only a few people, and they're all
different. I've now produced patches for what I believe to be all
three of the viable alternatives, and none of them have met with
universal acclaim:
v1: hard compatibility break, NOINHERIT no-op w/WARNING
v2: WITH INHERIT { TRUE | FALSE | DEFAULT }, DEFAULT => use rolinherit
that is current at time of use
v3: WITH INHERIT { TRUE | FALSE }, if unspecified => set grant-level
Boolean to rolinherit that is current at time of GRANT
Nobody seems desperately opposed to the basic idea of adding a
grant-level option, so probably it's OK to proceed with one of these.
Which one, though, is a bit of a puzzler.
Anyone else want to offer an opinion?
--
Robert Haas
EDB: http://www.enterprisedb.com
On Sat, Jul 9, 2022 at 1:27 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Tue, Jul 5, 2022 at 8:04 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Sun, Jul 3, 2022 at 1:17 PM Nathan Bossart <nathandbossart@gmail.com>
wrote:
If by "bolder" you mean "mark [NO]INHERIT as
deprecated-and-to-be-removed
and begin emitting WARNINGs when it and WITH INHERIT DEFAULT are
used," I
think it's worth consideration. I suspect it will be hard to sell
removing
[NO]INHERIT in v16 because it would introduce a compatibility break
without
giving users much time to migrate. I could be wrong, though.
It's a fair point. But, if our goal for v16 is to do something that
could lead to an eventual deprecation of [NO]INHERIT, I still think
removing WITH INHERIT DEFAULT from the patch set is probably a good
idea.So here is an updated patch with that change.
Thanks, Robert, I created a few objects with different privileges on v14.4
e.g
postgres=# \dp+ atest2
Access privileges
Schema | Name | Type | Access privileges |
Column privileges | Policies
--------+--------+-------+-----------------------------------------------+-------------------+----------
public | atest2 | table | regress_priv_user1=arwdDxt/regress_priv_user1+|
|
| | | regress_priv_user2=r/regress_priv_user1 +|
|
| | | regress_priv_user3=w/regress_priv_user1 +|
|
| | | regress_priv_user4=a/regress_priv_user1 +|
|
| | | regress_priv_user5=D/regress_priv_user1 |
|
(1 row)
and found that after pg_upgrade there is no change on privileges on
v16(w/patch)
One scenario where the syntax is created in pg_dumpall is wrong
postgres=# create user u1;
CREATE ROLE
postgres=# create group g1 with user u1;
CREATE ROLE
postgres=# grant g1 to u1 with admin option, inherit false;
GRANT ROLE
postgres=#
Perform pg_dumpall
This is the syntax coming
"
-- Role memberships
--
GRANT g1 TO u1 WITH ADMIN OPTION WITH INHERIT FALSE GRANTED BY edb;
"
If we run this syntax on psql, there is an error.
postgres=# GRANT g1 TO u1 WITH ADMIN OPTION WITH INHERIT FALSE GRANTED BY
edb;
ERROR: syntax error at or near "WITH"
regards,
On Mon, Jul 11, 2022 at 12:48 PM tushar <tushar.ahuja@enterprisedb.com> wrote:
One scenario where the syntax is created in pg_dumpall is wrong
postgres=# create user u1;
postgres=# create group g1 with user u1;
postgres=# grant g1 to u1 with admin option, inherit false;Perform pg_dumpall
GRANT g1 TO u1 WITH ADMIN OPTION WITH INHERIT FALSE GRANTED BY edb;
Oops. Here is a rebased version of v3 which aims to fix this bug.
It seems that I can replace the previous changes to src/backend/nodes
with nothing at all in view of Peter's commit to automatically
generate node support functions. Nice.
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v4-0001-Allow-grant-level-control-of-role-inheritance-beh.patchapplication/octet-stream; name=v4-0001-Allow-grant-level-control-of-role-inheritance-beh.patchDownload
From dd409cb5067ec909f6555beb1a62a19ba74d5b8f Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 8 Jul 2022 15:34:23 -0400
Subject: [PATCH v4] Allow grant-level control of role inheritance behavior.
The GRANT statement can now specify WITH INHERIT TRUE or WITH
INHERIT FALSE to control whether the member inherits the granted
role's permissions. For symmetry, you can now likewise write
WITH ADMIN TRUE or WITH ADMIN FALSE to turn ADMIN OPTION on or off.
If a GRANT does not specify WITH INHERIT, the behavior based on
whether the member role is marked INHERIT or NOINHERIT. This means
that if all roles are marked INHERIT or NOINHERIT before any role
grants are performed, the behavior is identical to what we had before;
otherwise, it's different, because ALTER ROLE [NO]INHERIT now only
changes the default behavior of future grants, and has no effect on
existing ones.
Patch by me. Reviewed and testing by Nathan Bossart and Tushar Ahuja,
with design-level comments from various others.
---
doc/src/sgml/catalogs.sgml | 10 ++
doc/src/sgml/ref/create_role.sgml | 29 ++--
doc/src/sgml/ref/grant.sgml | 26 ++-
doc/src/sgml/ref/revoke.sgml | 5 +-
src/backend/commands/user.c | 210 ++++++++++++++++++-----
src/backend/parser/gram.y | 49 +++++-
src/backend/tcop/utility.c | 2 +-
src/backend/utils/adt/acl.c | 55 +++---
src/bin/pg_dump/pg_dumpall.c | 36 +++-
src/include/catalog/pg_auth_members.h | 1 +
src/include/commands/user.h | 2 +-
src/include/nodes/parsenodes.h | 2 +-
src/test/regress/expected/privileges.out | 14 +-
src/test/regress/sql/privileges.sql | 13 +-
14 files changed, 342 insertions(+), 112 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4f3f375a84..1d2eadac18 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1708,6 +1708,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>roleid</structfield> to others
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>inherit_option</structfield> <type>bool</type>
+ </para>
+ <para>
+ True if the member automatically inherits the privileges of the
+ granted role
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index b6a4ea1f72..029a193361 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -133,17 +133,24 @@ in sync when changing the above synopsis!
<term><literal>NOINHERIT</literal></term>
<listitem>
<para>
- These clauses determine whether a role <quote>inherits</quote> the
- privileges of roles it is a member of.
- A role with the <literal>INHERIT</literal> attribute can automatically
- use whatever database privileges have been granted to all roles
- it is directly or indirectly a member of.
- Without <literal>INHERIT</literal>, membership in another role
- only grants the ability to <command>SET ROLE</command> to that other role;
- the privileges of the other role are only available after having
- done so.
- If not specified,
- <literal>INHERIT</literal> is the default.
+ When the <literal>GRANT</literal> statement is used to confer
+ membership in one role to another role, the <literal>GRANT</literal>
+ may use the <literal>WITH INHERIT</literal> clause to specify whether
+ the privileges of the granted role should be <quote>inherited</quote>
+ by the new member. If the <literal>GRANT</literal> statement does not
+ specify either inheritance behavior, the new <literal>GRANT</literal>
+ will be created <literal>WITH INHERIT TRUE</literal> if the member
+ role is set to <literal>INHERIT</literal> and to
+ <literal>WITH INHERIT FALSE</literal> if it is set to
+ <literal>NOINHERIT</literal>.
+ </para>
+
+ <para>
+ In <productname>PostgreSQL</productname> versions before 16,
+ the <literal>GRANT</literal> statement did not support
+ <literal>WITH INHERIT</literal>. Therefore, changing this role-level
+ property would also change the behavior of already-existing grants.
+ This is no longer the case.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index f744b05b55..6ef0393431 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -98,7 +98,7 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...]
- [ WITH ADMIN OPTION ]
+ [ WITH { ADMIN | INHERIT } { OPTION | TRUE | FALSE } ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
<phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
@@ -255,7 +255,17 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
</para>
<para>
- If <literal>WITH ADMIN OPTION</literal> is specified, the member can
+ The effect of membership in a role can be modified by specifying the
+ <literal>ADMIN</literal> or <literal>INHERIT</literal> option, each
+ of which can be set to either <literal>TRUE</literal> or
+ <literal>FALSE</literal>. The keyword <literal>OPTION</literal> is accepted
+ as a synonym for <literal>TRUE</literal>, so that
+ <literal>WITH ADMIN OPTION</literal>
+ is a synonym for <literal>WITH ADMIN TRUE</literal>.
+ </para>
+
+ <para>
+ The <literal>ADMIN</literal> option allows the member to
in turn grant membership in the role to others, and revoke membership
in the role as well. Without the admin option, ordinary users cannot
do that. A role is not considered to hold <literal>WITH ADMIN
@@ -265,6 +275,18 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
in any role that is not a superuser.
</para>
+ <para>
+ The <literal>INHERIT</literal> option, if it is set to
+ <literal>TRUE</literal>, causes the member to inherit the privileges of
+ the granted role. That is, it can automatically use whatever database
+ privileges have been granted to that role. If set to
+ <literal>FALSE</literal>, the member does not inherit the privileges
+ of the granted role. If this clause is not specified, it defaults to
+ true if the member role is set to <literal>INHERIT</literal> and to false
+ if the member role is set to <literal>NOINHERIT</literal>.
+ See <link linkend="sql-createrole"><command>CREATE ROLE</command></link>.
+ </para>
+
<para>
If <literal>GRANTED BY</literal> is specified, the grant is recorded as
having been done by the specified role. Only database superusers may
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 62f1971036..7a83a087c3 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -125,7 +125,7 @@ REVOKE [ GRANT OPTION FOR ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
-REVOKE [ ADMIN OPTION FOR ]
+REVOKE [ { ADMIN | INHERIT } OPTION FOR ]
<replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
@@ -198,6 +198,9 @@ REVOKE [ ADMIN OPTION FOR ]
<para>
When revoking membership in a role, <literal>GRANT OPTION</literal> is instead
called <literal>ADMIN OPTION</literal>, but the behavior is similar.
+ It is also possible to revoke the <literal>INHERIT OPTION</literal>,
+ which is equivalent to setting the value of that option to
+ <literal>FALSE</literal>.
This form of the command also allows a <literal>GRANTED BY</literal>
option, but that option is currently ignored (except for checking
the existence of the named role).
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 984305ba31..359be3bd07 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -42,6 +42,15 @@
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
+typedef struct
+{
+ unsigned specified;
+ bool admin;
+ bool inherit;
+} GrantRoleOptions;
+
+#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001
+#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
@@ -51,10 +60,11 @@ check_password_hook_type check_password_hook = NULL;
static void AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt);
+ Oid grantorId, GrantRoleOptions *popt);
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt);
+ GrantRoleOptions *popt);
+static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */
@@ -107,6 +117,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ GrantRoleOptions popt;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -429,6 +440,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
+ /* Default grant. */
+ InitGrantRoleOptions(&popt);
+
/*
* Add the new role to the specified existing roles.
*/
@@ -453,7 +467,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
- GetUserId(), false);
+ GetUserId(), &popt);
ReleaseSysCache(oldroletup);
}
@@ -463,12 +477,14 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
- AddRoleMems(stmt->role, roleid,
- adminmembers, roleSpecsToIds(adminmembers),
- GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+ popt.admin = true;
+ AddRoleMems(stmt->role, roleid,
+ adminmembers, roleSpecsToIds(adminmembers),
+ GetUserId(), &popt);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
@@ -519,6 +535,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
+ GrantRoleOptions popt;
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
@@ -792,6 +809,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ InitGrantRoleOptions(&popt);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -805,11 +824,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (stmt->action == +1) /* add members to role */
AddRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- false);
+ &popt);
}
/*
@@ -1224,13 +1243,48 @@ RenameRole(const char *oldname, const char *newname)
* Grant/Revoke roles to/from roles
*/
void
-GrantRole(GrantRoleStmt *stmt)
+GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
{
Relation pg_authid_rel;
Oid grantor;
List *grantee_ids;
ListCell *item;
+ GrantRoleOptions popt;
+ /* Parse options list. */
+ InitGrantRoleOptions(&popt);
+ foreach(item, stmt->opt)
+ {
+ DefElem *opt = (DefElem *) lfirst(item);
+ char *optval = defGetString(opt);
+
+ if (strcmp(opt->defname, "admin") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+
+ if (parse_bool(optval, &popt.admin))
+ continue;
+ }
+ else if (strcmp(opt->defname, "inherit") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT;
+ if (parse_bool(optval, &popt.inherit))
+ continue;
+ }
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized role option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location));
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized value for role option \"%s\": \"%s\"",
+ opt->defname, optval),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ /* Determine grantor. */
if (stmt->grantor)
grantor = get_rolespec_oid(stmt->grantor, false);
else
@@ -1243,8 +1297,7 @@ GrantRole(GrantRoleStmt *stmt)
/*
* Step through all of the granted roles and add/remove entries for the
- * grantees, or, if admin_opt is set, then just add/remove the admin
- * option.
+ * grantees, or, if opt != NIL, then just add/remove the named option(s).
*
* Note: Permissions checking is done by AddRoleMems/DelRoleMems
*/
@@ -1264,11 +1317,11 @@ GrantRole(GrantRoleStmt *stmt)
if (stmt->is_grant)
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- grantor, stmt->admin_opt);
+ grantor, &popt);
else
DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- stmt->admin_opt);
+ &popt);
}
/*
@@ -1375,7 +1428,7 @@ roleSpecsToIds(List *memberNames)
static void
AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt)
+ Oid grantorId, GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1481,38 +1534,65 @@ AddRoleMems(const char *rolename, Oid roleid,
errmsg("role \"%s\" is a member of role \"%s\"",
rolename, get_rolespec_name(memberRole))));
- /*
- * Check if entry for this role/member already exists; if so, give
- * warning unless we are adding admin option.
- */
- authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
- ObjectIdGetDatum(roleid),
- ObjectIdGetDatum(memberid));
- if (HeapTupleIsValid(authmem_tuple) &&
- (!admin_opt ||
- ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
- {
- ereport(NOTICE,
- (errmsg("role \"%s\" is already a member of role \"%s\"",
- get_rolespec_name(memberRole), rolename)));
- ReleaseSysCache(authmem_tuple);
- continue;
- }
-
- /* Build a tuple to insert or update */
+ /* Initialize bookkeeping for possible insert or update */
MemSet(new_record, 0, sizeof(new_record));
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
+ new_record[Anum_pg_auth_members_roleid - 1] =
+ ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_members_member - 1] =
+ ObjectIdGetDatum(memberid);
+ new_record[Anum_pg_auth_members_grantor - 1] =
+ ObjectIdGetDatum(grantorId);
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(popt->admin);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(popt->inherit);
+
+ /* Find any existing tuple */
+ authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
+ ObjectIdGetDatum(roleid),
+ ObjectIdGetDatum(memberid));
- new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
- new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
- new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
+ /*
+ * If we found a tuple, update it with new option values, unless
+ * there are no changes, in which case issue a WARNING.
+ *
+ * If we didn't find a tuple, just insert one.
+ */
if (HeapTupleIsValid(authmem_tuple))
{
- new_record_repl[Anum_pg_auth_members_grantor - 1] = true;
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ Form_pg_auth_members form;
+ bool at_least_one_change = false;
+
+ form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
+
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0
+ && form->admin_option != popt->admin)
+ {
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0
+ && form->inherit_option != popt->inherit)
+ {
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
+ if (!at_least_one_change)
+ {
+ ereport(NOTICE,
+ (errmsg("role \"%s\" is already a member of role \"%s\"",
+ get_rolespec_name(memberRole), rolename)));
+ ReleaseSysCache(authmem_tuple);
+ continue;
+ }
+
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
new_record_nulls, new_record_repl);
@@ -1521,6 +1601,21 @@ AddRoleMems(const char *rolename, Oid roleid,
}
else
{
+ /* If WITH INHERIT not specified, look up role-level property. */
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) == 0)
+ {
+ HeapTuple mrtup;
+ Form_pg_authid mrform;
+
+ mrtup = SearchSysCache1(AUTHOID, memberid);
+ if (!HeapTupleIsValid(mrtup))
+ elog(ERROR, "cache lookup failed for role %u", memberid);
+ mrform = (Form_pg_authid) GETSTRUCT(mrtup);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ mrform->rolinherit;
+ ReleaseSysCache(mrtup);
+ }
+
tuple = heap_form_tuple(pg_authmem_dsc,
new_record, new_record_nulls);
CatalogTupleInsert(pg_authmem_rel, tuple);
@@ -1548,7 +1643,7 @@ AddRoleMems(const char *rolename, Oid roleid,
static void
DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt)
+ GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1605,14 +1700,14 @@ DelRoleMems(const char *rolename, Oid roleid,
continue;
}
- if (!admin_opt)
+ if (popt->specified == 0)
{
/* Remove the entry altogether */
CatalogTupleDelete(pg_authmem_rel, &authmem_tuple->t_self);
}
else
{
- /* Just turn off the admin option */
+ /* Just turn off the specified option */
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members];
bool new_record_nulls[Natts_pg_auth_members];
@@ -1623,8 +1718,22 @@ DelRoleMems(const char *rolename, Oid roleid,
MemSet(new_record_nulls, false, sizeof(new_record_nulls));
MemSet(new_record_repl, false, sizeof(new_record_repl));
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0)
+ {
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ }
+ else if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+ {
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ }
+ else
+ elog(ERROR, "no role option to revoke?");
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
@@ -1643,3 +1752,14 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
table_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * Initialize a GrantRoleOptions object with default values.
+ */
+static void
+InitGrantRoleOptions(GrantRoleOptions *popt)
+{
+ popt->specified = 0;
+ popt->admin = false;
+ popt->inherit = false;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0523013f53..a5fcc61736 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -359,9 +359,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> utility_option_arg
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
- opt_grant_grant_option opt_grant_admin_option
+ opt_grant_grant_option
opt_nowait opt_if_exists opt_with_data
opt_transaction_chain
+%type <list> grant_role_opt_list
+%type <defelt> grant_role_opt
+%type <node> grant_role_opt_value
%type <ival> opt_nowait_or_skip
%type <list> OptRoleList AlterOptRoleList
@@ -7818,15 +7821,26 @@ opt_grant_grant_option:
*****************************************************************************/
GrantRoleStmt:
- GRANT privilege_list TO role_list opt_grant_admin_option opt_granted_by
+ GRANT privilege_list TO role_list opt_granted_by
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = true;
n->granted_roles = $2;
n->grantee_roles = $4;
- n->admin_opt = $5;
- n->grantor = $6;
+ n->opt = NIL;
+ n->grantor = $5;
+ $$ = (Node *) n;
+ }
+ | GRANT privilege_list TO role_list WITH grant_role_opt_list opt_granted_by
+ {
+ GrantRoleStmt *n = makeNode(GrantRoleStmt);
+
+ n->is_grant = true;
+ n->granted_roles = $2;
+ n->grantee_roles = $4;
+ n->opt = $6;
+ n->grantor = $7;
$$ = (Node *) n;
}
;
@@ -7837,18 +7851,21 @@ RevokeRoleStmt:
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false;
- n->admin_opt = false;
+ n->opt = NIL;
n->granted_roles = $2;
n->grantee_roles = $4;
n->behavior = $6;
$$ = (Node *) n;
}
- | REVOKE ADMIN OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
+ | REVOKE ColId OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
+ DefElem *opt;
+ opt = makeDefElem(pstrdup($2),
+ (Node *) makeBoolean(false), @2);
n->is_grant = false;
- n->admin_opt = true;
+ n->opt = list_make1(opt);
n->granted_roles = $5;
n->grantee_roles = $7;
n->behavior = $9;
@@ -7856,8 +7873,22 @@ RevokeRoleStmt:
}
;
-opt_grant_admin_option: WITH ADMIN OPTION { $$ = true; }
- | /*EMPTY*/ { $$ = false; }
+grant_role_opt_list:
+ grant_role_opt_list ',' grant_role_opt { $$ = lappend($1, $3); }
+ | grant_role_opt { $$ = list_make1($1); }
+ ;
+
+grant_role_opt:
+ ColLabel grant_role_opt_value
+ {
+ $$ = makeDefElem(pstrdup($1), $2, @1);
+ }
+ ;
+
+grant_role_opt_value:
+ OPTION { $$ = (Node *) makeBoolean(true); }
+ | TRUE_P { $$ = (Node *) makeBoolean(true); }
+ | FALSE_P { $$ = (Node *) makeBoolean(false); }
;
opt_granted_by: GRANTED BY RoleSpec { $$ = $3; }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6b0a865262..aa00815787 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -767,7 +767,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_GrantRoleStmt:
/* no event triggers for global objects */
- GrantRole((GrantRoleStmt *) parsetree);
+ GrantRole(pstate, (GrantRoleStmt *) parsetree);
break;
case T_CreatedbStmt:
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index b7fd3bcf05..b118482afe 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -66,7 +66,7 @@ typedef struct
*/
enum RoleRecurseType
{
- ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */
+ ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */
ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */
};
static Oid cached_role[] = {InvalidOid, InvalidOid};
@@ -4737,8 +4737,8 @@ initialize_acl(void)
/*
* In normal mode, set a callback on any syscache invalidation of rows
- * of pg_auth_members (for roles_is_member_of()), pg_authid (for
- * has_rolinherit()), or pg_database (for roles_is_member_of())
+ * of pg_auth_members (for roles_is_member_of()) pg_database (for
+ * roles_is_member_of())
*/
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
RoleMembershipCacheCallback,
@@ -4771,29 +4771,11 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
}
-
-/* Check if specified role has rolinherit set */
-static bool
-has_rolinherit(Oid roleid)
-{
- bool result = false;
- HeapTuple utup;
-
- utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- if (HeapTupleIsValid(utup))
- {
- result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- ReleaseSysCache(utup);
- }
- return result;
-}
-
-
/*
* Get a list of roles that the specified roleid is a member of
*
- * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit
- * set, while ROLERECURSE_MEMBERS recurses through all roles. This sets
+ * Type ROLERECURSE_PRIVS recurses only through inheritable grants,
+ * while ROLERECURSE_MEMBERS recurses through all grants. This sets
* *is_admin==true if and only if role "roleid" has an ADMIN OPTION membership
* in role "admin_of".
*
@@ -4858,23 +4840,32 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
CatCList *memlist;
int i;
- if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
- continue; /* ignore non-inheriting roles */
-
/* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
ObjectIdGetDatum(memberid));
for (i = 0; i < memlist->n_members; i++)
{
HeapTuple tup = &memlist->members[i]->tuple;
- Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+ Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
+ Oid otherid = form->roleid;
+
+ /*
+ * If we're supposed to ignore non-heritable grants, do so.
+ *
+ * Any given GRANT might be WITH INHERIT FALSE, in which case it
+ * is not inherited regardless of anything else, or it might be
+ * WITH INHERIT TRUE, in which case it definitely is inherited,
+ * or it might be WITH INHERIT DEFAULT, in which case it depends
+ * on whether the role is INHERIT or NOINHERIT.
+ */
+ if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+ continue;
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
*/
- if (otherid == admin_of &&
- ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
+ if (otherid == admin_of && form->admin_option &&
OidIsValid(admin_of))
*is_admin = true;
@@ -4917,8 +4908,8 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
/*
* Does member have the privileges of role (directly or indirectly)?
*
- * This is defined not to recurse through roles that don't have rolinherit
- * set; for such roles, membership implies the ability to do SET ROLE, but
+ * This is defined not to recurse through grants that are not inherited;
+ * in such cases, membership implies the ability to do SET ROLE, but
* the privileges are not available until you've done so.
*/
bool
@@ -4945,7 +4936,7 @@ has_privs_of_role(Oid member, Oid role)
/*
* Is member a member of role (directly or indirectly)?
*
- * This is defined to recurse through roles regardless of rolinherit.
+ * This is defined to recurse through grants whether they are inherited or not.
*
* Do not use this for privilege checking, instead use has_privs_of_role()
*/
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 26d3d53809..503f22f012 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -924,13 +924,21 @@ static void
dumpRoleMembership(PGconn *conn)
{
PQExpBuffer buf = createPQExpBuffer();
+ PQExpBuffer optbuf = createPQExpBuffer();
PGresult *res;
int i;
+ bool server_has_inherit_option;
+ int i_inherit_option;
+ int i_grantor;
+
+ server_has_inherit_option = (server_version >= 160000);
printfPQExpBuffer(buf, "SELECT ur.rolname AS roleid, "
"um.rolname AS member, "
- "a.admin_option, "
- "ug.rolname AS grantor "
+ "a.admin_option");
+ if (server_has_inherit_option)
+ appendPQExpBuffer(buf, ", a.inherit_option");
+ appendPQExpBuffer(buf, ", ug.rolname AS grantor "
"FROM pg_auth_members a "
"LEFT JOIN %s ur on ur.oid = a.roleid "
"LEFT JOIN %s um on um.oid = a.member "
@@ -938,6 +946,8 @@ dumpRoleMembership(PGconn *conn)
"WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')"
"ORDER BY 1,2,3", role_catalog, role_catalog, role_catalog);
res = executeQuery(conn, buf->data);
+ i_inherit_option = PQfnumber(res, "inherit_option");
+ i_grantor = PQfnumber(res, "grantor");
if (PQntuples(res) > 0)
fprintf(OPF, "--\n-- Role memberships\n--\n\n");
@@ -946,20 +956,32 @@ dumpRoleMembership(PGconn *conn)
{
char *roleid = PQgetvalue(res, i, 0);
char *member = PQgetvalue(res, i, 1);
- char *option = PQgetvalue(res, i, 2);
+ char *admin_option = PQgetvalue(res, i, 2);
+
+ resetPQExpBuffer(optbuf);
fprintf(OPF, "GRANT %s", fmtId(roleid));
fprintf(OPF, " TO %s", fmtId(member));
- if (*option == 't')
- fprintf(OPF, " WITH ADMIN OPTION");
+
+ if (*admin_option == 't')
+ appendPQExpBuffer(optbuf, "ADMIN OPTION");
+ if (server_has_inherit_option)
+ {
+ char *inherit_option = PQgetvalue(res, i, i_inherit_option);
+
+ appendPQExpBuffer(optbuf, "%sINHERIT %s",
+ optbuf->len > 0 ? ", " : "",
+ *inherit_option == 't' ? "TRUE" : "FALSE");
+ }
+ fprintf(OPF, " WITH %s", optbuf->data);
/*
* We don't track the grantor very carefully in the backend, so cope
* with the possibility that it has been dropped.
*/
- if (!PQgetisnull(res, i, 3))
+ if (!PQgetisnull(res, i, i_grantor))
{
- char *grantor = PQgetvalue(res, i, 3);
+ char *grantor = PQgetvalue(res, i, i_grantor);
fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
}
diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h
index 1bc027f133..1e144a75a8 100644
--- a/src/include/catalog/pg_auth_members.h
+++ b/src/include/catalog/pg_auth_members.h
@@ -33,6 +33,7 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_
Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */
Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */
bool admin_option; /* granted with admin option? */
+ bool inherit_option; /* exercise privileges without SET ROLE? */
} FormData_pg_auth_members;
/* ----------------
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d3dd8303d2..54c720d880 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -28,7 +28,7 @@ extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
extern void DropRole(DropRoleStmt *stmt);
-extern void GrantRole(GrantRoleStmt *stmt);
+extern void GrantRole(ParseState *pstate, GrantRoleStmt *stmt);
extern ObjectAddress RenameRole(const char *oldname, const char *newname);
extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 0b6a7bb365..40dd0dcbbc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2453,7 +2453,7 @@ typedef struct GrantRoleStmt
List *granted_roles; /* list of roles to be granted/revoked */
List *grantee_roles; /* list of member roles to add/delete */
bool is_grant; /* true = GRANT, false = REVOKE */
- bool admin_opt; /* with admin option */
+ List *opt; /* options e.g. WITH GRANT OPTION */
RoleSpec *grantor; /* set grantor to other than current role */
DropBehavior behavior; /* drop behavior (for REVOKE) */
} GrantRoleStmt;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index e10dd6f9ae..9bef770ac9 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -422,7 +422,19 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- ok because grant-level option is unchanged
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail
+ERROR: permission denied for table atest3
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- also fail
ERROR: permission denied for table atest3
ROLLBACK;
-- views
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 6d1fd3391a..53a035d0c2 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -288,7 +288,18 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- ok because grant-level option is unchanged
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- also fail
ROLLBACK;
-- views
--
2.24.3 (Apple Git-128)
On 7/11/22 11:01 PM, Robert Haas wrote:
Oops. Here is a rebased version of v3 which aims to fix this bug.
Thanks, Issue seems to be fixed with this patch.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On 7/11/22 11:01 PM, Robert Haas wrote:
Oops. Here is a rebased version of v3 which aims to fix this bug.
I found one issue where pg_upgrade is failing
PG v14.4 , create these below objects
create user u1 with superuser;
create user u3;
create group g2 with user u1;
now try to perform pg_upgrade from v16(w/patch), it is failing with
these messages
[edb@centos7tushar bin]$ tail -10
dc2/pg_upgrade_output.d/20220714T195919.494/log/pg_upgrade_utility.log
--
--
CREATE ROLE "u3";
CREATE ROLE
ALTER ROLE "u3" WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN
NOREPLICATION NOBYPASSRLS;
ALTER ROLE
GRANT "g2" TO "u1" WITH GRANTED BY "edb";
psql:dc2/pg_upgrade_output.d/20220714T195919.494/dump/pg_upgrade_dump_globals.sql:47:
ERROR: syntax error at or near "BY"
LINE 1: GRANT "g2" TO "u1" WITH GRANTED BY "edb";
^
This issue is not reproducible on PG v16 (without patch).
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On Thu, Jul 14, 2022 at 10:53 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
GRANT "g2" TO "u1" WITH GRANTED BY "edb";
Another good catch. Here is v5 with a fix for that problem.
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v5-0001-Allow-grant-level-control-of-role-inheritance-beh.patchapplication/octet-stream; name=v5-0001-Allow-grant-level-control-of-role-inheritance-beh.patchDownload
From 99cfc87bb4c959dacab9e1a86b79d6d2606f11b4 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 8 Jul 2022 15:34:23 -0400
Subject: [PATCH v5] Allow grant-level control of role inheritance behavior.
The GRANT statement can now specify WITH INHERIT TRUE or WITH
INHERIT FALSE to control whether the member inherits the granted
role's permissions. For symmetry, you can now likewise write
WITH ADMIN TRUE or WITH ADMIN FALSE to turn ADMIN OPTION on or off.
If a GRANT does not specify WITH INHERIT, the behavior based on
whether the member role is marked INHERIT or NOINHERIT. This means
that if all roles are marked INHERIT or NOINHERIT before any role
grants are performed, the behavior is identical to what we had before;
otherwise, it's different, because ALTER ROLE [NO]INHERIT now only
changes the default behavior of future grants, and has no effect on
existing ones.
Patch by me. Reviewed and testing by Nathan Bossart and Tushar Ahuja,
with design-level comments from various others.
---
doc/src/sgml/catalogs.sgml | 10 ++
doc/src/sgml/ref/create_role.sgml | 29 ++--
doc/src/sgml/ref/grant.sgml | 26 ++-
doc/src/sgml/ref/revoke.sgml | 5 +-
src/backend/commands/user.c | 204 ++++++++++++++++++-----
src/backend/parser/gram.y | 49 +++++-
src/backend/tcop/utility.c | 2 +-
src/backend/utils/adt/acl.c | 55 +++---
src/bin/pg_dump/pg_dumpall.c | 37 +++-
src/include/catalog/pg_auth_members.h | 1 +
src/include/commands/user.h | 2 +-
src/include/nodes/parsenodes.h | 2 +-
src/test/regress/expected/privileges.out | 14 +-
src/test/regress/sql/privileges.sql | 13 +-
14 files changed, 340 insertions(+), 109 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 670a5406d6..f52fcb6411 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1708,6 +1708,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>roleid</structfield> to others
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>inherit_option</structfield> <type>bool</type>
+ </para>
+ <para>
+ True if the member automatically inherits the privileges of the
+ granted role
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index b6a4ea1f72..029a193361 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -133,17 +133,24 @@ in sync when changing the above synopsis!
<term><literal>NOINHERIT</literal></term>
<listitem>
<para>
- These clauses determine whether a role <quote>inherits</quote> the
- privileges of roles it is a member of.
- A role with the <literal>INHERIT</literal> attribute can automatically
- use whatever database privileges have been granted to all roles
- it is directly or indirectly a member of.
- Without <literal>INHERIT</literal>, membership in another role
- only grants the ability to <command>SET ROLE</command> to that other role;
- the privileges of the other role are only available after having
- done so.
- If not specified,
- <literal>INHERIT</literal> is the default.
+ When the <literal>GRANT</literal> statement is used to confer
+ membership in one role to another role, the <literal>GRANT</literal>
+ may use the <literal>WITH INHERIT</literal> clause to specify whether
+ the privileges of the granted role should be <quote>inherited</quote>
+ by the new member. If the <literal>GRANT</literal> statement does not
+ specify either inheritance behavior, the new <literal>GRANT</literal>
+ will be created <literal>WITH INHERIT TRUE</literal> if the member
+ role is set to <literal>INHERIT</literal> and to
+ <literal>WITH INHERIT FALSE</literal> if it is set to
+ <literal>NOINHERIT</literal>.
+ </para>
+
+ <para>
+ In <productname>PostgreSQL</productname> versions before 16,
+ the <literal>GRANT</literal> statement did not support
+ <literal>WITH INHERIT</literal>. Therefore, changing this role-level
+ property would also change the behavior of already-existing grants.
+ This is no longer the case.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index f744b05b55..6ef0393431 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -98,7 +98,7 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...]
- [ WITH ADMIN OPTION ]
+ [ WITH { ADMIN | INHERIT } { OPTION | TRUE | FALSE } ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
<phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
@@ -255,7 +255,17 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
</para>
<para>
- If <literal>WITH ADMIN OPTION</literal> is specified, the member can
+ The effect of membership in a role can be modified by specifying the
+ <literal>ADMIN</literal> or <literal>INHERIT</literal> option, each
+ of which can be set to either <literal>TRUE</literal> or
+ <literal>FALSE</literal>. The keyword <literal>OPTION</literal> is accepted
+ as a synonym for <literal>TRUE</literal>, so that
+ <literal>WITH ADMIN OPTION</literal>
+ is a synonym for <literal>WITH ADMIN TRUE</literal>.
+ </para>
+
+ <para>
+ The <literal>ADMIN</literal> option allows the member to
in turn grant membership in the role to others, and revoke membership
in the role as well. Without the admin option, ordinary users cannot
do that. A role is not considered to hold <literal>WITH ADMIN
@@ -265,6 +275,18 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
in any role that is not a superuser.
</para>
+ <para>
+ The <literal>INHERIT</literal> option, if it is set to
+ <literal>TRUE</literal>, causes the member to inherit the privileges of
+ the granted role. That is, it can automatically use whatever database
+ privileges have been granted to that role. If set to
+ <literal>FALSE</literal>, the member does not inherit the privileges
+ of the granted role. If this clause is not specified, it defaults to
+ true if the member role is set to <literal>INHERIT</literal> and to false
+ if the member role is set to <literal>NOINHERIT</literal>.
+ See <link linkend="sql-createrole"><command>CREATE ROLE</command></link>.
+ </para>
+
<para>
If <literal>GRANTED BY</literal> is specified, the grant is recorded as
having been done by the specified role. Only database superusers may
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 62f1971036..7a83a087c3 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -125,7 +125,7 @@ REVOKE [ GRANT OPTION FOR ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
-REVOKE [ ADMIN OPTION FOR ]
+REVOKE [ { ADMIN | INHERIT } OPTION FOR ]
<replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
@@ -198,6 +198,9 @@ REVOKE [ ADMIN OPTION FOR ]
<para>
When revoking membership in a role, <literal>GRANT OPTION</literal> is instead
called <literal>ADMIN OPTION</literal>, but the behavior is similar.
+ It is also possible to revoke the <literal>INHERIT OPTION</literal>,
+ which is equivalent to setting the value of that option to
+ <literal>FALSE</literal>.
This form of the command also allows a <literal>GRANTED BY</literal>
option, but that option is currently ignored (except for checking
the existence of the named role).
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 5b24b6dcad..b2aeeb55af 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -42,6 +42,15 @@
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
+typedef struct
+{
+ unsigned specified;
+ bool admin;
+ bool inherit;
+} GrantRoleOptions;
+
+#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001
+#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
@@ -51,10 +60,11 @@ check_password_hook_type check_password_hook = NULL;
static void AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt);
+ Oid grantorId, GrantRoleOptions *popt);
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt);
+ GrantRoleOptions *popt);
+static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */
@@ -107,6 +117,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ GrantRoleOptions popt;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -425,6 +436,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
+ /* Default grant. */
+ InitGrantRoleOptions(&popt);
+
/*
* Add the new role to the specified existing roles.
*/
@@ -449,7 +463,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
- GetUserId(), false);
+ GetUserId(), &popt);
ReleaseSysCache(oldroletup);
}
@@ -459,12 +473,14 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
- AddRoleMems(stmt->role, roleid,
- adminmembers, roleSpecsToIds(adminmembers),
- GetUserId(), true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+ popt.admin = true;
+ AddRoleMems(stmt->role, roleid,
+ adminmembers, roleSpecsToIds(adminmembers),
+ GetUserId(), &popt);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
@@ -515,6 +531,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
+ GrantRoleOptions popt;
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
@@ -785,6 +802,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ InitGrantRoleOptions(&popt);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -798,11 +817,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (stmt->action == +1) /* add members to role */
AddRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- GetUserId(), false);
+ GetUserId(), &popt);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- false);
+ &popt);
}
/*
@@ -1217,13 +1236,48 @@ RenameRole(const char *oldname, const char *newname)
* Grant/Revoke roles to/from roles
*/
void
-GrantRole(GrantRoleStmt *stmt)
+GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
{
Relation pg_authid_rel;
Oid grantor;
List *grantee_ids;
ListCell *item;
+ GrantRoleOptions popt;
+
+ /* Parse options list. */
+ InitGrantRoleOptions(&popt);
+ foreach(item, stmt->opt)
+ {
+ DefElem *opt = (DefElem *) lfirst(item);
+ char *optval = defGetString(opt);
+
+ if (strcmp(opt->defname, "admin") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+
+ if (parse_bool(optval, &popt.admin))
+ continue;
+ }
+ else if (strcmp(opt->defname, "inherit") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT;
+ if (parse_bool(optval, &popt.inherit))
+ continue;
+ }
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized role option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location));
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized value for role option \"%s\": \"%s\"",
+ opt->defname, optval),
+ parser_errposition(pstate, opt->location)));
+ }
+ /* Determine grantor. */
if (stmt->grantor)
grantor = get_rolespec_oid(stmt->grantor, false);
else
@@ -1236,8 +1290,7 @@ GrantRole(GrantRoleStmt *stmt)
/*
* Step through all of the granted roles and add/remove entries for the
- * grantees, or, if admin_opt is set, then just add/remove the admin
- * option.
+ * grantees, or, if opt != NIL, then just add/remove the named option(s).
*
* Note: Permissions checking is done by AddRoleMems/DelRoleMems
*/
@@ -1257,11 +1310,11 @@ GrantRole(GrantRoleStmt *stmt)
if (stmt->is_grant)
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- grantor, stmt->admin_opt);
+ grantor, &popt);
else
DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- stmt->admin_opt);
+ &popt);
}
/*
@@ -1368,7 +1421,7 @@ roleSpecsToIds(List *memberNames)
static void
AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt)
+ Oid grantorId, GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1474,34 +1527,61 @@ AddRoleMems(const char *rolename, Oid roleid,
errmsg("role \"%s\" is a member of role \"%s\"",
rolename, get_rolespec_name(memberRole))));
- /*
- * Check if entry for this role/member already exists; if so, give
- * warning unless we are adding admin option.
- */
+ /* Initialize bookkeeping for possible insert or update */
+ new_record[Anum_pg_auth_members_roleid - 1] =
+ ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_members_member - 1] =
+ ObjectIdGetDatum(memberid);
+ new_record[Anum_pg_auth_members_grantor - 1] =
+ ObjectIdGetDatum(grantorId);
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(popt->admin);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(popt->inherit);
+
+ /* Find any existing tuple */
authmem_tuple = SearchSysCache2(AUTHMEMROLEMEM,
ObjectIdGetDatum(roleid),
ObjectIdGetDatum(memberid));
- if (HeapTupleIsValid(authmem_tuple) &&
- (!admin_opt ||
- ((Form_pg_auth_members) GETSTRUCT(authmem_tuple))->admin_option))
- {
- ereport(NOTICE,
- (errmsg("role \"%s\" is already a member of role \"%s\"",
- get_rolespec_name(memberRole), rolename)));
- ReleaseSysCache(authmem_tuple);
- continue;
- }
-
- /* Build a tuple to insert or update */
- new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
- new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
- new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
+ /*
+ * If we found a tuple, update it with new option values, unless
+ * there are no changes, in which case issue a WARNING.
+ *
+ * If we didn't find a tuple, just insert one.
+ */
if (HeapTupleIsValid(authmem_tuple))
{
- new_record_repl[Anum_pg_auth_members_grantor - 1] = true;
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ Form_pg_auth_members form;
+ bool at_least_one_change = false;
+
+ form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
+
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0
+ && form->admin_option != popt->admin)
+ {
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0
+ && form->inherit_option != popt->inherit)
+ {
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
+ if (!at_least_one_change)
+ {
+ ereport(NOTICE,
+ (errmsg("role \"%s\" is already a member of role \"%s\"",
+ get_rolespec_name(memberRole), rolename)));
+ ReleaseSysCache(authmem_tuple);
+ continue;
+ }
+
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
new_record_nulls, new_record_repl);
@@ -1510,6 +1590,21 @@ AddRoleMems(const char *rolename, Oid roleid,
}
else
{
+ /* If WITH INHERIT not specified, look up role-level property. */
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) == 0)
+ {
+ HeapTuple mrtup;
+ Form_pg_authid mrform;
+
+ mrtup = SearchSysCache1(AUTHOID, memberid);
+ if (!HeapTupleIsValid(mrtup))
+ elog(ERROR, "cache lookup failed for role %u", memberid);
+ mrform = (Form_pg_authid) GETSTRUCT(mrtup);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ mrform->rolinherit;
+ ReleaseSysCache(mrtup);
+ }
+
tuple = heap_form_tuple(pg_authmem_dsc,
new_record, new_record_nulls);
CatalogTupleInsert(pg_authmem_rel, tuple);
@@ -1537,7 +1632,7 @@ AddRoleMems(const char *rolename, Oid roleid,
static void
DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- bool admin_opt)
+ GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1594,22 +1689,36 @@ DelRoleMems(const char *rolename, Oid roleid,
continue;
}
- if (!admin_opt)
+ if (popt->specified == 0)
{
/* Remove the entry altogether */
CatalogTupleDelete(pg_authmem_rel, &authmem_tuple->t_self);
}
else
{
- /* Just turn off the admin option */
+ /* Just turn off the specified option */
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members] = {0};
bool new_record_nulls[Natts_pg_auth_members] = {0};
bool new_record_repl[Natts_pg_auth_members] = {0};
/* Build a tuple to update with */
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0)
+ {
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ }
+ else if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+ {
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ }
+ else
+ elog(ERROR, "no role option to revoke?");
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
@@ -1628,3 +1737,14 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
table_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * Initialize a GrantRoleOptions object with default values.
+ */
+static void
+InitGrantRoleOptions(GrantRoleOptions *popt)
+{
+ popt->specified = 0;
+ popt->admin = false;
+ popt->inherit = false;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c018140afe..d517408422 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -359,9 +359,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> utility_option_arg
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
- opt_grant_grant_option opt_grant_admin_option
+ opt_grant_grant_option
opt_nowait opt_if_exists opt_with_data
opt_transaction_chain
+%type <list> grant_role_opt_list
+%type <defelt> grant_role_opt
+%type <node> grant_role_opt_value
%type <ival> opt_nowait_or_skip
%type <list> OptRoleList AlterOptRoleList
@@ -7828,15 +7831,26 @@ opt_grant_grant_option:
*****************************************************************************/
GrantRoleStmt:
- GRANT privilege_list TO role_list opt_grant_admin_option opt_granted_by
+ GRANT privilege_list TO role_list opt_granted_by
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = true;
n->granted_roles = $2;
n->grantee_roles = $4;
- n->admin_opt = $5;
- n->grantor = $6;
+ n->opt = NIL;
+ n->grantor = $5;
+ $$ = (Node *) n;
+ }
+ | GRANT privilege_list TO role_list WITH grant_role_opt_list opt_granted_by
+ {
+ GrantRoleStmt *n = makeNode(GrantRoleStmt);
+
+ n->is_grant = true;
+ n->granted_roles = $2;
+ n->grantee_roles = $4;
+ n->opt = $6;
+ n->grantor = $7;
$$ = (Node *) n;
}
;
@@ -7847,18 +7861,21 @@ RevokeRoleStmt:
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false;
- n->admin_opt = false;
+ n->opt = NIL;
n->granted_roles = $2;
n->grantee_roles = $4;
n->behavior = $6;
$$ = (Node *) n;
}
- | REVOKE ADMIN OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
+ | REVOKE ColId OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
+ DefElem *opt;
+ opt = makeDefElem(pstrdup($2),
+ (Node *) makeBoolean(false), @2);
n->is_grant = false;
- n->admin_opt = true;
+ n->opt = list_make1(opt);
n->granted_roles = $5;
n->grantee_roles = $7;
n->behavior = $9;
@@ -7866,8 +7883,22 @@ RevokeRoleStmt:
}
;
-opt_grant_admin_option: WITH ADMIN OPTION { $$ = true; }
- | /*EMPTY*/ { $$ = false; }
+grant_role_opt_list:
+ grant_role_opt_list ',' grant_role_opt { $$ = lappend($1, $3); }
+ | grant_role_opt { $$ = list_make1($1); }
+ ;
+
+grant_role_opt:
+ ColLabel grant_role_opt_value
+ {
+ $$ = makeDefElem(pstrdup($1), $2, @1);
+ }
+ ;
+
+grant_role_opt_value:
+ OPTION { $$ = (Node *) makeBoolean(true); }
+ | TRUE_P { $$ = (Node *) makeBoolean(true); }
+ | FALSE_P { $$ = (Node *) makeBoolean(false); }
;
opt_granted_by: GRANTED BY RoleSpec { $$ = $3; }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6b0a865262..aa00815787 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -767,7 +767,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_GrantRoleStmt:
/* no event triggers for global objects */
- GrantRole((GrantRoleStmt *) parsetree);
+ GrantRole(pstate, (GrantRoleStmt *) parsetree);
break;
case T_CreatedbStmt:
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 6fa58dd8eb..be3526a8a2 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -66,7 +66,7 @@ typedef struct
*/
enum RoleRecurseType
{
- ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */
+ ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */
ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */
};
static Oid cached_role[] = {InvalidOid, InvalidOid};
@@ -4735,8 +4735,8 @@ initialize_acl(void)
/*
* In normal mode, set a callback on any syscache invalidation of rows
- * of pg_auth_members (for roles_is_member_of()), pg_authid (for
- * has_rolinherit()), or pg_database (for roles_is_member_of())
+ * of pg_auth_members (for roles_is_member_of()) pg_database (for
+ * roles_is_member_of())
*/
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
RoleMembershipCacheCallback,
@@ -4769,29 +4769,11 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
}
-
-/* Check if specified role has rolinherit set */
-static bool
-has_rolinherit(Oid roleid)
-{
- bool result = false;
- HeapTuple utup;
-
- utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- if (HeapTupleIsValid(utup))
- {
- result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- ReleaseSysCache(utup);
- }
- return result;
-}
-
-
/*
* Get a list of roles that the specified roleid is a member of
*
- * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit
- * set, while ROLERECURSE_MEMBERS recurses through all roles. This sets
+ * Type ROLERECURSE_PRIVS recurses only through inheritable grants,
+ * while ROLERECURSE_MEMBERS recurses through all grants. This sets
* *is_admin==true if and only if role "roleid" has an ADMIN OPTION membership
* in role "admin_of".
*
@@ -4856,23 +4838,32 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
CatCList *memlist;
int i;
- if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
- continue; /* ignore non-inheriting roles */
-
/* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
ObjectIdGetDatum(memberid));
for (i = 0; i < memlist->n_members; i++)
{
HeapTuple tup = &memlist->members[i]->tuple;
- Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+ Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
+ Oid otherid = form->roleid;
+
+ /*
+ * If we're supposed to ignore non-heritable grants, do so.
+ *
+ * Any given GRANT might be WITH INHERIT FALSE, in which case it
+ * is not inherited regardless of anything else, or it might be
+ * WITH INHERIT TRUE, in which case it definitely is inherited,
+ * or it might be WITH INHERIT DEFAULT, in which case it depends
+ * on whether the role is INHERIT or NOINHERIT.
+ */
+ if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+ continue;
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
*/
- if (otherid == admin_of &&
- ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
+ if (otherid == admin_of && form->admin_option &&
OidIsValid(admin_of))
*is_admin = true;
@@ -4915,8 +4906,8 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
/*
* Does member have the privileges of role (directly or indirectly)?
*
- * This is defined not to recurse through roles that don't have rolinherit
- * set; for such roles, membership implies the ability to do SET ROLE, but
+ * This is defined not to recurse through grants that are not inherited;
+ * in such cases, membership implies the ability to do SET ROLE, but
* the privileges are not available until you've done so.
*/
bool
@@ -4943,7 +4934,7 @@ has_privs_of_role(Oid member, Oid role)
/*
* Is member a member of role (directly or indirectly)?
*
- * This is defined to recurse through roles regardless of rolinherit.
+ * This is defined to recurse through grants whether they are inherited or not.
*
* Do not use this for privilege checking, instead use has_privs_of_role()
*/
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 26d3d53809..6d320267fa 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -924,13 +924,21 @@ static void
dumpRoleMembership(PGconn *conn)
{
PQExpBuffer buf = createPQExpBuffer();
+ PQExpBuffer optbuf = createPQExpBuffer();
PGresult *res;
int i;
+ bool server_has_inherit_option;
+ int i_inherit_option;
+ int i_grantor;
+
+ server_has_inherit_option = (server_version >= 160000);
printfPQExpBuffer(buf, "SELECT ur.rolname AS roleid, "
"um.rolname AS member, "
- "a.admin_option, "
- "ug.rolname AS grantor "
+ "a.admin_option");
+ if (server_has_inherit_option)
+ appendPQExpBuffer(buf, ", a.inherit_option");
+ appendPQExpBuffer(buf, ", ug.rolname AS grantor "
"FROM pg_auth_members a "
"LEFT JOIN %s ur on ur.oid = a.roleid "
"LEFT JOIN %s um on um.oid = a.member "
@@ -938,6 +946,8 @@ dumpRoleMembership(PGconn *conn)
"WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')"
"ORDER BY 1,2,3", role_catalog, role_catalog, role_catalog);
res = executeQuery(conn, buf->data);
+ i_inherit_option = PQfnumber(res, "inherit_option");
+ i_grantor = PQfnumber(res, "grantor");
if (PQntuples(res) > 0)
fprintf(OPF, "--\n-- Role memberships\n--\n\n");
@@ -946,20 +956,33 @@ dumpRoleMembership(PGconn *conn)
{
char *roleid = PQgetvalue(res, i, 0);
char *member = PQgetvalue(res, i, 1);
- char *option = PQgetvalue(res, i, 2);
+ char *admin_option = PQgetvalue(res, i, 2);
+
+ resetPQExpBuffer(optbuf);
fprintf(OPF, "GRANT %s", fmtId(roleid));
fprintf(OPF, " TO %s", fmtId(member));
- if (*option == 't')
- fprintf(OPF, " WITH ADMIN OPTION");
+
+ if (*admin_option == 't')
+ appendPQExpBuffer(optbuf, "ADMIN OPTION");
+ if (server_has_inherit_option)
+ {
+ char *inherit_option = PQgetvalue(res, i, i_inherit_option);
+
+ appendPQExpBuffer(optbuf, "%sINHERIT %s",
+ optbuf->len > 0 ? ", " : "",
+ *inherit_option == 't' ? "TRUE" : "FALSE");
+ }
+ if (optbuf->data[0] != '\0')
+ fprintf(OPF, " WITH %s", optbuf->data);
/*
* We don't track the grantor very carefully in the backend, so cope
* with the possibility that it has been dropped.
*/
- if (!PQgetisnull(res, i, 3))
+ if (!PQgetisnull(res, i, i_grantor))
{
- char *grantor = PQgetvalue(res, i, 3);
+ char *grantor = PQgetvalue(res, i, i_grantor);
fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
}
diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h
index 1bc027f133..1e144a75a8 100644
--- a/src/include/catalog/pg_auth_members.h
+++ b/src/include/catalog/pg_auth_members.h
@@ -33,6 +33,7 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_
Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */
Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */
bool admin_option; /* granted with admin option? */
+ bool inherit_option; /* exercise privileges without SET ROLE? */
} FormData_pg_auth_members;
/* ----------------
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d3dd8303d2..54c720d880 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -28,7 +28,7 @@ extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
extern void DropRole(DropRoleStmt *stmt);
-extern void GrantRole(GrantRoleStmt *stmt);
+extern void GrantRole(ParseState *pstate, GrantRoleStmt *stmt);
extern ObjectAddress RenameRole(const char *oldname, const char *newname);
extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 98fe1abaa2..3083f53c4e 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2450,7 +2450,7 @@ typedef struct GrantRoleStmt
List *granted_roles; /* list of roles to be granted/revoked */
List *grantee_roles; /* list of member roles to add/delete */
bool is_grant; /* true = GRANT, false = REVOKE */
- bool admin_opt; /* with admin option */
+ List *opt; /* options e.g. WITH GRANT OPTION */
RoleSpec *grantor; /* set grantor to other than current role */
DropBehavior behavior; /* drop behavior (for REVOKE) */
} GrantRoleStmt;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index e10dd6f9ae..9bef770ac9 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -422,7 +422,19 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- ok because grant-level option is unchanged
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail
+ERROR: permission denied for table atest3
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- also fail
ERROR: permission denied for table atest3
ROLLBACK;
-- views
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 6d1fd3391a..53a035d0c2 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -288,7 +288,18 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- ok because grant-level option is unchanged
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- also fail
ROLLBACK;
-- views
--
2.24.3 (Apple Git-128)
On 7/19/22 12:56 AM, Robert Haas wrote:
Another good catch. Here is v5 with a fix for that problem.
Thanks, the issue is fixed now.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On 7/19/22 12:56 AM, Robert Haas wrote:
Another good catch. Here is v5 with a fix for that problem.
Here is one scenario in which I have NOT granted (inherit false)
explicitly but still revoke
command is changing the current state
postgres=# create group foo;
CREATE ROLE
postgres=# create user bar in group foo;
CREATE ROLE
postgres=# revoke inherit option for foo from bar;
REVOKE ROLE
[edb@centos7tushar bin]$ ./pg_dumpall > /tmp/a11
[edb@centos7tushar bin]$ cat /tmp/a11 |grep 'inherit false' -i
GRANT foo TO bar WITH INHERIT FALSE GRANTED BY edb;
I think this revoke command should be ignored and inherit option should
remain 'TRUE'
as it was before?
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On Thu, Jul 28, 2022 at 10:16 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
On 7/19/22 12:56 AM, Robert Haas wrote:
Another good catch. Here is v5 with a fix for that problem.
Here is one scenario in which I have NOT granted (inherit false)
explicitly but still revoke
command is changing the current statepostgres=# create group foo;
CREATE ROLE
postgres=# create user bar in group foo;
CREATE ROLE
postgres=# revoke inherit option for foo from bar;
REVOKE ROLE[edb@centos7tushar bin]$ ./pg_dumpall > /tmp/a11
[edb@centos7tushar bin]$ cat /tmp/a11 |grep 'inherit false' -i
GRANT foo TO bar WITH INHERIT FALSE GRANTED BY edb;I think this revoke command should be ignored and inherit option should
remain 'TRUE'
as it was before?
No, it seems to me that's behaving as intended. REVOKE BLAH OPTION ...
is intended to be a way of switching an option off.
--
Robert Haas
EDB: http://www.enterprisedb.com
On 7/28/22 8:03 PM, Robert Haas wrote:
No, it seems to me that's behaving as intended. REVOKE BLAH OPTION ...
is intended to be a way of switching an option off.
Ok, Thanks, Robert. I tested with a couple of more scenarios like
pg_upgrade/pg_dumpall /grant/revoke .. with admin option/inherit
and things look good to me.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On Thu, Jul 28, 2022 at 12:32 PM tushar <tushar.ahuja@enterprisedb.com> wrote:
On 7/28/22 8:03 PM, Robert Haas wrote:
No, it seems to me that's behaving as intended. REVOKE BLAH OPTION ...
is intended to be a way of switching an option off.Ok, Thanks, Robert. I tested with a couple of more scenarios like
pg_upgrade/pg_dumpall /grant/revoke .. with admin option/inherit
and things look good to me.
This patch needed to be rebased pretty extensively after commit
ce6b672e4455820a0348214be0da1a024c3f619f. Here is a new version.
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v6-0001-Allow-grant-level-control-of-role-inheritance-beh.patchapplication/octet-stream; name=v6-0001-Allow-grant-level-control-of-role-inheritance-beh.patchDownload
From bd9210de1942e24c112cdbdb258c4c372979237a Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 23 Aug 2022 14:51:08 -0400
Subject: [PATCH v6] Allow grant-level control of role inheritance behavior.
The GRANT statement can now specify WITH INHERIT TRUE or WITH
INHERIT FALSE to control whether the member inherits the granted
role's permissions. For symmetry, you can now likewise write
WITH ADMIN TRUE or WITH ADMIN FALSE to turn ADMIN OPTION on or off.
If a GRANT does not specify WITH INHERIT, the behavior based on
whether the member role is marked INHERIT or NOINHERIT. This means
that if all roles are marked INHERIT or NOINHERIT before any role
grants are performed, the behavior is identical to what we had before;
otherwise, it's different, because ALTER ROLE [NO]INHERIT now only
changes the default behavior of future grants, and has no effect on
existing ones.
Patch by me. Reviewed and testing by Nathan Bossart and Tushar Ahuja,
with design-level comments from various others.
---
doc/src/sgml/catalogs.sgml | 10 +
doc/src/sgml/ref/create_role.sgml | 29 ++-
doc/src/sgml/ref/grant.sgml | 26 ++-
doc/src/sgml/ref/revoke.sgml | 9 +-
src/backend/commands/user.c | 262 ++++++++++++++++++-----
src/backend/parser/gram.y | 49 ++++-
src/backend/tcop/utility.c | 2 +-
src/backend/utils/adt/acl.c | 55 ++---
src/bin/pg_dump/pg_dumpall.c | 32 ++-
src/include/catalog/pg_auth_members.h | 1 +
src/include/commands/user.h | 2 +-
src/include/nodes/parsenodes.h | 2 +-
src/test/regress/expected/privileges.out | 14 +-
src/test/regress/sql/privileges.sql | 13 +-
14 files changed, 392 insertions(+), 114 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 2ce539aaf0..00f833d210 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1717,6 +1717,16 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
<structfield>roleid</structfield> to others
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>inherit_option</structfield> <type>bool</type>
+ </para>
+ <para>
+ True if the member automatically inherits the privileges of the
+ granted role
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index b6a4ea1f72..029a193361 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -133,17 +133,24 @@ in sync when changing the above synopsis!
<term><literal>NOINHERIT</literal></term>
<listitem>
<para>
- These clauses determine whether a role <quote>inherits</quote> the
- privileges of roles it is a member of.
- A role with the <literal>INHERIT</literal> attribute can automatically
- use whatever database privileges have been granted to all roles
- it is directly or indirectly a member of.
- Without <literal>INHERIT</literal>, membership in another role
- only grants the ability to <command>SET ROLE</command> to that other role;
- the privileges of the other role are only available after having
- done so.
- If not specified,
- <literal>INHERIT</literal> is the default.
+ When the <literal>GRANT</literal> statement is used to confer
+ membership in one role to another role, the <literal>GRANT</literal>
+ may use the <literal>WITH INHERIT</literal> clause to specify whether
+ the privileges of the granted role should be <quote>inherited</quote>
+ by the new member. If the <literal>GRANT</literal> statement does not
+ specify either inheritance behavior, the new <literal>GRANT</literal>
+ will be created <literal>WITH INHERIT TRUE</literal> if the member
+ role is set to <literal>INHERIT</literal> and to
+ <literal>WITH INHERIT FALSE</literal> if it is set to
+ <literal>NOINHERIT</literal>.
+ </para>
+
+ <para>
+ In <productname>PostgreSQL</productname> versions before 16,
+ the <literal>GRANT</literal> statement did not support
+ <literal>WITH INHERIT</literal>. Therefore, changing this role-level
+ property would also change the behavior of already-existing grants.
+ This is no longer the case.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index 2fd0f34d55..1cc2eb13bc 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -98,7 +98,7 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replaceable class="parameter">role_specification</replaceable> [, ...]
- [ WITH ADMIN OPTION ]
+ [ WITH { ADMIN | INHERIT } { OPTION | TRUE | FALSE } ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
<phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase>
@@ -255,7 +255,17 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
</para>
<para>
- If <literal>WITH ADMIN OPTION</literal> is specified, the member can
+ The effect of membership in a role can be modified by specifying the
+ <literal>ADMIN</literal> or <literal>INHERIT</literal> option, each
+ of which can be set to either <literal>TRUE</literal> or
+ <literal>FALSE</literal>. The keyword <literal>OPTION</literal> is accepted
+ as a synonym for <literal>TRUE</literal>, so that
+ <literal>WITH ADMIN OPTION</literal>
+ is a synonym for <literal>WITH ADMIN TRUE</literal>.
+ </para>
+
+ <para>
+ The <literal>ADMIN</literal> option allows the member to
in turn grant membership in the role to others, and revoke membership
in the role as well. Without the admin option, ordinary users cannot
do that. A role is not considered to hold <literal>WITH ADMIN
@@ -265,6 +275,18 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
in any role that is not a superuser.
</para>
+ <para>
+ The <literal>INHERIT</literal> option, if it is set to
+ <literal>TRUE</literal>, causes the member to inherit the privileges of
+ the granted role. That is, it can automatically use whatever database
+ privileges have been granted to that role. If set to
+ <literal>FALSE</literal>, the member does not inherit the privileges
+ of the granted role. If this clause is not specified, it defaults to
+ true if the member role is set to <literal>INHERIT</literal> and to false
+ if the member role is set to <literal>NOINHERIT</literal>.
+ See <link linkend="sql-createrole"><command>CREATE ROLE</command></link>.
+ </para>
+
<para>
If <literal>GRANTED BY</literal> is specified, the grant is recorded as
having been done by the specified role. A user can only attribute a grant
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 16e840458c..4fd4bfb3d7 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -125,7 +125,7 @@ REVOKE [ GRANT OPTION FOR ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
-REVOKE [ ADMIN OPTION FOR ]
+REVOKE [ { ADMIN | INHERIT } OPTION FOR ]
<replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ]
@@ -206,6 +206,13 @@ REVOKE [ ADMIN OPTION FOR ]
allow the noise word <literal>GROUP</literal>
in <replaceable class="parameter">role_specification</replaceable>.
</para>
+
+ <para>
+ Just as <literal>ADMIN OPTION</literal> can be removed from an existing
+ role grant, it is also possible to revoke <literal>INHERIT OPTION</literal>.
+ This is equivalent to setting the value of that option to
+ <literal>FALSE</literal>.
+ </para>
</refsect1>
<refsect1 id="sql-revoke-notes">
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 511ca8d8fd..265a48af7e 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -51,6 +51,9 @@
* RRG_REMOVE_ADMIN_OPTION indicates a grant that would need to have
* admin_option set to false by the operation.
*
+ * RRG_REMOVE_INHERIT_OPTION indicates a grant that would need to have
+ * inherit_option set to false by the operation.
+ *
* RRG_DELETE_GRANT indicates a grant that would need to be removed entirely
* by the operation.
*/
@@ -58,12 +61,22 @@ typedef enum
{
RRG_NOOP,
RRG_REMOVE_ADMIN_OPTION,
+ RRG_REMOVE_INHERIT_OPTION,
RRG_DELETE_GRANT
} RevokeRoleGrantAction;
/* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_pg_authid_oid = InvalidOid;
+typedef struct
+{
+ unsigned specified;
+ bool admin;
+ bool inherit;
+} GrantRoleOptions;
+
+#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001
+#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
@@ -73,17 +86,18 @@ check_password_hook_type check_password_hook = NULL;
static void AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt);
+ Oid grantorId, GrantRoleOptions *popt);
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt, DropBehavior behavior);
+ Oid grantorId, GrantRoleOptions *popt,
+ DropBehavior behavior);
static Oid check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId,
bool is_grant);
static RevokeRoleGrantAction *initialize_revoke_actions(CatCList *memlist);
static bool plan_single_revoke(CatCList *memlist,
RevokeRoleGrantAction *actions,
Oid member, Oid grantor,
- bool revoke_admin_option_only,
+ GrantRoleOptions *popt,
DropBehavior behavior);
static void plan_member_revoke(CatCList *memlist,
RevokeRoleGrantAction *actions, Oid member);
@@ -92,6 +106,7 @@ static void plan_recursive_revoke(CatCList *memlist,
int index,
bool revoke_admin_option_only,
DropBehavior behavior);
+static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */
@@ -144,6 +159,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ GrantRoleOptions popt;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
@@ -462,6 +478,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
+ /* Default grant. */
+ InitGrantRoleOptions(&popt);
+
/*
* Add the new role to the specified existing roles.
*/
@@ -486,7 +505,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
- InvalidOid, false);
+ InvalidOid, &popt);
ReleaseSysCache(oldroletup);
}
@@ -496,12 +515,14 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't.
*/
- AddRoleMems(stmt->role, roleid,
- adminmembers, roleSpecsToIds(adminmembers),
- InvalidOid, true);
AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- InvalidOid, false);
+ InvalidOid, &popt);
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+ popt.admin = true;
+ AddRoleMems(stmt->role, roleid,
+ adminmembers, roleSpecsToIds(adminmembers),
+ InvalidOid, &popt);
/* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
@@ -552,6 +573,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
+ GrantRoleOptions popt;
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
@@ -843,6 +865,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ InitGrantRoleOptions(&popt);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -856,11 +880,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (stmt->action == +1) /* add members to role */
AddRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- InvalidOid, false);
+ InvalidOid, &popt);
else if (stmt->action == -1) /* drop members from role */
DelRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers),
- InvalidOid, false, DROP_RESTRICT);
+ InvalidOid, &popt, DROP_RESTRICT);
}
/*
@@ -1337,13 +1361,48 @@ RenameRole(const char *oldname, const char *newname)
* Grant/Revoke roles to/from roles
*/
void
-GrantRole(GrantRoleStmt *stmt)
+GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
{
Relation pg_authid_rel;
Oid grantor;
List *grantee_ids;
ListCell *item;
+ GrantRoleOptions popt;
+
+ /* Parse options list. */
+ InitGrantRoleOptions(&popt);
+ foreach(item, stmt->opt)
+ {
+ DefElem *opt = (DefElem *) lfirst(item);
+ char *optval = defGetString(opt);
+
+ if (strcmp(opt->defname, "admin") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN;
+
+ if (parse_bool(optval, &popt.admin))
+ continue;
+ }
+ else if (strcmp(opt->defname, "inherit") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT;
+ if (parse_bool(optval, &popt.inherit))
+ continue;
+ }
+ else
+ ereport(ERROR,
+ errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized role option \"%s\"", opt->defname),
+ parser_errposition(pstate, opt->location));
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized value for role option \"%s\": \"%s\"",
+ opt->defname, optval),
+ parser_errposition(pstate, opt->location)));
+ }
+ /* Lookup OID of grantor, if specified. */
if (stmt->grantor)
grantor = get_rolespec_oid(stmt->grantor, false);
else
@@ -1355,11 +1414,11 @@ GrantRole(GrantRoleStmt *stmt)
pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock);
/*
- * Step through all of the granted roles and add/remove entries for the
- * grantees, or, if admin_opt is set, then just add/remove the admin
- * option.
- *
- * Note: Permissions checking is done by AddRoleMems/DelRoleMems
+ * Step through all of the granted roles and add, update, or remove
+ * entries in pg_auth_members as appropriate. If stmt->is_grant is true,
+ * we are adding new grants or, if they already exist, updating options
+ * on those grants. If stmt->is_grant is false, we are revoking grants or
+ * removing options from them.
*/
foreach(item, stmt->granted_roles)
{
@@ -1377,11 +1436,11 @@ GrantRole(GrantRoleStmt *stmt)
if (stmt->is_grant)
AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- grantor, stmt->admin_opt);
+ grantor, &popt);
else
DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids,
- grantor, stmt->admin_opt, stmt->behavior);
+ grantor, &popt, stmt->behavior);
}
/*
@@ -1483,12 +1542,12 @@ roleSpecsToIds(List *memberNames)
* memberSpecs: list of RoleSpec of roles to add (used only for error messages)
* memberIds: OIDs of roles to add
* grantorId: who is granting the membership (InvalidOid if not set explicitly)
- * admin_opt: granting admin option?
+ * popt: information about grant options
*/
static void
AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt)
+ Oid grantorId, GrantRoleOptions *popt)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1607,7 +1666,7 @@ AddRoleMems(const char *rolename, Oid roleid,
* has no other source of ADMIN OPTION on X, tries to give ADMIN OPTION on
* X back to A).
*/
- if (admin_opt && grantorId != BOOTSTRAP_SUPERUSERID)
+ if (popt->admin && grantorId != BOOTSTRAP_SUPERUSERID)
{
CatCList *memlist;
RevokeRoleGrantAction *actions;
@@ -1669,25 +1728,55 @@ AddRoleMems(const char *rolename, Oid roleid,
Datum new_record[Natts_pg_auth_members] = {0};
bool new_record_nulls[Natts_pg_auth_members] = {0};
bool new_record_repl[Natts_pg_auth_members] = {0};
- Form_pg_auth_members authmem_form;
- /*
- * Check if entry for this role/member already exists; if so, give
- * warning unless we are adding admin option.
- */
+ /* Common initialization for possible insert or update */
+ new_record[Anum_pg_auth_members_roleid - 1] =
+ ObjectIdGetDatum(roleid);
+ new_record[Anum_pg_auth_members_member - 1] =
+ ObjectIdGetDatum(memberid);
+ new_record[Anum_pg_auth_members_grantor - 1] =
+ ObjectIdGetDatum(grantorId);
+
+ /* Find any existing tuple */
authmem_tuple = SearchSysCache3(AUTHMEMROLEMEM,
ObjectIdGetDatum(roleid),
ObjectIdGetDatum(memberid),
ObjectIdGetDatum(grantorId));
- if (!HeapTupleIsValid(authmem_tuple))
- {
- authmem_form = NULL;
- }
- else
+
+ /*
+ * If we found a tuple, update it with new option values, unless
+ * there are no changes, in which case issue a WARNING.
+ *
+ * If we didn't find a tuple, just insert one.
+ */
+ if (HeapTupleIsValid(authmem_tuple))
{
+ Form_pg_auth_members authmem_form;
+ bool at_least_one_change = false;
+
authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple);
- if (!admin_opt || authmem_form->admin_option)
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0
+ && authmem_form->admin_option != popt->admin)
+ {
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(popt->admin);
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0
+ && authmem_form->inherit_option != popt->inherit)
+ {
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(popt->inherit);
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
+ if (!at_least_one_change)
{
ereport(NOTICE,
(errmsg("role \"%s\" has already been granted membership in role \"%s\" by role \"%s\"",
@@ -1696,17 +1785,7 @@ AddRoleMems(const char *rolename, Oid roleid,
ReleaseSysCache(authmem_tuple);
continue;
}
- }
- /* Build a tuple to insert or update */
- new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid);
- new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid);
- new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId);
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt);
-
- if (HeapTupleIsValid(authmem_tuple))
- {
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
new_record_nulls, new_record_repl);
@@ -1719,6 +1798,33 @@ AddRoleMems(const char *rolename, Oid roleid,
Oid objectId;
Oid *newmembers = palloc(sizeof(Oid));
+ /* Set admin option if user set it to true, otherwise not. */
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(popt->admin);
+
+ /*
+ * If the user specified a value for the inherit option, use
+ * whatever was specified. Otherwise, set the default value based
+ * on the role-level property.
+ */
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ popt->inherit;
+ else
+ {
+ HeapTuple mrtup;
+ Form_pg_authid mrform;
+
+ mrtup = SearchSysCache1(AUTHOID, memberid);
+ if (!HeapTupleIsValid(mrtup))
+ elog(ERROR, "cache lookup failed for role %u", memberid);
+ mrform = (Form_pg_authid) GETSTRUCT(mrtup);
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ mrform->rolinherit;
+ ReleaseSysCache(mrtup);
+ }
+
+ /* get an OID for the new row and insert it */
objectId = GetNewObjectId();
new_record[Anum_pg_auth_members_oid - 1] = objectId;
tuple = heap_form_tuple(pg_authmem_dsc,
@@ -1751,13 +1857,13 @@ AddRoleMems(const char *rolename, Oid roleid,
* memberSpecs: list of RoleSpec of roles to del (used only for error messages)
* memberIds: OIDs of roles to del
* grantorId: who is revoking the membership
- * admin_opt: remove admin option only?
+ * popt: information about grant options
* behavior: RESTRICT or CASCADE behavior for recursive removal
*/
static void
DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
- Oid grantorId, bool admin_opt, DropBehavior behavior)
+ Oid grantorId, GrantRoleOptions *popt, DropBehavior behavior)
{
Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc;
@@ -1824,7 +1930,7 @@ DelRoleMems(const char *rolename, Oid roleid,
Oid memberid = lfirst_oid(iditem);
if (!plan_single_revoke(memlist, actions, memberid, grantorId,
- admin_opt, behavior))
+ popt, behavior))
{
ereport(WARNING,
(errmsg("role \"%s\" has not been granted membership in role \"%s\" by role \"%s\"",
@@ -1862,15 +1968,29 @@ DelRoleMems(const char *rolename, Oid roleid,
}
else
{
- /* Just turn off the admin option */
+ /* Just turn off the specified option */
HeapTuple tuple;
Datum new_record[Natts_pg_auth_members] = {0};
bool new_record_nulls[Natts_pg_auth_members] = {0};
bool new_record_repl[Natts_pg_auth_members] = {0};
/* Build a tuple to update with */
- new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false);
- new_record_repl[Anum_pg_auth_members_admin_option - 1] = true;
+ if (actions[i] == RRG_REMOVE_ADMIN_OPTION)
+ {
+ new_record[Anum_pg_auth_members_admin_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_admin_option - 1] =
+ true;
+ }
+ else if (actions[i] == RRG_REMOVE_INHERIT_OPTION)
+ {
+ new_record[Anum_pg_auth_members_inherit_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
+ true;
+ }
+ else
+ elog(ERROR, "unknown role revoke action");
tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record,
@@ -2028,11 +2148,21 @@ initialize_revoke_actions(CatCList *memlist)
*/
static bool
plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
- Oid member, Oid grantor, bool revoke_admin_option_only,
+ Oid member, Oid grantor, GrantRoleOptions *popt,
DropBehavior behavior)
{
int i;
+ /*
+ * If popt.specified == 0, we're revoking the grant entirely; otherwise,
+ * we expect just one bit to be set, and we're revoking the corresponding
+ * option. As of this writing, there's no syntax that would allow for
+ * an attempt to revoke multiple options at once, and the logic below
+ * wouldn't work properly if such syntax were added, so assert that our
+ * caller isn't trying to do that.
+ */
+ Assert(pg_popcount32(popt->specified) <= 1);
+
for (i = 0; i < memlist->n_members; ++i)
{
HeapTuple authmem_tuple;
@@ -2044,8 +2174,27 @@ plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
if (authmem_form->member == member &&
authmem_form->grantor == grantor)
{
- plan_recursive_revoke(memlist, actions, i,
- revoke_admin_option_only, behavior);
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
+ {
+ /*
+ * Revoking the INHERIT option doesn't change anything for
+ * dependent privileges, so we don't need to recurse.
+ */
+ actions[i] = RRG_REMOVE_INHERIT_OPTION;
+ }
+ else
+ {
+ bool revoke_admin_option_only;
+
+ /*
+ * Revoking the grant entirely, or ADMIN option on a grant,
+ * implicates dependent privileges, so we may need to recurse.
+ */
+ revoke_admin_option_only =
+ (popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0;
+ plan_recursive_revoke(memlist, actions, i,
+ revoke_admin_option_only, behavior);
+ }
return true;
}
}
@@ -2172,3 +2321,14 @@ plan_recursive_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
}
}
}
+
+/*
+ * Initialize a GrantRoleOptions object with default values.
+ */
+static void
+InitGrantRoleOptions(GrantRoleOptions *popt)
+{
+ popt->specified = 0;
+ popt->admin = false;
+ popt->inherit = false;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c8bd66dd54..b5ab9d9c9a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -362,9 +362,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> utility_option_arg
%type <defelt> drop_option
%type <boolean> opt_or_replace opt_no
- opt_grant_grant_option opt_grant_admin_option
+ opt_grant_grant_option
opt_nowait opt_if_exists opt_with_data
opt_transaction_chain
+%type <list> grant_role_opt_list
+%type <defelt> grant_role_opt
+%type <node> grant_role_opt_value
%type <ival> opt_nowait_or_skip
%type <list> OptRoleList AlterOptRoleList
@@ -7848,15 +7851,26 @@ opt_grant_grant_option:
*****************************************************************************/
GrantRoleStmt:
- GRANT privilege_list TO role_list opt_grant_admin_option opt_granted_by
+ GRANT privilege_list TO role_list opt_granted_by
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = true;
n->granted_roles = $2;
n->grantee_roles = $4;
- n->admin_opt = $5;
- n->grantor = $6;
+ n->opt = NIL;
+ n->grantor = $5;
+ $$ = (Node *) n;
+ }
+ | GRANT privilege_list TO role_list WITH grant_role_opt_list opt_granted_by
+ {
+ GrantRoleStmt *n = makeNode(GrantRoleStmt);
+
+ n->is_grant = true;
+ n->granted_roles = $2;
+ n->grantee_roles = $4;
+ n->opt = $6;
+ n->grantor = $7;
$$ = (Node *) n;
}
;
@@ -7867,19 +7881,22 @@ RevokeRoleStmt:
GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false;
- n->admin_opt = false;
+ n->opt = NIL;
n->granted_roles = $2;
n->grantee_roles = $4;
n->grantor = $5;
n->behavior = $6;
$$ = (Node *) n;
}
- | REVOKE ADMIN OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
+ | REVOKE ColId OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior
{
GrantRoleStmt *n = makeNode(GrantRoleStmt);
+ DefElem *opt;
+ opt = makeDefElem(pstrdup($2),
+ (Node *) makeBoolean(false), @2);
n->is_grant = false;
- n->admin_opt = true;
+ n->opt = list_make1(opt);
n->granted_roles = $5;
n->grantee_roles = $7;
n->grantor = $8;
@@ -7888,8 +7905,22 @@ RevokeRoleStmt:
}
;
-opt_grant_admin_option: WITH ADMIN OPTION { $$ = true; }
- | /*EMPTY*/ { $$ = false; }
+grant_role_opt_list:
+ grant_role_opt_list ',' grant_role_opt { $$ = lappend($1, $3); }
+ | grant_role_opt { $$ = list_make1($1); }
+ ;
+
+grant_role_opt:
+ ColLabel grant_role_opt_value
+ {
+ $$ = makeDefElem(pstrdup($1), $2, @1);
+ }
+ ;
+
+grant_role_opt_value:
+ OPTION { $$ = (Node *) makeBoolean(true); }
+ | TRUE_P { $$ = (Node *) makeBoolean(true); }
+ | FALSE_P { $$ = (Node *) makeBoolean(false); }
;
opt_granted_by: GRANTED BY RoleSpec { $$ = $3; }
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6b0a865262..aa00815787 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -767,7 +767,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_GrantRoleStmt:
/* no event triggers for global objects */
- GrantRole((GrantRoleStmt *) parsetree);
+ GrantRole(pstate, (GrantRoleStmt *) parsetree);
break;
case T_CreatedbStmt:
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 3e045da31f..3e1058e2d1 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -66,7 +66,7 @@ typedef struct
*/
enum RoleRecurseType
{
- ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */
+ ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */
ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */
};
static Oid cached_role[] = {InvalidOid, InvalidOid};
@@ -4735,8 +4735,8 @@ initialize_acl(void)
/*
* In normal mode, set a callback on any syscache invalidation of rows
- * of pg_auth_members (for roles_is_member_of()), pg_authid (for
- * has_rolinherit()), or pg_database (for roles_is_member_of())
+ * of pg_auth_members (for roles_is_member_of()) pg_database (for
+ * roles_is_member_of())
*/
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
RoleMembershipCacheCallback,
@@ -4769,29 +4769,11 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
cached_role[ROLERECURSE_MEMBERS] = InvalidOid;
}
-
-/* Check if specified role has rolinherit set */
-static bool
-has_rolinherit(Oid roleid)
-{
- bool result = false;
- HeapTuple utup;
-
- utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
- if (HeapTupleIsValid(utup))
- {
- result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
- ReleaseSysCache(utup);
- }
- return result;
-}
-
-
/*
* Get a list of roles that the specified roleid is a member of
*
- * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit
- * set, while ROLERECURSE_MEMBERS recurses through all roles.
+ * Type ROLERECURSE_PRIVS recurses only through inheritable grants,
+ * while ROLERECURSE_MEMBERS recurses through all grants.
*
* Since indirect membership testing is relatively expensive, we cache
* a list of memberships. Hence, the result is only guaranteed good until
@@ -4861,23 +4843,32 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
CatCList *memlist;
int i;
- if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
- continue; /* ignore non-inheriting roles */
-
/* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
ObjectIdGetDatum(memberid));
for (i = 0; i < memlist->n_members; i++)
{
HeapTuple tup = &memlist->members[i]->tuple;
- Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
+ Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
+ Oid otherid = form->roleid;
+
+ /*
+ * If we're supposed to ignore non-heritable grants, do so.
+ *
+ * Any given GRANT might be WITH INHERIT FALSE, in which case it
+ * is not inherited regardless of anything else, or it might be
+ * WITH INHERIT TRUE, in which case it definitely is inherited,
+ * or it might be WITH INHERIT DEFAULT, in which case it depends
+ * on whether the role is INHERIT or NOINHERIT.
+ */
+ if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+ continue;
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
*/
- if (otherid == admin_of &&
- ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
+ if (otherid == admin_of && form->admin_option &&
OidIsValid(admin_of) && !OidIsValid(*admin_role))
*admin_role = memberid;
@@ -4920,8 +4911,8 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
/*
* Does member have the privileges of role (directly or indirectly)?
*
- * This is defined not to recurse through roles that don't have rolinherit
- * set; for such roles, membership implies the ability to do SET ROLE, but
+ * This is defined not to recurse through grants that are not inherited;
+ * in such cases, membership implies the ability to do SET ROLE, but
* the privileges are not available until you've done so.
*/
bool
@@ -4948,7 +4939,7 @@ has_privs_of_role(Oid member, Oid role)
/*
* Is member a member of role (directly or indirectly)?
*
- * This is defined to recurse through roles regardless of rolinherit.
+ * This is defined to recurse through grants whether they are inherited or not.
*
* Do not use this for privilege checking, instead use has_privs_of_role()
*/
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index e8a2bfa6bd..68c455f84b 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -947,11 +947,14 @@ static void
dumpRoleMembership(PGconn *conn)
{
PQExpBuffer buf = createPQExpBuffer();
+ PQExpBuffer optbuf = createPQExpBuffer();
PGresult *res;
int start = 0,
end,
total;
bool dump_grantors;
+ bool dump_inherit_option;
+ int i_inherit_option;
/*
* Previous versions of PostgreSQL didn't used to track the grantor very
@@ -962,19 +965,28 @@ dumpRoleMembership(PGconn *conn)
*/
dump_grantors = (PQserverVersion(conn) >= 160000);
+ /*
+ * Previous versions of PostgreSQL also did not have a grant-level
+ * INHERIT option.
+ */
+ dump_inherit_option = (server_version >= 160000);
+
/* Generate and execute query. */
printfPQExpBuffer(buf, "SELECT ur.rolname AS role, "
"um.rolname AS member, "
"ug.oid AS grantorid, "
"ug.rolname AS grantor, "
- "a.admin_option "
- "FROM pg_auth_members a "
+ "a.admin_option");
+ if (dump_inherit_option)
+ appendPQExpBuffer(buf, ", a.inherit_option");
+ appendPQExpBuffer(buf, " FROM pg_auth_members a "
"LEFT JOIN %s ur on ur.oid = a.roleid "
"LEFT JOIN %s um on um.oid = a.member "
"LEFT JOIN %s ug on ug.oid = a.grantor "
"WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')"
"ORDER BY 1,2,4", role_catalog, role_catalog, role_catalog);
res = executeQuery(conn, buf->data);
+ i_inherit_option = PQfnumber(res, "inherit_option");
if (PQntuples(res) > 0)
fprintf(OPF, "--\n-- Role memberships\n--\n\n");
@@ -1079,10 +1091,24 @@ dumpRoleMembership(PGconn *conn)
rolename_insert(ht, member, &found);
/* Generate the actual GRANT statement. */
+ resetPQExpBuffer(optbuf);
fprintf(OPF, "GRANT %s", fmtId(role));
fprintf(OPF, " TO %s", fmtId(member));
if (*admin_option == 't')
- fprintf(OPF, " WITH ADMIN OPTION");
+ appendPQExpBufferStr(optbuf, "ADMIN OPTION");
+ if (dump_inherit_option)
+ {
+ char *inherit_option;
+
+ if (optbuf->data[0] != '\0')
+ appendPQExpBufferStr(optbuf, ", ");
+ inherit_option = PQgetvalue(res, i, i_inherit_option);
+ appendPQExpBuffer(optbuf, "INHERIT %s",
+ *inherit_option == 't' ?
+ "TRUE" : "FALSE");
+ }
+ if (optbuf->data[0] != '\0')
+ fprintf(OPF, " WITH %s", optbuf->data);
if (dump_grantors)
fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
fprintf(OPF, ";\n");
diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h
index e57ec4f810..3ee6ae5f6a 100644
--- a/src/include/catalog/pg_auth_members.h
+++ b/src/include/catalog/pg_auth_members.h
@@ -34,6 +34,7 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_
Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */
Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */
bool admin_option; /* granted with admin option? */
+ bool inherit_option; /* exercise privileges without SET ROLE? */
} FormData_pg_auth_members;
/* ----------------
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index d3dd8303d2..54c720d880 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -28,7 +28,7 @@ extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
extern void DropRole(DropRoleStmt *stmt);
-extern void GrantRole(GrantRoleStmt *stmt);
+extern void GrantRole(ParseState *pstate, GrantRoleStmt *stmt);
extern ObjectAddress RenameRole(const char *oldname, const char *newname);
extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b376031856..469a5c46f6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2451,7 +2451,7 @@ typedef struct GrantRoleStmt
List *granted_roles; /* list of roles to be granted/revoked */
List *grantee_roles; /* list of member roles to add/delete */
bool is_grant; /* true = GRANT, false = REVOKE */
- bool admin_opt; /* with admin option */
+ List *opt; /* options e.g. WITH GRANT OPTION */
RoleSpec *grantor; /* set grantor to other than current role */
DropBehavior behavior; /* drop behavior (for REVOKE) */
} GrantRoleStmt;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 0154a09262..527c9d30a0 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -517,7 +517,19 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- ok because grant-level option is unchanged
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail
+ERROR: permission denied for table atest3
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- also fail
ERROR: permission denied for table atest3
ROLLBACK;
-- views
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index b4ef20f738..5dae42c2a9 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -351,7 +351,18 @@ BEGIN;
RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1;
-DELETE FROM atest3;
+SAVEPOINT s1;
+DELETE FROM atest3; -- ok because grant-level option is unchanged
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- fail
+ROLLBACK TO s1;
+RESET SESSION AUTHORIZATION;
+REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1;
+SET SESSION AUTHORIZATION regress_priv_user1;
+DELETE FROM atest3; -- also fail
ROLLBACK;
-- views
--
2.24.3 (Apple Git-128)
On 8/24/22 12:28 AM, Robert Haas wrote:
This patch needed to be rebased pretty extensively after commit
ce6b672e4455820a0348214be0da1a024c3f619f. Here is a new version.
Thanks, Robert, I have retested this patch with my previous scenarios
and things look good to me.
--
regards,tushar
EnterpriseDB https://www.enterprisedb.com/
The Enterprise PostgreSQL Company
On Wed, Aug 24, 2022 at 10:23 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
On 8/24/22 12:28 AM, Robert Haas wrote:
This patch needed to be rebased pretty extensively after commit
ce6b672e4455820a0348214be0da1a024c3f619f. Here is a new version.Thanks, Robert, I have retested this patch with my previous scenarios
and things look good to me.
I read through this again and found a comment that needed to be
updated, so I did that, bumped catversion, and committed this.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Thu, Aug 25, 2022 at 10:19 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Wed, Aug 24, 2022 at 10:23 AM tushar <tushar.ahuja@enterprisedb.com> wrote:
On 8/24/22 12:28 AM, Robert Haas wrote:
This patch needed to be rebased pretty extensively after commit
ce6b672e4455820a0348214be0da1a024c3f619f. Here is a new version.Thanks, Robert, I have retested this patch with my previous scenarios
and things look good to me.I read through this again and found a comment that needed to be
updated, so I did that, bumped catversion, and committed this.
Upon further review, this patch failed to update the documentation as
thoroughly as would have been desirable.
Here is a follow-up patch to correct a few oversights.
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v1-0001-docs-Fix-up-some-out-of-date-references-to-INHERI.patchapplication/octet-stream; name=v1-0001-docs-Fix-up-some-out-of-date-references-to-INHERI.patchDownload
From f226d7272d90e4f3ea5cad3611eaed620f67949d Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 26 Aug 2022 14:42:32 -0400
Subject: [PATCH v1] docs: Fix up some out-of-date references to
INHERIT/NOINHERIT.
Commit e3ce2de09d814f8770b2e3b3c152b7671bcdb83f should have updated
these sections of the documentation, but failed to do so.
---
doc/src/sgml/ref/set_role.sgml | 19 ++++++++++---------
doc/src/sgml/user-manag.sgml | 28 ++++++++++++++++------------
2 files changed, 26 insertions(+), 21 deletions(-)
diff --git a/doc/src/sgml/ref/set_role.sgml b/doc/src/sgml/ref/set_role.sgml
index 4e02322158..deecfe4120 100644
--- a/doc/src/sgml/ref/set_role.sgml
+++ b/doc/src/sgml/ref/set_role.sgml
@@ -71,15 +71,16 @@ RESET ROLE
<para>
Using this command, it is possible to either add privileges or restrict
- one's privileges. If the session user role has the <literal>INHERIT</literal>
- attribute, then it automatically has all the privileges of every role that
- it could <command>SET ROLE</command> to; in this case <command>SET ROLE</command>
- effectively drops all the privileges assigned directly to the session user
- and to the other roles it is a member of, leaving only the privileges
- available to the named role. On the other hand, if the session user role
- has the <literal>NOINHERIT</literal> attribute, <command>SET ROLE</command> drops the
- privileges assigned directly to the session user and instead acquires the
- privileges available to the named role.
+ one's privileges. If the session user role has been granted memberships
+ <literal>WITH INHERIT TRUE</literal>, it automatically has all the
+ privileges of every such role. In this case, <command>SET ROLE</command>
+ effectively drops all the privileges except for those which the target role
+ directly possesses or inherits. On the other hand, if the session user role
+ has been granted memberships <literal>WITH INHERIT FALSE</literal>, the
+ privileges of the granted roles can't be accessed by default. However, the
+ session user can use <command>SET ROLE</command> to drop the privileges
+ assigned directly to the session user and instead acquire the privileges
+ available to the named role.
</para>
<para>
diff --git a/doc/src/sgml/user-manag.sgml b/doc/src/sgml/user-manag.sgml
index 54cb253d95..709ed66b93 100644
--- a/doc/src/sgml/user-manag.sgml
+++ b/doc/src/sgml/user-manag.sgml
@@ -241,9 +241,12 @@ CREATE USER <replaceable>name</replaceable>;
<term>inheritance of privileges<indexterm><primary>role</primary><secondary>privilege to inherit</secondary></indexterm></term>
<listitem>
<para>
- A role is given permission to inherit the privileges of roles it is a
- member of, by default. However, to create a role without the permission,
- use <literal>CREATE ROLE <replaceable>name</replaceable> NOINHERIT</literal>.
+ A role inherits the privileges of roles it is a member of, by default.
+ However, to create a role which does not inherit privileges by
+ default, use <literal>CREATE ROLE <replaceable>name</replaceable>
+ NOINHERIT</literal>. Alternatively, inheritance can be overriden
+ for individual grants by using <literal>WITH INHERIT TRUE</literal>
+ or <literal>WITH INHERIT FALSE</literal>.
</para>
</listitem>
</varlistentry>
@@ -357,16 +360,17 @@ REVOKE <replaceable>group_role</replaceable> FROM <replaceable>role1</replaceabl
database session has access to the privileges of the group role rather
than the original login role, and any database objects created are
considered owned by the group role not the login role. Second, member
- roles that have the <literal>INHERIT</literal> attribute automatically have use
- of the privileges of roles of which they are members, including any
+ roles that have the been granted membership with the
+ <literal>INHERIT</literal> option automatically have use
+ of the privileges of those roles, including any
privileges inherited by those roles.
As an example, suppose we have done:
<programlisting>
-CREATE ROLE joe LOGIN INHERIT;
-CREATE ROLE admin NOINHERIT;
-CREATE ROLE wheel NOINHERIT;
-GRANT admin TO joe;
-GRANT wheel TO admin;
+CREATE ROLE joe LOGIN;
+CREATE ROLE admin;
+CREATE ROLE wheel;
+GRANT admin TO joe WITH INHERIT TRUE;
+GRANT wheel TO admin WITH INHERIT FALSE;
</programlisting>
Immediately after connecting as role <literal>joe</literal>, a database
session will have use of privileges granted directly to <literal>joe</literal>
@@ -374,8 +378,8 @@ GRANT wheel TO admin;
<quote>inherits</quote> <literal>admin</literal>'s privileges. However, privileges
granted to <literal>wheel</literal> are not available, because even though
<literal>joe</literal> is indirectly a member of <literal>wheel</literal>, the
- membership is via <literal>admin</literal> which has the <literal>NOINHERIT</literal>
- attribute. After:
+ membership is via <literal>admin</literal> which was granted using
+ <literal>WITH INHERIT FALSE</literal> After:
<programlisting>
SET ROLE admin;
</programlisting>
--
2.24.3 (Apple Git-128)
On Fri, Aug 26, 2022 at 02:46:59PM -0400, Robert Haas wrote:
- membership is via <literal>admin</literal> which has the <literal>NOINHERIT</literal> - attribute. After: + membership is via <literal>admin</literal> which was granted using + <literal>WITH INHERIT FALSE</literal> After:
nitpick: I believe there should be a period before "After."
Otherwise, LGTM.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Sun, Aug 28, 2022 at 5:14 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
nitpick: I believe there should be a period before "After."
Otherwise, LGTM.
Good catch. Thanks for the review. Committed with that correction.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Aug 29, 2022 at 10:17 AM Robert Haas <robertmhaas@gmail.com> wrote:
Good catch. Thanks for the review. Committed with that correction.
Argh, I found a bug, and one that I should have caught during testing,
too. I modelled the new function select_best_grantor() on
is_admin_of_role(), but it differs in that it calls
roles_is_member_of() with ROLERECURSE_PRIVS rather than
ROLECURSE_MEMBERS. Sadly, roles_is_member_of() handles
ROLERECURSE_PRIVS by completely ignoring non-inherited grants, which
is wrong, because then calls to select_best_grantor() treat a member
of a role with INHERIT FALSE, ADMIN TRUE is if they were not an admin
at all, which is incorrect.
Here is a patch to rearrange the logic slightly and also add a test
case memorializing the intended behavior. Without this change, the
regression test included in the patch fails like this:
ERROR: no possible grantors
...which is never supposed to happen.
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v1-0001-Fix-a-bug-in-roles_is_member_of.patchapplication/octet-stream; name=v1-0001-Fix-a-bug-in-roles_is_member_of.patchDownload
From 069f83d61a2ebe56c4b1305976b914af5ff538ad Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Mon, 29 Aug 2022 15:28:45 -0400
Subject: [PATCH v1] Fix a bug in roles_is_member_of.
Commit e3ce2de09d814f8770b2e3b3c152b7671bcdb83f rearranged this
function to be able to identify which inherited role had admin option
on the target role, but it got the order of operations wrong, causing
the function to return wrong answers in the presence of non-inherited
grants.
Fix that, and add a test case that verifies the correct behavior.
---
src/backend/utils/adt/acl.c | 8 +++---
src/test/regress/expected/privileges.out | 32 ++++++++++++++++++++++++
src/test/regress/sql/privileges.sql | 25 ++++++++++++++++++
3 files changed, 61 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index ea28da26a8..fd71a9b13e 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4852,10 +4852,6 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
Oid otherid = form->roleid;
- /* If we're supposed to ignore non-heritable grants, do so. */
- if (type == ROLERECURSE_PRIVS && !form->inherit_option)
- continue;
-
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
@@ -4864,6 +4860,10 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
OidIsValid(admin_of) && !OidIsValid(*admin_role))
*admin_role = memberid;
+ /* If we're supposed to ignore non-heritable grants, do so. */
+ if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+ continue;
+
/*
* Even though there shouldn't be any loops in the membership
* graph, we must test for having already seen this role. It is
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 527c9d30a0..d52f9f238e 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2777,3 +2777,35 @@ SELECT COUNT(*) >= 0 AS ok FROM pg_shmem_allocations;
RESET ROLE;
-- clean up
DROP ROLE regress_readallstats;
+-- test role grantor machinery
+CREATE ROLE regress_group;
+CREATE ROLE regress_group_direct_manager;
+CREATE ROLE regress_group_indirect_manager;
+CREATE ROLE regress_group_member;
+GRANT regress_group TO regress_group_direct_manager WITH INHERIT FALSE, ADMIN TRUE;
+GRANT regress_group_direct_manager TO regress_group_indirect_manager;
+SET SESSION AUTHORIZATION regress_group_direct_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, grantor::regrole::text FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+ member | grantor
+------------------------------+------------------------------
+ regress_group_direct_manager | rhaas
+ regress_group_member | regress_group_direct_manager
+(2 rows)
+
+REVOKE regress_group FROM regress_group_member;
+SET SESSION AUTHORIZATION regress_group_indirect_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, grantor::regrole::text FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+ member | grantor
+------------------------------+------------------------------
+ regress_group_direct_manager | rhaas
+ regress_group_member | regress_group_direct_manager
+(2 rows)
+
+REVOKE regress_group FROM regress_group_member;
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_group;
+DROP ROLE regress_group_direct_manager;
+DROP ROLE regress_group_indirect_manager;
+DROP ROLE regress_group_member;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 5dae42c2a9..679001a552 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1788,3 +1788,28 @@ RESET ROLE;
-- clean up
DROP ROLE regress_readallstats;
+
+-- test role grantor machinery
+CREATE ROLE regress_group;
+CREATE ROLE regress_group_direct_manager;
+CREATE ROLE regress_group_indirect_manager;
+CREATE ROLE regress_group_member;
+
+GRANT regress_group TO regress_group_direct_manager WITH INHERIT FALSE, ADMIN TRUE;
+GRANT regress_group_direct_manager TO regress_group_indirect_manager;
+
+SET SESSION AUTHORIZATION regress_group_direct_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, grantor::regrole::text FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+REVOKE regress_group FROM regress_group_member;
+
+SET SESSION AUTHORIZATION regress_group_indirect_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, grantor::regrole::text FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+REVOKE regress_group FROM regress_group_member;
+
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_group;
+DROP ROLE regress_group_direct_manager;
+DROP ROLE regress_group_indirect_manager;
+DROP ROLE regress_group_member;
--
2.24.3 (Apple Git-128)
On Mon, Aug 29, 2022 at 03:38:57PM -0400, Robert Haas wrote:
Here is a patch to rearrange the logic slightly and also add a test
case memorializing the intended behavior. Without this change, the
regression test included in the patch fails like this:ERROR: no possible grantors
...which is never supposed to happen.
The grantor column in the expected test output refers to "rhaas", so I
imagine the test only passes on your machines. Should we create a new
grantor role for this test?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Aug 29, 2022 at 6:04 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Mon, Aug 29, 2022 at 03:38:57PM -0400, Robert Haas wrote:
Here is a patch to rearrange the logic slightly and also add a test
case memorializing the intended behavior. Without this change, the
regression test included in the patch fails like this:ERROR: no possible grantors
...which is never supposed to happen.
The grantor column in the expected test output refers to "rhaas", so I
imagine the test only passes on your machines. Should we create a new
grantor role for this test?
That's the second time I've made that exact mistake *on this thread*.
I'm super good at this, I promise.
I'll figure out a fix tomorrow when I'm less tired. Thanks for catching it.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Mon, Aug 29, 2022 at 10:16 PM Robert Haas <robertmhaas@gmail.com> wrote:
I'll figure out a fix tomorrow when I'm less tired. Thanks for catching it.
Second try.
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v2-0001-Fix-a-bug-in-roles_is_member_of.patchapplication/octet-stream; name=v2-0001-Fix-a-bug-in-roles_is_member_of.patchDownload
From d10e28587ad367a29ecd12cf6ecb01eacf714704 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 30 Aug 2022 08:20:23 -0400
Subject: [PATCH v2] Fix a bug in roles_is_member_of.
Commit e3ce2de09d814f8770b2e3b3c152b7671bcdb83f rearranged this
function to be able to identify which inherited role had admin option
on the target role, but it got the order of operations wrong, causing
the function to return wrong answers in the presence of non-inherited
grants.
Fix that, and add a test case that verifies the correct behavior.
---
src/backend/utils/adt/acl.c | 8 +++---
src/test/regress/expected/privileges.out | 32 ++++++++++++++++++++++++
src/test/regress/sql/privileges.sql | 25 ++++++++++++++++++
3 files changed, 61 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index ea28da26a8..fd71a9b13e 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4852,10 +4852,6 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
Oid otherid = form->roleid;
- /* If we're supposed to ignore non-heritable grants, do so. */
- if (type == ROLERECURSE_PRIVS && !form->inherit_option)
- continue;
-
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
@@ -4864,6 +4860,10 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
OidIsValid(admin_of) && !OidIsValid(*admin_role))
*admin_role = memberid;
+ /* If we're supposed to ignore non-heritable grants, do so. */
+ if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+ continue;
+
/*
* Even though there shouldn't be any loops in the membership
* graph, we must test for having already seen this role. It is
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 527c9d30a0..d52f9f238e 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2777,3 +2777,35 @@ SELECT COUNT(*) >= 0 AS ok FROM pg_shmem_allocations;
RESET ROLE;
-- clean up
DROP ROLE regress_readallstats;
+-- test role grantor machinery
+CREATE ROLE regress_group;
+CREATE ROLE regress_group_direct_manager;
+CREATE ROLE regress_group_indirect_manager;
+CREATE ROLE regress_group_member;
+GRANT regress_group TO regress_group_direct_manager WITH INHERIT FALSE, ADMIN TRUE;
+GRANT regress_group_direct_manager TO regress_group_indirect_manager;
+SET SESSION AUTHORIZATION regress_group_direct_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, grantor::regrole::text FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+ member | grantor
+------------------------------+------------------------------
+ regress_group_direct_manager | rhaas
+ regress_group_member | regress_group_direct_manager
+(2 rows)
+
+REVOKE regress_group FROM regress_group_member;
+SET SESSION AUTHORIZATION regress_group_indirect_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, grantor::regrole::text FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+ member | grantor
+------------------------------+------------------------------
+ regress_group_direct_manager | rhaas
+ regress_group_member | regress_group_direct_manager
+(2 rows)
+
+REVOKE regress_group FROM regress_group_member;
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_group;
+DROP ROLE regress_group_direct_manager;
+DROP ROLE regress_group_indirect_manager;
+DROP ROLE regress_group_member;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 5dae42c2a9..4ad366470d 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1788,3 +1788,28 @@ RESET ROLE;
-- clean up
DROP ROLE regress_readallstats;
+
+-- test role grantor machinery
+CREATE ROLE regress_group;
+CREATE ROLE regress_group_direct_manager;
+CREATE ROLE regress_group_indirect_manager;
+CREATE ROLE regress_group_member;
+
+GRANT regress_group TO regress_group_direct_manager WITH INHERIT FALSE, ADMIN TRUE;
+GRANT regress_group_direct_manager TO regress_group_indirect_manager;
+
+SET SESSION AUTHORIZATION regress_group_direct_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, CASE WHEN grantor = 10 THEN 'BOOTSTRAP SUPERUSER' ELSE grantor::regrole::text END FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+REVOKE regress_group FROM regress_group_member;
+
+SET SESSION AUTHORIZATION regress_group_indirect_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, CASE WHEN grantor = 10 THEN 'BOOTSTRAP SUPERUSER' ELSE grantor::regrole::text END FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+REVOKE regress_group FROM regress_group_member;
+
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_group;
+DROP ROLE regress_group_direct_manager;
+DROP ROLE regress_group_indirect_manager;
+DROP ROLE regress_group_member;
--
2.24.3 (Apple Git-128)
On Tue, Aug 30, 2022 at 8:24 AM Robert Haas <robertmhaas@gmail.com> wrote:
On Mon, Aug 29, 2022 at 10:16 PM Robert Haas <robertmhaas@gmail.com> wrote:
I'll figure out a fix tomorrow when I'm less tired. Thanks for catching it.
Second try.
Third try.
--
Robert Haas
EDB: http://www.enterprisedb.com
Attachments:
v3-0001-Fix-a-bug-in-roles_is_member_of.patchapplication/octet-stream; name=v3-0001-Fix-a-bug-in-roles_is_member_of.patchDownload
From c6f422c69945aca8e8cde9a4dcc5dd0d981de70e Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 30 Aug 2022 08:32:35 -0400
Subject: [PATCH v3] Fix a bug in roles_is_member_of.
Commit e3ce2de09d814f8770b2e3b3c152b7671bcdb83f rearranged this
function to be able to identify which inherited role had admin option
on the target role, but it got the order of operations wrong, causing
the function to return wrong answers in the presence of non-inherited
grants.
Fix that, and add a test case that verifies the correct behavior.
---
src/backend/utils/adt/acl.c | 8 +++---
src/test/regress/expected/privileges.out | 32 ++++++++++++++++++++++++
src/test/regress/sql/privileges.sql | 25 ++++++++++++++++++
3 files changed, 61 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index ea28da26a8..fd71a9b13e 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -4852,10 +4852,6 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup);
Oid otherid = form->roleid;
- /* If we're supposed to ignore non-heritable grants, do so. */
- if (type == ROLERECURSE_PRIVS && !form->inherit_option)
- continue;
-
/*
* While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises.
@@ -4864,6 +4860,10 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
OidIsValid(admin_of) && !OidIsValid(*admin_role))
*admin_role = memberid;
+ /* If we're supposed to ignore non-heritable grants, do so. */
+ if (type == ROLERECURSE_PRIVS && !form->inherit_option)
+ continue;
+
/*
* Even though there shouldn't be any loops in the membership
* graph, we must test for having already seen this role. It is
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 527c9d30a0..bd3453ee91 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2777,3 +2777,35 @@ SELECT COUNT(*) >= 0 AS ok FROM pg_shmem_allocations;
RESET ROLE;
-- clean up
DROP ROLE regress_readallstats;
+-- test role grantor machinery
+CREATE ROLE regress_group;
+CREATE ROLE regress_group_direct_manager;
+CREATE ROLE regress_group_indirect_manager;
+CREATE ROLE regress_group_member;
+GRANT regress_group TO regress_group_direct_manager WITH INHERIT FALSE, ADMIN TRUE;
+GRANT regress_group_direct_manager TO regress_group_indirect_manager;
+SET SESSION AUTHORIZATION regress_group_direct_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, CASE WHEN grantor = 10 THEN 'BOOTSTRAP SUPERUSER' ELSE grantor::regrole::text END FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+ member | grantor
+------------------------------+------------------------------
+ regress_group_direct_manager | BOOTSTRAP SUPERUSER
+ regress_group_member | regress_group_direct_manager
+(2 rows)
+
+REVOKE regress_group FROM regress_group_member;
+SET SESSION AUTHORIZATION regress_group_indirect_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, CASE WHEN grantor = 10 THEN 'BOOTSTRAP SUPERUSER' ELSE grantor::regrole::text END FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+ member | grantor
+------------------------------+------------------------------
+ regress_group_direct_manager | BOOTSTRAP SUPERUSER
+ regress_group_member | regress_group_direct_manager
+(2 rows)
+
+REVOKE regress_group FROM regress_group_member;
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_group;
+DROP ROLE regress_group_direct_manager;
+DROP ROLE regress_group_indirect_manager;
+DROP ROLE regress_group_member;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 5dae42c2a9..4ad366470d 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1788,3 +1788,28 @@ RESET ROLE;
-- clean up
DROP ROLE regress_readallstats;
+
+-- test role grantor machinery
+CREATE ROLE regress_group;
+CREATE ROLE regress_group_direct_manager;
+CREATE ROLE regress_group_indirect_manager;
+CREATE ROLE regress_group_member;
+
+GRANT regress_group TO regress_group_direct_manager WITH INHERIT FALSE, ADMIN TRUE;
+GRANT regress_group_direct_manager TO regress_group_indirect_manager;
+
+SET SESSION AUTHORIZATION regress_group_direct_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, CASE WHEN grantor = 10 THEN 'BOOTSTRAP SUPERUSER' ELSE grantor::regrole::text END FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+REVOKE regress_group FROM regress_group_member;
+
+SET SESSION AUTHORIZATION regress_group_indirect_manager;
+GRANT regress_group TO regress_group_member;
+SELECT member::regrole::text, CASE WHEN grantor = 10 THEN 'BOOTSTRAP SUPERUSER' ELSE grantor::regrole::text END FROM pg_auth_members WHERE roleid = 'regress_group'::regrole ORDER BY 1, 2;
+REVOKE regress_group FROM regress_group_member;
+
+RESET SESSION AUTHORIZATION;
+DROP ROLE regress_group;
+DROP ROLE regress_group_direct_manager;
+DROP ROLE regress_group_indirect_manager;
+DROP ROLE regress_group_member;
--
2.24.3 (Apple Git-128)
On Mon, Aug 29, 2022 at 03:38:57PM -0400, Robert Haas wrote:
Argh, I found a bug, and one that I should have caught during testing,
too. I modelled the new function select_best_grantor() on
is_admin_of_role(), but it differs in that it calls
roles_is_member_of() with ROLERECURSE_PRIVS rather than
ROLECURSE_MEMBERS. Sadly, roles_is_member_of() handles
ROLERECURSE_PRIVS by completely ignoring non-inherited grants, which
is wrong, because then calls to select_best_grantor() treat a member
of a role with INHERIT FALSE, ADMIN TRUE is if they were not an admin
at all, which is incorrect.
I've been staring at this stuff for a while, and I'm still rather confused.
* You mention that you modelled the "new" function select_best_grantor() on
is_admin_of_role(), but it looks like that function was introduced in 2005.
IIUC you meant select_best_admin(), which was added much more recently.
* I don't see why we should ignore inheritance until after checking admin
option. Prior to your commit (e3ce2de), we skipped checking for admin
option if the role had [NO]INHERIT, and AFAICT your commit aimed to retain
that behavior. The comment above check_role_grantor() even mentions
"inheriting the privileges of a role which does have ADMIN OPTION." Within
the function, I see the following comment:
* Otherwise, the grantor must either have ADMIN OPTION on the role or
* inherit the privileges of a role which does. In the former case,
* record the grantor as the current user; in the latter, pick one of
* the roles that is "most directly" inherited by the current role
* (i.e. fewest "hops").
So, IIUC, the intent is for ADMIN OPTION to apply even if for non-inherited
grants, but we still choose to recurse in roles_is_member_of() based on
inheritance. This means that you can inherit the ADMIN OPTION to some
degree, but if you have membership WITH INHERIT FALSE to a role that has
ADMIN OPTION on another role, the current role effectively does not have
ADMIN OPTION on the other role. Is this correct?
As I'm writing this down, I think it's beginning to make more sense to me,
so this might just be a matter of under-caffeination. But perhaps some
extra comments or documentation wouldn't be out of the question...
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Aug 30, 2022 at 1:30 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
I've been staring at this stuff for a while, and I'm still rather confused.
* You mention that you modelled the "new" function select_best_grantor() on
is_admin_of_role(), but it looks like that function was introduced in 2005.
IIUC you meant select_best_admin(), which was added much more recently.
Well, a combination of the two. It's modeled after is_admin_of_role()
in the sense that it also calls roles_is_member_of() with a
non-InvalidOid third argument and a non-NULL fourth argument. All
other callers pass InvalidOid/NULL.
* I don't see why we should ignore inheritance until after checking admin
option. Prior to your commit (e3ce2de), we skipped checking for admin
option if the role had [NO]INHERIT, and AFAICT your commit aimed to retain
that behavior. The comment above check_role_grantor() even mentions
"inheriting the privileges of a role which does have ADMIN OPTION." Within
the function, I see the following comment:* Otherwise, the grantor must either have ADMIN OPTION on the role or
* inherit the privileges of a role which does. In the former case,
* record the grantor as the current user; in the latter, pick one of
* the roles that is "most directly" inherited by the current role
* (i.e. fewest "hops").So, IIUC, the intent is for ADMIN OPTION to apply even if for non-inherited
grants, but we still choose to recurse in roles_is_member_of() based on
inheritance. This means that you can inherit the ADMIN OPTION to some
degree, but if you have membership WITH INHERIT FALSE to a role that has
ADMIN OPTION on another role, the current role effectively does not have
ADMIN OPTION on the other role. Is this correct?
I think it's easiest if I get an example. Suppose role 'william'
inherits 'charles' and 'diana' and role 'charles' in turn inherits
from roles 'elizabeth' and 'philip'. Furthermore, 'william' has ADMIN
OPTION on 'cambridge', 'charles' on 'wales', and 'elizabeth' on 'uk'.
Now, because 'william' has ADMIN OPTION on role 'cambridge', he can
add members to that role and remove the ones he added. He can also
grant admin out option to others and later remove it (and all
dependent grants that those others have made). When he does this, he
is acting as himself, on his own authority. Because he inherits the
privileges of charles directly and of elizabeth via charles, he can
also add members to wales and uk. But if does this, he is not acting
as himself, because he himself does not possess the ADMIN OPTION on
either of those roles. Rather, he is acting on authority delegated
from some other role which does possess admin option on those roles.
Therefore, if 'william' adds a member to 'uk', the grantor MUST be
recorded as 'elizabeth', not 'william', because the grantor of record
must possess admin option on the role.
Now let's add another layer of complexity. Suppose that the 'uk' role
possesses ADMIN OPTION on some other role, let's say 'parliament'. Can
'william' use the fact that he inherits from 'elizabeth' to grant
membership in 'parliament'? That all depends on whether 'uk' was
granted to 'elizabeth' WITH INHERIT TRUE or WITH INHERIT FALSE. If it
was granted WITH INHERIT TRUE, then elizabeth freely exercises all the
powers of parliament, william freely exercises all of elizabeth's
powers, and thus william can grant membership in parliament. Such a
membership will be recorded as having been granted by uk, the role
that actually holds ADMIN OPTION on parliament. However, if elizabeth
was granted uk WITH INHERIT FALSE, then she can't mess with the
membership of parliament, and thus neither can william. At least, not
without first executing "SET ROLE uk", which in this scenario, either
could do, but perhaps the use of SET ROLE is logged, audited, and very
carefully scrutinized.
So, what does all of this mean for select_best_grantor() and its
helper function roles_is_member_of()? Well, first, we have to recurse.
william can act on his own behalf when administering cambridge, and on
behalf of charles or elizabeth when administering wales or uk
respectively, and there's no way to get that behavior without
recursing. Second, we have to stop the recursion when we reach a grant
that is not inherited. Otherwise, william might be able to act on
behalf of uk and administer membership in parliament even if uk is
granted to elizabeth WITH INHERIT FALSE. Third, and this is where it
gets tricky, we have to stop recursion *only after checking whether
we've found a valid grantor*. william is allowed to administer
membership cambridge whether or not it is granted to william WITH
INHERIT TRUE or WITH INHERIT FALSE. Either way, he possess ADMIN
OPTION on that role, and may administer membership in it. Likewise, he
can administer membership in wales or uk as charles or elizabeth,
respectively, because he definitely inherits the privileges of roles
which definitely have ADMIN OPTION on those roles.
Maybe a clearer way to look at is this: we're going to transit a
series of role-membership links going from william (who attempts some
operation) to the role in which he's trying to grant (or revoke)
membership. For the operation to succeed, we need every link but the
last in the chain to have INHERIT TRUE, and we need the last link in
the chain to have ADMIN TRUE. We don't care whether the links other
than the last one have ADMIN, and we don't care whether the last link
has INHERIT. Thus:
- william => cambridge is OK because the one and only grant involved
has ADMIN, whether or not it has INHERIT
- william => charles => wales is OK because the first hop has INHERIT
and the second hop has ADMIN
- william => charles => elizabeth => uk is OK because the first two
hops have ADMIN and the last hop has INHERIT
- william => charles => elizabeth => uk => parliament is probably not
OK because although the first two hops have ADMIN and the last has
INHERIT, the third hop probably lacks INHERIT
I hope this makes it clearer. select_best_grantor() can't completely
disregard links without INHERIT, because if it does, it can't pay any
attention to the last grant in the chain, which only needs to have
ADMIN, not INHERIT. But it must pay some attention to them, because
every earlier link in the chain does need to have INHERIT. By moving
the if-test up, the patch makes it behave just that way, or at least,
I think it does.
As I'm writing this down, I think it's beginning to make more sense to me,
so this might just be a matter of under-caffeination. But perhaps some
extra comments or documentation wouldn't be out of the question...
I do not rule that out!
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Aug 30, 2022 at 03:24:56PM -0400, Robert Haas wrote:
- william => charles => elizabeth => uk is OK because the first two
hops have ADMIN and the last hop has INHERIT
Don't you mean the first two hops have INHERIT and the last has ADMIN?
- william => charles => elizabeth => uk => parliament is probably not
OK because although the first two hops have ADMIN and the last has
INHERIT, the third hop probably lacks INHERIT
Same here. I don't mean to be pedantic, I just want to make sure I'm
thinking of this correctly.
I hope this makes it clearer. select_best_grantor() can't completely
disregard links without INHERIT, because if it does, it can't pay any
attention to the last grant in the chain, which only needs to have
ADMIN, not INHERIT. But it must pay some attention to them, because
every earlier link in the chain does need to have INHERIT. By moving
the if-test up, the patch makes it behave just that way, or at least,
I think it does.
Yes, this is very helpful. I always appreciate your detailed examples. I
think what you are describing matches the mental model I was beginning to
form.
Okay, now to take a closer look at the patch...
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Aug 30, 2022 at 08:33:46AM -0400, Robert Haas wrote:
Third try.
Now that I have this grantor stuff fresh in my mind, this patch looks good
to me.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Aug 30, 2022 at 5:20 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Tue, Aug 30, 2022 at 03:24:56PM -0400, Robert Haas wrote:
- william => charles => elizabeth => uk is OK because the first two
hops have ADMIN and the last hop has INHERITDon't you mean the first two hops have INHERIT and the last has ADMIN?
I do.
- william => charles => elizabeth => uk => parliament is probably not
OK because although the first two hops have ADMIN and the last has
INHERIT, the third hop probably lacks INHERITSame here. I don't mean to be pedantic, I just want to make sure I'm
thinking of this correctly.
Right. The first two hops have INHERIT and the last has ADMIN, but the
third hop probably lacks INHERIT.
Yes, this is very helpful. I always appreciate your detailed examples. I
think what you are describing matches the mental model I was beginning to
form.
Cool.
Okay, now to take a closer look at the patch...
Thanks.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Aug 30, 2022 at 6:10 PM Nathan Bossart <nathandbossart@gmail.com> wrote:
On Tue, Aug 30, 2022 at 08:33:46AM -0400, Robert Haas wrote:
Third try.
Now that I have this grantor stuff fresh in my mind, this patch looks good
to me.
Thanks for reviewing. Committed.
--
Robert Haas
EDB: http://www.enterprisedb.com
Hello,
Thanks for reviewing. Committed.
Let me return to this topic.
After looking at the changes in this patch, I have a suggestion.
The inheritance option for role memberships is important information to
know if the role privileges will be available automatically or if a
switch with a SET ROLE command is required. However, this information
cannot be obtained with psql commands, specifically \du or \dg.
Previously, you could see the inherit attribute of the role (its absence
is shown with \du). Now you have to look in the pg_auth_members system
catalog.
My suggestion is to add information about pg_auth_members.inherit_option
to the output of \du (\dg).
If so, we can also add information about pg_auth_members.admin_option.
Right now this information is not available in psql command output either.
I thought about how exactly to represent these options in the output
\du, but did not find a single solution. Considered the following choices:
1.
Add \du+ command and for each membership in the role add values of two
options. I haven't done a patch yet, but you can imagine the changes
like this:
CREATE ROLE alice LOGIN IN ROLE pg_read_all_data;
\du+ alice
List of roles
Role name | Attributes | Member of
-----------+------------+--------------------
alice | | {pg_read_all_data(admin=f inherit=t)}
It looks long, but for \du+ it's not a problem.
2.
I assume that the default values for these options will rarely change.
In that case, we can do without \du+ and output only the changed values
directly in the \du command.
GRANT pg_read_all_data TO alice WITH INHERIT FALSE;
2a.
\du alice
List of roles
Role name | Attributes | Member of
-----------+------------+--------------------
alice | | {pg_read_all_data(inherit=f)}
2b.
Similar to GRANT OPTION, we can use symbols instead of long text
(inherit=f) for options. For example, for the ADMIN OPTION we can use
"*" (again similar to GRANT OPTION), and for the missing INHERIT option
something else, such as "-":
GRANT pg_read_all_data TO alice WITH ADMIN TRUE;
GRANT pg_write_all_data TO alice WITH INHERIT FALSE;
\du alice
List of roles
Role name | Attributes | Member of
-----------+------------+--------------------
alice | | {pg_read_all_data*-,pg_write_all_data-}
But I think choices 2a and 2b are too complicated to understand.
Especially because the two options have different default values. And
even more. The default value for the INHERIT option depends on the value
of the INHERIT attribute for the role.
So I like the first choice with \du+ better.
But perhaps there are other choices as well.
If it's interesting, I'm ready to open a new thread (the commitfest
entry for this topic is now closed) and prepare a patch.
--
Pavel Luzanov
On Thu, Aug 25, 2022 at 10:19:39AM -0400, Robert Haas wrote:
I read through this again and found a comment that needed to be
updated, so I did that, bumped catversion, and committed this.
[commit e3ce2de]
@@ -4735,8 +4735,8 @@ initialize_acl(void)
/* * In normal mode, set a callback on any syscache invalidation of rows - * of pg_auth_members (for roles_is_member_of()), pg_authid (for - * has_rolinherit()), or pg_database (for roles_is_member_of()) + * of pg_auth_members (for roles_is_member_of()) pg_database (for + * roles_is_member_of()) */ CacheRegisterSyscacheCallback(AUTHMEMROLEMEM, RoleMembershipCacheCallback,
I agree one could remove the "CacheRegisterSyscacheCallback(AUTHOID, ...)".
This updated the comment as though the patch were including that removal, but
AUTHOID remains. Also, that comment needs s/pg_database/or &/.
These sites didn't change in v16 and may or may not warrant change:
doc/src/sgml/catalogs.sgml:1522: <structfield>rolinherit</structfield> <type>bool</type>
doc/src/sgml/system-views.sgml:2585: <structfield>rolinherit</structfield> <type>bool</type>
src/include/catalog/pg_authid.h:36: bool rolinherit; /* inherit privileges from other roles? */
I likely would leave pg_authid.h as-is but change the doc/ phrases.
/messages/by-id/17901-93eacb513e503f43@postgresql.org led me to notice
that v16 always inherits the implicit membership in role pg_database_owner,
with no way to override like one could in v15. That message's test procedure
doesn't "fail" in v16. I think that's fine, but I'm mentioning it since
pg_database_owner didn't appear upthread.