Fix improper interactions between session_authorization and role.
authorTom Lane <[email protected]>
Mon, 11 Nov 2024 15:29:54 +0000 (10:29 -0500)
committerTom Lane <[email protected]>
Mon, 11 Nov 2024 15:29:54 +0000 (10:29 -0500)
The SQL spec mandates that SET SESSION AUTHORIZATION implies
SET ROLE NONE.  We tried to implement that within the lowest-level
functions that manipulate these settings, but that was a bad idea.
In particular, guc.c assumes that it doesn't matter in what order
it applies GUC variable updates, but that was not the case for these
two variables.  This problem, compounded by some hackish attempts to
work around it, led to some security-grade issues:

* Rolling back a transaction that had done SET SESSION AUTHORIZATION
would revert to SET ROLE NONE, even if that had not been the previous
state, so that the effective user ID might now be different from what
it had been.

* The same for SET SESSION AUTHORIZATION in a function SET clause.

* If a parallel worker inspected current_setting('role'), it saw
"none" even when it should see something else.

Also, although the parallel worker startup code intended to cope
with the current role's pg_authid row having disappeared, its
implementation of that was incomplete so it would still fail.

Fix by fully separating the miscinit.c functions that assign
session_authorization from those that assign role.  To implement the
spec's requirement, teach set_config_option itself to perform "SET
ROLE NONE" when it sets session_authorization.  (This is undoubtedly
ugly, but the alternatives seem worse.  In particular, there's no way
to do it within assign_session_authorization without incompatible
changes in the API for GUC assign hooks.)  Also, improve
ParallelWorkerMain to directly set all the relevant user-ID variables
instead of relying on some of them to get set indirectly.  That
allows us to survive not finding the pg_authid row during worker
startup.

In v16 and earlier, this includes back-patching 9987a7bf3 which
fixed a violation of GUC coding rules: SetSessionAuthorization
is not an appropriate place to be throwing errors from.

Security: CVE-2024-10978

src/backend/access/transam/parallel.c
src/backend/commands/variable.c
src/backend/utils/init/miscinit.c
src/backend/utils/misc/guc.c
src/include/miscadmin.h
src/test/regress/expected/privileges.out
src/test/regress/sql/privileges.sql

index 4164fcc5542236ee02980769901e247d63b052e7..9a85a409dd8d1baa9ec6b6862dd217bfcfe49d0d 100644 (file)
@@ -81,12 +81,15 @@ typedef struct FixedParallelState
    /* Fixed-size state that workers must restore. */
    Oid         database_id;
    Oid         authenticated_user_id;
-   Oid         current_user_id;
+   Oid         session_user_id;
    Oid         outer_user_id;
+   Oid         current_user_id;
    Oid         temp_namespace_id;
    Oid         temp_toast_namespace_id;
    int         sec_context;
-   bool        is_superuser;
+   bool        authenticated_user_is_superuser;
+   bool        session_user_is_superuser;
+   bool        role_is_superuser;
    PGPROC     *parallel_master_pgproc;
    pid_t       parallel_master_pid;
    BackendId   parallel_master_backend_id;
@@ -321,9 +324,12 @@ InitializeParallelDSM(ParallelContext *pcxt)
        shm_toc_allocate(pcxt->toc, sizeof(FixedParallelState));
    fps->database_id = MyDatabaseId;
    fps->authenticated_user_id = GetAuthenticatedUserId();
+   fps->session_user_id = GetSessionUserId();
    fps->outer_user_id = GetCurrentRoleId();
-   fps->is_superuser = session_auth_is_superuser;
    GetUserIdAndSecContext(&fps->current_user_id, &fps->sec_context);
+   fps->authenticated_user_is_superuser = GetAuthenticatedUserIsSuperuser();
+   fps->session_user_is_superuser = GetSessionUserIsSuperuser();
+   fps->role_is_superuser = session_auth_is_superuser;
    GetTempNamespaceState(&fps->temp_namespace_id,
                          &fps->temp_toast_namespace_id);
    fps->parallel_master_pgproc = MyProc;
@@ -1357,6 +1363,18 @@ ParallelWorkerMain(Datum main_arg)
 
    entrypt = LookupParallelWorkerFunction(library_name, function_name);
 
+   /*
+    * Restore current session authorization and role id.  No verification
+    * happens here, we just blindly adopt the leader's state.  Note that this
+    * has to happen before InitPostgres, since InitializeSessionUserId will
+    * not set these variables.
+    */
+   SetAuthenticatedUserId(fps->authenticated_user_id,
+                          fps->authenticated_user_is_superuser);
+   SetSessionAuthorization(fps->session_user_id,
+                           fps->session_user_is_superuser);
+   SetCurrentRoleId(fps->outer_user_id, fps->role_is_superuser);
+
    /* Restore database connection. */
    BackgroundWorkerInitializeConnectionByOid(fps->database_id,
                                              fps->authenticated_user_id,
@@ -1422,13 +1440,13 @@ ParallelWorkerMain(Datum main_arg)
    InvalidateSystemCaches();
 
    /*
-    * Restore current role id.  Skip verifying whether session user is
-    * allowed to become this role and blindly restore the leader's state for
-    * current role.
+    * Restore current user ID and security context.  No verification happens
+    * here, we just blindly adopt the leader's state.  We can't do this till
+    * after restoring GUCs, else we'll get complaints about restoring
+    * session_authorization and role.  (In effect, we're assuming that all
+    * the restored values are okay to set, even if we are now inside a
+    * restricted context.)
     */
-   SetCurrentRoleId(fps->outer_user_id, fps->is_superuser);
-
-   /* Restore user ID and security context. */
    SetUserIdAndSecContext(fps->current_user_id, fps->sec_context);
 
    /* Restore temp-namespace state to ensure search path matches leader's. */
index 16fa68c18c68e1271a2e4a577d383c13399334d7..11ba35e1c8b3ed4982923b56b8b79a9daa240370 100644 (file)
@@ -753,40 +753,78 @@ check_session_authorization(char **newval, void **extra, GucSource source)
    if (*newval == NULL)
        return true;
 
-   if (!IsTransactionState())
+   if (InitializingParallelWorker)
    {
        /*
-        * Can't do catalog lookups, so fail.  The result of this is that
-        * session_authorization cannot be set in postgresql.conf, which seems
-        * like a good thing anyway, so we don't work hard to avoid it.
+        * In parallel worker initialization, we want to copy the leader's
+        * state even if it no longer matches the catalogs. ParallelWorkerMain
+        * already installed the correct role OID and superuser state.
         */
-       return false;
+       roleid = GetSessionUserId();
+       is_superuser = GetSessionUserIsSuperuser();
    }
-
-   /* Look up the username */
-   roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval));
-   if (!HeapTupleIsValid(roleTup))
+   else
    {
+       if (!IsTransactionState())
+       {
+           /*
+            * Can't do catalog lookups, so fail.  The result of this is that
+            * session_authorization cannot be set in postgresql.conf, which
+            * seems like a good thing anyway, so we don't work hard to avoid
+            * it.
+            */
+           return false;
+       }
+
        /*
         * When source == PGC_S_TEST, we don't throw a hard error for a
-        * nonexistent user name, only a NOTICE.  See comments in guc.h.
+        * nonexistent user name or insufficient privileges, only a NOTICE.
+        * See comments in guc.h.
         */
-       if (source == PGC_S_TEST)
+
+       /* Look up the username */
+       roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(*newval));
+       if (!HeapTupleIsValid(roleTup))
        {
-           ereport(NOTICE,
-                   (errcode(ERRCODE_UNDEFINED_OBJECT),
-                    errmsg("role \"%s\" does not exist", *newval)));
-           return true;
+           if (source == PGC_S_TEST)
+           {
+               ereport(NOTICE,
+                       (errcode(ERRCODE_UNDEFINED_OBJECT),
+                        errmsg("role \"%s\" does not exist", *newval)));
+               return true;
+           }
+           GUC_check_errmsg("role \"%s\" does not exist", *newval);
+           return false;
        }
-       GUC_check_errmsg("role \"%s\" does not exist", *newval);
-       return false;
-   }
 
-   roleform = (Form_pg_authid) GETSTRUCT(roleTup);
-   roleid = roleform->oid;
-   is_superuser = roleform->rolsuper;
+       roleform = (Form_pg_authid) GETSTRUCT(roleTup);
+       roleid = roleform->oid;
+       is_superuser = roleform->rolsuper;
 
-   ReleaseSysCache(roleTup);
+       ReleaseSysCache(roleTup);
+
+       /*
+        * Only superusers may SET SESSION AUTHORIZATION a role other than
+        * itself. Note that in case of multiple SETs in a single session, the
+        * original authenticated user's superuserness is what matters.
+        */
+       if (roleid != GetAuthenticatedUserId() &&
+           !GetAuthenticatedUserIsSuperuser())
+       {
+           if (source == PGC_S_TEST)
+           {
+               ereport(NOTICE,
+                       (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                        errmsg("permission will be denied to set session authorization \"%s\"",
+                               *newval)));
+               return true;
+           }
+           GUC_check_errcode(ERRCODE_INSUFFICIENT_PRIVILEGE);
+           GUC_check_errmsg("permission denied to set session authorization \"%s\"",
+                            *newval);
+           return false;
+       }
+   }
 
    /* Set up "extra" struct for assign_session_authorization to use */
    myextra = (role_auth_extra *) malloc(sizeof(role_auth_extra));
@@ -836,6 +874,16 @@ check_role(char **newval, void **extra, GucSource source)
        roleid = InvalidOid;
        is_superuser = false;
    }
+   else if (InitializingParallelWorker)
+   {
+       /*
+        * In parallel worker initialization, we want to copy the leader's
+        * state even if it no longer matches the catalogs. ParallelWorkerMain
+        * already installed the correct role OID and superuser state.
+        */
+       roleid = GetCurrentRoleId();
+       is_superuser = session_auth_is_superuser;
+   }
    else
    {
        if (!IsTransactionState())
@@ -875,13 +923,8 @@ check_role(char **newval, void **extra, GucSource source)
 
        ReleaseSysCache(roleTup);
 
-       /*
-        * Verify that session user is allowed to become this role, but skip
-        * this in parallel mode, where we must blindly recreate the parallel
-        * leader's state.
-        */
-       if (!InitializingParallelWorker &&
-           !is_member_of_role(GetSessionUserId(), roleid))
+       /* Verify that session user is allowed to become this role */
+       if (!is_member_of_role(GetSessionUserId(), roleid))
        {
            if (source == PGC_S_TEST)
            {
index 6747f20fa6ca3e105eb3eaae81f6b4309cb611a9..b2a33ab30735d7c58454c348b184049b02d2d78b 100644 (file)
@@ -31,6 +31,7 @@
 #endif
 
 #include "access/htup_details.h"
+#include "access/parallel.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
 #include "libpq/libpq.h"
@@ -404,7 +405,7 @@ GetOuterUserId(void)
 
 
 static void
-SetOuterUserId(Oid userid)
+SetOuterUserId(Oid userid, bool is_superuser)
 {
    AssertState(SecurityRestrictionContext == 0);
    AssertArg(OidIsValid(userid));
@@ -412,6 +413,11 @@ SetOuterUserId(Oid userid)
 
    /* We force the effective user ID to match, too */
    CurrentUserId = userid;
+
+   /* Also update the is_superuser GUC to match OuterUserId's property */
+   SetConfigOption("is_superuser",
+                   is_superuser ? "on" : "off",
+                   PGC_INTERNAL, PGC_S_OVERRIDE);
 }
 
 
@@ -425,6 +431,12 @@ GetSessionUserId(void)
    return SessionUserId;
 }
 
+bool
+GetSessionUserIsSuperuser(void)
+{
+   Assert(OidIsValid(SessionUserId));
+   return SessionUserIsSuperuser;
+}
 
 static void
 SetSessionUserId(Oid userid, bool is_superuser)
@@ -433,15 +445,11 @@ SetSessionUserId(Oid userid, bool is_superuser)
    AssertArg(OidIsValid(userid));
    SessionUserId = userid;
    SessionUserIsSuperuser = is_superuser;
-   SetRoleIsActive = false;
-
-   /* We force the effective user IDs to match, too */
-   OuterUserId = userid;
-   CurrentUserId = userid;
 }
 
 /*
- * GetAuthenticatedUserId - get the authenticated user ID
+ * GetAuthenticatedUserId/SetAuthenticatedUserId - get/set the authenticated
+ * user ID
  */
 Oid
 GetAuthenticatedUserId(void)
@@ -450,6 +458,32 @@ GetAuthenticatedUserId(void)
    return AuthenticatedUserId;
 }
 
+/*
+ * Return whether the authenticated user was superuser at connection start.
+ */
+bool
+GetAuthenticatedUserIsSuperuser(void)
+{
+   Assert(OidIsValid(AuthenticatedUserId));
+   return AuthenticatedUserIsSuperuser;
+}
+
+void
+SetAuthenticatedUserId(Oid userid, bool is_superuser)
+{
+   Assert(OidIsValid(userid));
+
+   /* call only once */
+   Assert(!OidIsValid(AuthenticatedUserId));
+
+   AuthenticatedUserId = userid;
+   AuthenticatedUserIsSuperuser = is_superuser;
+
+   /* Also mark our PGPROC entry with the authenticated user id */
+   /* (We assume this is an atomic store so no lock is needed) */
+   MyProc->roleId = userid;
+}
+
 
 /*
  * GetUserIdAndSecContext/SetUserIdAndSecContext - get/set the current user ID
@@ -595,6 +629,7 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
    HeapTuple   roleTup;
    Form_pg_authid rform;
    char       *rname;
+   bool        is_superuser;
 
    /*
     * Don't do scans if we're bootstrapping, none of the system catalogs
@@ -602,9 +637,6 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
     */
    AssertState(!IsBootstrapProcessingMode());
 
-   /* call only once */
-   AssertState(!OidIsValid(AuthenticatedUserId));
-
    /*
     * Make sure syscache entries are flushed for recent catalog changes. This
     * allows us to find roles that were created on-the-fly during
@@ -612,36 +644,52 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
     */
    AcceptInvalidationMessages();
 
+   /*
+    * Look up the role, either by name if that's given or by OID if not.
+    * Normally we have to fail if we don't find it, but in parallel workers
+    * just return without doing anything: all the critical work has been done
+    * already.  The upshot of that is that if the role has been deleted, we
+    * will not enforce its rolconnlimit against parallel workers anymore.
+    */
    if (rolename != NULL)
    {
        roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(rolename));
        if (!HeapTupleIsValid(roleTup))
+       {
+           if (InitializingParallelWorker)
+               return;
            ereport(FATAL,
                    (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                     errmsg("role \"%s\" does not exist", rolename)));
+       }
    }
    else
    {
        roleTup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
        if (!HeapTupleIsValid(roleTup))
+       {
+           if (InitializingParallelWorker)
+               return;
            ereport(FATAL,
                    (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
                     errmsg("role with OID %u does not exist", roleid)));
+       }
    }
 
    rform = (Form_pg_authid) GETSTRUCT(roleTup);
    roleid = rform->oid;
    rname = NameStr(rform->rolname);
+   is_superuser = rform->rolsuper;
 
-   AuthenticatedUserId = roleid;
-   AuthenticatedUserIsSuperuser = rform->rolsuper;
-
-   /* This sets OuterUserId/CurrentUserId too */
-   SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
+   /* In a parallel worker, ParallelWorkerMain already set these variables */
+   if (!InitializingParallelWorker)
+   {
+       SetAuthenticatedUserId(roleid, is_superuser);
 
-   /* Also mark our PGPROC entry with the authenticated user id */
-   /* (We assume this is an atomic store so no lock is needed) */
-   MyProc->roleId = roleid;
+       /* Set SessionUserId and related variables via the GUC mechanisms */
+       SetConfigOption("session_authorization", rname,
+                       PGC_BACKEND, PGC_S_OVERRIDE);
+   }
 
    /*
     * These next checks are not enforced when in standalone mode, so that
@@ -670,7 +718,7 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
         * just document that the connection limit is approximate.
         */
        if (rform->rolconnlimit >= 0 &&
-           !AuthenticatedUserIsSuperuser &&
+           !is_superuser &&
            CountUserBackends(roleid) > rform->rolconnlimit)
            ereport(FATAL,
                    (errcode(ERRCODE_TOO_MANY_CONNECTIONS),
@@ -678,13 +726,6 @@ InitializeSessionUserId(const char *rolename, Oid roleid)
                            rname)));
    }
 
-   /* Record username and superuser status as GUC settings too */
-   SetConfigOption("session_authorization", rname,
-                   PGC_BACKEND, PGC_S_OVERRIDE);
-   SetConfigOption("is_superuser",
-                   AuthenticatedUserIsSuperuser ? "on" : "off",
-                   PGC_INTERNAL, PGC_S_OVERRIDE);
-
    ReleaseSysCache(roleTup);
 }
 
@@ -707,48 +748,39 @@ InitializeSessionUserIdStandalone(void)
    AuthenticatedUserId = BOOTSTRAP_SUPERUSERID;
    AuthenticatedUserIsSuperuser = true;
 
-   SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
-
    /*
-    * XXX This should set SetConfigOption("session_authorization"), too.
-    * Since we don't, C code will get NULL, and current_setting() will get an
-    * empty string.
+    * XXX Ideally we'd do this via SetConfigOption("session_authorization"),
+    * but we lack the role name needed to do that, and we can't fetch it
+    * because one reason for this special case is to be able to start up even
+    * if something's happened to the BOOTSTRAP_SUPERUSERID's pg_authid row.
+    * Since we don't set the GUC itself, C code will see the value as NULL,
+    * and current_setting() will report an empty string within this session.
     */
-   SetConfigOption("is_superuser", "on",
-                   PGC_INTERNAL, PGC_S_DYNAMIC_DEFAULT);
+   SetSessionAuthorization(BOOTSTRAP_SUPERUSERID, true);
+
+   /* We could do SetConfigOption("role"), but let's be consistent */
+   SetCurrentRoleId(InvalidOid, false);
 }
 
 
 /*
  * Change session auth ID while running
  *
- * Only a superuser may set auth ID to something other than himself.  Note
- * that in case of multiple SETs in a single session, the original userid's
- * superuserness is what matters.  But we set the GUC variable is_superuser
- * to indicate whether the *current* session userid is a superuser.
- *
- * Note: this is not an especially clean place to do the permission check.
- * It's OK because the check does not require catalog access and can't
- * fail during an end-of-transaction GUC reversion, but we may someday
- * have to push it up into assign_session_authorization.
+ * The SQL standard says that SET SESSION AUTHORIZATION implies SET ROLE NONE.
+ * We mechanize that at higher levels not here, because this is the GUC
+ * assign hook for "session_authorization", and it must be commutative with
+ * SetCurrentRoleId (the hook for "role") because guc.c provides no guarantees
+ * which will run first during cases such as transaction rollback.  Therefore,
+ * we update derived state (OuterUserId/CurrentUserId/is_superuser) only if
+ * !SetRoleIsActive.
  */
 void
 SetSessionAuthorization(Oid userid, bool is_superuser)
 {
-   /* Must have authenticated already, else can't make permission check */
-   AssertState(OidIsValid(AuthenticatedUserId));
-
-   if (userid != AuthenticatedUserId &&
-       !AuthenticatedUserIsSuperuser)
-       ereport(ERROR,
-               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                errmsg("permission denied to set session authorization")));
-
    SetSessionUserId(userid, is_superuser);
 
-   SetConfigOption("is_superuser",
-                   is_superuser ? "on" : "off",
-                   PGC_INTERNAL, PGC_S_OVERRIDE);
+   if (!SetRoleIsActive)
+       SetOuterUserId(userid, is_superuser);
 }
 
 /*
@@ -784,28 +816,25 @@ SetCurrentRoleId(Oid roleid, bool is_superuser)
    /*
     * Get correct info if it's SET ROLE NONE
     *
-    * If SessionUserId hasn't been set yet, just do nothing --- the eventual
-    * SetSessionUserId call will fix everything.  This is needed since we
-    * will get called during GUC initialization.
+    * If SessionUserId hasn't been set yet, do nothing beyond updating
+    * SetRoleIsActive --- the eventual SetSessionAuthorization call will
+    * update the derived state.  This is needed since we will get called
+    * during GUC initialization.
     */
    if (!OidIsValid(roleid))
    {
+       SetRoleIsActive = false;
+
        if (!OidIsValid(SessionUserId))
            return;
 
        roleid = SessionUserId;
        is_superuser = SessionUserIsSuperuser;
-
-       SetRoleIsActive = false;
    }
    else
        SetRoleIsActive = true;
 
-   SetOuterUserId(roleid);
-
-   SetConfigOption("is_superuser",
-                   is_superuser ? "on" : "off",
-                   PGC_INTERNAL, PGC_S_OVERRIDE);
+   SetOuterUserId(roleid, is_superuser);
 }
 
 
index 41f46491edb75f726292cd76f01c613ee1886854..6c74de41fc22f55946ba8b2c0899d16a9a56794e 100644 (file)
@@ -7265,6 +7265,8 @@ set_config_option(const char *name, const char *value,
        case PGC_STRING:
            {
                struct config_string *conf = (struct config_string *) record;
+               GucContext  orig_context = context;
+               GucSource   orig_source = source;
 
 #define newval (newval_union.stringval)
 
@@ -7348,6 +7350,35 @@ set_config_option(const char *name, const char *value,
                                    newextra);
                    conf->gen.source = source;
                    conf->gen.scontext = context;
+
+                   /*
+                    * Ugly hack: during SET session_authorization, forcibly
+                    * do SET ROLE NONE with the same context/source/etc, so
+                    * that the effects will have identical lifespan.  This is
+                    * required by the SQL spec, and it's not possible to do
+                    * it within the variable's check hook or assign hook
+                    * because our APIs for those don't pass enough info.
+                    * However, don't do it if is_reload: in that case we
+                    * expect that if "role" isn't supposed to be default, it
+                    * has been or will be set by a separate reload action.
+                    *
+                    * A fine point: for RESET session_authorization, we do
+                    * "RESET role" not "SET ROLE NONE" (by passing down NULL
+                    * rather than "none" for the value).  This would have the
+                    * same effects in typical cases, but if the reset value
+                    * of "role" is not "none" it seems better to revert to
+                    * that.
+                    */
+                   if (!is_reload &&
+                       strcmp(conf->gen.name, "session_authorization") == 0)
+                       (void) set_config_option("role",
+                                                value ? "none" : NULL,
+                                                orig_context,
+                                                orig_source,
+                                                action,
+                                                true,
+                                                elevel,
+                                                false);
                }
 
                if (makeDefault)
@@ -9946,18 +9977,13 @@ read_nondefault_variables(void)
  * constants; a few, like server_encoding and lc_ctype, are handled specially
  * outside the serialize/restore procedure.  Therefore, SerializeGUCState()
  * never sends these, and RestoreGUCState() never changes them.
- *
- * Role is a special variable in the sense that its current value can be an
- * invalid value and there are multiple ways by which that can happen (like
- * after setting the role, someone drops it).  So we handle it outside of
- * serialize/restore machinery.
  */
 static bool
 can_skip_gucvar(struct config_generic *gconf)
 {
    return gconf->context == PGC_POSTMASTER ||
-       gconf->context == PGC_INTERNAL || gconf->source == PGC_S_DEFAULT ||
-       strcmp(gconf->name, "role") == 0;
+       gconf->context == PGC_INTERNAL ||
+       gconf->source == PGC_S_DEFAULT;
 }
 
 /*
index 11e9f5fcc96706da29130df3cc19de95afdfdcfe..ffd47d2724733d3d029f796ef59a12ddb9aaf7b5 100644 (file)
@@ -324,7 +324,10 @@ extern char *GetUserNameFromId(Oid roleid, bool noerr);
 extern Oid GetUserId(void);
 extern Oid GetOuterUserId(void);
 extern Oid GetSessionUserId(void);
+extern bool GetSessionUserIsSuperuser(void);
 extern Oid GetAuthenticatedUserId(void);
+extern bool GetAuthenticatedUserIsSuperuser(void);
+extern void SetAuthenticatedUserId(Oid userid, bool is_superuser);
 extern void GetUserIdAndSecContext(Oid *userid, int *sec_context);
 extern void SetUserIdAndSecContext(Oid userid, int sec_context);
 extern bool InLocalUserIdChange(void);
index 41cd8c22cc45b833f85a91de5aa1d74f9f3e2911..a2cd1b19bc03ede28652dfd3cbc77c79ffff9385 100644 (file)
@@ -26,6 +26,79 @@ 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_user8;
+CREATE USER regress_priv_user9;
+CREATE USER regress_priv_user10;
+-- test interaction of SET SESSION AUTHORIZATION and SET ROLE,
+-- as well as propagation of these settings to parallel workers
+GRANT regress_priv_user9 TO regress_priv_user8;
+SET SESSION AUTHORIZATION regress_priv_user8;
+SET ROLE regress_priv_user9;
+SET force_parallel_mode = 0;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+    session_user    |    current_role    |    current_user    |        role        
+--------------------+--------------------+--------------------+--------------------
+ regress_priv_user8 | regress_priv_user9 | regress_priv_user9 | regress_priv_user9
+(1 row)
+
+SET force_parallel_mode = 1;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+    session_user    |    current_role    |    current_user    |        role        
+--------------------+--------------------+--------------------+--------------------
+ regress_priv_user8 | regress_priv_user9 | regress_priv_user9 | regress_priv_user9
+(1 row)
+
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user10;
+SET force_parallel_mode = 0;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+    session_user     |    current_role     |    current_user     | role 
+---------------------+---------------------+---------------------+------
+ regress_priv_user10 | regress_priv_user10 | regress_priv_user10 | none
+(1 row)
+
+SET force_parallel_mode = 1;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+    session_user     |    current_role     |    current_user     | role 
+---------------------+---------------------+---------------------+------
+ regress_priv_user10 | regress_priv_user10 | regress_priv_user10 | none
+(1 row)
+
+ROLLBACK;
+SET force_parallel_mode = 0;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+    session_user    |    current_role    |    current_user    |        role        
+--------------------+--------------------+--------------------+--------------------
+ regress_priv_user8 | regress_priv_user9 | regress_priv_user9 | regress_priv_user9
+(1 row)
+
+SET force_parallel_mode = 1;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+    session_user    |    current_role    |    current_user    |        role        
+--------------------+--------------------+--------------------+--------------------
+ regress_priv_user8 | regress_priv_user9 | regress_priv_user9 | regress_priv_user9
+(1 row)
+
+RESET SESSION AUTHORIZATION;
+-- session_user at this point is installation-dependent
+SET force_parallel_mode = 0;
+SELECT session_user = current_role as c_r_ok, session_user = current_user as c_u_ok, current_setting('role') as role;
+ c_r_ok | c_u_ok | role 
+--------+--------+------
+ t      | t      | none
+(1 row)
+
+SET force_parallel_mode = 1;
+SELECT session_user = current_role as c_r_ok, session_user = current_user as c_u_ok, current_setting('role') as role;
+ c_r_ok | c_u_ok | role 
+--------+--------+------
+ t      | t      | none
+(1 row)
+
+RESET force_parallel_mode;
+DROP USER regress_priv_user10;
+DROP USER regress_priv_user9;
+DROP USER regress_priv_user8;
 CREATE GROUP regress_priv_group1;
 CREATE GROUP regress_priv_group2 WITH USER regress_priv_user1, regress_priv_user2;
 ALTER GROUP regress_priv_group1 ADD USER regress_priv_user4;
index 213e233a6859fa42f9b38b1e58fc38843d8dc463..6c309f1614ca7f32bf05ec6c3b696a5bee55661b 100644 (file)
@@ -30,6 +30,46 @@ CREATE USER regress_priv_user4;
 CREATE USER regress_priv_user5;
 CREATE USER regress_priv_user5;    -- duplicate
 
+CREATE USER regress_priv_user8;
+CREATE USER regress_priv_user9;
+CREATE USER regress_priv_user10;
+
+-- test interaction of SET SESSION AUTHORIZATION and SET ROLE,
+-- as well as propagation of these settings to parallel workers
+GRANT regress_priv_user9 TO regress_priv_user8;
+
+SET SESSION AUTHORIZATION regress_priv_user8;
+SET ROLE regress_priv_user9;
+SET force_parallel_mode = 0;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+SET force_parallel_mode = 1;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+
+BEGIN;
+SET SESSION AUTHORIZATION regress_priv_user10;
+SET force_parallel_mode = 0;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+SET force_parallel_mode = 1;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+ROLLBACK;
+SET force_parallel_mode = 0;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+SET force_parallel_mode = 1;
+SELECT session_user, current_role, current_user, current_setting('role') as role;
+
+RESET SESSION AUTHORIZATION;
+-- session_user at this point is installation-dependent
+SET force_parallel_mode = 0;
+SELECT session_user = current_role as c_r_ok, session_user = current_user as c_u_ok, current_setting('role') as role;
+SET force_parallel_mode = 1;
+SELECT session_user = current_role as c_r_ok, session_user = current_user as c_u_ok, current_setting('role') as role;
+
+RESET force_parallel_mode;
+
+DROP USER regress_priv_user10;
+DROP USER regress_priv_user9;
+DROP USER regress_priv_user8;
+
 CREATE GROUP regress_priv_group1;
 CREATE GROUP regress_priv_group2 WITH USER regress_priv_user1, regress_priv_user2;