v2-0002-Add-owners-to-roles.patch
application/octet-stream
Filename: v2-0002-Add-owners-to-roles.patch
Type: application/octet-stream
Part: 1
Patch
Format: format-patch
Series: patch v2-0002
Subject: Add owners to roles
| File | + | − |
|---|---|---|
| src/backend/catalog/aclchk.c | 57 | 2 |
| src/backend/catalog/pg_shdepend.c | 5 | 0 |
| src/backend/catalog/system_views.sql | 1 | 0 |
| src/backend/commands/alter.c | 3 | 0 |
| src/backend/commands/user.c | 141 | 1 |
| src/backend/nodes/copyfuncs.c | 1 | 0 |
| src/backend/nodes/equalfuncs.c | 1 | 0 |
| src/backend/parser/gram.y | 23 | 0 |
| src/bin/psql/describe.c | 12 | 0 |
| src/include/catalog/pg_authid.h | 1 | 0 |
| src/include/commands/user.h | 2 | 0 |
| src/include/nodes/parsenodes.h | 1 | 0 |
| src/include/utils/acl.h | 1 | 0 |
| src/test/modules/unsafe_tests/expected/rolenames.out | 5 | 1 |
| src/test/modules/unsafe_tests/sql/rolenames.sql | 2 | 1 |
| src/test/regress/expected/create_role.out | 124 | 9 |
| src/test/regress/expected/oidjoins.out | 1 | 0 |
| src/test/regress/expected/privileges.out | 8 | 1 |
| src/test/regress/expected/rules.out | 1 | 0 |
| src/test/regress/sql/create_role.sql | 59 | 5 |
| src/test/regress/sql/privileges.sql | 12 | 1 |
From 0a27010622339b7f80e7d622add73137c69a97c0 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Wed, 27 Oct 2021 07:34:18 -0700
Subject: [PATCH v2 2/4] Add owners to roles
All roles now have owners. By default, roles belong to the role
that created them, and initdb-time roles are owned by POSTGRES.
This is a preparatory patch for changing how CREATEROLE works.
---
src/backend/catalog/aclchk.c | 59 +++++++-
src/backend/catalog/pg_shdepend.c | 5 +
src/backend/catalog/system_views.sql | 1 +
src/backend/commands/alter.c | 3 +
src/backend/commands/user.c | 142 +++++++++++++++++-
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/parser/gram.y | 23 +++
src/bin/psql/describe.c | 12 ++
src/include/catalog/pg_authid.h | 1 +
src/include/commands/user.h | 2 +
src/include/nodes/parsenodes.h | 1 +
src/include/utils/acl.h | 1 +
.../unsafe_tests/expected/rolenames.out | 6 +-
.../modules/unsafe_tests/sql/rolenames.sql | 3 +-
src/test/regress/expected/create_role.out | 133 ++++++++++++++--
src/test/regress/expected/oidjoins.out | 1 +
src/test/regress/expected/privileges.out | 9 +-
src/test/regress/expected/rules.out | 1 +
src/test/regress/sql/create_role.sql | 64 +++++++-
src/test/regress/sql/privileges.sql | 13 +-
21 files changed, 461 insertions(+), 21 deletions(-)
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index ce0a4ff14e..ddd205d656 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3385,6 +3385,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_PUBLICATION:
msg = gettext_noop("permission denied for publication %s");
break;
+ case OBJECT_ROLE:
+ msg = gettext_noop("permission denied for role %s");
+ break;
case OBJECT_ROUTINE:
msg = gettext_noop("permission denied for routine %s");
break;
@@ -3429,7 +3432,6 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
- case OBJECT_ROLE:
case OBJECT_RULE:
case OBJECT_TABCONSTRAINT:
case OBJECT_TRANSFORM:
@@ -3511,6 +3513,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_PUBLICATION:
msg = gettext_noop("must be owner of publication %s");
break;
+ case OBJECT_ROLE:
+ msg = gettext_noop("must be owner of role %s");
+ break;
case OBJECT_ROUTINE:
msg = gettext_noop("must be owner of routine %s");
break;
@@ -3569,7 +3574,6 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DOMCONSTRAINT:
case OBJECT_PUBLICATION_NAMESPACE:
case OBJECT_PUBLICATION_REL:
- case OBJECT_ROLE:
case OBJECT_TRANSFORM:
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
@@ -5430,6 +5434,57 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid)
return has_privs_of_role(roleid, ownerId);
}
+/*
+ * Ownership check for a role (specified by OID)
+ */
+bool
+pg_role_ownercheck(Oid role_oid, Oid roleid)
+{
+ HeapTuple tuple;
+ Form_pg_authid authform;
+ Oid owner_oid;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ /* Otherwise, look up the owner of the role */
+ tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(role_oid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("role with OID %u does not exist",
+ role_oid)));
+ authform = (Form_pg_authid) GETSTRUCT(tuple);
+ owner_oid = authform->rolowner;
+
+ /*
+ * Roles must necessarily have owners. Even the bootstrap user has an
+ * owner. (It owns itself). Other roles must form a proper tree.
+ */
+ if (!OidIsValid(owner_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("role \"%s\" with OID %u has invalid owner",
+ authform->rolname.data, authform->oid)));
+ if (authform->oid != BOOTSTRAP_SUPERUSERID &&
+ authform->rolowner == authform->oid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("role \"%s\" with OID %u owns itself",
+ authform->rolname.data, authform->oid)));
+ if (authform->oid == BOOTSTRAP_SUPERUSERID &&
+ authform->rolowner != BOOTSTRAP_SUPERUSERID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_CORRUPTED),
+ errmsg("role \"%s\" with OID %u owned by role with OID %u",
+ authform->rolname.data, authform->oid,
+ authform->rolowner)));
+ ReleaseSysCache(tuple);
+
+ return (owner_oid == roleid);
+}
+
/*
* Check whether specified role has CREATEROLE privilege (or is a superuser)
*
diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c
index 8453d8fefd..88e479ca21 100644
--- a/src/backend/catalog/pg_shdepend.c
+++ b/src/backend/catalog/pg_shdepend.c
@@ -61,6 +61,7 @@
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
#include "commands/typecmds.h"
+#include "commands/user.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -1557,6 +1558,10 @@ shdepReassignOwned(List *roleids, Oid newrole)
AlterSubscriptionOwner_oid(sdepForm->objid, newrole);
break;
+ case AuthIdRelationId:
+ AlterRoleOwner_oid(sdepForm->objid, newrole);
+ break;
+
/* Generic alter owner cases */
case CollationRelationId:
case ConversionRelationId:
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 55f6e3711d..b03763695c 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -17,6 +17,7 @@
CREATE VIEW pg_roles AS
SELECT
rolname,
+ pg_get_userbyid(rolowner) AS rolowner,
rolsuper,
rolinherit,
rolcreaterole,
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 40044070cf..eb407cfc4c 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -840,6 +840,9 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
case OBJECT_DATABASE:
return AlterDatabaseOwner(strVal(stmt->object), newowner);
+ case OBJECT_ROLE:
+ return AlterRoleOwner(strVal(stmt->object), newowner);
+
case OBJECT_SCHEMA:
return AlterSchemaOwner(strVal(stmt->object), newowner);
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index aa69821be4..259968f1d4 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -55,6 +55,8 @@ static void AddRoleMems(const char *rolename, Oid roleid,
static void DelRoleMems(const char *rolename, Oid roleid,
List *memberSpecs, List *memberIds,
bool admin_opt);
+static void AlterRoleOwner_internal(HeapTuple tup, Relation rel,
+ Oid newOwnerId);
/* Check if current user has createrole privileges */
@@ -77,6 +79,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
Datum new_record[Natts_pg_authid];
bool new_record_nulls[Natts_pg_authid];
Oid roleid;
+ Oid owner_uid;
+ Oid saved_uid;
+ int save_sec_context;
ListCell *item;
ListCell *option;
char *password = NULL; /* user password */
@@ -108,6 +113,16 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DefElem *dvalidUntil = NULL;
DefElem *dbypassRLS = NULL;
+ GetUserIdAndSecContext(&saved_uid, &save_sec_context);
+
+ /*
+ * Who is supposed to own the new role?
+ */
+ if (stmt->authrole)
+ owner_uid = get_rolespec_oid(stmt->authrole, false);
+ else
+ owner_uid = saved_uid;
+
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
{
@@ -254,6 +269,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to create superusers")));
+ if (!superuser_arg(owner_uid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to own superusers")));
}
else if (isreplication)
{
@@ -310,6 +329,19 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
errmsg("role \"%s\" already exists",
stmt->role)));
+ /*
+ * If the requested authorization is different from the current user,
+ * temporarily set the current user so that the object(s) will be created
+ * with the correct ownership.
+ *
+ * (The setting will be restored at the end of this routine, or in case of
+ * error, transaction abort will clean things up.)
+ */
+ if (saved_uid != owner_uid)
+ SetUserIdAndSecContext(owner_uid,
+ save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+
+
/* Convert validuntil to internal form */
if (validUntil)
{
@@ -345,6 +377,7 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
+ new_record[Anum_pg_authid_rolowner - 1] = ObjectIdGetDatum(owner_uid);
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
@@ -422,6 +455,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
*/
CatalogTupleInsert(pg_authid_rel, tuple);
+ recordDependencyOnOwner(AuthIdRelationId, roleid, owner_uid);
+
/*
* Advance command counter so we can see new record; else tests in
* AddRoleMems may fail.
@@ -478,6 +513,9 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
*/
table_close(pg_authid_rel, NoLock);
+ /* Reset current user and security context */
+ SetUserIdAndSecContext(saved_uid, save_sec_context);
+
return roleid;
}
@@ -1078,8 +1116,9 @@ DropRole(DropRoleStmt *stmt)
systable_endscan(sscan);
/*
- * Remove any comments or security labels on this role.
+ * Remove any dependencies, comments or security labels on this role.
*/
+ deleteSharedDependencyRecordsFor(AuthIdRelationId, roleid, 0);
DeleteSharedComments(roleid, AuthIdRelationId);
DeleteSharedSecurityLabel(roleid, AuthIdRelationId);
@@ -1675,3 +1714,104 @@ DelRoleMems(const char *rolename, Oid roleid,
*/
table_close(pg_authmem_rel, NoLock);
}
+
+/*
+ * Change role owner
+ */
+ObjectAddress
+AlterRoleOwner(const char *name, Oid newOwnerId)
+{
+ Oid roleid;
+ HeapTuple tup;
+ Relation rel;
+ ObjectAddress address;
+ Form_pg_authid authform;
+
+ rel = table_open(AuthIdRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(AUTHNAME, CStringGetDatum(name));
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("role \"%s\" does not exist", name)));
+
+ authform = (Form_pg_authid) GETSTRUCT(tup);
+ roleid = authform->oid;
+
+ AlterRoleOwner_internal(tup, rel, newOwnerId);
+
+ ObjectAddressSet(address, AuthIdRelationId, roleid);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+
+ return address;
+}
+
+void
+AlterRoleOwner_oid(Oid roleOid, Oid newOwnerId)
+{
+ HeapTuple tup;
+ Relation rel;
+
+ rel = table_open(AuthIdRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleOid));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for role %u", roleOid);
+
+ AlterRoleOwner_internal(tup, rel, newOwnerId);
+
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+}
+
+static void
+AlterRoleOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerId)
+{
+ Form_pg_authid authForm;
+
+ Assert(tup->t_tableOid == AuthIdRelationId);
+ Assert(RelationGetRelid(rel) == AuthIdRelationId);
+
+ authForm = (Form_pg_authid) GETSTRUCT(tup);
+
+ /*
+ * If the new owner is the same as the existing owner, consider the
+ * command to have succeeded. This is for dump restoration purposes.
+ */
+ if (authForm->rolowner != newOwnerId)
+ {
+ /* Otherwise, must be owner of the existing object */
+ if (!pg_role_ownercheck(authForm->oid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_ROLE,
+ NameStr(authForm->rolname));
+
+ /* Must be able to become new owner */
+ check_is_member_of_role(GetUserId(), newOwnerId);
+
+ /*
+ * must have CREATEROLE rights
+ *
+ * NOTE: This is different from most other alter-owner checks in that
+ * the current user is checked for create privileges instead of the
+ * destination owner. This is consistent with the CREATE case for
+ * roles. Because superusers will always have this right, we need no
+ * special case for them.
+ */
+ if (!have_createrole_privilege())
+ aclcheck_error(ACLCHECK_NO_PRIV, OBJECT_ROLE,
+ NameStr(authForm->rolname));
+
+ authForm->rolowner = newOwnerId;
+ CatalogTupleUpdate(rel, &tup->t_self, tup);
+
+ /* Update owner dependency reference */
+ changeDependencyOnOwner(AuthIdRelationId, authForm->oid, newOwnerId);
+ }
+
+ InvokeObjectPostAlterHook(AuthIdRelationId,
+ authForm->oid, 0);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 82464c9889..f406763d71 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4514,6 +4514,7 @@ _copyCreateRoleStmt(const CreateRoleStmt *from)
COPY_SCALAR_FIELD(stmt_type);
COPY_STRING_FIELD(role);
+ COPY_NODE_FIELD(authrole);
COPY_NODE_FIELD(options);
return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f537d3eb96..0d2c32121b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2128,6 +2128,7 @@ _equalCreateRoleStmt(const CreateRoleStmt *a, const CreateRoleStmt *b)
{
COMPARE_SCALAR_FIELD(stmt_type);
COMPARE_STRING_FIELD(role);
+ COMPARE_NODE_FIELD(authrole);
COMPARE_NODE_FIELD(options);
return true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d0eb80e69c..08fd20e239 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1060,9 +1060,20 @@ CreateRoleStmt:
CreateRoleStmt *n = makeNode(CreateRoleStmt);
n->stmt_type = ROLESTMT_ROLE;
n->role = $3;
+ n->authrole = NULL;
n->options = $5;
$$ = (Node *)n;
}
+ |
+ CREATE ROLE RoleId AUTHORIZATION RoleSpec opt_with OptRoleList
+ {
+ CreateRoleStmt *n = makeNode(CreateRoleStmt);
+ n->stmt_type = ROLESTMT_ROLE;
+ n->role = $3;
+ n->authrole = $5;
+ n->options = $7;
+ $$ = (Node *)n;
+ }
;
@@ -1201,6 +1212,10 @@ CreateOptRoleElem:
{
$$ = makeDefElem("addroleto", (Node *)$3, @1);
}
+ | OWNER RoleSpec
+ {
+ $$ = makeDefElem("owner", (Node *)$2, @1);
+ }
;
@@ -9497,6 +9512,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec
n->newowner = $6;
$$ = (Node *)n;
}
+ | ALTER ROLE name OWNER TO RoleSpec
+ {
+ AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+ n->objectType = OBJECT_ROLE;
+ n->object = (Node *) makeString($3);
+ n->newowner = $6;
+ $$ = (Node *)n;
+ }
| ALTER ROUTINE function_with_argtypes OWNER TO RoleSpec
{
AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 006661412e..e051cab6f9 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3801,6 +3801,12 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
}
+ if (pset.sversion >= 150000)
+ {
+ appendPQExpBufferStr(&buf, "\n, r.rolowner");
+ ncols++;
+ }
+
appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
if (!showSystem && !pattern)
@@ -3837,6 +3843,8 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
printTableInit(&cont, &myopt, _("List of roles"), ncols, nrows);
printTableAddHeader(&cont, gettext_noop("Role name"), true, align);
+ if (pset.sversion >= 150000)
+ printTableAddHeader(&cont, gettext_noop("Owner"), true, align);
printTableAddHeader(&cont, gettext_noop("Attributes"), true, align);
/* ignores implicit memberships from superuser & pg_database_owner */
printTableAddHeader(&cont, gettext_noop("Member of"), true, align);
@@ -3848,6 +3856,10 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
{
printTableAddCell(&cont, PQgetvalue(res, i, 0), false, false);
+ if (pset.sversion >= 150000)
+ printTableAddCell(&cont, PQgetvalue(res, i, (verbose ? 12 : 11)),
+ false, false);
+
resetPQExpBuffer(&buf);
if (strcmp(PQgetvalue(res, i, 1), "t") == 0)
add_role_attribute(&buf, _("Superuser"));
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 2d7115e31d..cce43388d8 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -32,6 +32,7 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
{
Oid oid; /* oid */
NameData rolname; /* name of role */
+ Oid rolowner BKI_DEFAULT(POSTGRES) BKI_LOOKUP(pg_authid); /* owner of this role */
bool rolsuper; /* read this field via superuser() only! */
bool rolinherit; /* inherit privileges from other roles? */
bool rolcreaterole; /* allowed to create more roles? */
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 0b7a3cd65f..c32127e41e 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -33,5 +33,7 @@ extern ObjectAddress RenameRole(const char *oldname, const char *newname);
extern void DropOwnedObjects(DropOwnedStmt *stmt);
extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt);
extern List *roleSpecsToIds(List *memberNames);
+extern ObjectAddress AlterRoleOwner(const char *name, Oid newOwnerId);
+extern void AlterRoleOwner_oid(Oid roleOid, Oid newOwnerId);
#endif /* USER_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 49123e28a4..0d5cfd3eb9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2621,6 +2621,7 @@ typedef struct CreateRoleStmt
NodeTag type;
RoleStmtType stmt_type; /* ROLE/USER/GROUP */
char *role; /* role name */
+ RoleSpec *authrole; /* the owner of the created role */
List *options; /* List of DefElem nodes */
} CreateRoleStmt;
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index af771c901d..ec9d480d67 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -316,6 +316,7 @@ extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
extern bool pg_publication_ownercheck(Oid pub_oid, Oid roleid);
extern bool pg_subscription_ownercheck(Oid sub_oid, Oid roleid);
extern bool pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid);
+extern bool pg_role_ownercheck(Oid role_oid, Oid roleid);
extern bool has_createrole_privilege(Oid roleid);
extern bool has_bypassrls_privilege(Oid roleid);
diff --git a/src/test/modules/unsafe_tests/expected/rolenames.out b/src/test/modules/unsafe_tests/expected/rolenames.out
index eb608fdc2e..8b79a63b80 100644
--- a/src/test/modules/unsafe_tests/expected/rolenames.out
+++ b/src/test/modules/unsafe_tests/expected/rolenames.out
@@ -1086,6 +1086,10 @@ REVOKE pg_read_all_settings FROM regress_role_haspriv;
\c
DROP SCHEMA test_roles_schema;
DROP OWNED BY regress_testrol0, "Public", "current_role", "current_user", regress_testrol1, regress_testrol2, regress_testrolx CASCADE;
-DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx;
+DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx; -- fails with owner of role regress_role_haspriv
+ERROR: role "regress_testrol2" cannot be dropped because some objects depend on it
+DETAIL: owner of role regress_role_haspriv
+owner of role regress_role_nopriv
DROP ROLE "Public", "None", "current_role", "current_user", "session_user", "user";
DROP ROLE regress_role_haspriv, regress_role_nopriv;
+DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx; -- ok now
diff --git a/src/test/modules/unsafe_tests/sql/rolenames.sql b/src/test/modules/unsafe_tests/sql/rolenames.sql
index adac36536d..95a54ce70d 100644
--- a/src/test/modules/unsafe_tests/sql/rolenames.sql
+++ b/src/test/modules/unsafe_tests/sql/rolenames.sql
@@ -499,6 +499,7 @@ REVOKE pg_read_all_settings FROM regress_role_haspriv;
DROP SCHEMA test_roles_schema;
DROP OWNED BY regress_testrol0, "Public", "current_role", "current_user", regress_testrol1, regress_testrol2, regress_testrolx CASCADE;
-DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx;
+DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx; -- fails with owner of role regress_role_haspriv
DROP ROLE "Public", "None", "current_role", "current_user", "session_user", "user";
DROP ROLE regress_role_haspriv, regress_role_nopriv;
+DROP ROLE regress_testrol0, regress_testrol1, regress_testrol2, regress_testrolx; -- ok now
diff --git a/src/test/regress/expected/create_role.out b/src/test/regress/expected/create_role.out
index 57010bbb58..e1ce5f3a66 100644
--- a/src/test/regress/expected/create_role.out
+++ b/src/test/regress/expected/create_role.out
@@ -1,6 +1,7 @@
-- ok, superuser can create users with any set of privileges
CREATE ROLE regress_role_super SUPERUSER;
CREATE ROLE regress_role_1 CREATEDB CREATEROLE REPLICATION BYPASSRLS;
+GRANT CREATE ON DATABASE regression TO regress_role_1;
-- fail, only superusers can create users with these privileges
SET SESSION AUTHORIZATION regress_role_1;
CREATE ROLE regress_role_2 SUPERUSER;
@@ -11,14 +12,95 @@ CREATE ROLE regress_role_4 REPLICATION;
ERROR: must be superuser to create replication users
CREATE ROLE regress_role_5 BYPASSRLS;
ERROR: must be superuser to create bypassrls users
+-- fail, only superusers can own superusers
+RESET SESSION AUTHORIZATION;
+CREATE ROLE regress_role_2 AUTHORIZATION regress_role_1 SUPERUSER;
+ERROR: must be superuser to own superusers
+-- ok, superuser can create superusers belonging to other superusers
+CREATE ROLE regress_role_2 AUTHORIZATION regress_role_super SUPERUSER;
+-- ok, superuser can create users with these privileges for normal role
+CREATE ROLE regress_role_3 AUTHORIZATION regress_role_1 REPLICATION BYPASSRLS;
+CREATE ROLE regress_role_4 AUTHORIZATION regress_role_1 REPLICATION;
+CREATE ROLE regress_role_5 AUTHORIZATION regress_role_1 BYPASSRLS;
+\du+ regress_role_2
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+----------------+--------------------+-------------------------+-----------+-------------
+ regress_role_2 | regress_role_super | Superuser, Cannot login | {} |
+
+\du+ regress_role_3
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+----------------+----------------+---------------------------------------+-----------+-------------
+ regress_role_3 | regress_role_1 | Cannot login, Replication, Bypass RLS | {} |
+
+\du+ regress_role_4
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+----------------+----------------+---------------------------+-----------+-------------
+ regress_role_4 | regress_role_1 | Cannot login, Replication | {} |
+
+\du+ regress_role_5
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+----------------+----------------+--------------------------+-----------+-------------
+ regress_role_5 | regress_role_1 | Cannot login, Bypass RLS | {} |
+
-- ok, having CREATEROLE is enough to create users with these privileges
+SET SESSION AUTHORIZATION regress_role_1;
CREATE ROLE regress_role_6 CREATEDB;
CREATE ROLE regress_role_7 CREATEROLE;
CREATE ROLE regress_role_8 LOGIN;
CREATE ROLE regress_role_9 INHERIT;
CREATE ROLE regress_role_10 CONNECTION LIMIT 5;
-CREATE ROLE regress_role_11 ENCRYPTED PASSWORD 'foo';
-CREATE ROLE regress_role_12 PASSWORD NULL;
+CREATE ROLE regress_role_11 PASSWORD NULL;
+CREATE ROLE regress_role_12
+ CREATEDB CREATEROLE LOGIN INHERIT CONNECTION LIMIT 2 ENCRYPTED PASSWORD 'foo'
+ IN ROLE regress_role_6, regress_role_7, regress_role_8;
+\du+ regress_role_6
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+----------------+----------------+-------------------------+-----------+-------------
+ regress_role_6 | regress_role_1 | Create DB, Cannot login | {} |
+
+\du+ regress_role_7
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+----------------+----------------+---------------------------+-----------+-------------
+ regress_role_7 | regress_role_1 | Create role, Cannot login | {} |
+
+\du+ regress_role_8
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+----------------+----------------+------------+-----------+-------------
+ regress_role_8 | regress_role_1 | | {} |
+
+\du+ regress_role_9
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+----------------+----------------+--------------+-----------+-------------
+ regress_role_9 | regress_role_1 | Cannot login | {} |
+
+\du+ regress_role_10
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+-----------------+----------------+---------------+-----------+-------------
+ regress_role_10 | regress_role_1 | Cannot login +| {} |
+ | | 5 connections | |
+
+\du+ regress_role_11
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+-----------------+----------------+--------------+-----------+-------------
+ regress_role_11 | regress_role_1 | Cannot login | {} |
+
+\du+ regress_role_12
+ List of roles
+ Role name | Owner | Attributes | Member of | Description
+-----------------+----------------+------------------------+------------------------------------------------+-------------
+ regress_role_12 | regress_role_1 | Create role, Create DB+| {regress_role_6,regress_role_7,regress_role_8} |
+ | | 2 connections | |
+
-- ok, backwards compatible noise words should be ignored
CREATE ROLE regress_role_13 SYSID 12345;
NOTICE: SYSID can no longer be specified
@@ -84,16 +166,24 @@ CREATE ROLE regress_role_29 IN ROLE pg_read_server_files;
CREATE ROLE regress_role_30 IN ROLE pg_write_server_files;
CREATE ROLE regress_role_31 IN ROLE pg_execute_server_program;
CREATE ROLE regress_role_32 IN ROLE pg_signal_backend;
--- fail, creation of these roles failed above so they do not now exist
+-- fail, cannot take ownership of these objects from regress_role_7
SET SESSION AUTHORIZATION regress_role_1;
+ALTER ROLE regress_role_20 OWNER TO regress_role_1;
+ERROR: must be owner of role regress_role_20
+REASSIGN OWNED BY regress_role_20 TO regress_role_1;
+ERROR: permission denied to reassign objects
+-- superuser can do it, though
+RESET SESSION AUTHORIZATION;
+ALTER ROLE regress_role_20 OWNER TO regress_role_1;
+REASSIGN OWNED BY regress_role_20 TO regress_role_1;
+-- ok, superuser roles can drop superuser roles they own
+SET SESSION AUTHORIZATION regress_role_super;
DROP ROLE regress_role_2;
-ERROR: role "regress_role_2" does not exist
+-- ok, non-superuser roles can drop non-superuser roles they own
+SET SESSION AUTHORIZATION regress_role_1;
DROP ROLE regress_role_3;
-ERROR: role "regress_role_3" does not exist
DROP ROLE regress_role_4;
-ERROR: role "regress_role_4" does not exist
DROP ROLE regress_role_5;
-ERROR: role "regress_role_5" does not exist
DROP ROLE regress_role_14;
ERROR: role "regress_role_14" does not exist
DROP ROLE regress_role_15;
@@ -103,9 +193,23 @@ ERROR: role "regress_role_17" does not exist
DROP ROLE regress_role_19;
ERROR: role "regress_role_19" does not exist
DROP ROLE regress_role_20;
--- ok, should be able to drop non-superuser roles we created
-DROP ROLE regress_role_6;
+-- fail, cannot drop roles that own other roles
DROP ROLE regress_role_7;
+ERROR: role "regress_role_7" cannot be dropped because some objects depend on it
+DETAIL: owner of role regress_role_21
+owner of role regress_role_22
+owner of role regress_role_23
+owner of role regress_role_24
+owner of role regress_role_25
+owner of role regress_role_26
+owner of role regress_role_27
+owner of role regress_role_28
+owner of role regress_role_29
+owner of role regress_role_30
+owner of role regress_role_31
+owner of role regress_role_32
+-- ok, should be able to drop these non-superuser roles
+DROP ROLE regress_role_6;
DROP ROLE regress_role_8;
DROP ROLE regress_role_9;
DROP ROLE regress_role_10;
@@ -130,6 +234,10 @@ DROP ROLE regress_role_22;
ERROR: role "regress_role_22" cannot be dropped because some objects depend on it
DETAIL: owner of table regress_tbl_22
owner of view regress_view_22
+-- fail, role still owns other roles
+DROP ROLE regress_role_7;
+ERROR: role "regress_role_7" cannot be dropped because some objects depend on it
+DETAIL: owner of role regress_role_22
-- fail, cannot drop ourself nor superusers
DROP ROLE regress_role_super;
ERROR: must be superuser to drop superusers
@@ -141,5 +249,12 @@ DROP INDEX regress_idx_22;
DROP TABLE regress_tbl_22;
DROP VIEW regress_view_22;
DROP ROLE regress_role_22;
+DROP ROLE regress_role_7;
+-- fail, cannot drop role with remaining privileges
+DROP ROLE regress_role_1;
+ERROR: role "regress_role_1" cannot be dropped because some objects depend on it
+DETAIL: privileges for database regression
+-- ok, can drop role if we revoke privileges first
+REVOKE CREATE ON DATABASE regression FROM regress_role_1;
DROP ROLE regress_role_1;
DROP ROLE regress_role_super;
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index 215eb899be..266a30a85b 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -194,6 +194,7 @@ NOTICE: checking pg_database {dattablespace} => pg_tablespace {oid}
NOTICE: checking pg_db_role_setting {setdatabase} => pg_database {oid}
NOTICE: checking pg_db_role_setting {setrole} => pg_authid {oid}
NOTICE: checking pg_tablespace {spcowner} => pg_authid {oid}
+NOTICE: checking pg_authid {rolowner} => pg_authid {oid}
NOTICE: checking pg_auth_members {roleid} => pg_authid {oid}
NOTICE: checking pg_auth_members {member} => pg_authid {oid}
NOTICE: checking pg_auth_members {grantor} => pg_authid {oid}
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 83cff902f3..c4456cadce 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -27,8 +27,10 @@ CREATE USER regress_priv_user4;
CREATE USER regress_priv_user5;
CREATE USER regress_priv_user5; -- duplicate
ERROR: role "regress_priv_user5" already exists
-CREATE USER regress_priv_user6;
+CREATE USER regress_priv_user6 CREATEROLE;
+SET SESSION AUTHORIZATION regress_priv_user6;
CREATE USER regress_priv_user7;
+RESET SESSION AUTHORIZATION;
GRANT pg_read_all_data TO regress_priv_user6;
GRANT pg_write_all_data TO regress_priv_user7;
CREATE GROUP regress_priv_group1;
@@ -2327,7 +2329,12 @@ DROP USER regress_priv_user3;
DROP USER regress_priv_user4;
DROP USER regress_priv_user5;
DROP USER regress_priv_user6;
+ERROR: role "regress_priv_user6" cannot be dropped because some objects depend on it
+DETAIL: owner of role regress_priv_user7
+SET SESSION AUTHORIZATION regress_priv_user6;
DROP USER regress_priv_user7;
+RESET SESSION AUTHORIZATION;
+DROP USER regress_priv_user6;
DROP USER regress_priv_user8; -- does not exist
ERROR: role "regress_priv_user8" does not exist
-- permissions with LOCK TABLE
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2fa00a3c29..c452cc433c 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1482,6 +1482,7 @@ pg_replication_slots| SELECT l.slot_name,
FROM (pg_get_replication_slots() l(slot_name, plugin, slot_type, datoid, temporary, active, active_pid, xmin, catalog_xmin, restart_lsn, confirmed_flush_lsn, wal_status, safe_wal_size, two_phase)
LEFT JOIN pg_database d ON ((l.datoid = d.oid)));
pg_roles| SELECT pg_authid.rolname,
+ pg_get_userbyid(pg_authid.rolowner) AS rolowner,
pg_authid.rolsuper,
pg_authid.rolinherit,
pg_authid.rolcreaterole,
diff --git a/src/test/regress/sql/create_role.sql b/src/test/regress/sql/create_role.sql
index e00893de4e..8a4f177574 100644
--- a/src/test/regress/sql/create_role.sql
+++ b/src/test/regress/sql/create_role.sql
@@ -1,6 +1,7 @@
-- ok, superuser can create users with any set of privileges
CREATE ROLE regress_role_super SUPERUSER;
CREATE ROLE regress_role_1 CREATEDB CREATEROLE REPLICATION BYPASSRLS;
+GRANT CREATE ON DATABASE regression TO regress_role_1;
-- fail, only superusers can create users with these privileges
SET SESSION AUTHORIZATION regress_role_1;
@@ -9,14 +10,42 @@ CREATE ROLE regress_role_3 REPLICATION BYPASSRLS;
CREATE ROLE regress_role_4 REPLICATION;
CREATE ROLE regress_role_5 BYPASSRLS;
+-- fail, only superusers can own superusers
+RESET SESSION AUTHORIZATION;
+CREATE ROLE regress_role_2 AUTHORIZATION regress_role_1 SUPERUSER;
+
+-- ok, superuser can create superusers belonging to other superusers
+CREATE ROLE regress_role_2 AUTHORIZATION regress_role_super SUPERUSER;
+
+-- ok, superuser can create users with these privileges for normal role
+CREATE ROLE regress_role_3 AUTHORIZATION regress_role_1 REPLICATION BYPASSRLS;
+CREATE ROLE regress_role_4 AUTHORIZATION regress_role_1 REPLICATION;
+CREATE ROLE regress_role_5 AUTHORIZATION regress_role_1 BYPASSRLS;
+
+\du+ regress_role_2
+\du+ regress_role_3
+\du+ regress_role_4
+\du+ regress_role_5
+
-- ok, having CREATEROLE is enough to create users with these privileges
+SET SESSION AUTHORIZATION regress_role_1;
CREATE ROLE regress_role_6 CREATEDB;
CREATE ROLE regress_role_7 CREATEROLE;
CREATE ROLE regress_role_8 LOGIN;
CREATE ROLE regress_role_9 INHERIT;
CREATE ROLE regress_role_10 CONNECTION LIMIT 5;
-CREATE ROLE regress_role_11 ENCRYPTED PASSWORD 'foo';
-CREATE ROLE regress_role_12 PASSWORD NULL;
+CREATE ROLE regress_role_11 PASSWORD NULL;
+CREATE ROLE regress_role_12
+ CREATEDB CREATEROLE LOGIN INHERIT CONNECTION LIMIT 2 ENCRYPTED PASSWORD 'foo'
+ IN ROLE regress_role_6, regress_role_7, regress_role_8;
+
+\du+ regress_role_6
+\du+ regress_role_7
+\du+ regress_role_8
+\du+ regress_role_9
+\du+ regress_role_10
+\du+ regress_role_11
+\du+ regress_role_12
-- ok, backwards compatible noise words should be ignored
CREATE ROLE regress_role_13 SYSID 12345;
@@ -86,9 +115,22 @@ CREATE ROLE regress_role_30 IN ROLE pg_write_server_files;
CREATE ROLE regress_role_31 IN ROLE pg_execute_server_program;
CREATE ROLE regress_role_32 IN ROLE pg_signal_backend;
--- fail, creation of these roles failed above so they do not now exist
+-- fail, cannot take ownership of these objects from regress_role_7
SET SESSION AUTHORIZATION regress_role_1;
+ALTER ROLE regress_role_20 OWNER TO regress_role_1;
+REASSIGN OWNED BY regress_role_20 TO regress_role_1;
+
+-- superuser can do it, though
+RESET SESSION AUTHORIZATION;
+ALTER ROLE regress_role_20 OWNER TO regress_role_1;
+REASSIGN OWNED BY regress_role_20 TO regress_role_1;
+
+-- ok, superuser roles can drop superuser roles they own
+SET SESSION AUTHORIZATION regress_role_super;
DROP ROLE regress_role_2;
+
+-- ok, non-superuser roles can drop non-superuser roles they own
+SET SESSION AUTHORIZATION regress_role_1;
DROP ROLE regress_role_3;
DROP ROLE regress_role_4;
DROP ROLE regress_role_5;
@@ -98,9 +140,11 @@ DROP ROLE regress_role_17;
DROP ROLE regress_role_19;
DROP ROLE regress_role_20;
--- ok, should be able to drop non-superuser roles we created
-DROP ROLE regress_role_6;
+-- fail, cannot drop roles that own other roles
DROP ROLE regress_role_7;
+
+-- ok, should be able to drop these non-superuser roles
+DROP ROLE regress_role_6;
DROP ROLE regress_role_8;
DROP ROLE regress_role_9;
DROP ROLE regress_role_10;
@@ -124,6 +168,9 @@ DROP ROLE regress_role_32;
-- fail, role still owns database objects
DROP ROLE regress_role_22;
+-- fail, role still owns other roles
+DROP ROLE regress_role_7;
+
-- fail, cannot drop ourself nor superusers
DROP ROLE regress_role_super;
DROP ROLE regress_role_1;
@@ -134,5 +181,12 @@ DROP INDEX regress_idx_22;
DROP TABLE regress_tbl_22;
DROP VIEW regress_view_22;
DROP ROLE regress_role_22;
+DROP ROLE regress_role_7;
+
+-- fail, cannot drop role with remaining privileges
+DROP ROLE regress_role_1;
+
+-- ok, can drop role if we revoke privileges first
+REVOKE CREATE ON DATABASE regression FROM regress_role_1;
DROP ROLE regress_role_1;
DROP ROLE regress_role_super;
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 3d1a1db987..bd2d67691c 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -29,9 +29,14 @@ CREATE USER regress_priv_user2;
CREATE USER regress_priv_user3;
CREATE USER regress_priv_user4;
CREATE USER regress_priv_user5;
+
CREATE USER regress_priv_user5; -- duplicate
-CREATE USER regress_priv_user6;
+
+CREATE USER regress_priv_user6 CREATEROLE;
+
+SET SESSION AUTHORIZATION regress_priv_user6;
CREATE USER regress_priv_user7;
+RESET SESSION AUTHORIZATION;
GRANT pg_read_all_data TO regress_priv_user6;
GRANT pg_write_all_data TO regress_priv_user7;
@@ -1389,8 +1394,14 @@ DROP USER regress_priv_user2;
DROP USER regress_priv_user3;
DROP USER regress_priv_user4;
DROP USER regress_priv_user5;
+
DROP USER regress_priv_user6;
+
+SET SESSION AUTHORIZATION regress_priv_user6;
DROP USER regress_priv_user7;
+RESET SESSION AUTHORIZATION;
+
+DROP USER regress_priv_user6;
DROP USER regress_priv_user8; -- does not exist
--
2.21.1 (Apple Git-122.3)