* 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.
*/
{
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;
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);
int index,
bool revoke_admin_option_only,
DropBehavior behavior);
+static void InitGrantRoleOptions(GrantRoleOptions *popt);
/* Check if current user has createrole privileges */
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)
if (addroleto || adminmembers || rolemembers)
CommandCounterIncrement();
+ /* Default grant. */
+ InitGrantRoleOptions(&popt);
+
/*
* Add the new role to the specified existing roles.
*/
AddRoleMems(oldrolename, oldroleid,
thisrole_list,
thisrole_oidlist,
- InvalidOid, false);
+ InvalidOid, &popt);
ReleaseSysCache(oldroletup);
}
* 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);
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
Oid roleid;
+ GrantRoleOptions popt;
check_rolespec_name(stmt->role,
"Cannot alter reserved roles.");
ReleaseSysCache(tuple);
heap_freetuple(new_tuple);
+ InitGrantRoleOptions(&popt);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
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);
}
/*
* 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
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)
{
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);
}
/*
* 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;
* 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;
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\"",
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);
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,
* 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;
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\"",
}
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,
*/
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;
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;
}
}
}
}
}
+
+/*
+ * Initialize a GrantRoleOptions object with default values.
+ */
+static void
+InitGrantRoleOptions(GrantRoleOptions *popt)
+{
+ popt->specified = 0;
+ popt->admin = false;
+ popt->inherit = false;
+}
*/
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};
/*
* 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,
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
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;
/*
* 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
/*
* 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()
*/