aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/alter.c2
-rw-r--r--src/backend/commands/dbcommands.c4
-rw-r--r--src/backend/commands/foreigncmds.c2
-rw-r--r--src/backend/commands/publicationcmds.c2
-rw-r--r--src/backend/commands/schemacmds.c4
-rw-r--r--src/backend/commands/tablecmds.c2
-rw-r--r--src/backend/commands/typecmds.c2
-rw-r--r--src/backend/commands/user.c44
-rw-r--r--src/backend/commands/variable.c2
-rw-r--r--src/backend/utils/adt/acl.c106
-rw-r--r--src/bin/pg_dump/pg_dumpall.c23
-rw-r--r--src/include/catalog/pg_auth_members.h1
-rw-r--r--src/include/utils/acl.h3
-rw-r--r--src/test/regress/expected/alter_generic.out38
-rw-r--r--src/test/regress/expected/foreign_data.out2
-rw-r--r--src/test/regress/expected/privileges.out41
-rw-r--r--src/test/regress/sql/privileges.sql39
17 files changed, 251 insertions, 66 deletions
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index b2089d785b6..10b6fe19a2c 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -999,7 +999,7 @@ AlterObjectOwner_internal(Relation rel, Oid objectId, Oid new_ownerId)
objname);
}
/* Must be able to become new owner */
- check_is_member_of_role(GetUserId(), new_ownerId);
+ check_can_set_role(GetUserId(), new_ownerId);
/* New owner must have CREATE privilege on namespace */
if (OidIsValid(namespaceId))
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index a67ea86619c..6eb87427181 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -941,7 +941,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to create database")));
- check_is_member_of_role(GetUserId(), datdba);
+ check_can_set_role(GetUserId(), datdba);
/*
* Lookup database (template) to be cloned, and obtain share lock on it.
@@ -2495,7 +2495,7 @@ AlterDatabaseOwner(const char *dbname, Oid newOwnerId)
dbname);
/* Must be able to become new owner */
- check_is_member_of_role(GetUserId(), newOwnerId);
+ check_can_set_role(GetUserId(), newOwnerId);
/*
* must have createdb rights
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index 55b0be9e1d1..28b5c75f110 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -363,7 +363,7 @@ AlterForeignServerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
NameStr(form->srvname));
/* Must be able to become new owner */
- check_is_member_of_role(GetUserId(), newOwnerId);
+ check_can_set_role(GetUserId(), newOwnerId);
/* New owner must have USAGE privilege on foreign-data wrapper */
aclresult = object_aclcheck(ForeignDataWrapperRelationId, form->srvfdw, newOwnerId, ACL_USAGE);
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 940655b9be0..20fa72c5c80 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -1911,7 +1911,7 @@ AlterPublicationOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
NameStr(form->pubname));
/* Must be able to become new owner */
- check_is_member_of_role(GetUserId(), newOwnerId);
+ check_can_set_role(GetUserId(), newOwnerId);
/* New owner must have CREATE privilege on database */
aclresult = object_aclcheck(DatabaseRelationId, MyDatabaseId, newOwnerId, ACL_CREATE);
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index b03f07a2322..12cbfba7d06 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -97,7 +97,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString,
aclcheck_error(aclresult, OBJECT_DATABASE,
get_database_name(MyDatabaseId));
- check_is_member_of_role(saved_uid, owner_uid);
+ check_can_set_role(saved_uid, owner_uid);
/* Additional check to protect reserved schema names */
if (!allowSystemTableMods && IsReservedName(schemaName))
@@ -370,7 +370,7 @@ AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
NameStr(nspForm->nspname));
/* Must be able to become new owner */
- check_is_member_of_role(GetUserId(), newOwnerId);
+ check_can_set_role(GetUserId(), newOwnerId);
/*
* must have create-schema rights
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f0068078520..845208d662b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -13833,7 +13833,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
RelationGetRelationName(target_rel));
/* Must be able to become new owner */
- check_is_member_of_role(GetUserId(), newOwnerId);
+ check_can_set_role(GetUserId(), newOwnerId);
/* New owner must have CREATE privilege on namespace */
aclresult = object_aclcheck(NamespaceRelationId, namespaceOid, newOwnerId,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index ecc8b3f44c9..7770a86bee0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3745,7 +3745,7 @@ AlterTypeOwner(List *names, Oid newOwnerId, ObjectType objecttype)
aclcheck_error_type(ACLCHECK_NOT_OWNER, typTup->oid);
/* Must be able to become new owner */
- check_is_member_of_role(GetUserId(), newOwnerId);
+ check_can_set_role(GetUserId(), newOwnerId);
/* New owner must have CREATE privilege on namespace */
aclresult = object_aclcheck(NamespaceRelationId, typTup->typnamespace,
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2369cc600c7..8b6543edee2 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -51,8 +51,8 @@
* 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.
+ * Similarly, RRG_REMOVE_INHERIT_OPTION and RRG_REMOVE_SET_OPTION indicate
+ * grants that would need to have the corresponding options set to false.
*
* RRG_DELETE_GRANT indicates a grant that would need to be removed entirely
* by the operation.
@@ -62,6 +62,7 @@ typedef enum
RRG_NOOP,
RRG_REMOVE_ADMIN_OPTION,
RRG_REMOVE_INHERIT_OPTION,
+ RRG_REMOVE_SET_OPTION,
RRG_DELETE_GRANT
} RevokeRoleGrantAction;
@@ -73,10 +74,12 @@ typedef struct
unsigned specified;
bool admin;
bool inherit;
+ bool set;
} GrantRoleOptions;
#define GRANT_ROLE_SPECIFIED_ADMIN 0x0001
#define GRANT_ROLE_SPECIFIED_INHERIT 0x0002
+#define GRANT_ROLE_SPECIFIED_SET 0x0004
/* GUC parameter */
int Password_encryption = PASSWORD_TYPE_SCRAM_SHA_256;
@@ -1389,6 +1392,12 @@ GrantRole(ParseState *pstate, GrantRoleStmt *stmt)
if (parse_bool(optval, &popt.inherit))
continue;
}
+ else if (strcmp(opt->defname, "set") == 0)
+ {
+ popt.specified |= GRANT_ROLE_SPECIFIED_SET;
+ if (parse_bool(optval, &popt.set))
+ continue;
+ }
else
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
@@ -1776,6 +1785,16 @@ AddRoleMems(const char *rolename, Oid roleid,
at_least_one_change = true;
}
+ if ((popt->specified & GRANT_ROLE_SPECIFIED_SET) != 0
+ && authmem_form->set_option != popt->set)
+ {
+ new_record[Anum_pg_auth_members_set_option - 1] =
+ BoolGetDatum(popt->set);
+ new_record_repl[Anum_pg_auth_members_set_option - 1] =
+ true;
+ at_least_one_change = true;
+ }
+
if (!at_least_one_change)
{
ereport(NOTICE,
@@ -1798,9 +1817,15 @@ AddRoleMems(const char *rolename, Oid roleid,
Oid objectId;
Oid *newmembers = palloc(sizeof(Oid));
- /* Set admin option if user set it to true, otherwise not. */
+ /*
+ * The values for these options can be taken directly from 'popt'.
+ * Either they were specified, or the defaults as set by
+ * InitGrantRoleOptions are correct.
+ */
new_record[Anum_pg_auth_members_admin_option - 1] =
BoolGetDatum(popt->admin);
+ new_record[Anum_pg_auth_members_set_option - 1] =
+ BoolGetDatum(popt->set);
/*
* If the user specified a value for the inherit option, use
@@ -1989,6 +2014,13 @@ DelRoleMems(const char *rolename, Oid roleid,
new_record_repl[Anum_pg_auth_members_inherit_option - 1] =
true;
}
+ else if (actions[i] == RRG_REMOVE_SET_OPTION)
+ {
+ new_record[Anum_pg_auth_members_set_option - 1] =
+ BoolGetDatum(false);
+ new_record_repl[Anum_pg_auth_members_set_option - 1] =
+ true;
+ }
else
elog(ERROR, "unknown role revoke action");
@@ -2182,6 +2214,11 @@ plan_single_revoke(CatCList *memlist, RevokeRoleGrantAction *actions,
*/
actions[i] = RRG_REMOVE_INHERIT_OPTION;
}
+ else if ((popt->specified & GRANT_ROLE_SPECIFIED_SET) != 0)
+ {
+ /* Here too, no need to recurse. */
+ actions[i] = RRG_REMOVE_SET_OPTION;
+ }
else
{
bool revoke_admin_option_only;
@@ -2331,4 +2368,5 @@ InitGrantRoleOptions(GrantRoleOptions *popt)
popt->specified = 0;
popt->admin = false;
popt->inherit = false;
+ popt->set = true;
}
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 791bac6715c..00d8d54d820 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -939,7 +939,7 @@ check_role(char **newval, void **extra, GucSource source)
* leader's state.
*/
if (!InitializingParallelWorker &&
- !is_member_of_role(GetSessionUserId(), roleid))
+ !member_can_set_role(GetSessionUserId(), roleid))
{
if (source == PGC_S_TEST)
{
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
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 083012ca39d..76a186b6394 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -955,8 +955,9 @@ dumpRoleMembership(PGconn *conn)
end,
total;
bool dump_grantors;
- bool dump_inherit_option;
+ bool dump_grant_options;
int i_inherit_option;
+ int i_set_option;
/*
* Previous versions of PostgreSQL didn't used to track the grantor very
@@ -968,10 +969,10 @@ dumpRoleMembership(PGconn *conn)
dump_grantors = (PQserverVersion(conn) >= 160000);
/*
- * Previous versions of PostgreSQL also did not have a grant-level
+ * Previous versions of PostgreSQL also did not have grant-level options.
* INHERIT option.
*/
- dump_inherit_option = (server_version >= 160000);
+ dump_grant_options = (server_version >= 160000);
/* Generate and execute query. */
printfPQExpBuffer(buf, "SELECT ur.rolname AS role, "
@@ -979,8 +980,8 @@ dumpRoleMembership(PGconn *conn)
"ug.oid AS grantorid, "
"ug.rolname AS grantor, "
"a.admin_option");
- if (dump_inherit_option)
- appendPQExpBufferStr(buf, ", a.inherit_option");
+ if (dump_grant_options)
+ appendPQExpBufferStr(buf, ", a.inherit_option, a.set_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 "
@@ -989,6 +990,7 @@ dumpRoleMembership(PGconn *conn)
"ORDER BY 1,2,4", role_catalog, role_catalog, role_catalog);
res = executeQuery(conn, buf->data);
i_inherit_option = PQfnumber(res, "inherit_option");
+ i_set_option = PQfnumber(res, "set_option");
if (PQntuples(res) > 0)
fprintf(OPF, "--\n-- Role memberships\n--\n\n");
@@ -1059,6 +1061,7 @@ dumpRoleMembership(PGconn *conn)
char *admin_option;
char *grantorid;
char *grantor;
+ char *set_option = "true";
bool found;
/* If we already did this grant, don't do it again. */
@@ -1069,6 +1072,8 @@ dumpRoleMembership(PGconn *conn)
grantorid = PQgetvalue(res, i, 2);
grantor = PQgetvalue(res, i, 3);
admin_option = PQgetvalue(res, i, 4);
+ if (dump_grant_options)
+ set_option = PQgetvalue(res, i, i_set_option);
/*
* If we're not dumping grantors or if the grantor is the
@@ -1098,7 +1103,7 @@ dumpRoleMembership(PGconn *conn)
fprintf(OPF, " TO %s", fmtId(member));
if (*admin_option == 't')
appendPQExpBufferStr(optbuf, "ADMIN OPTION");
- if (dump_inherit_option)
+ if (dump_grant_options)
{
char *inherit_option;
@@ -1109,6 +1114,12 @@ dumpRoleMembership(PGconn *conn)
*inherit_option == 't' ?
"TRUE" : "FALSE");
}
+ if (*set_option != 't')
+ {
+ if (optbuf->data[0] != '\0')
+ appendPQExpBufferStr(optbuf, ", ");
+ appendPQExpBuffer(optbuf, "SET FALSE");
+ }
if (optbuf->data[0] != '\0')
fprintf(OPF, " WITH %s", optbuf->data);
if (dump_grantors)
diff --git a/src/include/catalog/pg_auth_members.h b/src/include/catalog/pg_auth_members.h
index 3ee6ae5f6a4..b145fce1ed8 100644
--- a/src/include/catalog/pg_auth_members.h
+++ b/src/include/catalog/pg_auth_members.h
@@ -35,6 +35,7 @@ CATALOG(pg_auth_members,1261,AuthMemRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_
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? */
+ bool set_option; /* use SET ROLE to the target role? */
} FormData_pg_auth_members;
/* ----------------
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index 35b3d8dd88b..afbfdccf53c 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -209,11 +209,12 @@ extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId,
extern int aclmembers(const Acl *acl, Oid **roleids);
extern bool has_privs_of_role(Oid member, Oid role);
+extern bool member_can_set_role(Oid member, Oid role);
+extern void check_can_set_role(Oid member, Oid role);
extern bool is_member_of_role(Oid member, Oid role);
extern bool is_member_of_role_nosuper(Oid member, Oid role);
extern bool is_admin_of_role(Oid member, Oid role);
extern Oid select_best_admin(Oid member, Oid role);
-extern void check_is_member_of_role(Oid member, Oid role);
extern Oid get_role_oid(const char *rolname, bool missing_ok);
extern Oid get_role_oid_or_public(const char *rolname);
extern Oid get_rolespec_oid(const RoleSpec *role, bool missing_ok);
diff --git a/src/test/regress/expected/alter_generic.out b/src/test/regress/expected/alter_generic.out
index 54d3fe5764d..ae54cb254f9 100644
--- a/src/test/regress/expected/alter_generic.out
+++ b/src/test/regress/expected/alter_generic.out
@@ -46,7 +46,7 @@ ALTER FUNCTION alt_func1(int) RENAME TO alt_func2; -- failed (name conflict)
ERROR: function alt_func2(integer) already exists in schema "alt_nsp1"
ALTER FUNCTION alt_func1(int) RENAME TO alt_func3; -- OK
ALTER FUNCTION alt_func2(int) OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER FUNCTION alt_func2(int) OWNER TO regress_alter_generic_user3; -- OK
ALTER FUNCTION alt_func2(int) SET SCHEMA alt_nsp1; -- OK, already there
ALTER FUNCTION alt_func2(int) SET SCHEMA alt_nsp2; -- OK
@@ -54,7 +54,7 @@ ALTER AGGREGATE alt_agg1(int) RENAME TO alt_agg2; -- failed (name conflict)
ERROR: function alt_agg2(integer) already exists in schema "alt_nsp1"
ALTER AGGREGATE alt_agg1(int) RENAME TO alt_agg3; -- OK
ALTER AGGREGATE alt_agg2(int) OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER AGGREGATE alt_agg2(int) OWNER TO regress_alter_generic_user3; -- OK
ALTER AGGREGATE alt_agg2(int) SET SCHEMA alt_nsp2; -- OK
SET SESSION AUTHORIZATION regress_alter_generic_user2;
@@ -74,7 +74,7 @@ ALTER FUNCTION alt_func1(int) RENAME TO alt_func4; -- OK
ALTER FUNCTION alt_func3(int) OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of function alt_func3
ALTER FUNCTION alt_func2(int) OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER FUNCTION alt_func3(int) SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of function alt_func3
ALTER FUNCTION alt_func2(int) SET SCHEMA alt_nsp2; -- failed (name conflicts)
@@ -85,7 +85,7 @@ ALTER AGGREGATE alt_agg1(int) RENAME TO alt_agg4; -- OK
ALTER AGGREGATE alt_agg3(int) OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of function alt_agg3
ALTER AGGREGATE alt_agg2(int) OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER AGGREGATE alt_agg3(int) SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of function alt_agg3
ALTER AGGREGATE alt_agg2(int) SET SCHEMA alt_nsp2; -- failed (name conflict)
@@ -122,7 +122,7 @@ ALTER CONVERSION alt_conv1 RENAME TO alt_conv2; -- failed (name conflict)
ERROR: conversion "alt_conv2" already exists in schema "alt_nsp1"
ALTER CONVERSION alt_conv1 RENAME TO alt_conv3; -- OK
ALTER CONVERSION alt_conv2 OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER CONVERSION alt_conv2 OWNER TO regress_alter_generic_user3; -- OK
ALTER CONVERSION alt_conv2 SET SCHEMA alt_nsp2; -- OK
SET SESSION AUTHORIZATION regress_alter_generic_user2;
@@ -134,7 +134,7 @@ ALTER CONVERSION alt_conv1 RENAME TO alt_conv4; -- OK
ALTER CONVERSION alt_conv3 OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of conversion alt_conv3
ALTER CONVERSION alt_conv2 OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER CONVERSION alt_conv3 SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of conversion alt_conv3
ALTER CONVERSION alt_conv2 SET SCHEMA alt_nsp2; -- failed (name conflict)
@@ -196,7 +196,7 @@ ALTER LANGUAGE alt_lang1 RENAME TO alt_lang3; -- OK
ALTER LANGUAGE alt_lang2 OWNER TO regress_alter_generic_user3; -- failed (not owner)
ERROR: must be owner of language alt_lang2
ALTER LANGUAGE alt_lang3 OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER LANGUAGE alt_lang3 OWNER TO regress_alter_generic_user3; -- OK
RESET SESSION AUTHORIZATION;
SELECT lanname, a.rolname
@@ -216,7 +216,7 @@ SET SESSION AUTHORIZATION regress_alter_generic_user1;
CREATE OPERATOR @-@ ( leftarg = int4, rightarg = int4, procedure = int4mi );
CREATE OPERATOR @+@ ( leftarg = int4, rightarg = int4, procedure = int4pl );
ALTER OPERATOR @+@(int4, int4) OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER OPERATOR @+@(int4, int4) OWNER TO regress_alter_generic_user3; -- OK
ALTER OPERATOR @-@(int4, int4) SET SCHEMA alt_nsp2; -- OK
SET SESSION AUTHORIZATION regress_alter_generic_user2;
@@ -224,7 +224,7 @@ CREATE OPERATOR @-@ ( leftarg = int4, rightarg = int4, procedure = int4mi );
ALTER OPERATOR @+@(int4, int4) OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of operator @+@
ALTER OPERATOR @-@(int4, int4) OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER OPERATOR @+@(int4, int4) SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of operator @+@
-- can't test this: the error message includes the raw oid of namespace
@@ -259,14 +259,14 @@ ALTER OPERATOR FAMILY alt_opf1 USING hash RENAME TO alt_opf2; -- failed (name c
ERROR: operator family "alt_opf2" for access method "hash" already exists in schema "alt_nsp1"
ALTER OPERATOR FAMILY alt_opf1 USING hash RENAME TO alt_opf3; -- OK
ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user3; -- OK
ALTER OPERATOR FAMILY alt_opf2 USING hash SET SCHEMA alt_nsp2; -- OK
ALTER OPERATOR CLASS alt_opc1 USING hash RENAME TO alt_opc2; -- failed (name conflict)
ERROR: operator class "alt_opc2" for access method "hash" already exists in schema "alt_nsp1"
ALTER OPERATOR CLASS alt_opc1 USING hash RENAME TO alt_opc3; -- OK
ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user3; -- OK
ALTER OPERATOR CLASS alt_opc2 USING hash SET SCHEMA alt_nsp2; -- OK
RESET SESSION AUTHORIZATION;
@@ -285,7 +285,7 @@ ALTER OPERATOR FAMILY alt_opf1 USING hash RENAME TO alt_opf4; -- OK
ALTER OPERATOR FAMILY alt_opf3 USING hash OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of operator family alt_opf3
ALTER OPERATOR FAMILY alt_opf2 USING hash OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER OPERATOR FAMILY alt_opf3 USING hash SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of operator family alt_opf3
ALTER OPERATOR FAMILY alt_opf2 USING hash SET SCHEMA alt_nsp2; -- failed (name conflict)
@@ -296,7 +296,7 @@ ALTER OPERATOR CLASS alt_opc1 USING hash RENAME TO alt_opc4; -- OK
ALTER OPERATOR CLASS alt_opc3 USING hash OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of operator class alt_opc3
ALTER OPERATOR CLASS alt_opc2 USING hash OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER OPERATOR CLASS alt_opc3 USING hash SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of operator class alt_opc3
ALTER OPERATOR CLASS alt_opc2 USING hash SET SCHEMA alt_nsp2; -- failed (name conflict)
@@ -531,7 +531,7 @@ ALTER STATISTICS alt_stat1 RENAME TO alt_stat2; -- failed (name conflict)
ERROR: statistics object "alt_stat2" already exists in schema "alt_nsp1"
ALTER STATISTICS alt_stat1 RENAME TO alt_stat3; -- OK
ALTER STATISTICS alt_stat2 OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER STATISTICS alt_stat2 OWNER TO regress_alter_generic_user3; -- OK
ALTER STATISTICS alt_stat2 SET SCHEMA alt_nsp2; -- OK
SET SESSION AUTHORIZATION regress_alter_generic_user2;
@@ -544,7 +544,7 @@ ALTER STATISTICS alt_stat1 RENAME TO alt_stat4; -- OK
ALTER STATISTICS alt_stat3 OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of statistics object alt_stat3
ALTER STATISTICS alt_stat2 OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER STATISTICS alt_stat3 SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of statistics object alt_stat3
ALTER STATISTICS alt_stat2 SET SCHEMA alt_nsp2; -- failed (name conflict)
@@ -573,7 +573,7 @@ ALTER TEXT SEARCH DICTIONARY alt_ts_dict1 RENAME TO alt_ts_dict2; -- failed (na
ERROR: text search dictionary "alt_ts_dict2" already exists in schema "alt_nsp1"
ALTER TEXT SEARCH DICTIONARY alt_ts_dict1 RENAME TO alt_ts_dict3; -- OK
ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 OWNER TO regress_alter_generic_user3; -- OK
ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 SET SCHEMA alt_nsp2; -- OK
SET SESSION AUTHORIZATION regress_alter_generic_user2;
@@ -585,7 +585,7 @@ ALTER TEXT SEARCH DICTIONARY alt_ts_dict1 RENAME TO alt_ts_dict4; -- OK
ALTER TEXT SEARCH DICTIONARY alt_ts_dict3 OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of text search dictionary alt_ts_dict3
ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER TEXT SEARCH DICTIONARY alt_ts_dict3 SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of text search dictionary alt_ts_dict3
ALTER TEXT SEARCH DICTIONARY alt_ts_dict2 SET SCHEMA alt_nsp2; -- failed (name conflict)
@@ -614,7 +614,7 @@ ALTER TEXT SEARCH CONFIGURATION alt_ts_conf1 RENAME TO alt_ts_conf2; -- failed
ERROR: text search configuration "alt_ts_conf2" already exists in schema "alt_nsp1"
ALTER TEXT SEARCH CONFIGURATION alt_ts_conf1 RENAME TO alt_ts_conf3; -- OK
ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 OWNER TO regress_alter_generic_user2; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user2"
+ERROR: must be able to SET ROLE "regress_alter_generic_user2"
ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 OWNER TO regress_alter_generic_user3; -- OK
ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 SET SCHEMA alt_nsp2; -- OK
SET SESSION AUTHORIZATION regress_alter_generic_user2;
@@ -626,7 +626,7 @@ ALTER TEXT SEARCH CONFIGURATION alt_ts_conf1 RENAME TO alt_ts_conf4; -- OK
ALTER TEXT SEARCH CONFIGURATION alt_ts_conf3 OWNER TO regress_alter_generic_user2; -- failed (not owner)
ERROR: must be owner of text search configuration alt_ts_conf3
ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 OWNER TO regress_alter_generic_user3; -- failed (no role membership)
-ERROR: must be member of role "regress_alter_generic_user3"
+ERROR: must be able to SET ROLE "regress_alter_generic_user3"
ALTER TEXT SEARCH CONFIGURATION alt_ts_conf3 SET SCHEMA alt_nsp2; -- failed (not owner)
ERROR: must be owner of text search configuration alt_ts_conf3
ALTER TEXT SEARCH CONFIGURATION alt_ts_conf2 SET SCHEMA alt_nsp2; -- failed (name conflict)
diff --git a/src/test/regress/expected/foreign_data.out b/src/test/regress/expected/foreign_data.out
index 47bf56adbf2..5b30ee49f3e 100644
--- a/src/test/regress/expected/foreign_data.out
+++ b/src/test/regress/expected/foreign_data.out
@@ -442,7 +442,7 @@ ERROR: invalid option "foo"
ALTER SERVER s8 OPTIONS (connect_timeout '30', SET dbname 'db1', DROP host);
SET ROLE regress_test_role;
ALTER SERVER s1 OWNER TO regress_test_indirect; -- ERROR
-ERROR: must be member of role "regress_test_indirect"
+ERROR: must be able to SET ROLE "regress_test_indirect"
RESET ROLE;
GRANT regress_test_indirect TO regress_test_role;
SET ROLE regress_test_role;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index bd3453ee914..a497db94a82 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -132,6 +132,15 @@ SET SESSION AUTHORIZATION regress_priv_user8;
SET ROLE pg_read_all_settings;
RESET ROLE;
RESET SESSION AUTHORIZATION;
+REVOKE SET OPTION FOR pg_read_all_settings FROM regress_priv_user8;
+GRANT pg_read_all_stats TO regress_priv_user8 WITH SET FALSE;
+SET SESSION AUTHORIZATION regress_priv_user8;
+SET ROLE pg_read_all_settings; -- fail, no SET option any more
+ERROR: permission denied to set role "pg_read_all_settings"
+SET ROLE pg_read_all_stats; -- fail, granted without SET option
+ERROR: permission denied to set role "pg_read_all_stats"
+RESET ROLE;
+RESET SESSION AUTHORIZATION;
REVOKE pg_read_all_settings FROM regress_priv_user8;
DROP USER regress_priv_user10;
DROP USER regress_priv_user9;
@@ -2809,3 +2818,35 @@ DROP ROLE regress_group;
DROP ROLE regress_group_direct_manager;
DROP ROLE regress_group_indirect_manager;
DROP ROLE regress_group_member;
+-- test SET and INHERIT options with object ownership changes
+CREATE ROLE regress_roleoption_protagonist;
+CREATE ROLE regress_roleoption_donor;
+CREATE ROLE regress_roleoption_recipient;
+CREATE SCHEMA regress_roleoption;
+GRANT CREATE, USAGE ON SCHEMA regress_roleoption TO PUBLIC;
+GRANT regress_roleoption_donor TO regress_roleoption_protagonist WITH INHERIT TRUE, SET FALSE;
+GRANT regress_roleoption_recipient TO regress_roleoption_protagonist WITH INHERIT FALSE, SET TRUE;
+SET SESSION AUTHORIZATION regress_roleoption_protagonist;
+CREATE TABLE regress_roleoption.t1 (a int);
+CREATE TABLE regress_roleoption.t2 (a int);
+SET SESSION AUTHORIZATION regress_roleoption_donor;
+CREATE TABLE regress_roleoption.t3 (a int);
+SET SESSION AUTHORIZATION regress_roleoption_recipient;
+CREATE TABLE regress_roleoption.t4 (a int);
+SET SESSION AUTHORIZATION regress_roleoption_protagonist;
+ALTER TABLE regress_roleoption.t1 OWNER TO regress_roleoption_donor; -- fails, can't be come donor
+ERROR: must be able to SET ROLE "regress_roleoption_donor"
+ALTER TABLE regress_roleoption.t2 OWNER TO regress_roleoption_recipient; -- works
+ALTER TABLE regress_roleoption.t3 OWNER TO regress_roleoption_protagonist; -- works
+ALTER TABLE regress_roleoption.t4 OWNER TO regress_roleoption_protagonist; -- fails, we don't inherit from recipient
+ERROR: must be owner of table t4
+RESET SESSION AUTHORIZATION;
+DROP TABLE regress_roleoption.t1;
+DROP TABLE regress_roleoption.t2;
+DROP TABLE regress_roleoption.t3;
+DROP TABLE regress_roleoption.t4;
+DROP SCHEMA regress_roleoption;
+DROP ROLE regress_roleoption_recipient;
+DROP ROLE regress_roleoption_donor;
+DROP ROLE regress_roleoption_donor;
+ERROR: role "regress_roleoption_donor" does not exist
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 4ad366470d9..daecf0ec64c 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -115,6 +115,15 @@ SET ROLE pg_read_all_settings;
RESET ROLE;
RESET SESSION AUTHORIZATION;
+REVOKE SET OPTION FOR pg_read_all_settings FROM regress_priv_user8;
+GRANT pg_read_all_stats TO regress_priv_user8 WITH SET FALSE;
+
+SET SESSION AUTHORIZATION regress_priv_user8;
+SET ROLE pg_read_all_settings; -- fail, no SET option any more
+SET ROLE pg_read_all_stats; -- fail, granted without SET option
+RESET ROLE;
+
+RESET SESSION AUTHORIZATION;
REVOKE pg_read_all_settings FROM regress_priv_user8;
DROP USER regress_priv_user10;
@@ -1813,3 +1822,33 @@ DROP ROLE regress_group;
DROP ROLE regress_group_direct_manager;
DROP ROLE regress_group_indirect_manager;
DROP ROLE regress_group_member;
+
+-- test SET and INHERIT options with object ownership changes
+CREATE ROLE regress_roleoption_protagonist;
+CREATE ROLE regress_roleoption_donor;
+CREATE ROLE regress_roleoption_recipient;
+CREATE SCHEMA regress_roleoption;
+GRANT CREATE, USAGE ON SCHEMA regress_roleoption TO PUBLIC;
+GRANT regress_roleoption_donor TO regress_roleoption_protagonist WITH INHERIT TRUE, SET FALSE;
+GRANT regress_roleoption_recipient TO regress_roleoption_protagonist WITH INHERIT FALSE, SET TRUE;
+SET SESSION AUTHORIZATION regress_roleoption_protagonist;
+CREATE TABLE regress_roleoption.t1 (a int);
+CREATE TABLE regress_roleoption.t2 (a int);
+SET SESSION AUTHORIZATION regress_roleoption_donor;
+CREATE TABLE regress_roleoption.t3 (a int);
+SET SESSION AUTHORIZATION regress_roleoption_recipient;
+CREATE TABLE regress_roleoption.t4 (a int);
+SET SESSION AUTHORIZATION regress_roleoption_protagonist;
+ALTER TABLE regress_roleoption.t1 OWNER TO regress_roleoption_donor; -- fails, can't be come donor
+ALTER TABLE regress_roleoption.t2 OWNER TO regress_roleoption_recipient; -- works
+ALTER TABLE regress_roleoption.t3 OWNER TO regress_roleoption_protagonist; -- works
+ALTER TABLE regress_roleoption.t4 OWNER TO regress_roleoption_protagonist; -- fails, we don't inherit from recipient
+RESET SESSION AUTHORIZATION;
+DROP TABLE regress_roleoption.t1;
+DROP TABLE regress_roleoption.t2;
+DROP TABLE regress_roleoption.t3;
+DROP TABLE regress_roleoption.t4;
+DROP SCHEMA regress_roleoption;
+DROP ROLE regress_roleoption_recipient;
+DROP ROLE regress_roleoption_donor;
+DROP ROLE regress_roleoption_donor;