From 11788c71e39ad18be122c80ecc9cb974d1fe1bd1 Mon Sep 17 00:00:00 2001
From: Mike Palmiotto <mike.palmiotto@crunchydata.com>
Date: Fri, 15 Sep 2017 07:37:58 -0400
Subject: [PATCH] Add group role support to superuser whitelist

This feature adds support for '+<group_role>' syntax in the superuser
whitelist. This should allow users to be added to the whitelist by
association with the specified "<group_role>".

Resolves: #7
---
 README.md  | 12 ++++++++----
 set_user.c | 22 ++++++++++++++++++----
 2 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index e38e428..e898d68 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,11 @@ reset_user(text token) returns text
   * set_user.block_alter_system = off (defaults to "on")
   * set_user.block_copy_program = off (defaults to "on")
   * set_user.block_log_statement = off (defaults to "on")
-  * set_user.superuser_whitelist = '```<user1>```,```<user2>```,...,```<userN>```' (defaults to '*')
+  * set_user.superuser_whitelist = '```<role list>```
+    * ```<role list>``` can contain any of the following:
+      * list of user roles (i.e. ```<role1>, <role2>,...,<roleN>```)
+      * Group roles may be indicated by ```+<roleN>``'
+      * The wildcard character ```*```
 
 ## Description
 
@@ -37,7 +41,7 @@ This PostgreSQL extension allows switching users and optionally privilege escala
   variations will be blocked.
 * If set_user.block_log_statement is set to "on" and ```rolename``` is a database superuser, the current log_statement setting is changed to "all", meaning every SQL statement executed
 
-Only users with EXECUTE permission on ```set_user_u('rolename')``` may escalate to superuser. Additionally, only users explicitly listed in set_user.superuser_whitelist can escalate to superuser. If set_user.superuser_whitelist is explicitly set to the empty set, '', superuser escalation is blocked for all users. If the whitelist is equal to the wildcard character, '*', all users with EXECUTE permission on ```set_user_u()``` can escalate to superuser. The default value of set_user.superuser_whitelist is '*'.
+Only users with EXECUTE permission on ```set_user_u('rolename')``` may escalate to superuser. Additionally, only roles explicitly listed or included by a group that is explicitly listed (e.g. '+admin') in set_user.superuser_whitelist can escalate to superuser. If set_user.superuser_whitelist is explicitly set to the empty set, '', superuser escalation is blocked for all users. If the whitelist is equal to the wildcard character, '*', all users with EXECUTE permission on ```set_user_u()``` can escalate to superuser. The default value of set_user.superuser_whitelist is '*'.
 
 Additionally, with ```set_user('rolename','token')``` the ```token``` is stored in session lifetime memory.
 
@@ -203,8 +207,8 @@ CREATE EXTENSION set_user;
   * set_user.block_copy_program = on
 * Block SET log_statement commands
   * set_user.block_log_statement = on
-* Allow list of users to escalate to superuser
-  * set_user.superuser_whitelist = '```<user1>,<user2>,...,<userN>```'
+* Allow list of roles to escalate to superuser
+  * set_user.superuser_whitelist = '```<role1>,<role2>,...,<roleN>```'
 
 
 ## Examples
diff --git a/set_user.c b/set_user.c
index 7070c11..c976eb1 100644
--- a/set_user.c
+++ b/set_user.c
@@ -93,6 +93,7 @@
 #include "catalog/pg_proc.h"
 #include "miscadmin.h"
 #include "tcop/utility.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
@@ -208,9 +209,21 @@ check_user_whitelist(Oid userId, const char *whitelist)
 	{
 		char	   *elem = (char *) lfirst(l);
 
-		if (pg_strcasecmp(elem, GETUSERNAMEFROMID(userId)) == 0)
-			result = true;
-		else if (pg_strcasecmp(elem, WHITELIST_WILDCARD) == 0)
+		if (elem[0] == '+')
+		{
+			Oid roleId;
+			roleId = get_role_oid(elem + 1, false);
+			if (!OidIsValid(roleId))
+				result = false;
+
+			/* Check to see if userId is contained by group role in whitelist */
+			result = has_privs_of_role(userId, roleId);
+		}
+		else
+		{
+			if (pg_strcasecmp(elem, GETUSERNAMEFROMID(userId)) == 0)
+				result = true;
+			else if(pg_strcasecmp(elem, WHITELIST_WILDCARD) == 0)
 				/* No explicit usernames intermingled with wildcard. */
 				ereport(ERROR,
 						(errcode(ERRCODE_SYNTAX_ERROR),
@@ -219,8 +232,9 @@ check_user_whitelist(Oid userId, const char *whitelist)
 								 "or remove the wildcard character \"%s\". The whitelist "
 								 "cannot contain both.",
 								 WHITELIST_WILDCARD)));
+				result = true;
+		}
 	}
-
 	return result;
 }