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.

Discussion: http://postgr.es/m/CA+Tgmoa5Sf4PiWrfxA=sGzDKg0Ojo3dADw=wAHOhR9dggV=RmQ@mail.gmail.com
This commit is contained in:
Robert Haas 2022-08-25 10:06:02 -04:00
parent 2059c5e3b0
commit e3ce2de09d
15 changed files with 385 additions and 115 deletions

View File

@ -1717,6 +1717,16 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
<structfield>roleid</structfield> to others <structfield>roleid</structfield> to others
</para></entry> </para></entry>
</row> </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> </tbody>
</tgroup> </tgroup>
</table> </table>

View File

@ -133,17 +133,24 @@ in sync when changing the above synopsis!
<term><literal>NOINHERIT</literal></term> <term><literal>NOINHERIT</literal></term>
<listitem> <listitem>
<para> <para>
These clauses determine whether a role <quote>inherits</quote> the When the <literal>GRANT</literal> statement is used to confer
privileges of roles it is a member of. membership in one role to another role, the <literal>GRANT</literal>
A role with the <literal>INHERIT</literal> attribute can automatically may use the <literal>WITH INHERIT</literal> clause to specify whether
use whatever database privileges have been granted to all roles the privileges of the granted role should be <quote>inherited</quote>
it is directly or indirectly a member of. by the new member. If the <literal>GRANT</literal> statement does not
Without <literal>INHERIT</literal>, membership in another role specify either inheritance behavior, the new <literal>GRANT</literal>
only grants the ability to <command>SET ROLE</command> to that other role; will be created <literal>WITH INHERIT TRUE</literal> if the member
the privileges of the other role are only available after having role is set to <literal>INHERIT</literal> and to
done so. <literal>WITH INHERIT FALSE</literal> if it is set to
If not specified, <literal>NOINHERIT</literal>.
<literal>INHERIT</literal> is the default. </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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -98,7 +98,7 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
GRANT <replaceable class="parameter">role_name</replaceable> [, ...] TO <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> ] [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
<phrase>where <replaceable class="parameter">role_specification</replaceable> can be:</phrase> <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>
<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 turn grant membership in the role to others, and revoke membership
in the role as well. Without the admin option, ordinary users cannot in the role as well. Without the admin option, ordinary users cannot
do that. A role is not considered to hold <literal>WITH ADMIN 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. in any role that is not a superuser.
</para> </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> <para>
If <literal>GRANTED BY</literal> is specified, the grant is recorded as 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 having been done by the specified role. A user can only attribute a grant

View File

@ -125,7 +125,7 @@ REVOKE [ GRANT OPTION FOR ]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ] [ CASCADE | RESTRICT ]
REVOKE [ ADMIN OPTION FOR ] REVOKE [ { ADMIN | INHERIT } OPTION FOR ]
<replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_specification</replaceable> [, ...] <replaceable class="parameter">role_name</replaceable> [, ...] FROM <replaceable class="parameter">role_specification</replaceable> [, ...]
[ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ] [ GRANTED BY <replaceable class="parameter">role_specification</replaceable> ]
[ CASCADE | RESTRICT ] [ CASCADE | RESTRICT ]
@ -206,6 +206,13 @@ REVOKE [ ADMIN OPTION FOR ]
allow the noise word <literal>GROUP</literal> allow the noise word <literal>GROUP</literal>
in <replaceable class="parameter">role_specification</replaceable>. in <replaceable class="parameter">role_specification</replaceable>.
</para> </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>
<refsect1 id="sql-revoke-notes"> <refsect1 id="sql-revoke-notes">

View File

@ -51,6 +51,9 @@
* RRG_REMOVE_ADMIN_OPTION indicates a grant that would need to have * RRG_REMOVE_ADMIN_OPTION indicates a grant that would need to have
* admin_option set to false by the operation. * 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 * RRG_DELETE_GRANT indicates a grant that would need to be removed entirely
* by the operation. * by the operation.
*/ */
@ -58,12 +61,22 @@ typedef enum
{ {
RRG_NOOP, RRG_NOOP,
RRG_REMOVE_ADMIN_OPTION, RRG_REMOVE_ADMIN_OPTION,
RRG_REMOVE_INHERIT_OPTION,
RRG_DELETE_GRANT RRG_DELETE_GRANT
} RevokeRoleGrantAction; } RevokeRoleGrantAction;
/* Potentially set by pg_upgrade_support functions */ /* Potentially set by pg_upgrade_support functions */
Oid binary_upgrade_next_pg_authid_oid = InvalidOid; 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 */ /* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256; 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, static void AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds, List *memberSpecs, List *memberIds,
Oid grantorId, bool admin_opt); Oid grantorId, GrantRoleOptions *popt);
static void DelRoleMems(const char *rolename, Oid roleid, static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds, 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, static Oid check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId,
bool is_grant); bool is_grant);
static RevokeRoleGrantAction *initialize_revoke_actions(CatCList *memlist); static RevokeRoleGrantAction *initialize_revoke_actions(CatCList *memlist);
static bool plan_single_revoke(CatCList *memlist, static bool plan_single_revoke(CatCList *memlist,
RevokeRoleGrantAction *actions, RevokeRoleGrantAction *actions,
Oid member, Oid grantor, Oid member, Oid grantor,
bool revoke_admin_option_only, GrantRoleOptions *popt,
DropBehavior behavior); DropBehavior behavior);
static void plan_member_revoke(CatCList *memlist, static void plan_member_revoke(CatCList *memlist,
RevokeRoleGrantAction *actions, Oid member); RevokeRoleGrantAction *actions, Oid member);
@ -92,6 +106,7 @@ static void plan_recursive_revoke(CatCList *memlist,
int index, int index,
bool revoke_admin_option_only, bool revoke_admin_option_only,
DropBehavior behavior); DropBehavior behavior);
static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */ /* Check if current user has createrole privileges */
@ -144,6 +159,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL; DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL; DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL; DefElem *dbypassRLS = NULL;
GrantRoleOptions popt;
/* The defaults can vary depending on the original statement type */ /* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type) switch (stmt->stmt_type)
@ -462,6 +478,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
if (addroleto || adminmembers || rolemembers) if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement(); CommandCounterIncrement();
/* Default grant. */
InitGrantRoleOptions(&popt);
/* /*
* Add the new role to the specified existing roles. * Add the new role to the specified existing roles.
*/ */
@ -486,7 +505,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
AddRoleMems(oldrolename, oldroleid, AddRoleMems(oldrolename, oldroleid,
thisrole_list, thisrole_list,
thisrole_oidlist, thisrole_oidlist,
InvalidOid, false); InvalidOid, &popt);
ReleaseSysCache(oldroletup); ReleaseSysCache(oldroletup);
} }
@ -496,12 +515,14 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
* Add the specified members to this new role. adminmembers get the admin * Add the specified members to this new role. adminmembers get the admin
* option, rolemembers don't. * option, rolemembers don't.
*/ */
AddRoleMems(stmt->role, roleid,
adminmembers, roleSpecsToIds(adminmembers),
InvalidOid, true);
AddRoleMems(stmt->role, roleid, AddRoleMems(stmt->role, roleid,
rolemembers, roleSpecsToIds(rolemembers), 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 */ /* Post creation hook for new role */
InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0); InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0);
@ -552,6 +573,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
DefElem *dvalidUntil = NULL; DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL; DefElem *dbypassRLS = NULL;
Oid roleid; Oid roleid;
GrantRoleOptions popt;
check_rolespec_name(stmt->role, check_rolespec_name(stmt->role,
"Cannot alter reserved roles."); "Cannot alter reserved roles.");
@ -843,6 +865,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
heap_freetuple(new_tuple); heap_freetuple(new_tuple);
InitGrantRoleOptions(&popt);
/* /*
* Advance command counter so we can see new record; else tests in * Advance command counter so we can see new record; else tests in
* AddRoleMems may fail. * AddRoleMems may fail.
@ -856,11 +880,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
if (stmt->action == +1) /* add members to role */ if (stmt->action == +1) /* add members to role */
AddRoleMems(rolename, roleid, AddRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers), rolemembers, roleSpecsToIds(rolemembers),
InvalidOid, false); InvalidOid, &popt);
else if (stmt->action == -1) /* drop members from role */ else if (stmt->action == -1) /* drop members from role */
DelRoleMems(rolename, roleid, DelRoleMems(rolename, roleid,
rolemembers, roleSpecsToIds(rolemembers), 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 * Grant/Revoke roles to/from roles
*/ */
void void
GrantRole(GrantRoleStmt *stmt) GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
{ {
Relation pg_authid_rel; Relation pg_authid_rel;
Oid grantor; Oid grantor;
List *grantee_ids; List *grantee_ids;
ListCell *item; 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) if (stmt->grantor)
grantor = get_rolespec_oid(stmt->grantor, false); grantor = get_rolespec_oid(stmt->grantor, false);
else else
@ -1355,11 +1414,11 @@ GrantRole(GrantRoleStmt *stmt)
pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock); pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock);
/* /*
* Step through all of the granted roles and add/remove entries for the * Step through all of the granted roles and add, update, or remove
* grantees, or, if admin_opt is set, then just add/remove the admin * entries in pg_auth_members as appropriate. If stmt->is_grant is true,
* option. * 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
* Note: Permissions checking is done by AddRoleMems/DelRoleMems * removing options from them.
*/ */
foreach(item, stmt->granted_roles) foreach(item, stmt->granted_roles)
{ {
@ -1377,11 +1436,11 @@ GrantRole(GrantRoleStmt *stmt)
if (stmt->is_grant) if (stmt->is_grant)
AddRoleMems(rolename, roleid, AddRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids, stmt->grantee_roles, grantee_ids,
grantor, stmt->admin_opt); grantor, &popt);
else else
DelRoleMems(rolename, roleid, DelRoleMems(rolename, roleid,
stmt->grantee_roles, grantee_ids, 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) * memberSpecs: list of RoleSpec of roles to add (used only for error messages)
* memberIds: OIDs of roles to add * memberIds: OIDs of roles to add
* grantorId: who is granting the membership (InvalidOid if not set explicitly) * grantorId: who is granting the membership (InvalidOid if not set explicitly)
* admin_opt: granting admin option? * popt: information about grant options
*/ */
static void static void
AddRoleMems(const char *rolename, Oid roleid, AddRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds, List *memberSpecs, List *memberIds,
Oid grantorId, bool admin_opt) Oid grantorId, GrantRoleOptions *popt)
{ {
Relation pg_authmem_rel; Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc; 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 * has no other source of ADMIN OPTION on X, tries to give ADMIN OPTION on
* X back to A). * X back to A).
*/ */
if (admin_opt && grantorId != BOOTSTRAP_SUPERUSERID) if (popt->admin && grantorId != BOOTSTRAP_SUPERUSERID)
{ {
CatCList *memlist; CatCList *memlist;
RevokeRoleGrantAction *actions; RevokeRoleGrantAction *actions;
@ -1669,25 +1728,55 @@ AddRoleMems(const char *rolename, Oid roleid,
Datum new_record[Natts_pg_auth_members] = {0}; Datum new_record[Natts_pg_auth_members] = {0};
bool new_record_nulls[Natts_pg_auth_members] = {0}; bool new_record_nulls[Natts_pg_auth_members] = {0};
bool new_record_repl[Natts_pg_auth_members] = {0}; bool new_record_repl[Natts_pg_auth_members] = {0};
Form_pg_auth_members authmem_form;
/* /* Common initialization for possible insert or update */
* Check if entry for this role/member already exists; if so, give new_record[Anum_pg_auth_members_roleid - 1] =
* warning unless we are adding admin option. 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, authmem_tuple = SearchSysCache3(AUTHMEMROLEMEM,
ObjectIdGetDatum(roleid), ObjectIdGetDatum(roleid),
ObjectIdGetDatum(memberid), ObjectIdGetDatum(memberid),
ObjectIdGetDatum(grantorId)); ObjectIdGetDatum(grantorId));
if (!HeapTupleIsValid(authmem_tuple))
{ /*
authmem_form = NULL; * If we found a tuple, update it with new option values, unless
} * there are no changes, in which case issue a WARNING.
else *
* 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); 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, ereport(NOTICE,
(errmsg("role \"%s\" has already been granted membership in role \"%s\" by role \"%s\"", (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); ReleaseSysCache(authmem_tuple);
continue; 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, tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record, new_record,
new_record_nulls, new_record_repl); new_record_nulls, new_record_repl);
@ -1719,6 +1798,33 @@ AddRoleMems(const char *rolename, Oid roleid,
Oid objectId; Oid objectId;
Oid *newmembers = palloc(sizeof(Oid)); 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(); objectId = GetNewObjectId();
new_record[Anum_pg_auth_members_oid - 1] = objectId; new_record[Anum_pg_auth_members_oid - 1] = objectId;
tuple = heap_form_tuple(pg_authmem_dsc, 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) * memberSpecs: list of RoleSpec of roles to del (used only for error messages)
* memberIds: OIDs of roles to del * memberIds: OIDs of roles to del
* grantorId: who is revoking the membership * 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 * behavior: RESTRICT or CASCADE behavior for recursive removal
*/ */
static void static void
DelRoleMems(const char *rolename, Oid roleid, DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds, List *memberSpecs, List *memberIds,
Oid grantorId, bool admin_opt, DropBehavior behavior) Oid grantorId, GrantRoleOptions *popt, DropBehavior behavior)
{ {
Relation pg_authmem_rel; Relation pg_authmem_rel;
TupleDesc pg_authmem_dsc; TupleDesc pg_authmem_dsc;
@ -1824,7 +1930,7 @@ DelRoleMems(const char *rolename, Oid roleid,
Oid memberid = lfirst_oid(iditem); Oid memberid = lfirst_oid(iditem);
if (!plan_single_revoke(memlist, actions, memberid, grantorId, if (!plan_single_revoke(memlist, actions, memberid, grantorId,
admin_opt, behavior)) popt, behavior))
{ {
ereport(WARNING, ereport(WARNING,
(errmsg("role \"%s\" has not been granted membership in role \"%s\" by role \"%s\"", (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 else
{ {
/* Just turn off the admin option */ /* Just turn off the specified option */
HeapTuple tuple; HeapTuple tuple;
Datum new_record[Natts_pg_auth_members] = {0}; Datum new_record[Natts_pg_auth_members] = {0};
bool new_record_nulls[Natts_pg_auth_members] = {0}; bool new_record_nulls[Natts_pg_auth_members] = {0};
bool new_record_repl[Natts_pg_auth_members] = {0}; bool new_record_repl[Natts_pg_auth_members] = {0};
/* Build a tuple to update with */ /* Build a tuple to update with */
new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false); if (actions[i] == RRG_REMOVE_ADMIN_OPTION)
new_record_repl[Anum_pg_auth_members_admin_option - 1] = true; {
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, tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc,
new_record, new_record,
@ -2028,11 +2148,21 @@ initialize_revoke_actions(CatCList *memlist)
*/ */
static bool static bool
plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions, plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
Oid member, Oid grantor, bool revoke_admin_option_only, Oid member, Oid grantor, GrantRoleOptions *popt,
DropBehavior behavior) DropBehavior behavior)
{ {
int i; 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) for (i = 0; i < memlist->n_members; ++i)
{ {
HeapTuple authmem_tuple; HeapTuple authmem_tuple;
@ -2044,8 +2174,27 @@ plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
if (authmem_form->member == member && if (authmem_form->member == member &&
authmem_form->grantor == grantor) authmem_form->grantor == grantor)
{ {
plan_recursive_revoke(memlist, actions, i, if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0)
revoke_admin_option_only, behavior); {
/*
* 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; 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;
}

View File

@ -362,9 +362,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> utility_option_arg %type <node> utility_option_arg
%type <defelt> drop_option %type <defelt> drop_option
%type <boolean> opt_or_replace opt_no %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_nowait opt_if_exists opt_with_data
opt_transaction_chain 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 <ival> opt_nowait_or_skip
%type <list> OptRoleList AlterOptRoleList %type <list> OptRoleList AlterOptRoleList
@ -7848,15 +7851,26 @@ opt_grant_grant_option:
*****************************************************************************/ *****************************************************************************/
GrantRoleStmt: 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); GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = true; n->is_grant = true;
n->granted_roles = $2; n->granted_roles = $2;
n->grantee_roles = $4; n->grantee_roles = $4;
n->admin_opt = $5; n->opt = NIL;
n->grantor = $6; 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; $$ = (Node *) n;
} }
; ;
@ -7867,19 +7881,22 @@ RevokeRoleStmt:
GrantRoleStmt *n = makeNode(GrantRoleStmt); GrantRoleStmt *n = makeNode(GrantRoleStmt);
n->is_grant = false; n->is_grant = false;
n->admin_opt = false; n->opt = NIL;
n->granted_roles = $2; n->granted_roles = $2;
n->grantee_roles = $4; n->grantee_roles = $4;
n->grantor = $5; n->grantor = $5;
n->behavior = $6; n->behavior = $6;
$$ = (Node *) n; $$ = (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); GrantRoleStmt *n = makeNode(GrantRoleStmt);
DefElem *opt;
opt = makeDefElem(pstrdup($2),
(Node *) makeBoolean(false), @2);
n->is_grant = false; n->is_grant = false;
n->admin_opt = true; n->opt = list_make1(opt);
n->granted_roles = $5; n->granted_roles = $5;
n->grantee_roles = $7; n->grantee_roles = $7;
n->grantor = $8; n->grantor = $8;
@ -7888,8 +7905,22 @@ RevokeRoleStmt:
} }
; ;
opt_grant_admin_option: WITH ADMIN OPTION { $$ = true; } grant_role_opt_list:
| /*EMPTY*/ { $$ = false; } 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; } opt_granted_by: GRANTED BY RoleSpec { $$ = $3; }

View File

@ -767,7 +767,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
case T_GrantRoleStmt: case T_GrantRoleStmt:
/* no event triggers for global objects */ /* no event triggers for global objects */
GrantRole((GrantRoleStmt *) parsetree); GrantRole(pstate, (GrantRoleStmt *) parsetree);
break; break;
case T_CreatedbStmt: case T_CreatedbStmt:

View File

@ -66,7 +66,7 @@ typedef struct
*/ */
enum RoleRecurseType enum RoleRecurseType
{ {
ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */ ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */
ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */ ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */
}; };
static Oid cached_role[] = {InvalidOid, InvalidOid}; 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 * In normal mode, set a callback on any syscache invalidation of rows
* of pg_auth_members (for roles_is_member_of()), pg_authid (for * of pg_auth_members (for roles_is_member_of()) pg_database (for
* has_rolinherit()), or pg_database (for roles_is_member_of()) * roles_is_member_of())
*/ */
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM, CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
RoleMembershipCacheCallback, RoleMembershipCacheCallback,
@ -4769,29 +4769,11 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
cached_role[ROLERECURSE_MEMBERS] = InvalidOid; 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 * Get a list of roles that the specified roleid is a member of
* *
* Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit * Type ROLERECURSE_PRIVS recurses only through inheritable grants,
* set, while ROLERECURSE_MEMBERS recurses through all roles. * while ROLERECURSE_MEMBERS recurses through all grants.
* *
* Since indirect membership testing is relatively expensive, we cache * Since indirect membership testing is relatively expensive, we cache
* a list of memberships. Hence, the result is only guaranteed good until * 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; CatCList *memlist;
int i; int i;
if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid))
continue; /* ignore non-inheriting roles */
/* Find roles that memberid is directly a member of */ /* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList1(AUTHMEMMEMROLE, memlist = SearchSysCacheList1(AUTHMEMMEMROLE,
ObjectIdGetDatum(memberid)); ObjectIdGetDatum(memberid));
for (i = 0; i < memlist->n_members; i++) for (i = 0; i < memlist->n_members; i++)
{ {
HeapTuple tup = &memlist->members[i]->tuple; 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 * While otherid==InvalidOid shouldn't appear in the catalog, the
* OidIsValid() avoids crashing if that arises. * OidIsValid() avoids crashing if that arises.
*/ */
if (otherid == admin_of && if (otherid == admin_of && form->admin_option &&
((Form_pg_auth_members) GETSTRUCT(tup))->admin_option &&
OidIsValid(admin_of) && !OidIsValid(*admin_role)) OidIsValid(admin_of) && !OidIsValid(*admin_role))
*admin_role = memberid; *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)? * 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 are not inherited;
* set; for such roles, membership implies the ability to do SET ROLE, but * in such cases, membership implies the ability to do SET ROLE, but
* the privileges are not available until you've done so. * the privileges are not available until you've done so.
*/ */
bool bool
@ -4948,7 +4931,7 @@ has_privs_of_role(Oid member, Oid role)
/* /*
* Is member a member of role (directly or indirectly)? * 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() * Do not use this for privilege checking, instead use has_privs_of_role()
*/ */

View File

@ -947,11 +947,14 @@ static void
dumpRoleMembership(PGconn *conn) dumpRoleMembership(PGconn *conn)
{ {
PQExpBuffer buf = createPQExpBuffer(); PQExpBuffer buf = createPQExpBuffer();
PQExpBuffer optbuf = createPQExpBuffer();
PGresult *res; PGresult *res;
int start = 0, int start = 0,
end, end,
total; total;
bool dump_grantors; bool dump_grantors;
bool dump_inherit_option;
int i_inherit_option;
/* /*
* Previous versions of PostgreSQL didn't used to track the grantor very * Previous versions of PostgreSQL didn't used to track the grantor very
@ -962,19 +965,28 @@ dumpRoleMembership(PGconn *conn)
*/ */
dump_grantors = (PQserverVersion(conn) >= 160000); 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. */ /* Generate and execute query. */
printfPQExpBuffer(buf, "SELECT ur.rolname AS role, " printfPQExpBuffer(buf, "SELECT ur.rolname AS role, "
"um.rolname AS member, " "um.rolname AS member, "
"ug.oid AS grantorid, " "ug.oid AS grantorid, "
"ug.rolname AS grantor, " "ug.rolname AS grantor, "
"a.admin_option " "a.admin_option");
"FROM pg_auth_members a " 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 ur on ur.oid = a.roleid "
"LEFT JOIN %s um on um.oid = a.member " "LEFT JOIN %s um on um.oid = a.member "
"LEFT JOIN %s ug on ug.oid = a.grantor " "LEFT JOIN %s ug on ug.oid = a.grantor "
"WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')" "WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')"
"ORDER BY 1,2,4", role_catalog, role_catalog, role_catalog); "ORDER BY 1,2,4", role_catalog, role_catalog, role_catalog);
res = executeQuery(conn, buf->data); res = executeQuery(conn, buf->data);
i_inherit_option = PQfnumber(res, "inherit_option");
if (PQntuples(res) > 0) if (PQntuples(res) > 0)
fprintf(OPF, "--\n-- Role memberships\n--\n\n"); fprintf(OPF, "--\n-- Role memberships\n--\n\n");
@ -1079,10 +1091,24 @@ dumpRoleMembership(PGconn *conn)
rolename_insert(ht, member, &found); rolename_insert(ht, member, &found);
/* Generate the actual GRANT statement. */ /* Generate the actual GRANT statement. */
resetPQExpBuffer(optbuf);
fprintf(OPF, "GRANT %s", fmtId(role)); fprintf(OPF, "GRANT %s", fmtId(role));
fprintf(OPF, " TO %s", fmtId(member)); fprintf(OPF, " TO %s", fmtId(member));
if (*admin_option == 't') 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) if (dump_grantors)
fprintf(OPF, " GRANTED BY %s", fmtId(grantor)); fprintf(OPF, " GRANTED BY %s", fmtId(grantor));
fprintf(OPF, ";\n"); fprintf(OPF, ";\n");

View File

@ -57,6 +57,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 202208221 #define CATALOG_VERSION_NO 202208251
#endif #endif

View File

@ -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 member BKI_LOOKUP(pg_authid); /* ID of a member of that role */
Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */ Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */
bool admin_option; /* granted with admin option? */ bool admin_option; /* granted with admin option? */
bool inherit_option; /* exercise privileges without SET ROLE? */
} FormData_pg_auth_members; } FormData_pg_auth_members;
/* ---------------- /* ----------------

View File

@ -28,7 +28,7 @@ extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt);
extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt); extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt);
extern Oid AlterRoleSet(AlterRoleSetStmt *stmt); extern Oid AlterRoleSet(AlterRoleSetStmt *stmt);
extern void DropRole(DropRoleStmt *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 ObjectAddress RenameRole(const char *oldname, const char *newname);
extern void DropOwnedObjects(DropOwnedStmt *stmt); extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt); extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);

View File

@ -2451,7 +2451,7 @@ typedef struct GrantRoleStmt
List *granted_roles; /* list of roles to be granted/revoked */ List *granted_roles; /* list of roles to be granted/revoked */
List *grantee_roles; /* list of member roles to add/delete */ List *grantee_roles; /* list of member roles to add/delete */
bool is_grant; /* true = GRANT, false = REVOKE */ 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 */ RoleSpec *grantor; /* set grantor to other than current role */
DropBehavior behavior; /* drop behavior (for REVOKE) */ DropBehavior behavior; /* drop behavior (for REVOKE) */
} GrantRoleStmt; } GrantRoleStmt;

View File

@ -517,7 +517,19 @@ BEGIN;
RESET SESSION AUTHORIZATION; RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT; ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1; 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 ERROR: permission denied for table atest3
ROLLBACK; ROLLBACK;
-- views -- views

View File

@ -351,7 +351,18 @@ BEGIN;
RESET SESSION AUTHORIZATION; RESET SESSION AUTHORIZATION;
ALTER ROLE regress_priv_user1 NOINHERIT; ALTER ROLE regress_priv_user1 NOINHERIT;
SET SESSION AUTHORIZATION regress_priv_user1; 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; ROLLBACK;
-- views -- views