From e5d6b91220d69c87f44e1ce0095516946abc6d6c Mon Sep 17 00:00:00 2001 From: Tom Lane <tgl@sss.pgh.pa.us> Date: Mon, 25 Jul 2005 22:12:34 +0000 Subject: [PATCH] Add SET ROLE. This is a partial commit of Stephen Frost's recent patch; I'm still working on the has_role function and information_schema changes. --- doc/src/sgml/func.sgml | 6 +- doc/src/sgml/ref/allfiles.sgml | 3 +- doc/src/sgml/ref/pg_dump.sgml | 6 +- doc/src/sgml/ref/pg_dumpall.sgml | 6 +- doc/src/sgml/ref/pg_restore.sgml | 6 +- doc/src/sgml/ref/set_role.sgml | 116 +++++++++++++++++ doc/src/sgml/ref/set_session_auth.sgml | 29 +++-- doc/src/sgml/reference.sgml | 3 +- src/backend/access/transam/xact.c | 6 +- src/backend/commands/user.c | 29 ++++- src/backend/commands/variable.c | 142 ++++++++++++++++++++- src/backend/parser/gram.y | 9 +- src/backend/utils/init/miscinit.c | 165 +++++++++++++++++++++---- src/backend/utils/misc/check_guc | 2 +- src/backend/utils/misc/guc.c | 87 ++++++++----- src/include/commands/variable.h | 5 +- src/include/miscadmin.h | 10 +- 17 files changed, 533 insertions(+), 97 deletions(-) create mode 100644 doc/src/sgml/ref/set_role.sgml diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 59813e16f16..37087355150 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.269 2005/07/22 21:16:14 momjian Exp $ +$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.270 2005/07/25 22:12:30 tgl Exp $ PostgreSQL documentation --> @@ -8266,7 +8266,9 @@ select current_date + s.a as dates from generate_series(0,14,7) as s(a); with <xref linkend="sql-set-session-authorization" endterm="sql-set-session-authorization-title">. The <function>current_user</function> is the user identifier that is applicable for permission checking. Normally, it is equal - to the session user, but it changes during the execution of + to the session user, but it can be changed with + <xref linkend="sql-set-role" endterm="sql-set-role-title">. + It also changes during the execution of functions with the attribute <literal>SECURITY DEFINER</literal>. In Unix parlance, the session user is the <quote>real user</quote> and the current user is the <quote>effective user</quote>. diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 33e9e68b9d5..d993b64ad04 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.63 2005/06/17 22:32:42 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.64 2005/07/25 22:12:31 tgl Exp $ PostgreSQL documentation Complete list of usable sgml source files in this directory. --> @@ -102,6 +102,7 @@ Complete list of usable sgml source files in this directory. <!entity selectInto system "select_into.sgml"> <!entity set system "set.sgml"> <!entity setConstraints system "set_constraints.sgml"> +<!entity setRole system "set_role.sgml"> <!entity setSessionAuth system "set_session_auth.sgml"> <!entity setTransaction system "set_transaction.sgml"> <!entity show system "show.sgml"> diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index a6d8bb24078..288ae20a411 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/pg_dump.sgml,v 1.79 2005/07/10 15:08:52 momjian Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/pg_dump.sgml,v 1.80 2005/07/25 22:12:31 tgl Exp $ PostgreSQL documentation --> @@ -474,8 +474,8 @@ PostgreSQL documentation <term><option>--use-set-session-authorization</></term> <listitem> <para> - Output SQL standard SET SESSION AUTHORIZATION commands instead - of OWNER TO commands. This makes the dump more standards compatible, + Output SQL standard SET SESSION AUTHORIZATION commands instead of + ALTER OWNER commands. This makes the dump more standards compatible, but depending on the history of the objects in the dump, may not restore properly. </para> diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index c61ae094042..4cee1a4ed72 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -1,5 +1,5 @@ <!-- -$PostgreSQL: pgsql/doc/src/sgml/ref/pg_dumpall.sgml,v 1.51 2005/06/21 20:45:43 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/ref/pg_dumpall.sgml,v 1.52 2005/07/25 22:12:31 tgl Exp $ PostgreSQL documentation --> @@ -277,8 +277,8 @@ PostgreSQL documentation <term><option>--use-set-session-authorization</></term> <listitem> <para> - Output SQL standard SET SESSION AUTHORIZATION commands instead - of OWNER TO commands. This makes the dump more standards compatible, + Output SQL standard SET SESSION AUTHORIZATION commands instead of + ALTER OWNER commands. This makes the dump more standards compatible, but depending on the history of the objects in the dump, may not restore properly. </para> diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index 9b2b5fc3f26..d4a1a3e0f05 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/pg_restore.sgml,v 1.53 2005/06/21 20:45:43 tgl Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/pg_restore.sgml,v 1.54 2005/07/25 22:12:31 tgl Exp $ --> <refentry id="APP-PGRESTORE"> <refmeta> @@ -361,8 +361,8 @@ <term><option>--use-set-session-authorization</option></term> <listitem> <para> - Output SQL standard SET SESSION AUTHORIZATION commands instead - of OWNER TO commands. This makes the dump more standards compatible, + Output SQL standard SET SESSION AUTHORIZATION commands instead of + ALTER OWNER commands. This makes the dump more standards compatible, but depending on the history of the objects in the dump, may not restore properly. </para> diff --git a/doc/src/sgml/ref/set_role.sgml b/doc/src/sgml/ref/set_role.sgml new file mode 100644 index 00000000000..6fbe40fabff --- /dev/null +++ b/doc/src/sgml/ref/set_role.sgml @@ -0,0 +1,116 @@ +<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_role.sgml,v 1.1 2005/07/25 22:12:31 tgl Exp $ --> +<refentry id="SQL-SET-ROLE"> + <refmeta> + <refentrytitle id="sql-set-role-title">SET ROLE</refentrytitle> + <refmiscinfo>SQL - Language Statements</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>SET ROLE</refname> + <refpurpose>set the current user identifier of the current session</refpurpose> + </refnamediv> + + <indexterm zone="sql-set-role"> + <primary>SET ROLE</primary> + </indexterm> + + <refsynopsisdiv> +<synopsis> +SET [ SESSION | LOCAL ] ROLE <replaceable class="parameter">rolename</replaceable> +SET [ SESSION | LOCAL ] ROLE NONE +RESET ROLE +</synopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para> + This command sets the current user + identifier of the current SQL-session context to be <replaceable + class="parameter">rolename</replaceable>. The role name may be + written as either an identifier or a string literal. Using this + command, it is possible to either add privileges or restrict one's + privileges. + </para> + + <para> + The specified <replaceable class="parameter">rolename</replaceable> + must be a role that the current session user is a member of. + (If the session user is a superuser, any role can be selected.) + </para> + + <para> + The <literal>SESSION</> and <literal>LOCAL</> modifiers act the same + as for the regular <xref linkend="SQL-SET" endterm="SQL-SET-title"> + command. + </para> + + <para> + The <literal>NONE</> and <literal>RESET</> forms reset the current + user identifier to be the current session user identifier. + These forms may be executed by any user. + </para> + </refsect1> + + <refsect1> + <title>Examples</title> + +<programlisting> +SELECT SESSION_USER, CURRENT_USER; + + session_user | current_user +--------------+-------------- + peter | peter + +SET ROLE 'paul'; + +SELECT SESSION_USER, CURRENT_USER; + + session_user | current_user +--------------+-------------- + peter | paul +</programlisting> + </refsect1> + + <refsect1> + <title>Compatibility</title> + + <para> + <productname>PostgreSQL</productname> + allows identifier syntax (<literal>"rolename"</literal>), while + the SQL standard requires the role name to be written as a string + literal. SQL does not allow this command during a transaction; + <productname>PostgreSQL</productname> does not make this + restriction because there is no reason to. + The <literal>SESSION</> and <literal>LOCAL</> modifiers are a + <productname>PostgreSQL</productname> extension, as is the + <literal>RESET</> syntax. + </para> + </refsect1> + + <refsect1> + <title>See Also</title> + + <simplelist type="inline"> + <member><xref linkend="sql-set-session-authorization" endterm="sql-set-session-authorization-title"></member> + </simplelist> + </refsect1> +</refentry> + +<!-- Keep this comment at the end of the file +Local variables: +mode:sgml +sgml-omittag:nil +sgml-shorttag:t +sgml-minimize-attributes:nil +sgml-always-quote-attributes:t +sgml-indent-step:1 +sgml-indent-data:t +sgml-parent-document:nil +sgml-default-dtd-file:"../reference.ced" +sgml-exposed-tags:nil +sgml-local-catalogs:("/usr/lib/sgml/catalog") +sgml-local-ecat-files:nil +End: +--> diff --git a/doc/src/sgml/ref/set_session_auth.sgml b/doc/src/sgml/ref/set_session_auth.sgml index 7014b8d2ab3..334847fb00e 100644 --- a/doc/src/sgml/ref/set_session_auth.sgml +++ b/doc/src/sgml/ref/set_session_auth.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_session_auth.sgml,v 1.12 2003/11/29 19:51:39 pgsql Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_session_auth.sgml,v 1.13 2005/07/25 22:12:31 tgl Exp $ --> <refentry id="SQL-SET-SESSION-AUTHORIZATION"> <refmeta> <refentrytitle id="sql-set-session-authorization-title">SET SESSION AUTHORIZATION</refentrytitle> @@ -31,7 +31,7 @@ RESET SESSION AUTHORIZATION 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 - unprivileged user and later switch back to become a superuser. + unprivileged user and later switch back to being a superuser. </para> <para> @@ -39,8 +39,9 @@ RESET SESSION AUTHORIZATION 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> - functions and similar mechanisms. The current user identifier is - relevant for permission checking. + functions and similar mechanisms; it can also be changed by + <xref linkend="sql-set-role" endterm="sql-set-role-title">. + The current user identifier is relevant for permission checking. </para> <para> @@ -93,10 +94,24 @@ SELECT SESSION_USER, CURRENT_USER; allows identifier syntax (<literal>"username"</literal>), which SQL does not. SQL does not allow this command during a transaction; <productname>PostgreSQL</productname> does not make this - restriction because there is no reason to. The privileges - necessary to execute this command are left implementation-defined - by the standard. + restriction because there is no reason to. + The <literal>SESSION</> and <literal>LOCAL</> modifiers are a + <productname>PostgreSQL</productname> extension, as is the + <literal>RESET</> syntax. </para> + + <para> + The privileges necessary to execute this command are left + implementation-defined by the standard. + </para> + </refsect1> + + <refsect1> + <title>See Also</title> + + <simplelist type="inline"> + <member><xref linkend="sql-set-role" endterm="sql-set-role-title"></member> + </simplelist> </refsect1> </refentry> diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index 4edec85c122..63ecfe12041 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -1,5 +1,5 @@ <!-- reference.sgml -$PostgreSQL: pgsql/doc/src/sgml/reference.sgml,v 1.53 2005/06/17 22:32:42 tgl Exp $ +$PostgreSQL: pgsql/doc/src/sgml/reference.sgml,v 1.54 2005/07/25 22:12:30 tgl Exp $ PostgreSQL Reference Manual --> @@ -134,6 +134,7 @@ PostgreSQL Reference Manual &selectInto; &set; &setConstraints; + &setRole; &setSessionAuth; &setTransaction; &show; diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index c75da3d432c..ee33030292f 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.210 2005/07/13 22:46:09 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.211 2005/07/25 22:12:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1865,7 +1865,7 @@ AbortTransaction(void) /* * Reset user id which might have been changed transiently. We cannot - * use s->currentUser, but must get the session userid from + * use s->currentUser, but must get the session outer-level userid from * miscinit.c. * * (Note: it is not necessary to restore session authorization here @@ -1874,7 +1874,7 @@ AbortTransaction(void) * DEFINER function could send control here with the wrong current * userid.) */ - SetUserId(GetSessionUserId()); + SetUserId(GetOuterUserId()); /* * do abort processing diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 4a46343d5d8..5f8eeae30df 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.156 2005/07/07 20:39:58 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.157 2005/07/25 22:12:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -227,7 +227,8 @@ CreateRole(CreateRoleStmt *stmt) errmsg("permission denied to create role"))); } - if (strcmp(stmt->role, "public") == 0) + if (strcmp(stmt->role, "public") == 0 || + strcmp(stmt->role, "none") == 0) ereport(ERROR, (errcode(ERRCODE_RESERVED_NAME), errmsg("role name \"%s\" is reserved", @@ -760,11 +761,15 @@ DropRole(DropRoleStmt *stmt) if (roleid == GetUserId()) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("current role cannot be dropped"))); + errmsg("current user cannot be dropped"))); + if (roleid == GetOuterUserId()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg("current user cannot be dropped"))); if (roleid == GetSessionUserId()) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("session role cannot be dropped"))); + errmsg("session user cannot be dropped"))); /* * For safety's sake, we allow createrole holders to drop ordinary @@ -893,7 +898,8 @@ RenameRole(const char *oldname, const char *newname) * XXX Client applications probably store the session user somewhere, * so renaming it could cause confusion. On the other hand, there may * not be an actual problem besides a little confusion, so think about - * this and decide. + * this and decide. Same for SET ROLE ... we don't restrict renaming + * the current effective userid, though. */ roleid = HeapTupleGetOid(oldtuple); @@ -901,7 +907,11 @@ RenameRole(const char *oldname, const char *newname) if (roleid == GetSessionUserId()) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("session role may not be renamed"))); + errmsg("session user may not be renamed"))); + if (roleid == GetOuterUserId()) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("current user may not be renamed"))); /* make sure the new name doesn't exist */ if (SearchSysCacheExists(AUTHNAME, @@ -911,6 +921,13 @@ RenameRole(const char *oldname, const char *newname) (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("role \"%s\" already exists", newname))); + if (strcmp(newname, "public") == 0 || + strcmp(newname, "none") == 0) + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("role name \"%s\" is reserved", + newname))); + /* * createrole is enough privilege unless you want to mess with a superuser */ diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c index 494ab6b491e..9254d57e345 100644 --- a/src/backend/commands/variable.c +++ b/src/backend/commands/variable.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.111 2005/07/21 03:56:10 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.112 2005/07/25 22:12:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,6 +24,7 @@ #include "miscadmin.h" #include "parser/scansup.h" #include "pgtime.h" +#include "utils/acl.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/syscache.h" @@ -684,3 +685,142 @@ show_session_authorization(void) return endptr + 1; } + + +/* + * SET ROLE + * + * When resetting session auth after an error, we can't expect to do catalog + * lookups. Hence, the stored form of the value must provide a numeric oid + * that can be re-used directly. We implement this exactly like SET + * SESSION AUTHORIZATION. + * + * The SQL spec requires "SET ROLE NONE" to unset the role, so we hardwire + * a translation of "none" to InvalidOid. + */ +extern char *role_string; /* in guc.c */ + +const char * +assign_role(const char *value, bool doit, GucSource source) +{ + Oid roleid = InvalidOid; + bool is_superuser = false; + const char *actual_rolename = value; + char *result; + + if (strspn(value, "x") == NAMEDATALEN && + (value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F')) + { + /* might be a saved userid string */ + Oid savedoid; + char *endptr; + + savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10); + + if (endptr != value + NAMEDATALEN + 1 && *endptr == ',') + { + /* syntactically valid, so break out the data */ + roleid = savedoid; + is_superuser = (value[NAMEDATALEN] == 'T'); + actual_rolename = endptr + 1; + } + } + + if (roleid == InvalidOid && + strcmp(actual_rolename, "none") != 0) + { + /* not a saved ID, so look it up */ + HeapTuple roleTup; + + if (!IsTransactionState()) + { + /* + * Can't do catalog lookups, so fail. The upshot of this is + * that role cannot be set in postgresql.conf, which seems + * like a good thing anyway. + */ + return NULL; + } + + roleTup = SearchSysCache(AUTHNAME, + PointerGetDatum(value), + 0, 0, 0); + if (!HeapTupleIsValid(roleTup)) + { + if (source >= PGC_S_INTERACTIVE) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("role \"%s\" does not exist", value))); + return NULL; + } + + roleid = HeapTupleGetOid(roleTup); + is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper; + + ReleaseSysCache(roleTup); + + /* + * Verify that session user is allowed to become this role + */ + if (!is_member_of_role(GetSessionUserId(), roleid)) + { + if (source >= PGC_S_INTERACTIVE) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to set role \"%s\"", + value))); + return NULL; + } + } + + if (doit) + SetCurrentRoleId(roleid, is_superuser); + + result = (char *) malloc(NAMEDATALEN + 32 + strlen(actual_rolename)); + if (!result) + return NULL; + + memset(result, 'x', NAMEDATALEN); + + sprintf(result + NAMEDATALEN, "%c%u,%s", + is_superuser ? 'T' : 'F', + roleid, + actual_rolename); + + return result; +} + +const char * +show_role(void) +{ + /* + * Extract the role name from the stored string; see + * assign_role + */ + const char *value = role_string; + Oid savedoid; + char *endptr; + + /* This special case only applies if no SET ROLE has been done */ + if (value == NULL || strcmp(value, "none") == 0) + return "none"; + + Assert(strspn(value, "x") == NAMEDATALEN && + (value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F')); + + savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10); + + Assert(endptr != value + NAMEDATALEN + 1 && *endptr == ','); + + /* + * Check that the stored string still matches the effective setting, + * else return "none". This is a kluge to deal with the fact that + * SET SESSION AUTHORIZATION logically resets SET ROLE to NONE, but + * we cannot set the GUC role variable from assign_session_authorization + * (because we haven't got enough info to call set_config_option). + */ + if (savedoid != GetCurrentRoleId()) + return "none"; + + return endptr + 1; +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 8afc948a07a..3730068915f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.501 2005/06/29 20:34:13 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.502 2005/07/25 22:12:32 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1004,6 +1004,13 @@ set_rest: var_name TO var_list_or_default n->args = list_make1(makeStringConst($2, NULL)); $$ = n; } + | ROLE ColId_or_Sconst + { + VariableSetStmt *n = makeNode(VariableSetStmt); + n->name = "role"; + n->args = list_make1(makeStringConst($2, NULL)); + $$ = n; + } | SESSION AUTHORIZATION ColId_or_Sconst { VariableSetStmt *n = makeNode(VariableSetStmt); diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 389ad06f2fe..66d6d1725e0 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.146 2005/07/14 05:13:41 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.147 2005/07/25 22:12:33 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -270,24 +270,44 @@ make_absolute_path(const char *path) /* ---------------------------------------------------------------- - * Role ID things + * User ID state * - * The authenticated user is determined at connection start and never - * changes. The session user can be changed only by SET SESSION - * AUTHORIZATION. The current user may change when "setuid" functions - * 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. + * We have to track several different values associated with the concept + * of "user ID". + * + * AuthenticatedUserId is determined at connection start and never changes. + * + * SessionUserId is initially the same as AuthenticatedUserId, but can be + * changed by SET SESSION AUTHORIZATION (if AuthenticatedUserIsSuperuser). + * This is the ID reported by the SESSION_USER SQL function. + * + * OuterUserId is the current user ID in effect at the "outer level" (outside + * any transaction or function). This is initially the same as SessionUserId, + * but can be changed by SET ROLE to any role that SessionUserId is a + * member of. We store this mainly so that AbortTransaction knows what to + * reset CurrentUserId to. + * + * CurrentUserId is the current effective user ID; this is the one to use + * for all normal permissions-checking purposes. At outer level this will + * be the same as OuterUserId, but it changes during calls to SECURITY + * DEFINER functions, as well as locally in some specialized commands. * ---------------------------------------------------------------- */ static Oid AuthenticatedUserId = InvalidOid; static Oid SessionUserId = InvalidOid; +static Oid OuterUserId = InvalidOid; static Oid CurrentUserId = InvalidOid; +/* We also have to remember the superuser state of some of these levels */ static bool AuthenticatedUserIsSuperuser = false; +static bool SessionUserIsSuperuser = false; + +/* We also remember if a SET ROLE is currently active */ +static bool SetRoleIsActive = false; + /* - * This function is relevant for all privilege checks. + * GetUserId/SetUserId - get/set the current effective user ID. */ Oid GetUserId(void) @@ -298,15 +318,37 @@ GetUserId(void) void -SetUserId(Oid roleid) +SetUserId(Oid userid) { - AssertArg(OidIsValid(roleid)); - CurrentUserId = roleid; + AssertArg(OidIsValid(userid)); + CurrentUserId = userid; } /* - * This value is only relevant for informational purposes. + * GetOuterUserId/SetOuterUserId - get/set the outer-level user ID. + */ +Oid +GetOuterUserId(void) +{ + AssertState(OidIsValid(OuterUserId)); + return OuterUserId; +} + + +static void +SetOuterUserId(Oid userid) +{ + AssertArg(OidIsValid(userid)); + OuterUserId = userid; + + /* We force the effective user ID to match, too */ + CurrentUserId = userid; +} + + +/* + * GetSessionUserId/SetSessionUserId - get/set the session user ID. */ Oid GetSessionUserId(void) @@ -316,17 +358,23 @@ GetSessionUserId(void) } -void -SetSessionUserId(Oid roleid) +static void +SetSessionUserId(Oid userid, bool is_superuser) { - AssertArg(OidIsValid(roleid)); - SessionUserId = roleid; - /* Current user defaults to session user. */ - if (!OidIsValid(CurrentUserId)) - CurrentUserId = roleid; + AssertArg(OidIsValid(userid)); + SessionUserId = userid; + SessionUserIsSuperuser = is_superuser; + SetRoleIsActive = false; + + /* We force the effective user IDs to match, too */ + OuterUserId = userid; + CurrentUserId = userid; } +/* + * Initialize user identity during normal backend startup + */ void InitializeSessionUserId(const char *rolename) { @@ -364,7 +412,8 @@ InitializeSessionUserId(const char *rolename) AuthenticatedUserId = roleid; AuthenticatedUserIsSuperuser = rform->rolsuper; - SetSessionUserId(roleid); /* sets CurrentUserId too */ + /* This sets OuterUserId/CurrentUserId too */ + SetSessionUserId(roleid, AuthenticatedUserIsSuperuser); /* Record username and superuser status as GUC settings too */ SetConfigOption("session_authorization", rolename, @@ -391,6 +440,9 @@ InitializeSessionUserId(const char *rolename) } +/* + * Initialize user identity during special backend startup + */ void InitializeSessionUserIdStandalone(void) { @@ -403,7 +455,7 @@ InitializeSessionUserIdStandalone(void) AuthenticatedUserId = BOOTSTRAP_SUPERUSERID; AuthenticatedUserIsSuperuser = true; - SetSessionUserId(BOOTSTRAP_SUPERUSERID); + SetSessionUserId(BOOTSTRAP_SUPERUSERID, true); } @@ -414,21 +466,82 @@ InitializeSessionUserIdStandalone(void) * 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. */ void -SetSessionAuthorization(Oid roleid, bool is_superuser) +SetSessionAuthorization(Oid userid, bool is_superuser) { /* Must have authenticated already, else can't make permission check */ AssertState(OidIsValid(AuthenticatedUserId)); - if (roleid != AuthenticatedUserId && + if (userid != AuthenticatedUserId && !AuthenticatedUserIsSuperuser) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to set session authorization"))); - SetSessionUserId(roleid); - SetUserId(roleid); + SetSessionUserId(userid, is_superuser); + + SetConfigOption("is_superuser", + is_superuser ? "on" : "off", + PGC_INTERNAL, PGC_S_OVERRIDE); +} + +/* + * Report current role id + * This follows the semantics of SET ROLE, ie return the outer-level ID + * not the current effective ID, and return InvalidOid when the setting + * is logically SET ROLE NONE. + */ +Oid +GetCurrentRoleId(void) +{ + if (SetRoleIsActive) + return OuterUserId; + else + return InvalidOid; +} + +/* + * Change Role ID while running (SET ROLE) + * + * If roleid is InvalidOid, we are doing SET ROLE NONE: revert to the + * session user authorization. In this case the is_superuser argument + * is ignored. + * + * When roleid is not InvalidOid, the caller must have checked whether + * the session user has permission to become that role. (We cannot check + * here because this routine must be able to execute in a failed transaction + * to restore a prior value of the ROLE GUC variable.) + */ +void +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 (!OidIsValid(roleid)) + { + if (!OidIsValid(SessionUserId)) + return; + + roleid = SessionUserId; + is_superuser = SessionUserIsSuperuser; + + SetRoleIsActive = false; + } + else + SetRoleIsActive = true; + + SetOuterUserId(roleid); SetConfigOption("is_superuser", is_superuser ? "on" : "off", diff --git a/src/backend/utils/misc/check_guc b/src/backend/utils/misc/check_guc index 5b545d5f436..3332e636427 100755 --- a/src/backend/utils/misc/check_guc +++ b/src/backend/utils/misc/check_guc @@ -18,7 +18,7 @@ ## can be ignored INTENTIONALLY_NOT_INCLUDED="autocommit debug_deadlocks exit_on_error \ is_superuser lc_collate lc_ctype lc_messages lc_monetary lc_numeric lc_time \ -pre_auth_delay seed server_encoding server_version session_authorization \ +pre_auth_delay role seed server_encoding server_version session_authorization \ trace_lock_oidmin trace_lock_table trace_locks trace_lwlocks trace_notify \ trace_userlocks transaction_isolation transaction_read_only \ zero_damaged_pages" diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 6400ef566b2..726a093d0d7 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10,7 +10,7 @@ * Written by Peter Eisentraut <peter_e@gmx.net>. * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.277 2005/07/23 21:05:47 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.278 2005/07/25 22:12:33 tgl Exp $ * *-------------------------------------------------------------------- */ @@ -195,7 +195,8 @@ static int block_size; static bool integer_datetimes; static bool standard_compliant_strings; -/* should be static, but commands/variable.c needs to get at it */ +/* should be static, but commands/variable.c needs to get at these */ +char *role_string; char *session_authorization_string; @@ -1828,6 +1829,17 @@ static struct config_string ConfigureNamesString[] = PG_VERSION, NULL, NULL }, + { + /* Not for general use --- used by SET ROLE */ + {"role", PGC_USERSET, UNGROUPED, + gettext_noop("Sets the current role."), + NULL, + GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &role_string, + "none", assign_role, show_role + }, + { /* Not for general use --- used by SET SESSION AUTHORIZATION */ {"session_authorization", PGC_USERSET, UNGROUPED, @@ -2048,8 +2060,6 @@ static bool guc_dirty; /* TRUE if need to do commit/abort work */ static bool reporting_enabled; /* TRUE to enable GUC_REPORT */ -static char *guc_string_workspace; /* for avoiding memory leaks */ - static int guc_var_compare(const void *a, const void *b); static int guc_name_compare(const char *namea, const char *nameb); @@ -2576,8 +2586,6 @@ InitializeGUCOptions(void) reporting_enabled = false; - guc_string_workspace = NULL; - /* * Prevent any attempt to override the transaction modes from * non-interactive sources. @@ -2976,13 +2984,6 @@ AtEOXact_GUC(bool isCommit, bool isSubXact) if (!guc_dirty) return; - /* Prevent memory leak if ereport during an assign_hook */ - if (guc_string_workspace) - { - free(guc_string_workspace); - guc_string_workspace = NULL; - } - my_level = GetCurrentTransactionNestLevel(); Assert(isSubXact ? (my_level > 1) : (my_level == 1)); @@ -3389,6 +3390,33 @@ parse_real(const char *value, double *result) } +/* + * Call a GucStringAssignHook function, being careful to free the + * "newval" string if the hook ereports. + * + * This is split out of set_config_option just to avoid the "volatile" + * qualifiers that would otherwise have to be plastered all over. + */ +static const char * +call_string_assign_hook(GucStringAssignHook assign_hook, + char *newval, bool doit, GucSource source) +{ + const char *result; + + PG_TRY(); + { + result = (*assign_hook) (newval, doit, source); + } + PG_CATCH(); + { + free(newval); + PG_RE_THROW(); + } + PG_END_TRY(); + + return result; +} + /* * Sets option `name' to given value. The value should be a string @@ -3833,21 +3861,18 @@ set_config_option(const char *name, const char *value, break; } - /* - * Remember string in workspace, so that we can free it - * and avoid a permanent memory leak if hook ereports. - */ - if (guc_string_workspace) - free(guc_string_workspace); - guc_string_workspace = newval; - if (conf->assign_hook) { const char *hookresult; - hookresult = (*conf->assign_hook) (newval, - changeVal, source); - guc_string_workspace = NULL; + /* + * If the hook ereports, we have to make sure we free + * newval, else it will be a permanent memory leak. + */ + hookresult = call_string_assign_hook(conf->assign_hook, + newval, + changeVal, + source); if (hookresult == NULL) { free(newval); @@ -3874,8 +3899,6 @@ set_config_option(const char *name, const char *value, } } - guc_string_workspace = NULL; - if (changeVal || makeDefault) { /* Save old value to support transaction abort */ @@ -4305,8 +4328,7 @@ init_custom_variable(struct config_generic * gen, } void -DefineCustomBoolVariable( - const char *name, +DefineCustomBoolVariable(const char *name, const char *short_desc, const char *long_desc, bool *valueAddr, @@ -4328,8 +4350,7 @@ DefineCustomBoolVariable( } void -DefineCustomIntVariable( - const char *name, +DefineCustomIntVariable(const char *name, const char *short_desc, const char *long_desc, int *valueAddr, @@ -4355,8 +4376,7 @@ DefineCustomIntVariable( } void -DefineCustomRealVariable( - const char *name, +DefineCustomRealVariable(const char *name, const char *short_desc, const char *long_desc, double *valueAddr, @@ -4382,8 +4402,7 @@ DefineCustomRealVariable( } void -DefineCustomStringVariable( - const char *name, +DefineCustomStringVariable(const char *name, const char *short_desc, const char *long_desc, char **valueAddr, diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h index f3d4dc681c8..9814336325d 100644 --- a/src/include/commands/variable.h +++ b/src/include/commands/variable.h @@ -5,7 +5,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/variable.h,v 1.25 2004/12/31 22:03:28 pgsql Exp $ + * $PostgreSQL: pgsql/src/include/commands/variable.h,v 1.26 2005/07/25 22:12:34 tgl Exp $ */ #ifndef VARIABLE_H #define VARIABLE_H @@ -26,6 +26,9 @@ extern bool assign_random_seed(double value, extern const char *show_random_seed(void); extern const char *assign_client_encoding(const char *value, bool doit, GucSource source); +extern const char *assign_role(const char *value, + bool doit, GucSource source); +extern const char *show_role(void); extern const char *assign_session_authorization(const char *value, bool doit, GucSource source); extern const char *show_session_authorization(void); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 1ee085e51a0..5697a691e63 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -13,7 +13,7 @@ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.177 2005/07/04 04:51:52 tgl Exp $ + * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.178 2005/07/25 22:12:34 tgl Exp $ * * NOTES * some of the information in this file should be moved to other files. @@ -230,12 +230,14 @@ extern void SetDatabasePath(const char *path); extern char *GetUserNameFromId(Oid roleid); extern Oid GetUserId(void); -extern void SetUserId(Oid roleid); +extern void SetUserId(Oid userid); +extern Oid GetOuterUserId(void); extern Oid GetSessionUserId(void); -extern void SetSessionUserId(Oid roleid); extern void InitializeSessionUserId(const char *rolename); extern void InitializeSessionUserIdStandalone(void); -extern void SetSessionAuthorization(Oid roleid, bool is_superuser); +extern void SetSessionAuthorization(Oid userid, bool is_superuser); +extern Oid GetCurrentRoleId(void); +extern void SetCurrentRoleId(Oid roleid, bool is_superuser); extern void SetDataDir(const char *dir); extern void ChangeToDataDir(void); -- GitLab