<para>
This command sets the session user identifier and the current user
- identifier of the current SQL-session context to be <replaceable
+ identifier of the current SQL session to be <replaceable
class="parameter">username</replaceable>. The user name may be
written as either an identifier or a string literal. Using this
command, it is possible, for example, to temporarily become an
The session user identifier is initially set to be the (possibly
authenticated) user name provided by the client. The current user
identifier is normally equal to the session user identifier, but
- may change temporarily in the context of <quote>setuid</quote>
+ might change temporarily in the context of <literal>SECURITY DEFINER</>
functions and similar mechanisms. The current user identifier is
relevant for permission checking.
</para>
</para>
</refsect1>
+ <refsect1>
+ <title>Notes</title>
+
+ <para>
+ <command>SET SESSION AUTHORIZATION</> cannot be used within a
+ <literal>SECURITY DEFINER</> function.
+ </para>
+ </refsect1>
+
<refsect1>
<title>Examples</title>
static TransactionState CurrentTransactionState = &CurrentTransactionStateData;
+static AclId prevUser; /* CurrentUserId at transaction start */
+
/*
* User-tweakable parameters
*/
StartTransaction(void)
{
TransactionState s = CurrentTransactionState;
+ bool prevSecDefCxt;
/*
* check the current transaction state
s->commandId = FirstCommandId;
s->startTime = GetCurrentAbsoluteTimeUsec(&(s->startTimeUsec));
+ GetUserIdAndContext(&prevUser, &prevSecDefCxt);
+ /* SecurityDefinerContext should never be set outside a transaction */
+ Assert(!prevSecDefCxt);
+
/*
* initialize the various transaction subsystems
*/
AtAbort_Memory();
/*
- * Reset user id which might have been changed transiently
+ * Reset user ID which might have been changed transiently. We need this
+ * to clean up in case control escaped out of a SECURITY DEFINER function
+ * or other local change of CurrentUserId; therefore, the prior value
+ * of SecurityDefinerContext also needs to be restored.
+ *
+ * (Note: it is not necessary to restore session authorization
+ * setting here because that can only be changed via GUC, and GUC will
+ * take care of rolling it back if need be.)
*/
- SetUserId(GetSessionUserId());
+ SetUserIdAndContext(prevUser, false);
/*
* do abort processing
IndexInfo *indexInfo)
{
RegProcedure procedure;
+ AclId save_userid;
+ bool save_secdefcxt;
/*
* sanity checks
procedure = indexRelation->rd_am->ambuild;
Assert(RegProcedureIsValid(procedure));
+ /*
+ * Switch to the table owner's userid, so that any index functions are
+ * run as that user.
+ */
+ GetUserIdAndContext(&save_userid, &save_secdefcxt);
+ SetUserIdAndContext(heapRelation->rd_rel->relowner, true);
+
/*
* Call the access method's build procedure
*/
PointerGetDatum(heapRelation),
PointerGetDatum(indexRelation),
PointerGetDatum(indexInfo));
+
+ /* Restore userid */
+ SetUserIdAndContext(save_userid, save_secdefcxt);
}
const char *owner_name;
AclId owner_userid;
AclId saved_userid;
+ bool saved_secdefcxt;
AclResult aclresult;
- saved_userid = GetUserId();
+ GetUserIdAndContext(&saved_userid, &saved_secdefcxt);
/*
* Figure out user identities.
* (This will revert to session user on error or at the end of
* this routine.)
*/
- SetUserId(owner_userid);
+ SetUserIdAndContext(owner_userid, true);
}
else
{
PopSpecialNamespace(namespaceId);
/* Reset current user */
- SetUserId(saved_userid);
+ SetUserIdAndContext(saved_userid, saved_secdefcxt);
}
LockRelId onerelid;
Oid toast_relid;
bool result;
+ AclId save_userid;
+ bool save_secdefcxt;
/* Begin a transaction for vacuuming this relation */
StartTransactionCommand();
*/
toast_relid = onerel->rd_rel->reltoastrelid;
+ /*
+ * Switch to the table owner's userid, so that any index functions are
+ * run as that user. (This is unnecessary, but harmless, for lazy
+ * VACUUM.)
+ */
+ GetUserIdAndContext(&save_userid, &save_secdefcxt);
+ SetUserIdAndContext(onerel->rd_rel->relowner, true);
+
/*
* Do the actual work --- either FULL or "lazy" vacuum
*/
result = true; /* did the vacuum */
+ /* Restore userid */
+ SetUserIdAndContext(save_userid, save_secdefcxt);
+
/* all done with this class, but hold lock until commit */
relation_close(onerel, NoLock);
/* not a saved ID, so look it up */
HeapTuple userTup;
+ if (InSecurityDefinerContext())
+ {
+ /*
+ * Disallow SET SESSION AUTHORIZATION inside a security definer
+ * context. We need to do this because when we exit the context,
+ * GUC won't be notified, leaving things out of sync. Note that
+ * this test is positioned so that restoring a previously saved
+ * setting isn't prevented.
+ */
+ if (interactive)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot set session authorization within security-definer function")));
+ return NULL;
+ }
+
if (!IsTransactionState())
{
/*
{
void *qplan;
Relation query_rel;
- AclId save_uid;
+ AclId save_userid;
+ bool save_secdefcxt;
/*
* The query is always run against the FK table except when this is an
query_rel = fk_rel;
/* Switch to proper UID to perform check as */
- save_uid = GetUserId();
- SetUserId(RelationGetForm(query_rel)->relowner);
+ GetUserIdAndContext(&save_userid, &save_secdefcxt);
+ SetUserIdAndContext(RelationGetForm(query_rel)->relowner, true);
/* Create the plan */
qplan = SPI_prepare(querystr, nargs, argtypes);
elog(ERROR, "SPI_prepare returned %d for %s", SPI_result, querystr);
/* Restore UID */
- SetUserId(save_uid);
+ SetUserIdAndContext(save_userid, save_secdefcxt);
/* Save the plan if requested */
if (cache_plan)
bool useCurrentSnapshot;
int limit;
int spi_result;
- AclId save_uid;
+ AclId save_userid;
+ bool save_secdefcxt;
Datum vals[RI_MAX_NUMKEYS * 2];
char nulls[RI_MAX_NUMKEYS * 2];
limit = (expect_OK == SPI_OK_SELECT) ? 1 : 0;
/* Switch to proper UID to perform check as */
- save_uid = GetUserId();
- SetUserId(RelationGetForm(query_rel)->relowner);
+ GetUserIdAndContext(&save_userid, &save_secdefcxt);
+ SetUserIdAndContext(RelationGetForm(query_rel)->relowner, true);
/* Finally we can run the query. */
spi_result = SPI_execp_current(qplan, vals, nulls,
useCurrentSnapshot, limit);
/* Restore UID */
- SetUserId(save_uid);
+ SetUserIdAndContext(save_userid, save_secdefcxt);
/* Check result */
if (spi_result < 0)
FmgrInfo *save_flinfo;
struct fmgr_security_definer_cache *fcache;
AclId save_userid;
+ bool save_secdefcxt;
HeapTuple tuple;
if (!fcinfo->flinfo->fn_extra)
else
fcache = fcinfo->flinfo->fn_extra;
+ GetUserIdAndContext(&save_userid, &save_secdefcxt);
+ SetUserIdAndContext(fcache->userid, true);
+
save_flinfo = fcinfo->flinfo;
fcinfo->flinfo = &fcache->flinfo;
- save_userid = GetUserId();
- SetUserId(fcache->userid);
result = FunctionCallInvoke(fcinfo);
- SetUserId(save_userid);
fcinfo->flinfo = save_flinfo;
+ SetUserIdAndContext(save_userid, save_secdefcxt);
+
return result;
}
* are implemented. Conceptually there is a stack, whose bottom
* is the session user. You are yourself responsible to save and
* restore the current user id if you need to change it.
+ *
+ * SecurityDefinerContext is TRUE if we are within a SECURITY DEFINER function
+ * or another context that temporarily changes CurrentUserId.
* ----------------------------------------------------------------
*/
static AclId AuthenticatedUserId = 0;
static bool AuthenticatedUserIsSuperuser = false;
+static bool SecurityDefinerContext = false;
+
+
/*
- * This function is relevant for all privilege checks.
+ * GetUserId - get the current effective user ID.
+ *
+ * Note: there's no SetUserId() anymore; use SetUserIdAndContext().
*/
AclId
GetUserId(void)
}
-void
-SetUserId(AclId newid)
-{
- AssertArg(AclIdIsValid(newid));
- CurrentUserId = newid;
-}
-
-
/*
* This value is only relevant for informational purposes.
*/
}
-void
+static void
SetSessionUserId(AclId newid)
{
+ AssertState(!SecurityDefinerContext);
AssertArg(AclIdIsValid(newid));
SessionUserId = newid;
- /* Current user defaults to session user. */
- if (!AclIdIsValid(CurrentUserId))
- CurrentUserId = newid;
+ CurrentUserId = newid;
}
+/*
+ * GetUserIdAndContext/SetUserIdAndContext - get/set the current user ID
+ * and the SecurityDefinerContext flag.
+ *
+ * Unlike GetUserId, GetUserIdAndContext does *not* Assert that the current
+ * value of CurrentUserId is valid; nor does SetUserIdAndContext require
+ * the new value to be valid. In fact, these routines had better not
+ * ever throw any kind of error. This is because they are used by
+ * StartTransaction and AbortTransaction to save/restore the settings,
+ * and during the first transaction within a backend, the value to be saved
+ * and perhaps restored is indeed invalid. We have to be able to get
+ * through AbortTransaction without asserting in case InitPostgres fails.
+ */
+void
+GetUserIdAndContext(AclId *userid, bool *sec_def_context)
+{
+ *userid = CurrentUserId;
+ *sec_def_context = SecurityDefinerContext;
+}
+
+void
+SetUserIdAndContext(AclId userid, bool sec_def_context)
+{
+ CurrentUserId = userid;
+ SecurityDefinerContext = sec_def_context;
+}
+
+
+/*
+ * InSecurityDefinerContext - are we inside a SECURITY DEFINER context?
+ */
+bool
+InSecurityDefinerContext(void)
+{
+ return SecurityDefinerContext;
+}
+
+
+/*
+ * Initialize user identity during normal backend startup
+ */
void
InitializeSessionUserId(const char *username)
{
errmsg("permission denied to set session authorization")));
SetSessionUserId(userid);
- SetUserId(userid);
SetConfigOption("is_superuser",
is_superuser ? "on" : "off",
extern char *GetUserNameFromId(AclId userid);
extern AclId GetUserId(void);
-extern void SetUserId(AclId userid);
extern AclId GetSessionUserId(void);
-extern void SetSessionUserId(AclId userid);
+extern void GetUserIdAndContext(AclId *userid, bool *sec_def_context);
+extern void SetUserIdAndContext(AclId userid, bool sec_def_context);
+extern bool InSecurityDefinerContext(void);
extern void InitializeSessionUserId(const char *username);
extern void InitializeSessionUserIdStandalone(void);
extern void SetSessionAuthorization(AclId userid, bool is_superuser);