v2-0003-system_user-implementation.patch

text/plain

Filename: v2-0003-system_user-implementation.patch
Type: text/plain
Part: 0
Message: Re: SYSTEM_USER reserved word implementation

Patch

Format: unified
Series: patch v2-0003
File+
doc/src/sgml/func.sgml 17 0
src/backend/access/transam/parallel.c 18 1
src/backend/executor/execExprInterp.c 5 0
src/backend/parser/gram.y 7 1
src/backend/parser/parse_expr.c 1 0
src/backend/parser/parse_target.c 3 0
src/backend/utils/adt/name.c 11 1
src/backend/utils/adt/ruleutils.c 3 0
src/backend/utils/init/miscinit.c 84 0
src/backend/utils/init/postinit.c 1 0
src/include/catalog/pg_proc.dat 3 0
src/include/miscadmin.h 8 0
src/include/nodes/primnodes.h 1 0
src/include/parser/kwlist.h 1 0
src/test/authentication/t/001_password.pl 25 0
src/test/kerberos/t/001_auth.pl 25 2
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7b652460a1..a59d33074b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23660,6 +23660,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
        </para></entry>
       </row>
 
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>system_user</primary>
+        </indexterm>
+        <function>system_user</function>
+        <returnvalue>name</returnvalue>
+       </para>
+       <para>
+        Returns the authentication method and the identity (if any) that the
+        user presented during the authentication cycle, before they were
+        assigned a database role. It is represented as 'auth_method:identity' or
+        is NULL if the user has not actually been authenticated (for example if
+        the <xref linkend="auth-trust"/> has been used).
+       </para></entry>
+      </row>
+
       <row>
        <entry role="func_table_entry"><para role="func_signature">
         <indexterm>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index df0cd77558..3b49189bfd 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -76,6 +76,7 @@
 #define PARALLEL_KEY_REINDEX_STATE			UINT64CONST(0xFFFFFFFFFFFF000C)
 #define PARALLEL_KEY_RELMAPPER_STATE		UINT64CONST(0xFFFFFFFFFFFF000D)
 #define PARALLEL_KEY_UNCOMMITTEDENUMS		UINT64CONST(0xFFFFFFFFFFFF000E)
+#define PARALLEL_KEY_SYSTEMUSER				UINT64CONST(0xFFFFFFFFFFFF000F)
 
 /* Fixed-size parallel state. */
 typedef struct FixedParallelState
@@ -212,6 +213,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	Size		reindexlen = 0;
 	Size		relmapperlen = 0;
 	Size		uncommittedenumslen = 0;
+	Size		systemuserlen = 0;
 	Size		segsize = 0;
 	int			i;
 	FixedParallelState *fps;
@@ -272,8 +274,10 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		shm_toc_estimate_chunk(&pcxt->estimator, relmapperlen);
 		uncommittedenumslen = EstimateUncommittedEnumsSpace();
 		shm_toc_estimate_chunk(&pcxt->estimator, uncommittedenumslen);
+		systemuserlen = EstimateSystemUserSpace();
+		shm_toc_estimate_chunk(&pcxt->estimator, systemuserlen);
 		/* If you add more chunks here, you probably need to add keys. */
-		shm_toc_estimate_keys(&pcxt->estimator, 11);
+		shm_toc_estimate_keys(&pcxt->estimator, 12);
 
 		/* Estimate space need for error queues. */
 		StaticAssertStmt(BUFFERALIGN(PARALLEL_ERROR_QUEUE_SIZE) ==
@@ -352,6 +356,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		char	   *session_dsm_handle_space;
 		char	   *entrypointstate;
 		char	   *uncommittedenumsspace;
+		char	   *systemuserspace;
 		Size		lnamelen;
 
 		/* Serialize shared libraries we have loaded. */
@@ -422,6 +427,12 @@ InitializeParallelDSM(ParallelContext *pcxt)
 		shm_toc_insert(pcxt->toc, PARALLEL_KEY_UNCOMMITTEDENUMS,
 					   uncommittedenumsspace);
 
+		/* Serialize our Systemuser. */
+		systemuserspace = shm_toc_allocate(pcxt->toc, systemuserlen);
+		SerializeSystemUser(systemuserlen, systemuserspace);
+		shm_toc_insert(pcxt->toc, PARALLEL_KEY_SYSTEMUSER,
+					   systemuserspace);
+
 		/* Allocate space for worker information. */
 		pcxt->worker = palloc0(sizeof(ParallelWorkerInfo) * pcxt->nworkers);
 
@@ -1270,6 +1281,7 @@ ParallelWorkerMain(Datum main_arg)
 	char	   *reindexspace;
 	char	   *relmapperspace;
 	char	   *uncommittedenumsspace;
+	char	   *systemuserspace;
 	StringInfoData msgbuf;
 	char	   *session_dsm_handle_space;
 	Snapshot	tsnapshot;
@@ -1479,6 +1491,11 @@ ParallelWorkerMain(Datum main_arg)
 										   false);
 	RestoreUncommittedEnums(uncommittedenumsspace);
 
+	/* Restore the SystemUser. */
+	systemuserspace = shm_toc_lookup(toc, PARALLEL_KEY_SYSTEMUSER,
+										 false);
+	RestoreSystemUser(systemuserspace);
+
 	/* Attach to the leader's serializable transaction, if SERIALIZABLE. */
 	AttachSerializableXact(fps->serializable_xact_handle);
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index eaec697bb3..431a94a3de 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2543,6 +2543,11 @@ ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op)
 			*op->resvalue = session_user(fcinfo);
 			*op->resnull = fcinfo->isnull;
 			break;
+		case SVFOP_SYSTEM_USER:
+			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
+			*op->resvalue = system_user(fcinfo);
+			*op->resnull = fcinfo->isnull;
+			break;
 		case SVFOP_CURRENT_CATALOG:
 			InitFunctionCallInfoData(*fcinfo, NULL, 0, InvalidOid, NULL, NULL);
 			*op->resvalue = current_database(fcinfo);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..29dcc77724 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -842,7 +842,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SEQUENCE SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF
 	SHARE SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRING STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_USER
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -15457,6 +15457,10 @@ func_expr_common_subexpr:
 				{
 					$$ = makeSQLValueFunction(SVFOP_SESSION_USER, -1, @1);
 				}
+			| SYSTEM_USER
+				{
+					$$ = makeSQLValueFunction(SVFOP_SYSTEM_USER, -1, @1);
+				}
 			| USER
 				{
 					$$ = makeSQLValueFunction(SVFOP_USER, -1, @1);
@@ -18173,6 +18177,7 @@ reserved_keyword:
 			| SESSION_USER
 			| SOME
 			| SYMMETRIC
+			| SYSTEM_USER
 			| TABLE
 			| THEN
 			| TO
@@ -18578,6 +18583,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_USER
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 0dc2fc472e..8256cc177d 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2299,6 +2299,7 @@ transformSQLValueFunction(ParseState *pstate, SQLValueFunction *svf)
 		case SVFOP_CURRENT_USER:
 		case SVFOP_USER:
 		case SVFOP_SESSION_USER:
+		case SVFOP_SYSTEM_USER:
 		case SVFOP_CURRENT_CATALOG:
 		case SVFOP_CURRENT_SCHEMA:
 			svf->type = NAMEOID;
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 2a1d44b813..8ad50e31d2 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1917,6 +1917,9 @@ FigureColnameInternal(Node *node, char **name)
 				case SVFOP_SESSION_USER:
 					*name = "session_user";
 					return 2;
+				case SVFOP_SYSTEM_USER:
+					*name = "system_user";
+					return 2;
 				case SVFOP_CURRENT_CATALOG:
 					*name = "current_catalog";
 					return 2;
diff --git a/src/backend/utils/adt/name.c b/src/backend/utils/adt/name.c
index e8bba3670c..aa28b32cf0 100644
--- a/src/backend/utils/adt/name.c
+++ b/src/backend/utils/adt/name.c
@@ -257,7 +257,7 @@ namestrcmp(Name name, const char *str)
 
 
 /*
- * SQL-functions CURRENT_USER, SESSION_USER
+ * SQL-functions CURRENT_USER, SESSION_USER, SYSTEM_USER
  */
 Datum
 current_user(PG_FUNCTION_ARGS)
@@ -271,6 +271,16 @@ session_user(PG_FUNCTION_ARGS)
 	PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(GetUserNameFromId(GetSessionUserId(), false))));
 }
 
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+	const char   *sysuser = GetSystemUser();
+
+	if (sysuser)
+		PG_RETURN_DATUM(DirectFunctionCall1(namein, CStringGetDatum(sysuser)));
+	else
+		PG_RETURN_NULL();
+}
 
 /*
  * SQL-functions CURRENT_SCHEMA, CURRENT_SCHEMAS
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index c3937a60fd..ffdd818f1e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9319,6 +9319,9 @@ get_rule_expr(Node *node, deparse_context *context,
 					case SVFOP_SESSION_USER:
 						appendStringInfoString(buf, "SESSION_USER");
 						break;
+					case SVFOP_SYSTEM_USER:
+						appendStringInfoString(buf, "SYSTEM_USER");
+						break;
 					case SVFOP_CURRENT_CATALOG:
 						appendStringInfoString(buf, "CURRENT_CATALOG");
 						break;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index b25bd0e583..e872536f01 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -473,6 +473,7 @@ static Oid	AuthenticatedUserId = InvalidOid;
 static Oid	SessionUserId = InvalidOid;
 static Oid	OuterUserId = InvalidOid;
 static Oid	CurrentUserId = InvalidOid;
+static const char *SystemUser = NULL;
 
 /* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
@@ -544,6 +545,16 @@ SetSessionUserId(Oid userid, bool is_superuser)
 	CurrentUserId = userid;
 }
 
+/*
+ * Return the system user representing the authenticated identity.
+ * It is defined in InitializeSystemUser() as auth_method:authn_id.
+ */
+const char *
+GetSystemUser(void)
+{
+	return SystemUser;
+}
+
 /*
  * GetAuthenticatedUserId - get the authenticated user ID
  */
@@ -814,6 +825,32 @@ InitializeSessionUserIdStandalone(void)
 	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
+/*
+ * Initialize the system user during normal backend startup.
+ */
+void
+InitializeSystemUser(const Port *port)
+{
+	/* call only once */
+	Assert(SystemUser == NULL);
+
+	if (port->authn_id)
+	{
+		/* Build sysuser as auth_method:authn_id */
+		char *system_user;
+		Size authname_len = strlen(hba_authname(port->hba->auth_method));
+		Size authn_id_len = strlen(port->authn_id);
+
+		system_user = palloc0(authname_len + authn_id_len + 2);
+		strcat(system_user, hba_authname(port->hba->auth_method));
+		strcat(system_user, ":");
+		strcat(system_user, port->authn_id);
+
+		/* Store SystemUser in long-lived storage */
+		SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+		pfree(system_user);
+	}
+}
 
 /*
  * Change session auth ID while running
@@ -932,6 +969,53 @@ GetUserNameFromId(Oid roleid, bool noerr)
 	return result;
 }
 
+/*
+ * Calculate the space needed to serialize SystemUser.
+ */
+Size
+EstimateSystemUserSpace(void)
+{
+	Size		size = 1;
+	const char	*sysuser = GetSystemUser();
+
+	if (sysuser)
+		size = add_size(size, strlen(sysuser) + 1);
+
+	return size;
+}
+
+/*
+ * Serialize SystemUser for use by parallel workers.
+ */
+void
+SerializeSystemUser(Size maxsize, char *start_address)
+{
+	const char 	*sysuser = GetSystemUser();
+
+	Assert(maxsize > 0);
+
+	if (sysuser)
+	{
+		Size len;
+		len = strlcpy(start_address, sysuser, maxsize) + 1;
+		Assert(len <= maxsize);
+		start_address += len;
+	}
+	start_address[0] = '\0';
+}
+
+/*
+ * Restore SystemUser from its serialized representation.
+ */
+void
+RestoreSystemUser(char *sysuser)
+{
+	if (sysuser[0] == '\0')
+		SystemUser = NULL;
+	else
+		SystemUser = MemoryContextStrdup(TopMemoryContext, sysuser);
+}
+
 
 /*-------------------------------------------------------------------------
  *				Interlock-file support
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6b9082604f..d9a879382e 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -835,6 +835,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		Assert(MyProcPort != NULL);
 		PerformAuthentication(MyProcPort);
 		InitializeSessionUserId(username, useroid);
+		InitializeSystemUser(MyProcPort);
 		am_superuser = superuser();
 	}
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87aa571a33..4406396055 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1508,6 +1508,9 @@
 { oid => '746', descr => 'session user name',
   proname => 'session_user', provolatile => 's', prorettype => 'name',
   proargtypes => '', prosrc => 'session_user' },
+{ oid => '786', descr => 'system user name',
+  proname => 'system_user', provolatile => 's', prorettype => 'name',
+  proargtypes => '', prosrc => 'system_user' },
 
 { oid => '744',
   proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 0af130fbc5..32fe3b8983 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -364,6 +364,10 @@ extern void InitializeSessionUserIdStandalone(void);
 extern void SetSessionAuthorization(Oid userid, bool is_superuser);
 extern Oid	GetCurrentRoleId(void);
 extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
+/* kluge to avoid including libpq/libpq-be.h here */
+struct Port;
+extern void InitializeSystemUser(const struct Port *port);
+extern const char* GetSystemUser(void);
 
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
@@ -486,6 +490,10 @@ extern bool has_rolreplication(Oid roleid);
 typedef void (*shmem_request_hook_type) (void);
 extern PGDLLIMPORT shmem_request_hook_type shmem_request_hook;
 
+extern Size EstimateSystemUserSpace(void);
+extern void SerializeSystemUser(Size maxsize, char *start_address);
+extern void RestoreSystemUser(char *sysuser);
+
 /* in executor/nodeHash.c */
 extern size_t get_hash_memory_limit(void);
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 51505eee85..c23b0f2507 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1186,6 +1186,7 @@ typedef enum SQLValueFunctionOp
 	SVFOP_CURRENT_USER,
 	SVFOP_USER,
 	SVFOP_SESSION_USER,
+	SVFOP_SYSTEM_USER,
 	SVFOP_CURRENT_CATALOG,
 	SVFOP_CURRENT_SCHEMA
 } SQLValueFunctionOp;
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index ae35f03251..e309988b26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -434,6 +434,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_user", SYSTEM_USER, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 3e3079c824..56620b1ffd 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -72,6 +72,13 @@ $node->safe_psql('postgres',
 $node->safe_psql('postgres',
 	"SET password_encryption='md5'; CREATE ROLE md5_role LOGIN PASSWORD 'pass';"
 );
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+    'CREATE TABLE nulls (n) AS SELECT NULL FROM generate_series(1, 200000);'
+);
+$node->safe_psql('postgres',
+    'GRANT SELECT ON nulls TO md5_role;'
+);
 $ENV{"PGPASSWORD"} = 'pass';
 
 # For "trust" method, all users should be able to connect. These users are not
@@ -82,6 +89,24 @@ test_role($node, 'scram_role', 'trust', 0,
 test_role($node, 'md5_role', 'trust', 0,
 	log_unlike => [qr/connection authenticated:/]);
 
+# Test SYSTEM_USER is null when not authenticated.
+my $res =
+  $node->safe_psql('postgres', "SELECT SYSTEM_USER IS NULL;");
+is($res, 't', "users with trust authentication have NULL SYSTEM_USER");
+
+# Test SYSTEM_USER with parallel workers.
+$res = $node->safe_psql(
+    'postgres', '
+        SET min_parallel_table_scan_size TO 0;
+        SET parallel_setup_cost TO 0;
+        SET parallel_tuple_cost TO 0;
+        SET max_parallel_workers_per_gather TO 2;
+
+        SELECT bool_and(SYSTEM_USER IS NOT DISTINCT FROM n) FROM nulls;
+    ',
+    connstr => "user=md5_role");
+is($res, 't', "parallel workers return a null SYSTEM_USER when not authenticated");
+
 # For plain "password" method, all users should also be able to connect.
 reset_pg_hba($node, 'password');
 test_role($node, 'scram_role', 'password', 0,
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..9bf17985d5 100644
--- a/src/test/kerberos/t/001_auth.pl
+++ b/src/test/kerberos/t/001_auth.pl
@@ -4,8 +4,8 @@
 # Sets up a KDC and then runs a variety of tests to make sure that the
 # GSSAPI/Kerberos authentication and encryption are working properly,
 # that the options in pg_hba.conf and pg_ident.conf are handled correctly,
-# and that the server-side pg_stat_gssapi view reports what we expect to
-# see for each test.
+# that the server-side pg_stat_gssapi view reports what we expect to
+# see for each test and that SYSTEM_USER returns what we expect to see.
 #
 # Since this requires setting up a full KDC, it doesn't make much sense
 # to have multiple test scripts (since they'd have to also create their
@@ -307,6 +307,29 @@ test_query(
 	'gssencmode=require',
 	'sending 100K lines works');
 
+# Test that SYSTEM_USER works.
+test_query(
+	$node,
+	'test1',
+	'SELECT SYSTEM_USER;',
+	qr/^gss:test1\@$realm$/s,
+	'gssencmode=require',
+	'testing system_user');
+
+# Test that SYSTEM_USER works with parallel workers.
+test_query(
+	$node,
+	'test1',
+	"CREATE TEMP TABLE mytab (n) as SELECT NULL FROM generate_series(1, 200000);\n"
+	. "SET min_parallel_table_scan_size TO 0;\n"
+	. "SET parallel_setup_cost TO 0;\n"
+	. "SET parallel_tuple_cost TO 0;\n"
+	. "SET max_parallel_workers_per_gather TO 2;\n"
+	. "SELECT bool_and(SYSTEM_USER IS DISTINCT FROM n) FROM mytab;",
+	qr/^t$/s,
+	'gssencmode=require',
+	'testing system_user with parallel workers');
+
 unlink($node->data_dir . '/pg_hba.conf');
 $node->append_conf('pg_hba.conf',
 	qq{hostgssenc all all $hostaddr/32 gss map=mymap});