aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/acl.c')
-rw-r--r--src/backend/utils/adt/acl.c106
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