diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/commands/user.c | 262 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 49 | ||||
-rw-r--r-- | src/backend/tcop/utility.c | 2 | ||||
-rw-r--r-- | src/backend/utils/adt/acl.c | 47 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_dumpall.c | 32 | ||||
-rw-r--r-- | src/include/catalog/catversion.h | 2 | ||||
-rw-r--r-- | src/include/catalog/pg_auth_members.h | 1 | ||||
-rw-r--r-- | src/include/commands/user.h | 2 | ||||
-rw-r--r-- | src/include/nodes/parsenodes.h | 2 | ||||
-rw-r--r-- | src/test/regress/expected/privileges.out | 14 | ||||
-rw-r--r-- | src/test/regress/sql/privileges.sql | 13 |
11 files changed, 325 insertions, 101 deletions
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 511ca8d8fd1..265a48af7eb 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -51,6 +51,9 @@ * RRG_REMOVE_ADMIN_OPTION indicates a grant that would need to have * admin_option set to false by the operation. * + * RRG_REMOVE_INHERIT_OPTION indicates a grant that would need to have + * inherit_option set to false by the operation. + * * RRG_DELETE_GRANT indicates a grant that would need to be removed entirely * by the operation. */ @@ -58,12 +61,22 @@ typedef enum { RRG_NOOP, RRG_REMOVE_ADMIN_OPTION, + RRG_REMOVE_INHERIT_OPTION, RRG_DELETE_GRANT } RevokeRoleGrantAction; /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_pg_authid_oid = InvalidOid; +typedef struct +{ + unsigned specified; + bool admin; + bool inherit; +} GrantRoleOptions; + +#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001 +#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002 /* GUC parameter */ int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256; @@ -73,17 +86,18 @@ check_password_hook_type check_password_hook = NULL; static void AddRoleMems(const char *rolename, Oid roleid, List *memberSpecs, List *memberIds, - Oid grantorId, bool admin_opt); + Oid grantorId, GrantRoleOptions *popt); static void DelRoleMems(const char *rolename, Oid roleid, List *memberSpecs, List *memberIds, - Oid grantorId, bool admin_opt, DropBehavior behavior); + Oid grantorId, GrantRoleOptions *popt, + DropBehavior behavior); static Oid check_role_grantor(Oid currentUserId, Oid roleid, Oid grantorId, bool is_grant); static RevokeRoleGrantAction *initialize_revoke_actions(CatCList *memlist); static bool plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions, Oid member, Oid grantor, - bool revoke_admin_option_only, + GrantRoleOptions *popt, DropBehavior behavior); static void plan_member_revoke(CatCList *memlist, RevokeRoleGrantAction *actions, Oid member); @@ -92,6 +106,7 @@ static void plan_recursive_revoke(CatCList *memlist, int index, bool revoke_admin_option_only, DropBehavior behavior); +static void InitGrantRoleOptions(GrantRoleOptions *popt); /* Check if current user has createrole privileges */ @@ -144,6 +159,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) DefElem *dadminmembers = NULL; DefElem *dvalidUntil = NULL; DefElem *dbypassRLS = NULL; + GrantRoleOptions popt; /* The defaults can vary depending on the original statement type */ switch (stmt->stmt_type) @@ -462,6 +478,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) if (addroleto || adminmembers || rolemembers) CommandCounterIncrement(); + /* Default grant. */ + InitGrantRoleOptions(&popt); + /* * Add the new role to the specified existing roles. */ @@ -486,7 +505,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) AddRoleMems(oldrolename, oldroleid, thisrole_list, thisrole_oidlist, - InvalidOid, false); + InvalidOid, &popt); ReleaseSysCache(oldroletup); } @@ -497,11 +516,13 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) * option, rolemembers don't. */ AddRoleMems(stmt->role, roleid, - adminmembers, roleSpecsToIds(adminmembers), - InvalidOid, true); - AddRoleMems(stmt->role, roleid, rolemembers, roleSpecsToIds(rolemembers), - InvalidOid, false); + InvalidOid, &popt); + popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN; + popt.admin = true; + AddRoleMems(stmt->role, roleid, + adminmembers, roleSpecsToIds(adminmembers), + InvalidOid, &popt); /* Post creation hook for new role */ InvokeObjectPostCreateHook(AuthIdRelationId, roleid, 0); @@ -552,6 +573,7 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt) DefElem *dvalidUntil = NULL; DefElem *dbypassRLS = NULL; Oid roleid; + GrantRoleOptions popt; check_rolespec_name(stmt->role, "Cannot alter reserved roles."); @@ -843,6 +865,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt) ReleaseSysCache(tuple); heap_freetuple(new_tuple); + InitGrantRoleOptions(&popt); + /* * Advance command counter so we can see new record; else tests in * AddRoleMems may fail. @@ -856,11 +880,11 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt) if (stmt->action == +1) /* add members to role */ AddRoleMems(rolename, roleid, rolemembers, roleSpecsToIds(rolemembers), - InvalidOid, false); + InvalidOid, &popt); else if (stmt->action == -1) /* drop members from role */ DelRoleMems(rolename, roleid, rolemembers, roleSpecsToIds(rolemembers), - InvalidOid, false, DROP_RESTRICT); + InvalidOid, &popt, DROP_RESTRICT); } /* @@ -1337,13 +1361,48 @@ RenameRole(const char *oldname, const char *newname) * Grant/Revoke roles to/from roles */ void -GrantRole(GrantRoleStmt *stmt) +GrantRole(ParseState *pstate, GrantRoleStmt *stmt) { Relation pg_authid_rel; Oid grantor; List *grantee_ids; ListCell *item; + GrantRoleOptions popt; + + /* Parse options list. */ + InitGrantRoleOptions(&popt); + foreach(item, stmt->opt) + { + DefElem *opt = (DefElem *) lfirst(item); + char *optval = defGetString(opt); + + if (strcmp(opt->defname, "admin") == 0) + { + popt.specified |= GRANT_ROLE_SPECIFIED_ADMIN; + + if (parse_bool(optval, &popt.admin)) + continue; + } + else if (strcmp(opt->defname, "inherit") == 0) + { + popt.specified |= GRANT_ROLE_SPECIFIED_INHERIT; + if (parse_bool(optval, &popt.inherit)) + continue; + } + else + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized role option \"%s\"", opt->defname), + parser_errposition(pstate, opt->location)); + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized value for role option \"%s\": \"%s\"", + opt->defname, optval), + parser_errposition(pstate, opt->location))); + } + /* Lookup OID of grantor, if specified. */ if (stmt->grantor) grantor = get_rolespec_oid(stmt->grantor, false); else @@ -1355,11 +1414,11 @@ GrantRole(GrantRoleStmt *stmt) pg_authid_rel = table_open(AuthIdRelationId, AccessShareLock); /* - * Step through all of the granted roles and add/remove entries for the - * grantees, or, if admin_opt is set, then just add/remove the admin - * option. - * - * Note: Permissions checking is done by AddRoleMems/DelRoleMems + * Step through all of the granted roles and add, update, or remove + * entries in pg_auth_members as appropriate. If stmt->is_grant is true, + * we are adding new grants or, if they already exist, updating options + * on those grants. If stmt->is_grant is false, we are revoking grants or + * removing options from them. */ foreach(item, stmt->granted_roles) { @@ -1377,11 +1436,11 @@ GrantRole(GrantRoleStmt *stmt) if (stmt->is_grant) AddRoleMems(rolename, roleid, stmt->grantee_roles, grantee_ids, - grantor, stmt->admin_opt); + grantor, &popt); else DelRoleMems(rolename, roleid, stmt->grantee_roles, grantee_ids, - grantor, stmt->admin_opt, stmt->behavior); + grantor, &popt, stmt->behavior); } /* @@ -1483,12 +1542,12 @@ roleSpecsToIds(List *memberNames) * memberSpecs: list of RoleSpec of roles to add (used only for error messages) * memberIds: OIDs of roles to add * grantorId: who is granting the membership (InvalidOid if not set explicitly) - * admin_opt: granting admin option? + * popt: information about grant options */ static void AddRoleMems(const char *rolename, Oid roleid, List *memberSpecs, List *memberIds, - Oid grantorId, bool admin_opt) + Oid grantorId, GrantRoleOptions *popt) { Relation pg_authmem_rel; TupleDesc pg_authmem_dsc; @@ -1607,7 +1666,7 @@ AddRoleMems(const char *rolename, Oid roleid, * has no other source of ADMIN OPTION on X, tries to give ADMIN OPTION on * X back to A). */ - if (admin_opt && grantorId != BOOTSTRAP_SUPERUSERID) + if (popt->admin && grantorId != BOOTSTRAP_SUPERUSERID) { CatCList *memlist; RevokeRoleGrantAction *actions; @@ -1669,25 +1728,55 @@ AddRoleMems(const char *rolename, Oid roleid, Datum new_record[Natts_pg_auth_members] = {0}; bool new_record_nulls[Natts_pg_auth_members] = {0}; bool new_record_repl[Natts_pg_auth_members] = {0}; - Form_pg_auth_members authmem_form; - /* - * Check if entry for this role/member already exists; if so, give - * warning unless we are adding admin option. - */ + /* Common initialization for possible insert or update */ + new_record[Anum_pg_auth_members_roleid - 1] = + ObjectIdGetDatum(roleid); + new_record[Anum_pg_auth_members_member - 1] = + ObjectIdGetDatum(memberid); + new_record[Anum_pg_auth_members_grantor - 1] = + ObjectIdGetDatum(grantorId); + + /* Find any existing tuple */ authmem_tuple = SearchSysCache3(AUTHMEMROLEMEM, ObjectIdGetDatum(roleid), ObjectIdGetDatum(memberid), ObjectIdGetDatum(grantorId)); - if (!HeapTupleIsValid(authmem_tuple)) - { - authmem_form = NULL; - } - else + + /* + * If we found a tuple, update it with new option values, unless + * there are no changes, in which case issue a WARNING. + * + * If we didn't find a tuple, just insert one. + */ + if (HeapTupleIsValid(authmem_tuple)) { + Form_pg_auth_members authmem_form; + bool at_least_one_change = false; + authmem_form = (Form_pg_auth_members) GETSTRUCT(authmem_tuple); - if (!admin_opt || authmem_form->admin_option) + if ((popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0 + && authmem_form->admin_option != popt->admin) + { + new_record[Anum_pg_auth_members_admin_option - 1] = + BoolGetDatum(popt->admin); + new_record_repl[Anum_pg_auth_members_admin_option - 1] = + true; + at_least_one_change = true; + } + + if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0 + && authmem_form->inherit_option != popt->inherit) + { + new_record[Anum_pg_auth_members_inherit_option - 1] = + BoolGetDatum(popt->inherit); + new_record_repl[Anum_pg_auth_members_inherit_option - 1] = + true; + at_least_one_change = true; + } + + if (!at_least_one_change) { ereport(NOTICE, (errmsg("role \"%s\" has already been granted membership in role \"%s\" by role \"%s\"", @@ -1696,17 +1785,7 @@ AddRoleMems(const char *rolename, Oid roleid, ReleaseSysCache(authmem_tuple); continue; } - } - /* Build a tuple to insert or update */ - new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid); - new_record[Anum_pg_auth_members_member - 1] = ObjectIdGetDatum(memberid); - new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId); - new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt); - - if (HeapTupleIsValid(authmem_tuple)) - { - new_record_repl[Anum_pg_auth_members_admin_option - 1] = true; tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, new_record, new_record_nulls, new_record_repl); @@ -1719,6 +1798,33 @@ AddRoleMems(const char *rolename, Oid roleid, Oid objectId; Oid *newmembers = palloc(sizeof(Oid)); + /* Set admin option if user set it to true, otherwise not. */ + new_record[Anum_pg_auth_members_admin_option - 1] = + BoolGetDatum(popt->admin); + + /* + * If the user specified a value for the inherit option, use + * whatever was specified. Otherwise, set the default value based + * on the role-level property. + */ + if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0) + new_record[Anum_pg_auth_members_inherit_option - 1] = + popt->inherit; + else + { + HeapTuple mrtup; + Form_pg_authid mrform; + + mrtup = SearchSysCache1(AUTHOID, memberid); + if (!HeapTupleIsValid(mrtup)) + elog(ERROR, "cache lookup failed for role %u", memberid); + mrform = (Form_pg_authid) GETSTRUCT(mrtup); + new_record[Anum_pg_auth_members_inherit_option - 1] = + mrform->rolinherit; + ReleaseSysCache(mrtup); + } + + /* get an OID for the new row and insert it */ objectId = GetNewObjectId(); new_record[Anum_pg_auth_members_oid - 1] = objectId; tuple = heap_form_tuple(pg_authmem_dsc, @@ -1751,13 +1857,13 @@ AddRoleMems(const char *rolename, Oid roleid, * memberSpecs: list of RoleSpec of roles to del (used only for error messages) * memberIds: OIDs of roles to del * grantorId: who is revoking the membership - * admin_opt: remove admin option only? + * popt: information about grant options * behavior: RESTRICT or CASCADE behavior for recursive removal */ static void DelRoleMems(const char *rolename, Oid roleid, List *memberSpecs, List *memberIds, - Oid grantorId, bool admin_opt, DropBehavior behavior) + Oid grantorId, GrantRoleOptions *popt, DropBehavior behavior) { Relation pg_authmem_rel; TupleDesc pg_authmem_dsc; @@ -1824,7 +1930,7 @@ DelRoleMems(const char *rolename, Oid roleid, Oid memberid = lfirst_oid(iditem); if (!plan_single_revoke(memlist, actions, memberid, grantorId, - admin_opt, behavior)) + popt, behavior)) { ereport(WARNING, (errmsg("role \"%s\" has not been granted membership in role \"%s\" by role \"%s\"", @@ -1862,15 +1968,29 @@ DelRoleMems(const char *rolename, Oid roleid, } else { - /* Just turn off the admin option */ + /* Just turn off the specified option */ HeapTuple tuple; Datum new_record[Natts_pg_auth_members] = {0}; bool new_record_nulls[Natts_pg_auth_members] = {0}; bool new_record_repl[Natts_pg_auth_members] = {0}; /* Build a tuple to update with */ - new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(false); - new_record_repl[Anum_pg_auth_members_admin_option - 1] = true; + if (actions[i] == RRG_REMOVE_ADMIN_OPTION) + { + new_record[Anum_pg_auth_members_admin_option - 1] = + BoolGetDatum(false); + new_record_repl[Anum_pg_auth_members_admin_option - 1] = + true; + } + else if (actions[i] == RRG_REMOVE_INHERIT_OPTION) + { + new_record[Anum_pg_auth_members_inherit_option - 1] = + BoolGetDatum(false); + new_record_repl[Anum_pg_auth_members_inherit_option - 1] = + true; + } + else + elog(ERROR, "unknown role revoke action"); tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, new_record, @@ -2028,11 +2148,21 @@ initialize_revoke_actions(CatCList *memlist) */ static bool plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions, - Oid member, Oid grantor, bool revoke_admin_option_only, + Oid member, Oid grantor, GrantRoleOptions *popt, DropBehavior behavior) { int i; + /* + * If popt.specified == 0, we're revoking the grant entirely; otherwise, + * we expect just one bit to be set, and we're revoking the corresponding + * option. As of this writing, there's no syntax that would allow for + * an attempt to revoke multiple options at once, and the logic below + * wouldn't work properly if such syntax were added, so assert that our + * caller isn't trying to do that. + */ + Assert(pg_popcount32(popt->specified) <= 1); + for (i = 0; i < memlist->n_members; ++i) { HeapTuple authmem_tuple; @@ -2044,8 +2174,27 @@ plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions, if (authmem_form->member == member && authmem_form->grantor == grantor) { - plan_recursive_revoke(memlist, actions, i, - revoke_admin_option_only, behavior); + if ((popt->specified & GRANT_ROLE_SPECIFIED_INHERIT) != 0) + { + /* + * Revoking the INHERIT option doesn't change anything for + * dependent privileges, so we don't need to recurse. + */ + actions[i] = RRG_REMOVE_INHERIT_OPTION; + } + else + { + bool revoke_admin_option_only; + + /* + * Revoking the grant entirely, or ADMIN option on a grant, + * implicates dependent privileges, so we may need to recurse. + */ + revoke_admin_option_only = + (popt->specified & GRANT_ROLE_SPECIFIED_ADMIN) != 0; + plan_recursive_revoke(memlist, actions, i, + revoke_admin_option_only, behavior); + } return true; } } @@ -2172,3 +2321,14 @@ plan_recursive_revoke(CatCList *memlist, RevokeRoleGrantAction *actions, } } } + +/* + * Initialize a GrantRoleOptions object with default values. + */ +static void +InitGrantRoleOptions(GrantRoleOptions *popt) +{ + popt->specified = 0; + popt->admin = false; + popt->inherit = false; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c8bd66dd543..b5ab9d9c9a3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -362,9 +362,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type <node> utility_option_arg %type <defelt> drop_option %type <boolean> opt_or_replace opt_no - opt_grant_grant_option opt_grant_admin_option + opt_grant_grant_option opt_nowait opt_if_exists opt_with_data opt_transaction_chain +%type <list> grant_role_opt_list +%type <defelt> grant_role_opt +%type <node> grant_role_opt_value %type <ival> opt_nowait_or_skip %type <list> OptRoleList AlterOptRoleList @@ -7848,15 +7851,26 @@ opt_grant_grant_option: *****************************************************************************/ GrantRoleStmt: - GRANT privilege_list TO role_list opt_grant_admin_option opt_granted_by + GRANT privilege_list TO role_list opt_granted_by { GrantRoleStmt *n = makeNode(GrantRoleStmt); n->is_grant = true; n->granted_roles = $2; n->grantee_roles = $4; - n->admin_opt = $5; - n->grantor = $6; + n->opt = NIL; + n->grantor = $5; + $$ = (Node *) n; + } + | GRANT privilege_list TO role_list WITH grant_role_opt_list opt_granted_by + { + GrantRoleStmt *n = makeNode(GrantRoleStmt); + + n->is_grant = true; + n->granted_roles = $2; + n->grantee_roles = $4; + n->opt = $6; + n->grantor = $7; $$ = (Node *) n; } ; @@ -7867,19 +7881,22 @@ RevokeRoleStmt: GrantRoleStmt *n = makeNode(GrantRoleStmt); n->is_grant = false; - n->admin_opt = false; + n->opt = NIL; n->granted_roles = $2; n->grantee_roles = $4; n->grantor = $5; n->behavior = $6; $$ = (Node *) n; } - | REVOKE ADMIN OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior + | REVOKE ColId OPTION FOR privilege_list FROM role_list opt_granted_by opt_drop_behavior { GrantRoleStmt *n = makeNode(GrantRoleStmt); + DefElem *opt; + opt = makeDefElem(pstrdup($2), + (Node *) makeBoolean(false), @2); n->is_grant = false; - n->admin_opt = true; + n->opt = list_make1(opt); n->granted_roles = $5; n->grantee_roles = $7; n->grantor = $8; @@ -7888,8 +7905,22 @@ RevokeRoleStmt: } ; -opt_grant_admin_option: WITH ADMIN OPTION { $$ = true; } - | /*EMPTY*/ { $$ = false; } +grant_role_opt_list: + grant_role_opt_list ',' grant_role_opt { $$ = lappend($1, $3); } + | grant_role_opt { $$ = list_make1($1); } + ; + +grant_role_opt: + ColLabel grant_role_opt_value + { + $$ = makeDefElem(pstrdup($1), $2, @1); + } + ; + +grant_role_opt_value: + OPTION { $$ = (Node *) makeBoolean(true); } + | TRUE_P { $$ = (Node *) makeBoolean(true); } + | FALSE_P { $$ = (Node *) makeBoolean(false); } ; opt_granted_by: GRANTED BY RoleSpec { $$ = $3; } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 6b0a8652622..aa008157878 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -767,7 +767,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, case T_GrantRoleStmt: /* no event triggers for global objects */ - GrantRole((GrantRoleStmt *) parsetree); + GrantRole(pstate, (GrantRoleStmt *) parsetree); break; case T_CreatedbStmt: diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 3e045da31fc..ea28da26a89 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -66,7 +66,7 @@ typedef struct */ enum RoleRecurseType { - ROLERECURSE_PRIVS = 0, /* recurse if rolinherit */ + ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */ ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */ }; static Oid cached_role[] = {InvalidOid, InvalidOid}; @@ -4735,8 +4735,8 @@ initialize_acl(void) /* * In normal mode, set a callback on any syscache invalidation of rows - * of pg_auth_members (for roles_is_member_of()), pg_authid (for - * has_rolinherit()), or pg_database (for roles_is_member_of()) + * of pg_auth_members (for roles_is_member_of()) pg_database (for + * roles_is_member_of()) */ CacheRegisterSyscacheCallback(AUTHMEMROLEMEM, RoleMembershipCacheCallback, @@ -4769,29 +4769,11 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue) cached_role[ROLERECURSE_MEMBERS] = InvalidOid; } - -/* Check if specified role has rolinherit set */ -static bool -has_rolinherit(Oid roleid) -{ - bool result = false; - HeapTuple utup; - - utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); - if (HeapTupleIsValid(utup)) - { - result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit; - ReleaseSysCache(utup); - } - return result; -} - - /* * Get a list of roles that the specified roleid is a member of * - * Type ROLERECURSE_PRIVS recurses only through roles that have rolinherit - * set, while ROLERECURSE_MEMBERS recurses through all roles. + * Type ROLERECURSE_PRIVS recurses only through inheritable grants, + * while ROLERECURSE_MEMBERS recurses through all grants. * * Since indirect membership testing is relatively expensive, we cache * a list of memberships. Hence, the result is only guaranteed good until @@ -4861,23 +4843,24 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type, CatCList *memlist; int i; - if (type == ROLERECURSE_PRIVS && !has_rolinherit(memberid)) - continue; /* ignore non-inheriting roles */ - /* Find roles that memberid is directly a member of */ memlist = SearchSysCacheList1(AUTHMEMMEMROLE, ObjectIdGetDatum(memberid)); for (i = 0; i < memlist->n_members; i++) { HeapTuple tup = &memlist->members[i]->tuple; - Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid; + Form_pg_auth_members form = (Form_pg_auth_members) GETSTRUCT(tup); + Oid otherid = form->roleid; + + /* If we're supposed to ignore non-heritable grants, do so. */ + if (type == ROLERECURSE_PRIVS && !form->inherit_option) + continue; /* * While otherid==InvalidOid shouldn't appear in the catalog, the * OidIsValid() avoids crashing if that arises. */ - if (otherid == admin_of && - ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option && + if (otherid == admin_of && form->admin_option && OidIsValid(admin_of) && !OidIsValid(*admin_role)) *admin_role = memberid; @@ -4920,8 +4903,8 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type, /* * Does member have the privileges of role (directly or indirectly)? * - * This is defined not to recurse through roles that don't have rolinherit - * set; for such roles, membership implies the ability to do SET ROLE, but + * This is defined not to recurse through grants that are not inherited; + * in such cases, membership implies the ability to do SET ROLE, but * the privileges are not available until you've done so. */ bool @@ -4948,7 +4931,7 @@ has_privs_of_role(Oid member, Oid role) /* * Is member a member of role (directly or indirectly)? * - * This is defined to recurse through roles regardless of rolinherit. + * This is defined to recurse through grants whether they are inherited or not. * * Do not use this for privilege checking, instead use has_privs_of_role() */ diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index e8a2bfa6bd1..68c455f84b2 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -947,11 +947,14 @@ static void dumpRoleMembership(PGconn *conn) { PQExpBuffer buf = createPQExpBuffer(); + PQExpBuffer optbuf = createPQExpBuffer(); PGresult *res; int start = 0, end, total; bool dump_grantors; + bool dump_inherit_option; + int i_inherit_option; /* * Previous versions of PostgreSQL didn't used to track the grantor very @@ -962,19 +965,28 @@ dumpRoleMembership(PGconn *conn) */ dump_grantors = (PQserverVersion(conn) >= 160000); + /* + * Previous versions of PostgreSQL also did not have a grant-level + * INHERIT option. + */ + dump_inherit_option = (server_version >= 160000); + /* Generate and execute query. */ printfPQExpBuffer(buf, "SELECT ur.rolname AS role, " "um.rolname AS member, " "ug.oid AS grantorid, " "ug.rolname AS grantor, " - "a.admin_option " - "FROM pg_auth_members a " + "a.admin_option"); + if (dump_inherit_option) + appendPQExpBuffer(buf, ", a.inherit_option"); + appendPQExpBuffer(buf, " FROM pg_auth_members a " "LEFT JOIN %s ur on ur.oid = a.roleid " "LEFT JOIN %s um on um.oid = a.member " "LEFT JOIN %s ug on ug.oid = a.grantor " "WHERE NOT (ur.rolname ~ '^pg_' AND um.rolname ~ '^pg_')" "ORDER BY 1,2,4", role_catalog, role_catalog, role_catalog); res = executeQuery(conn, buf->data); + i_inherit_option = PQfnumber(res, "inherit_option"); if (PQntuples(res) > 0) fprintf(OPF, "--\n-- Role memberships\n--\n\n"); @@ -1079,10 +1091,24 @@ dumpRoleMembership(PGconn *conn) rolename_insert(ht, member, &found); /* Generate the actual GRANT statement. */ + resetPQExpBuffer(optbuf); fprintf(OPF, "GRANT %s", fmtId(role)); fprintf(OPF, " TO %s", fmtId(member)); if (*admin_option == 't') - fprintf(OPF, " WITH ADMIN OPTION"); + appendPQExpBufferStr(optbuf, "ADMIN OPTION"); + if (dump_inherit_option) + { + char *inherit_option; + + if (optbuf->data[0] != '\0') + appendPQExpBufferStr(optbuf, ", "); + inherit_option = PQgetvalue(res, i, i_inherit_option); + appendPQExpBuffer(optbuf, "INHERIT %s", + *inherit_option == 't' ? + "TRUE" : "FALSE"); + } + if (optbuf->data[0] != '\0') + fprintf(OPF, " WITH %s", optbuf->data); if (dump_grantors) fprintf(OPF, " GRANTED BY %s", fmtId(grantor)); fprintf(OPF, ";\n"); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index b227c7a3377..7482c85a86c 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202208221 +#define CATALOG_VERSION_NO 202208251 #endif diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h index e57ec4f810c..3ee6ae5f6a4 100644 --- a/src/include/catalog/pg_auth_members.h +++ b/src/include/catalog/pg_auth_members.h @@ -34,6 +34,7 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_ Oid member BKI_LOOKUP(pg_authid); /* ID of a member of that role */ Oid grantor BKI_LOOKUP(pg_authid); /* who granted the membership */ bool admin_option; /* granted with admin option? */ + bool inherit_option; /* exercise privileges without SET ROLE? */ } FormData_pg_auth_members; /* ---------------- diff --git a/src/include/commands/user.h b/src/include/commands/user.h index d3dd8303d28..54c720d8801 100644 --- a/src/include/commands/user.h +++ b/src/include/commands/user.h @@ -28,7 +28,7 @@ extern Oid CreateRole(ParseState *pstate, CreateRoleStmt *stmt); extern Oid AlterRole(ParseState *pstate, AlterRoleStmt *stmt); extern Oid AlterRoleSet(AlterRoleSetStmt *stmt); extern void DropRole(DropRoleStmt *stmt); -extern void GrantRole(GrantRoleStmt *stmt); +extern void GrantRole(ParseState *pstate, GrantRoleStmt *stmt); extern ObjectAddress RenameRole(const char *oldname, const char *newname); extern void DropOwnedObjects(DropOwnedStmt *stmt); extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index b3760318562..469a5c46f62 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2451,7 +2451,7 @@ typedef struct GrantRoleStmt List *granted_roles; /* list of roles to be granted/revoked */ List *grantee_roles; /* list of member roles to add/delete */ bool is_grant; /* true = GRANT, false = REVOKE */ - bool admin_opt; /* with admin option */ + List *opt; /* options e.g. WITH GRANT OPTION */ RoleSpec *grantor; /* set grantor to other than current role */ DropBehavior behavior; /* drop behavior (for REVOKE) */ } GrantRoleStmt; diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out index 0154a09262e..527c9d30a0b 100644 --- a/src/test/regress/expected/privileges.out +++ b/src/test/regress/expected/privileges.out @@ -517,7 +517,19 @@ BEGIN; RESET SESSION AUTHORIZATION; ALTER ROLE regress_priv_user1 NOINHERIT; SET SESSION AUTHORIZATION regress_priv_user1; -DELETE FROM atest3; +SAVEPOINT s1; +DELETE FROM atest3; -- ok because grant-level option is unchanged +ROLLBACK TO s1; +RESET SESSION AUTHORIZATION; +GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE; +SET SESSION AUTHORIZATION regress_priv_user1; +DELETE FROM atest3; -- fail +ERROR: permission denied for table atest3 +ROLLBACK TO s1; +RESET SESSION AUTHORIZATION; +REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1; +SET SESSION AUTHORIZATION regress_priv_user1; +DELETE FROM atest3; -- also fail ERROR: permission denied for table atest3 ROLLBACK; -- views diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index b4ef20f738e..5dae42c2a98 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -351,7 +351,18 @@ BEGIN; RESET SESSION AUTHORIZATION; ALTER ROLE regress_priv_user1 NOINHERIT; SET SESSION AUTHORIZATION regress_priv_user1; -DELETE FROM atest3; +SAVEPOINT s1; +DELETE FROM atest3; -- ok because grant-level option is unchanged +ROLLBACK TO s1; +RESET SESSION AUTHORIZATION; +GRANT regress_priv_group2 TO regress_priv_user1 WITH INHERIT FALSE; +SET SESSION AUTHORIZATION regress_priv_user1; +DELETE FROM atest3; -- fail +ROLLBACK TO s1; +RESET SESSION AUTHORIZATION; +REVOKE INHERIT OPTION FOR regress_priv_group2 FROM regress_priv_user1; +SET SESSION AUTHORIZATION regress_priv_user1; +DELETE FROM atest3; -- also fail ROLLBACK; -- views |