From 8e12d14ee01ef00ebb1b044449809ff593bfd580 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 18 Nov 2022 16:03:06 -0500
Subject: [PATCH v1 4/4] Add role attributes INHERITCREATEDROLES and
 SETCREATEDROLES.

These control whether the implicit grants created when a
CREATEROLE user creates a new role are marked with the
inherit and set options respectively.

FIXME: Add some regression tests.
FIXME: Add documentation.
FIXME: REMEMBER TO BUMP THE CATALOG VERSION
---
 src/backend/commands/user.c       | 66 +++++++++++++++++++++++++++++--
 src/backend/parser/gram.y         |  8 ++++
 src/include/catalog/pg_authid.dat | 39 ++++++++++++------
 src/include/catalog/pg_authid.h   |  2 +
 4 files changed, 99 insertions(+), 16 deletions(-)

diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 1bfc42a157..599d545363 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -141,6 +141,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	bool		issuper = false;	/* Make the user a superuser? */
 	bool		inherit = true; /* Auto inherit privileges? */
 	bool		createrole = false; /* Can this user create roles? */
+	bool		inheritcreatedroles = false; /* inherit any created roles? */
+	bool		setcreatedroles = false; /* set role to any created roles? */
 	bool		createdb = false;	/* Can the user create databases? */
 	bool		canlogin = false;	/* Can this user login? */
 	bool		isreplication = false;	/* Is this a replication role? */
@@ -156,6 +158,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
+	DefElem    *dinheritcreatedroles = NULL;
+	DefElem    *dsetcreatedroles = NULL;
 	DefElem    *dcreatedb = NULL;
 	DefElem    *dcanlogin = NULL;
 	DefElem    *disreplication = NULL;
@@ -214,6 +218,18 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			dcreaterole = defel;
 		}
+		else if (strcmp(defel->defname, "inheritcreatedroles") == 0)
+		{
+			if (dinheritcreatedroles)
+				errorConflictingDefElem(defel, pstate);
+			dinheritcreatedroles = defel;
+		}
+		else if (strcmp(defel->defname, "setcreatedroles") == 0)
+		{
+			if (dsetcreatedroles)
+				errorConflictingDefElem(defel, pstate);
+			dsetcreatedroles = defel;
+		}
 		else if (strcmp(defel->defname, "createdb") == 0)
 		{
 			if (dcreatedb)
@@ -281,6 +297,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 		inherit = boolVal(dinherit->arg);
 	if (dcreaterole)
 		createrole = boolVal(dcreaterole->arg);
+	if (dinheritcreatedroles)
+		inheritcreatedroles = boolVal(dinheritcreatedroles->arg);
+	if (dsetcreatedroles)
+		setcreatedroles = boolVal(dsetcreatedroles->arg);
 	if (dcreatedb)
 		createdb = boolVal(dcreatedb->arg);
 	if (dcanlogin)
@@ -402,6 +422,10 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
 	new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
 	new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
+	new_record[Anum_pg_authid_rolinheritcreatedroles - 1] =
+		BoolGetDatum(inheritcreatedroles);
+	new_record[Anum_pg_authid_rolsetcreatedroles - 1] =
+		BoolGetDatum(setcreatedroles);
 	new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
 	new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin);
 	new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication);
@@ -531,18 +555,28 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	 */
 	if (!superuser())
 	{
-		RoleSpec   *current_role = makeNode(RoleSpec);
+		RoleSpec   *current_role;
+		HeapTuple	utup;
+		Form_pg_authid	uform;
 		GrantRoleOptions	poptself;
 
+		current_role = makeNode(RoleSpec);
 		current_role->roletype = ROLESPEC_CURRENT_ROLE;
 		current_role->location = -1;
 
+		utup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(GetUserId()));
+		if (!HeapTupleIsValid(utup))
+			elog(LOG, "cache lookup failed for role %u", GetUserId());
+		uform = (Form_pg_authid) GETSTRUCT(utup);
+
 		poptself.specified = GRANT_ROLE_SPECIFIED_ADMIN
 			| GRANT_ROLE_SPECIFIED_INHERIT
 			| GRANT_ROLE_SPECIFIED_SET;
 		poptself.admin = true;
-		poptself.inherit = false;
-		poptself.set = false;
+		poptself.inherit = uform->rolinheritcreatedroles;
+		poptself.set = uform->rolsetcreatedroles;
+
+		ReleaseSysCache(utup);
 
 		AddRoleMems(BOOTSTRAP_SUPERUSERID, stmt->role, roleid,
 					list_make1(current_role), list_make1_oid(GetUserId()),
@@ -612,6 +646,8 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 	DefElem    *dissuper = NULL;
 	DefElem    *dinherit = NULL;
 	DefElem    *dcreaterole = NULL;
+	DefElem    *dinheritcreatedroles = NULL;
+	DefElem    *dsetcreatedroles = NULL;
 	DefElem    *dcreatedb = NULL;
 	DefElem    *dcanlogin = NULL;
 	DefElem    *disreplication = NULL;
@@ -655,6 +691,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 				errorConflictingDefElem(defel, pstate);
 			dcreaterole = defel;
 		}
+		else if (strcmp(defel->defname, "inheritcreatedroles") == 0)
+		{
+			if (dinheritcreatedroles)
+				errorConflictingDefElem(defel, pstate);
+			dinheritcreatedroles = defel;
+		}
+		else if (strcmp(defel->defname, "setcreatedroles") == 0)
+		{
+			if (dsetcreatedroles)
+				errorConflictingDefElem(defel, pstate);
+			dsetcreatedroles = defel;
+		}
 		else if (strcmp(defel->defname, "createdb") == 0)
 		{
 			if (dcreatedb)
@@ -841,6 +889,18 @@ AlterRole(ParseState *pstate, AlterRoleStmt *stmt)
 		new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true;
 	}
 
+	if (dinheritcreatedroles)
+	{
+		new_record[Anum_pg_authid_rolinheritcreatedroles - 1] = BoolGetDatum(boolVal(dinheritcreatedroles->arg));
+		new_record_repl[Anum_pg_authid_rolinheritcreatedroles - 1] = true;
+	}
+
+	if (dsetcreatedroles)
+	{
+		new_record[Anum_pg_authid_rolsetcreatedroles - 1] = BoolGetDatum(boolVal(dsetcreatedroles->arg));
+		new_record_repl[Anum_pg_authid_rolsetcreatedroles - 1] = true;
+	}
+
 	if (dcreatedb)
 	{
 		new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(boolVal(dcreatedb->arg));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9384214942..0d6bdf1531 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -1206,6 +1206,14 @@ AlterOptRoleElem:
 						$$ = makeDefElem("createrole", (Node *) makeBoolean(true), @1);
 					else if (strcmp($1, "nocreaterole") == 0)
 						$$ = makeDefElem("createrole", (Node *) makeBoolean(false), @1);
+					else if (strcmp($1, "inheritcreatedroles") == 0)
+						$$ = makeDefElem("inheritcreatedroles", (Node *) makeBoolean(true), @1);
+					else if (strcmp($1, "noinheritcreatedroles") == 0)
+						$$ = makeDefElem("inheritcreatedroles", (Node *) makeBoolean(false), @1);
+					else if (strcmp($1, "setcreatedroles") == 0)
+						$$ = makeDefElem("setcreatedroles", (Node *) makeBoolean(true), @1);
+					else if (strcmp($1, "nosetcreatedroles") == 0)
+						$$ = makeDefElem("setcreatedroles", (Node *) makeBoolean(false), @1);
 					else if (strcmp($1, "replication") == 0)
 						$$ = makeDefElem("isreplication", (Node *) makeBoolean(true), @1);
 					else if (strcmp($1, "noreplication") == 0)
diff --git a/src/include/catalog/pg_authid.dat b/src/include/catalog/pg_authid.dat
index 3343a69ddb..79390240ca 100644
--- a/src/include/catalog/pg_authid.dat
+++ b/src/include/catalog/pg_authid.dat
@@ -21,67 +21,80 @@
 
 { oid => '10', oid_symbol => 'BOOTSTRAP_SUPERUSERID',
   rolname => 'POSTGRES', rolsuper => 't', rolinherit => 't',
-  rolcreaterole => 't', rolcreatedb => 't', rolcanlogin => 't',
+  rolcreaterole => 't', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 't', rolcanlogin => 't',
   rolreplication => 't', rolbypassrls => 't', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '6171', oid_symbol => 'ROLE_PG_DATABASE_OWNER',
   rolname => 'pg_database_owner', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '6181', oid_symbol => 'ROLE_PG_READ_ALL_DATA',
   rolname => 'pg_read_all_data', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '6182', oid_symbol => 'ROLE_PG_WRITE_ALL_DATA',
   rolname => 'pg_write_all_data', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '3373', oid_symbol => 'ROLE_PG_MONITOR',
   rolname => 'pg_monitor', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '3374', oid_symbol => 'ROLE_PG_READ_ALL_SETTINGS',
   rolname => 'pg_read_all_settings', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '3375', oid_symbol => 'ROLE_PG_READ_ALL_STATS',
   rolname => 'pg_read_all_stats', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '3377', oid_symbol => 'ROLE_PG_STAT_SCAN_TABLES',
   rolname => 'pg_stat_scan_tables', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4569', oid_symbol => 'ROLE_PG_READ_SERVER_FILES',
   rolname => 'pg_read_server_files', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4570', oid_symbol => 'ROLE_PG_WRITE_SERVER_FILES',
   rolname => 'pg_write_server_files', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4571', oid_symbol => 'ROLE_PG_EXECUTE_SERVER_PROGRAM',
   rolname => 'pg_execute_server_program', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4200', oid_symbol => 'ROLE_PG_SIGNAL_BACKEND',
   rolname => 'pg_signal_backend', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 { oid => '4544', oid_symbol => 'ROLE_PG_CHECKPOINT',
   rolname => 'pg_checkpoint', rolsuper => 'f', rolinherit => 't',
-  rolcreaterole => 'f', rolcreatedb => 'f', rolcanlogin => 'f',
+  rolcreaterole => 'f', rolinheritcreatedroles => 't',
+  rolsetcreatedroles => 't', rolcreatedb => 'f', rolcanlogin => 'f',
   rolreplication => 'f', rolbypassrls => 'f', rolconnlimit => '-1',
   rolpassword => '_null_', rolvaliduntil => '_null_' },
 
diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h
index 3512601c80..83fca311ed 100644
--- a/src/include/catalog/pg_authid.h
+++ b/src/include/catalog/pg_authid.h
@@ -35,6 +35,8 @@ CATALOG(pg_authid,1260,AuthIdRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID(284
 	bool		rolsuper;		/* read this field via superuser() only! */
 	bool		rolinherit;		/* inherit privileges from other roles? */
 	bool		rolcreaterole;	/* allowed to create more roles? */
+	bool		rolinheritcreatedroles; /* if creates role, inherit it? */
+	bool		rolsetcreatedroles; /* if creates role, set role to it? */
 	bool		rolcreatedb;	/* allowed to create databases? */
 	bool		rolcanlogin;	/* allowed to log in as session user? */
 	bool		rolreplication; /* role used for streaming replication */
-- 
2.24.3 (Apple Git-128)

