From 499d7d5e41c9b3abc6b8d492e207b1e585a388f5 Mon Sep 17 00:00:00 2001 From: Mark Dilger Date: Tue, 11 Jan 2022 11:15:02 -0800 Subject: [PATCH v6 2/5] 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 | 148 +++++++++++++++++- 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 | 136 ++++++++++++++-- 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 | 67 +++++++- src/test/regress/sql/privileges.sql | 10 +- 21 files changed, 470 insertions(+), 21 deletions(-) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 1dd03a8e51..e5c7324340 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 owned_role_oid, Oid owner_roleid) +{ + HeapTuple tuple; + Form_pg_authid authform; + Oid owner_oid; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(owner_roleid)) + return true; + + /* Otherwise, look up the owner of the role */ + tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(owned_role_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role with OID %u does not exist", + owned_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 == owner_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 3e8fa008b9..52f69ad76e 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" @@ -1578,6 +1579,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 3cb69b1f87..057d17460b 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 1f64c8aa51..e6c7c84c87 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 f9d3c1246b..3903791ff0 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; } @@ -1051,8 +1089,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); @@ -1648,3 +1687,110 @@ 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)); + + /* Only the bootstrap superuser is allowed to own itself. */ + if (newOwnerId != BOOTSTRAP_SUPERUSERID && authForm->oid == newOwnerId) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("role may not own itself"))); + + 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 90b5da51c9..f43c92a70f 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -4522,6 +4522,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 06345da3ba..328f155208 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 b5966712ce..df2f84143f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -1077,9 +1077,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; + } ; @@ -1218,6 +1229,10 @@ CreateOptRoleElem: { $$ = makeDefElem("addroleto", (Node *)$3, @1); } + | OWNER RoleSpec + { + $$ = makeDefElem("owner", (Node *)$2, @1); + } ; @@ -9585,6 +9600,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 40433e32fa..e6198c0332 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3518,6 +3518,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) @@ -3538,6 +3544,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); @@ -3549,6 +3557,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 4b65e39a1f..3af0f3908c 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 3e9bdc781f..b9d124d38f 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2623,6 +2623,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 1ce4c5556e..42f85d0e84 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 owned_role_oid, Oid owner_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 4e67d72760..66d4c29cf7 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_admin CREATEDB CREATEROLE REPLICATION BYPASSRLS; +GRANT CREATE ON DATABASE regression TO regress_role_admin; -- fail, only superusers can create users with these privileges SET SESSION AUTHORIZATION regress_role_admin; CREATE ROLE regress_nosuch_superuser SUPERUSER; @@ -11,14 +12,98 @@ CREATE ROLE regress_nosuch_replication REPLICATION; ERROR: must be superuser to create replication users CREATE ROLE regress_nosuch_bypassrls BYPASSRLS; ERROR: must be superuser to create bypassrls users +-- fail, only superusers can own superusers +RESET SESSION AUTHORIZATION; +CREATE ROLE regress_nosuch_superuser AUTHORIZATION regress_role_admin SUPERUSER; +ERROR: must be superuser to own superusers +-- ok, superuser can create superusers belonging to other superusers +CREATE ROLE regress_nosuch_superuser AUTHORIZATION regress_role_super SUPERUSER; +-- ok, superuser can create users with these privileges for normal role +CREATE ROLE regress_nosuch_replication_bypassrls AUTHORIZATION regress_role_admin REPLICATION BYPASSRLS; +CREATE ROLE regress_nosuch_replication AUTHORIZATION regress_role_admin REPLICATION; +CREATE ROLE regress_nosuch_bypassrls AUTHORIZATION regress_role_admin BYPASSRLS; +\du+ regress_nosuch_superuser + List of roles + Role name | Owner | Attributes | Member of | Description +--------------------------+--------------------+-------------------------+-----------+------------- + regress_nosuch_superuser | regress_role_super | Superuser, Cannot login | {} | + +\du+ regress_nosuch_replication_bypassrls + List of roles + Role name | Owner | Attributes | Member of | Description +--------------------------------------+--------------------+---------------------------------------+-----------+------------- + regress_nosuch_replication_bypassrls | regress_role_admin | Cannot login, Replication, Bypass RLS | {} | + +\du+ regress_nosuch_replication + List of roles + Role name | Owner | Attributes | Member of | Description +----------------------------+--------------------+---------------------------+-----------+------------- + regress_nosuch_replication | regress_role_admin | Cannot login, Replication | {} | + +\du+ regress_nosuch_bypassrls + List of roles + Role name | Owner | Attributes | Member of | Description +--------------------------+--------------------+--------------------------+-----------+------------- + regress_nosuch_bypassrls | regress_role_admin | Cannot login, Bypass RLS | {} | + +-- fail, roles are not allowed to own themselves +ALTER ROLE regress_nosuch_bypassrls OWNER TO regress_nosuch_bypassrls; +ERROR: role may not own itself -- ok, having CREATEROLE is enough to create users with these privileges +SET SESSION AUTHORIZATION regress_role_admin; CREATE ROLE regress_createdb CREATEDB; CREATE ROLE regress_createrole CREATEROLE; CREATE ROLE regress_login LOGIN; CREATE ROLE regress_inherit INHERIT; CREATE ROLE regress_connection_limit CONNECTION LIMIT 5; -CREATE ROLE regress_encrypted_password ENCRYPTED PASSWORD 'foo'; -CREATE ROLE regress_password_null PASSWORD NULL; +CREATE ROLE regress_encrypted_password PASSWORD NULL; +CREATE ROLE regress_password_null + CREATEDB CREATEROLE LOGIN INHERIT CONNECTION LIMIT 2 ENCRYPTED PASSWORD 'foo' + IN ROLE regress_createdb, regress_createrole, regress_login; +\du+ regress_createdb + List of roles + Role name | Owner | Attributes | Member of | Description +------------------+--------------------+-------------------------+-----------+------------- + regress_createdb | regress_role_admin | Create DB, Cannot login | {} | + +\du+ regress_createrole + List of roles + Role name | Owner | Attributes | Member of | Description +--------------------+--------------------+---------------------------+-----------+------------- + regress_createrole | regress_role_admin | Create role, Cannot login | {} | + +\du+ regress_login + List of roles + Role name | Owner | Attributes | Member of | Description +---------------+--------------------+------------+-----------+------------- + regress_login | regress_role_admin | | {} | + +\du+ regress_inherit + List of roles + Role name | Owner | Attributes | Member of | Description +-----------------+--------------------+--------------+-----------+------------- + regress_inherit | regress_role_admin | Cannot login | {} | + +\du+ regress_connection_limit + List of roles + Role name | Owner | Attributes | Member of | Description +--------------------------+--------------------+---------------+-----------+------------- + regress_connection_limit | regress_role_admin | Cannot login +| {} | + | | 5 connections | | + +\du+ regress_encrypted_password + List of roles + Role name | Owner | Attributes | Member of | Description +----------------------------+--------------------+--------------+-----------+------------- + regress_encrypted_password | regress_role_admin | Cannot login | {} | + +\du+ regress_password_null + List of roles + Role name | Owner | Attributes | Member of | Description +-----------------------+--------------------+------------------------+-----------------------------------------------------+------------- + regress_password_null | regress_role_admin | Create role, Create DB+| {regress_createdb,regress_createrole,regress_login} | + | | 2 connections | | + -- ok, backwards compatible noise words should be ignored CREATE ROLE regress_noiseword SYSID 12345; NOTICE: SYSID can no longer be specified @@ -84,16 +169,24 @@ CREATE ROLE regress_read_server_files IN ROLE pg_read_server_files; CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files; CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program; CREATE ROLE regress_signal_backend 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_createrole SET SESSION AUTHORIZATION regress_role_admin; +ALTER ROLE regress_plainrole OWNER TO regress_role_admin; +ERROR: must be owner of role regress_plainrole +REASSIGN OWNED BY regress_plainrole TO regress_role_admin; +ERROR: permission denied to reassign objects +-- superuser can do it, though +RESET SESSION AUTHORIZATION; +ALTER ROLE regress_plainrole OWNER TO regress_role_admin; +REASSIGN OWNED BY regress_plainrole TO regress_role_admin; +-- ok, superuser roles can drop superuser roles they own +SET SESSION AUTHORIZATION regress_role_super; DROP ROLE regress_nosuch_superuser; -ERROR: role "regress_nosuch_superuser" does not exist +-- ok, non-superuser roles can drop non-superuser roles they own +SET SESSION AUTHORIZATION regress_role_admin; DROP ROLE regress_nosuch_replication_bypassrls; -ERROR: role "regress_nosuch_replication_bypassrls" does not exist DROP ROLE regress_nosuch_replication; -ERROR: role "regress_nosuch_replication" does not exist DROP ROLE regress_nosuch_bypassrls; -ERROR: role "regress_nosuch_bypassrls" does not exist DROP ROLE regress_nosuch_super; ERROR: role "regress_nosuch_super" does not exist DROP ROLE regress_nosuch_dbowner; @@ -103,9 +196,23 @@ ERROR: role "regress_nosuch_recursive" does not exist DROP ROLE regress_nosuch_admin_recursive; ERROR: role "regress_nosuch_admin_recursive" does not exist DROP ROLE regress_plainrole; --- ok, should be able to drop non-superuser roles we created -DROP ROLE regress_createdb; +-- fail, cannot drop roles that own other roles DROP ROLE regress_createrole; +ERROR: role "regress_createrole" cannot be dropped because some objects depend on it +DETAIL: owner of role regress_rolecreator +owner of role regress_tenant +owner of role regress_read_all_data +owner of role regress_write_all_data +owner of role regress_monitor +owner of role regress_read_all_settings +owner of role regress_read_all_stats +owner of role regress_stat_scan_tables +owner of role regress_read_server_files +owner of role regress_write_server_files +owner of role regress_execute_server_program +owner of role regress_signal_backend +-- ok, should be able to drop these non-superuser roles +DROP ROLE regress_createdb; DROP ROLE regress_login; DROP ROLE regress_inherit; DROP ROLE regress_connection_limit; @@ -130,6 +237,10 @@ DROP ROLE regress_tenant; ERROR: role "regress_tenant" cannot be dropped because some objects depend on it DETAIL: owner of table tenant_table owner of view tenant_view +-- fail, role still owns other roles +DROP ROLE regress_createrole; +ERROR: role "regress_createrole" cannot be dropped because some objects depend on it +DETAIL: owner of role regress_tenant -- fail, cannot drop ourself nor superusers DROP ROLE regress_role_super; ERROR: must be superuser to drop superusers @@ -141,5 +252,12 @@ DROP INDEX tenant_idx; DROP TABLE tenant_table; DROP VIEW tenant_view; DROP ROLE regress_tenant; +DROP ROLE regress_createrole; +-- fail, cannot drop role with remaining privileges +DROP ROLE regress_role_admin; +ERROR: role "regress_role_admin" 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_admin; DROP ROLE regress_role_admin; 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 291e21d7a6..9ce619fd5f 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; CREATE USER regress_priv_user8; CREATE USER regress_priv_user9; CREATE USER regress_priv_user10; @@ -2356,7 +2358,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 d652f7b5fb..b6e3c508ba 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 292dc08797..091b116dd6 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_admin CREATEDB CREATEROLE REPLICATION BYPASSRLS; +GRANT CREATE ON DATABASE regression TO regress_role_admin; -- fail, only superusers can create users with these privileges SET SESSION AUTHORIZATION regress_role_admin; @@ -9,14 +10,45 @@ CREATE ROLE regress_nosuch_replication_bypassrls REPLICATION BYPASSRLS; CREATE ROLE regress_nosuch_replication REPLICATION; CREATE ROLE regress_nosuch_bypassrls BYPASSRLS; +-- fail, only superusers can own superusers +RESET SESSION AUTHORIZATION; +CREATE ROLE regress_nosuch_superuser AUTHORIZATION regress_role_admin SUPERUSER; + +-- ok, superuser can create superusers belonging to other superusers +CREATE ROLE regress_nosuch_superuser AUTHORIZATION regress_role_super SUPERUSER; + +-- ok, superuser can create users with these privileges for normal role +CREATE ROLE regress_nosuch_replication_bypassrls AUTHORIZATION regress_role_admin REPLICATION BYPASSRLS; +CREATE ROLE regress_nosuch_replication AUTHORIZATION regress_role_admin REPLICATION; +CREATE ROLE regress_nosuch_bypassrls AUTHORIZATION regress_role_admin BYPASSRLS; + +\du+ regress_nosuch_superuser +\du+ regress_nosuch_replication_bypassrls +\du+ regress_nosuch_replication +\du+ regress_nosuch_bypassrls + +-- fail, roles are not allowed to own themselves +ALTER ROLE regress_nosuch_bypassrls OWNER TO regress_nosuch_bypassrls; + -- ok, having CREATEROLE is enough to create users with these privileges +SET SESSION AUTHORIZATION regress_role_admin; CREATE ROLE regress_createdb CREATEDB; CREATE ROLE regress_createrole CREATEROLE; CREATE ROLE regress_login LOGIN; CREATE ROLE regress_inherit INHERIT; CREATE ROLE regress_connection_limit CONNECTION LIMIT 5; -CREATE ROLE regress_encrypted_password ENCRYPTED PASSWORD 'foo'; -CREATE ROLE regress_password_null PASSWORD NULL; +CREATE ROLE regress_encrypted_password PASSWORD NULL; +CREATE ROLE regress_password_null + CREATEDB CREATEROLE LOGIN INHERIT CONNECTION LIMIT 2 ENCRYPTED PASSWORD 'foo' + IN ROLE regress_createdb, regress_createrole, regress_login; + +\du+ regress_createdb +\du+ regress_createrole +\du+ regress_login +\du+ regress_inherit +\du+ regress_connection_limit +\du+ regress_encrypted_password +\du+ regress_password_null -- ok, backwards compatible noise words should be ignored CREATE ROLE regress_noiseword SYSID 12345; @@ -86,9 +118,22 @@ CREATE ROLE regress_write_server_files IN ROLE pg_write_server_files; CREATE ROLE regress_execute_server_program IN ROLE pg_execute_server_program; CREATE ROLE regress_signal_backend 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_createrole SET SESSION AUTHORIZATION regress_role_admin; +ALTER ROLE regress_plainrole OWNER TO regress_role_admin; +REASSIGN OWNED BY regress_plainrole TO regress_role_admin; + +-- superuser can do it, though +RESET SESSION AUTHORIZATION; +ALTER ROLE regress_plainrole OWNER TO regress_role_admin; +REASSIGN OWNED BY regress_plainrole TO regress_role_admin; + +-- ok, superuser roles can drop superuser roles they own +SET SESSION AUTHORIZATION regress_role_super; DROP ROLE regress_nosuch_superuser; + +-- ok, non-superuser roles can drop non-superuser roles they own +SET SESSION AUTHORIZATION regress_role_admin; DROP ROLE regress_nosuch_replication_bypassrls; DROP ROLE regress_nosuch_replication; DROP ROLE regress_nosuch_bypassrls; @@ -98,9 +143,11 @@ DROP ROLE regress_nosuch_recursive; DROP ROLE regress_nosuch_admin_recursive; DROP ROLE regress_plainrole; --- ok, should be able to drop non-superuser roles we created -DROP ROLE regress_createdb; +-- fail, cannot drop roles that own other roles DROP ROLE regress_createrole; + +-- ok, should be able to drop these non-superuser roles +DROP ROLE regress_createdb; DROP ROLE regress_login; DROP ROLE regress_inherit; DROP ROLE regress_connection_limit; @@ -124,6 +171,9 @@ DROP ROLE regress_signal_backend; -- fail, role still owns database objects DROP ROLE regress_tenant; +-- fail, role still owns other roles +DROP ROLE regress_createrole; + -- fail, cannot drop ourself nor superusers DROP ROLE regress_role_super; DROP ROLE regress_role_admin; @@ -134,5 +184,12 @@ DROP INDEX tenant_idx; DROP TABLE tenant_table; DROP VIEW tenant_view; DROP ROLE regress_tenant; +DROP ROLE regress_createrole; + +-- fail, cannot drop role with remaining privileges +DROP ROLE regress_role_admin; + +-- ok, can drop role if we revoke privileges first +REVOKE CREATE ON DATABASE regression FROM regress_role_admin; DROP ROLE regress_role_admin; DROP ROLE regress_role_super; diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql index c8c545b64c..dcf84f91fd 100644 --- a/src/test/regress/sql/privileges.sql +++ b/src/test/regress/sql/privileges.sql @@ -30,8 +30,10 @@ 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; CREATE USER regress_priv_user8; CREATE USER regress_priv_user9; CREATE USER regress_priv_user10; @@ -1426,8 +1428,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)