diff options
Diffstat (limited to 'src/backend/utils/adt/acl.c')
-rw-r--r-- | src/backend/utils/adt/acl.c | 106 |
1 files changed, 80 insertions, 26 deletions
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 8bdb9461b7f..d4d68f97243 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -67,16 +67,17 @@ typedef struct * * Each element of cached_roles is an OID list of constituent roles for the * corresponding element of cached_role (always including the cached_role - * itself). One cache has ROLERECURSE_PRIVS semantics, and the other has - * ROLERECURSE_MEMBERS semantics. + * itself). There's a separate cache for each RoleRecurseType, with the + * corresponding semantics. */ enum RoleRecurseType { - ROLERECURSE_PRIVS = 0, /* recurse through inheritable grants */ - ROLERECURSE_MEMBERS = 1 /* recurse unconditionally */ + ROLERECURSE_MEMBERS = 0, /* recurse unconditionally */ + ROLERECURSE_PRIVS = 1, /* recurse through inheritable grants */ + ROLERECURSE_SETROLE = 2 /* recurse through grants with set_option */ }; -static Oid cached_role[] = {InvalidOid, InvalidOid}; -static List *cached_roles[] = {NIL, NIL}; +static Oid cached_role[] = {InvalidOid, InvalidOid, InvalidOid}; +static List *cached_roles[] = {NIL, NIL, NIL}; static uint32 cached_db_hash; @@ -4691,10 +4692,13 @@ convert_role_priv_string(text *priv_type_text) static const priv_map role_priv_map[] = { {"USAGE", ACL_USAGE}, {"MEMBER", ACL_CREATE}, + {"SET", ACL_SET}, {"USAGE WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"USAGE WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"MEMBER WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {"MEMBER WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"SET WITH GRANT OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, + {"SET WITH ADMIN OPTION", ACL_GRANT_OPTION_FOR(ACL_CREATE)}, {NULL, 0} }; @@ -4723,6 +4727,11 @@ pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode) if (has_privs_of_role(roleid, role_oid)) return ACLCHECK_OK; } + if (mode & ACL_SET) + { + if (member_can_set_role(roleid, role_oid)) + return ACLCHECK_OK; + } return ACLCHECK_NO_PRIV; } @@ -4771,15 +4780,17 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue) } /* Force membership caches to be recomputed on next use */ - cached_role[ROLERECURSE_PRIVS] = InvalidOid; cached_role[ROLERECURSE_MEMBERS] = InvalidOid; + cached_role[ROLERECURSE_PRIVS] = InvalidOid; + cached_role[ROLERECURSE_SETROLE] = InvalidOid; } /* * Get a list of roles that the specified roleid is a member of * - * Type ROLERECURSE_PRIVS recurses only through inheritable grants, - * while ROLERECURSE_MEMBERS recurses through all grants. + * Type ROLERECURSE_MEMBERS recurses through all grants; ROLERECURSE_PRIVS + * recurses only through inheritable grants; and ROLERECURSE_SETROLe recurses + * only through grants with set_option. * * Since indirect membership testing is relatively expensive, we cache * a list of memberships. Hence, the result is only guaranteed good until @@ -4870,6 +4881,10 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type, if (type == ROLERECURSE_PRIVS && !form->inherit_option) continue; + /* If we're supposed to ignore non-SET grants, do so. */ + if (type == ROLERECURSE_SETROLE && !form->set_option) + continue; + /* * Even though there shouldn't be any loops in the membership * graph, we must test for having already seen this role. It is @@ -4909,9 +4924,10 @@ 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 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. + * This is defined not to recurse through grants that are not inherited, + * and only inherited grants confer the associated privileges automatically. + * + * See also member_can_set_role, below. */ bool has_privs_of_role(Oid member, Oid role) @@ -4933,49 +4949,87 @@ has_privs_of_role(Oid member, Oid role) role); } - /* - * Is member a member of role (directly or indirectly)? + * Can member use SET ROLE to this role? * - * This is defined to recurse through grants whether they are inherited or not. + * There must be a chain of grants from 'member' to 'role' each of which + * permits SET ROLE; that is, each of which has set_option = true. * - * Do not use this for privilege checking, instead use has_privs_of_role() + * It doesn't matter whether the grants are inheritable. That's a separate + * question; see has_privs_of_role. + * + * This function should be used to determine whether the session user can + * use SET ROLE to become the target user. We also use it to determine whether + * the session user can change an existing object to be owned by the target + * user, or create new objects owned by the target user. */ bool -is_member_of_role(Oid member, Oid role) +member_can_set_role(Oid member, Oid role) { /* Fast path for simple case */ if (member == role) return true; - /* Superusers have every privilege, so are part of every role */ + /* Superusers have every privilege, so can always SET ROLE */ if (superuser_arg(member)) return true; /* - * Find all the roles that member is a member of, including multi-level - * recursion, then see if target role is any one of them. + * Find all the roles that member can access via SET ROLE, including + * multi-level recursion, then see if target role is any one of them. */ - return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS, + return list_member_oid(roles_is_member_of(member, ROLERECURSE_SETROLE, InvalidOid, NULL), role); } /* - * check_is_member_of_role - * is_member_of_role with a standard permission-violation error if not + * Permission violation eror unless able to SET ROLE to target role. */ void -check_is_member_of_role(Oid member, Oid role) +check_can_set_role(Oid member, Oid role) { - if (!is_member_of_role(member, role)) + if (!member_can_set_role(member, role)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be member of role \"%s\"", + errmsg("must be able to SET ROLE \"%s\"", GetUserNameFromId(role, false)))); } /* + * Is member a member of role (directly or indirectly)? + * + * 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(). + * Don't use it for determining whether it's possible to SET ROLE to some + * other role; for that, use member_can_set_role(). And don't use it for + * determining whether it's OK to create an object owned by some other role: + * use member_can_set_role() for that, too. + * + * In short, calling this function is the wrong thing to do nearly everywhere. + */ +bool +is_member_of_role(Oid member, Oid role) +{ + /* Fast path for simple case */ + if (member == role) + return true; + + /* Superusers have every privilege, so are part of every role */ + if (superuser_arg(member)) + return true; + + /* + * Find all the roles that member is a member of, including multi-level + * recursion, then see if target role is any one of them. + */ + return list_member_oid(roles_is_member_of(member, ROLERECURSE_MEMBERS, + InvalidOid, NULL), + role); +} + +/* * Is member a member of role, not considering superuserness? * * This is identical to is_member_of_role except we ignore superuser |