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
</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>

View File

@ -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>

View File

@ -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

View File

@ -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">

View File

@ -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;
}

View File

@ -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; }

View File

@ -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:

View File

@ -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()
*/

View File

@ -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");

View File

@ -57,6 +57,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 202208221
#define CATALOG_VERSION_NO 202208251
#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 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;
/* ----------------

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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