diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index f87afefeae..470ff74a28 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -23663,6 +23663,23 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
+
+
+
+ system_user
+
+ system_user
+ name
+
+
+ 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 has been used).
+
+
+
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bc93101ff7..965bf431b6 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1496,6 +1496,9 @@ ParallelWorkerMain(Datum main_arg)
false);
RestoreClientConnectionInfo(clientconninfospace);
+ /* Initialize SystemUser now that MyClientConnectionInfo is restored. */
+ InitializeSystemUser(MyClientConnectionInfo);
+
/* 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 636794ca6f..e0d912cda2 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2569,6 +2569,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 b5ab9d9c9a..05ed3c270c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -850,7 +850,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
@@ -15454,6 +15454,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);
@@ -18170,6 +18174,7 @@ reserved_keyword:
| SESSION_USER
| SOME
| SYMMETRIC
+ | SYSTEM_USER
| TABLE
| THEN
| TO
@@ -18575,6 +18580,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 fabb5f7207..64a76b26cf 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2297,6 +2297,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 16a0fe59e2..2c257a7612 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1916,6 +1916,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/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9959f6910e..06658b7a3b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -9346,6 +9346,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 683f616b1a..3da87861bd 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -477,6 +477,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;
@@ -548,6 +549,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
*/
@@ -818,6 +829,46 @@ InitializeSessionUserIdStandalone(void)
SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
}
+/*
+ * Initialize the system user.
+ */
+void
+InitializeSystemUser(ClientConnectionInfo conninfo)
+{
+ /* call only once */
+ Assert(SystemUser == NULL);
+
+ if (conninfo.authn_id)
+ {
+ /* Build sysuser as auth_method:authn_id */
+ char *system_user;
+ Size authname_len = strlen(hba_authname(conninfo.auth_method));
+ Size authn_id_len = strlen(conninfo.authn_id);
+
+ system_user = palloc0(authname_len + authn_id_len + 2);
+ strcat(system_user, hba_authname(conninfo.auth_method));
+ strcat(system_user, ":");
+ strcat(system_user, conninfo.authn_id);
+
+ /* Store SystemUser in long-lived storage */
+ SystemUser = MemoryContextStrdup(TopMemoryContext, system_user);
+ pfree(system_user);
+ }
+}
+
+/*
+ * SQL-function SYSTEM_USER
+ */
+Datum
+system_user(PG_FUNCTION_ARGS)
+{
+ const char *sysuser = GetSystemUser();
+
+ if (sysuser)
+ PG_RETURN_DATUM(CStringGetDatum(sysuser));
+ else
+ PG_RETURN_NULL();
+}
/*
* Change session auth ID while running
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 0d557a8684..df174b9d3f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -855,6 +855,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
Assert(MyProcPort != NULL);
PerformAuthentication(MyProcPort);
InitializeSessionUserId(username, useroid);
+ InitializeSystemUser(MyClientConnectionInfo);
am_superuser = superuser();
}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index be47583122..061db181cc 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 => 'text',
+ proargtypes => '', prosrc => 'system_user' },
{ oid => '744',
proname => 'array_eq', prorettype => 'bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 65cf4ba50f..e49c696c3a 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -357,6 +357,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 ClientConnectionInfo;
+extern void InitializeSystemUser(struct ClientConnectionInfo conninfo);
+extern const char* GetSystemUser(void);
/* in utils/misc/superuser.c */
extern bool superuser(void); /* current user is superuser */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 3aa96bb685..d0bc2ac92e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1327,6 +1327,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/authentication/t/003_peer.pl b/src/test/authentication/t/003_peer.pl
new file mode 100644
index 0000000000..f2f3541f58
--- /dev/null
+++ b/src/test/authentication/t/003_peer.pl
@@ -0,0 +1,107 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+# Tests for peer authentication, user name map and SYSTEM_USER.
+# The peer authentication method is checked through this test.
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Delete pg_hba.conf from the given node, add a new entry to it
+# and then execute a reload to refresh it.
+sub reset_pg_hba
+{
+ my $node = shift;
+ my $hba_method = shift;
+
+ unlink($node->data_dir . '/pg_hba.conf');
+ # just for testing purposes, use a continuation line
+ $node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
+ $node->reload;
+ return;
+}
+
+# Initialize primary node
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init;
+$node->start;
+
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+ "CREATE TABLE ids (id) AS SELECT 'peer:'||session_user FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+ 'GRANT SELECT ON ids TO public;'
+);
+
+# Get the session_user to define the user name map test.
+my $session_user =
+ $node->safe_psql('postgres', 'select session_user');
+
+# Create a new user for the user name map test.
+$node->safe_psql('postgres',
+ "CREATE user john;");
+
+# Set pg_hba.conf with the peer authentication.
+reset_pg_hba($node, 'peer');
+
+# Check that peer authentication is supported on this platform
+my $log_location = -s $node->logfile;
+
+$node->psql('postgres', undef, connstr => "user=$session_user");
+
+my $logfile = slurp_file($node->logfile, $log_location);
+
+# If not supported, then skip the rest of the test
+if ($logfile =~ qr/peer authentication is not supported on this platform/)
+{
+ plan skip_all => 'peer authentication is not supported on this platform';
+}
+
+# Test SYSTEM_USER when peer authentication is used.
+my $res =
+ $node->safe_psql('postgres', "select true where system_user = 'peer:'||session_user;");
+is($res, 't', "users with peer authentication have the correct 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 = id) FROM ids;');
+is($res, 't', "parallel workers return the correct SYSTEM_USER when peer authentication is used");
+
+# Define a user name map.
+$node->append_conf('pg_ident.conf', qq{mypeermap $session_user john});
+
+# Set pg_hba.conf with the peer authentication and the user name map.
+reset_pg_hba($node, 'peer map=mypeermap');
+
+# Test that the user name map is well defined and working.
+$res =
+ $node->safe_psql('postgres', "select true where session_user = 'john'", connstr => "user=john");
+is($res, 't', "user name map is well defined and working");
+
+# Test SYSTEM_USER when peer authentication is used with user name map.
+$res =
+ $node->safe_psql('postgres', qq{select true where system_user = 'peer:'||'$session_user'}, connstr => "user=john");
+is($res, 't', "users with peer authentication and user name map have the correct SYSTEM_USER");
+
+# Test SYSTEM_USER with user name map and 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 = id) FROM ids;',
+ connstr => "user=john");
+is($res, 't', "parallel workers return the correct SYSTEM_USER when peer authentication and user name map is used");
+
+done_testing();
diff --git a/src/test/kerberos/t/001_auth.pl b/src/test/kerberos/t/001_auth.pl
index 62e0542639..be0dd7c62d 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
@@ -176,6 +176,15 @@ $node->start;
$node->safe_psql('postgres', 'CREATE USER test1;');
+# Set up a table for SYSTEM_USER parallel worker testing.
+$node->safe_psql('postgres',
+ "CREATE TABLE ids (id) AS SELECT 'gss:test1\@$realm' FROM generate_series(1, 200000);"
+);
+
+$node->safe_psql('postgres',
+ 'GRANT SELECT ON ids TO public;'
+);
+
note "running tests";
# Test connection success or failure, and if success, that query returns true.
@@ -307,6 +316,28 @@ 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',
+ "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 = id) FROM ids;",
+ 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});