summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/catalogs.sgml10
-rw-r--r--doc/src/sgml/ref/create_role.sgml29
-rw-r--r--doc/src/sgml/ref/grant.sgml26
-rw-r--r--doc/src/sgml/ref/revoke.sgml9
-rw-r--r--src/backend/commands/user.c262
-rw-r--r--src/backend/parser/gram.y49
-rw-r--r--src/backend/tcop/utility.c2
-rw-r--r--src/backend/utils/adt/acl.c47
-rw-r--r--src/bin/pg_dump/pg_dumpall.c32
-rw-r--r--src/include/catalog/catversion.h2
-rw-r--r--src/include/catalog/pg_auth_members.h1
-rw-r--r--src/include/commands/user.h2
-rw-r--r--src/include/nodes/parsenodes.h2
-rw-r--r--src/test/regress/expected/privileges.out14
-rw-r--r--src/test/regress/sql/privileges.sql13
15 files changed, 385 insertions, 115 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>&lt;iteration count&gt;</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 d7a055b729..dea19cd348 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
@@ -266,6 +276,18 @@ GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <replace
</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
to another role if they possess the privileges of that role. The role
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);
}
@@ -497,11 +516,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* 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..ea28da26a8 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,24 @@ 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. */
+ 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 +4903,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 +4931,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/catversion.h b/src/include/catalog/catversion.h
index b227c7a337..7482c85a86 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202208221
+#define CATALOG_VERSION_NO 202208251
#endif
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