Add a role property 'rolinherit' which, when false, denotes that the role

doesn't automatically inherit the privileges of roles it is a member of;
for such a role, membership in another role can be exploited only by doing
explicit SET ROLE.  The default inherit setting is TRUE, so by default
the behavior doesn't change, but creating a user with NOINHERIT gives closer
adherence to our current reading of SQL99.  Documentation still lacking,
and I think the information schema needs another look.
This commit is contained in:
Tom Lane 2005-07-26 16:38:29 +00:00
parent f9fd176461
commit af019fb9ae
15 changed files with 328 additions and 81 deletions

View File

@ -1,6 +1,6 @@
<!--
Documentation of the system catalogs, directed toward PostgreSQL developers
$PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.108 2005/07/14 05:13:38 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.109 2005/07/26 16:38:25 tgl Exp $
-->
<chapter id="catalogs">
@ -976,6 +976,14 @@
<entry>Role has superuser privileges</entry>
</row>
<row>
<entry><structfield>rolinherit</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
<entry>Role automatically inherits privileges of roles it is a
member of</entry>
</row>
<row>
<entry><structfield>rolcreaterole</structfield></entry>
<entry><type>bool</type></entry>
@ -4728,6 +4736,11 @@
that blanks out the password field.
</para>
<para>
This view explicitly exposes the OID column of the underlying table,
since that is needed to do joins to other catalogs.
</para>
<table>
<title><structname>pg_roles</> Columns</title>
@ -4756,6 +4769,14 @@
<entry>Role has superuser privileges</entry>
</row>
<row>
<entry><structfield>rolinherit</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
<entry>Role automatically inherits privileges of roles it is a
member of</entry>
</row>
<row>
<entry><structfield>rolcreaterole</structfield></entry>
<entry><type>bool</type></entry>
@ -4811,6 +4832,13 @@
<entry></entry>
<entry>Session defaults for run-time configuration variables</entry>
</row>
<row>
<entry><structfield>oid</structfield></entry>
<entry><type>oid</type></entry>
<entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry>
<entry>ID of role</entry>
</row>
</tbody>
</tgroup>
</table>

View File

@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.271 2005/07/26 00:04:17 tgl Exp $
$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.272 2005/07/26 16:38:25 tgl Exp $
PostgreSQL documentation
-->
@ -8559,7 +8559,12 @@ SELECT has_function_privilege('joeuser', 'myfunc(int, text)', 'execute');
can access a role in a particular way. The possibilities for its
arguments are analogous to <function>has_table_privilege</function>.
The desired access privilege type must evaluate to
<literal>MEMBER</literal>.
<literal>MEMBER</literal> or
<literal>USAGE</literal>.
<literal>MEMBER</literal> denotes direct or indirect membership in
the role (that is, the right to do <literal>SET ROLE</>), while
<literal>USAGE</literal> denotes whether the privileges of the role
are immediately available without doing <literal>SET ROLE</>.
</para>
<para>

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.115 2005/07/07 20:39:57 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.116 2005/07/26 16:38:26 tgl Exp $
*
* NOTES
* See acl.h.
@ -1984,7 +1984,7 @@ pg_class_ownercheck(Oid class_oid, Oid roleid)
ReleaseSysCache(tuple);
return is_member_of_role(roleid, ownerId);
return has_privs_of_role(roleid, ownerId);
}
/*
@ -2012,7 +2012,7 @@ pg_type_ownercheck(Oid type_oid, Oid roleid)
ReleaseSysCache(tuple);
return is_member_of_role(roleid, ownerId);
return has_privs_of_role(roleid, ownerId);
}
/*
@ -2040,7 +2040,7 @@ pg_oper_ownercheck(Oid oper_oid, Oid roleid)
ReleaseSysCache(tuple);
return is_member_of_role(roleid, ownerId);
return has_privs_of_role(roleid, ownerId);
}
/*
@ -2068,7 +2068,7 @@ pg_proc_ownercheck(Oid proc_oid, Oid roleid)
ReleaseSysCache(tuple);
return is_member_of_role(roleid, ownerId);
return has_privs_of_role(roleid, ownerId);
}
/*
@ -2096,7 +2096,7 @@ pg_namespace_ownercheck(Oid nsp_oid, Oid roleid)
ReleaseSysCache(tuple);
return is_member_of_role(roleid, ownerId);
return has_privs_of_role(roleid, ownerId);
}
/*
@ -2135,7 +2135,7 @@ pg_tablespace_ownercheck(Oid spc_oid, Oid roleid)
heap_endscan(scan);
heap_close(pg_tablespace, AccessShareLock);
return is_member_of_role(roleid, spcowner);
return has_privs_of_role(roleid, spcowner);
}
/*
@ -2164,7 +2164,7 @@ pg_opclass_ownercheck(Oid opc_oid, Oid roleid)
ReleaseSysCache(tuple);
return is_member_of_role(roleid, ownerId);
return has_privs_of_role(roleid, ownerId);
}
/*
@ -2203,7 +2203,7 @@ pg_database_ownercheck(Oid db_oid, Oid roleid)
heap_endscan(scan);
heap_close(pg_database, AccessShareLock);
return is_member_of_role(roleid, dba);
return has_privs_of_role(roleid, dba);
}
/*
@ -2231,5 +2231,5 @@ pg_conversion_ownercheck(Oid conv_oid, Oid roleid)
ReleaseSysCache(tuple);
return is_member_of_role(roleid, ownerId);
return has_privs_of_role(roleid, ownerId);
}

View File

@ -3,20 +3,22 @@
*
* Copyright (c) 1996-2005, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.16 2005/06/28 05:08:52 tgl Exp $
* $PostgreSQL: pgsql/src/backend/catalog/system_views.sql,v 1.17 2005/07/26 16:38:26 tgl Exp $
*/
CREATE VIEW pg_roles AS
SELECT
rolname,
rolsuper,
rolinherit,
rolcreaterole,
rolcreatedb,
rolcatupdate,
rolcanlogin,
'********'::text as rolpassword,
rolvaliduntil,
rolconfig
rolconfig,
oid
FROM pg_authid;
CREATE VIEW pg_shadow AS

View File

@ -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.157 2005/07/25 22:12:31 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.158 2005/07/26 16:38:26 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -82,6 +82,7 @@ CreateRole(CreateRoleStmt *stmt)
bool encrypt_password = Password_encryption; /* encrypt password? */
char encrypted_password[MD5_PASSWD_LEN + 1];
bool issuper = false; /* Make the user a superuser? */
bool inherit = true; /* Auto inherit privileges? */
bool createrole = false; /* Can this user create roles? */
bool createdb = false; /* Can the user create databases? */
bool canlogin = false; /* Can this user login? */
@ -91,6 +92,7 @@ CreateRole(CreateRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
@ -99,6 +101,19 @@ CreateRole(CreateRoleStmt *stmt)
DefElem *dadminmembers = NULL;
DefElem *dvalidUntil = NULL;
/* The defaults can vary depending on the original statement type */
switch (stmt->stmt_type)
{
case ROLESTMT_ROLE:
break;
case ROLESTMT_USER:
canlogin = true;
/* may eventually want inherit to default to false here */
break;
case ROLESTMT_GROUP:
break;
}
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
@ -120,7 +135,7 @@ CreateRole(CreateRoleStmt *stmt)
}
else if (strcmp(defel->defname, "sysid") == 0)
{
ereport(WARNING,
ereport(NOTICE,
(errmsg("SYSID can no longer be specified")));
}
else if (strcmp(defel->defname, "superuser") == 0)
@ -131,6 +146,14 @@ CreateRole(CreateRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dissuper = defel;
}
else if (strcmp(defel->defname, "inherit") == 0)
{
if (dinherit)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dinherit = defel;
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
@ -196,6 +219,8 @@ CreateRole(CreateRoleStmt *stmt)
password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg) != 0;
if (dinherit)
inherit = intVal(dinherit->arg) != 0;
if (dcreaterole)
createrole = intVal(dcreaterole->arg) != 0;
if (dcreatedb)
@ -261,6 +286,7 @@ CreateRole(CreateRoleStmt *stmt)
DirectFunctionCall1(namein, CStringGetDatum(stmt->role));
new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper);
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit);
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole);
new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb);
/* superuser gets catupdate right by default */
@ -367,6 +393,7 @@ AlterRole(AlterRoleStmt *stmt)
bool encrypt_password = Password_encryption; /* encrypt password? */
char encrypted_password[MD5_PASSWD_LEN + 1];
int issuper = -1; /* Make the user a superuser? */
int inherit = -1; /* Auto inherit privileges? */
int createrole = -1; /* Can this user create roles? */
int createdb = -1; /* Can the user create databases? */
int canlogin = -1; /* Can this user login? */
@ -374,6 +401,7 @@ AlterRole(AlterRoleStmt *stmt)
char *validUntil = NULL; /* time the login is valid until */
DefElem *dpassword = NULL;
DefElem *dissuper = NULL;
DefElem *dinherit = NULL;
DefElem *dcreaterole = NULL;
DefElem *dcreatedb = NULL;
DefElem *dcanlogin = NULL;
@ -408,6 +436,14 @@ AlterRole(AlterRoleStmt *stmt)
errmsg("conflicting or redundant options")));
dissuper = defel;
}
else if (strcmp(defel->defname, "inherit") == 0)
{
if (dinherit)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("conflicting or redundant options")));
dinherit = defel;
}
else if (strcmp(defel->defname, "createrole") == 0)
{
if (dcreaterole)
@ -458,6 +494,8 @@ AlterRole(AlterRoleStmt *stmt)
password = strVal(dpassword->arg);
if (dissuper)
issuper = intVal(dissuper->arg);
if (dinherit)
inherit = intVal(dinherit->arg);
if (dcreaterole)
createrole = intVal(dcreaterole->arg);
if (dcreatedb)
@ -497,10 +535,10 @@ AlterRole(AlterRoleStmt *stmt)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to alter superusers")));
}
else
else if (!have_createrole_privilege())
{
if (!have_createrole_privilege() &&
!(createrole < 0 &&
if (!(inherit < 0 &&
createrole < 0 &&
createdb < 0 &&
canlogin < 0 &&
!rolemembers &&
@ -536,6 +574,12 @@ AlterRole(AlterRoleStmt *stmt)
new_record_repl[Anum_pg_authid_rolcatupdate - 1] = 'r';
}
if (inherit >= 0)
{
new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0);
new_record_repl[Anum_pg_authid_rolinherit - 1] = 'r';
}
if (createrole >= 0)
{
new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0);

View File

@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.311 2005/07/02 23:00:39 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.312 2005/07/26 16:38:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2392,6 +2392,7 @@ _copyCreateRoleStmt(CreateRoleStmt *from)
{
CreateRoleStmt *newnode = makeNode(CreateRoleStmt);
COPY_SCALAR_FIELD(stmt_type);
COPY_STRING_FIELD(role);
COPY_NODE_FIELD(options);

View File

@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.248 2005/07/02 23:00:39 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.249 2005/07/26 16:38:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1308,6 +1308,7 @@ _equalDropPLangStmt(DropPLangStmt *a, DropPLangStmt *b)
static bool
_equalCreateRoleStmt(CreateRoleStmt *a, CreateRoleStmt *b)
{
COMPARE_SCALAR_FIELD(stmt_type);
COMPARE_STRING_FIELD(role);
COMPARE_NODE_FIELD(options);

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.502 2005/07/25 22:12:32 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.503 2005/07/26 16:38:27 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -361,7 +361,7 @@ static void doNegateFloat(Value *v);
HANDLER HAVING HEADER HOLD HOUR_P
ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT
INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P
INDEX INHERIT INHERITS INITIALLY INNER_P INOUT INPUT_P
INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT
INTERVAL INTO INVOKER IS ISNULL ISOLATION
@ -376,8 +376,8 @@ static void doNegateFloat(Value *v);
MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
NAMES NATIONAL NATURAL NCHAR NEW NEXT NO NOCREATEDB
NOCREATEROLE NOCREATEUSER NOLOGIN_P NONE NOSUPERUSER NOT NOTHING NOTIFY
NOTNULL NOWAIT NULL_P NULLIF NUMERIC
NOCREATEROLE NOCREATEUSER NOINHERIT NOLOGIN_P NONE NOSUPERUSER
NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF NUMERIC
OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OR
ORDER OUT_P OUTER_P OVERLAPS OVERLAY OWNER
@ -581,6 +581,7 @@ CreateRoleStmt:
CREATE ROLE RoleId opt_with OptRoleList
{
CreateRoleStmt *n = makeNode(CreateRoleStmt);
n->stmt_type = ROLESTMT_ROLE;
n->role = $3;
n->options = $5;
$$ = (Node *)n;
@ -630,6 +631,14 @@ OptRoleElem:
{
$$ = makeDefElem("superuser", (Node *)makeInteger(FALSE));
}
| INHERIT
{
$$ = makeDefElem("inherit", (Node *)makeInteger(TRUE));
}
| NOINHERIT
{
$$ = makeDefElem("inherit", (Node *)makeInteger(FALSE));
}
| CREATEDB
{
$$ = makeDefElem("createdb", (Node *)makeInteger(TRUE));
@ -700,10 +709,9 @@ CreateUserStmt:
CREATE USER RoleId opt_with OptRoleList
{
CreateRoleStmt *n = makeNode(CreateRoleStmt);
n->stmt_type = ROLESTMT_USER;
n->role = $3;
n->options = lappend($5,
makeDefElem("canlogin",
(Node *)makeInteger(TRUE)));
n->options = $5;
$$ = (Node *)n;
}
;
@ -829,6 +837,7 @@ CreateGroupStmt:
CREATE GROUP_P RoleId opt_with OptRoleList
{
CreateRoleStmt *n = makeNode(CreateRoleStmt);
n->stmt_type = ROLESTMT_GROUP;
n->role = $3;
n->options = $5;
$$ = (Node *)n;
@ -7996,6 +8005,7 @@ unreserved_keyword:
| INCLUDING
| INCREMENT
| INDEX
| INHERIT
| INHERITS
| INPUT_P
| INSENSITIVE
@ -8028,6 +8038,7 @@ unreserved_keyword:
| NOCREATEDB
| NOCREATEROLE
| NOCREATEUSER
| NOINHERIT
| NOLOGIN_P
| NOSUPERUSER
| NOTHING

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.162 2005/06/29 20:34:14 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.163 2005/07/26 16:38:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -165,6 +165,7 @@ static const ScanKeyword ScanKeywords[] = {
{"including", INCLUDING},
{"increment", INCREMENT},
{"index", INDEX},
{"inherit", INHERIT},
{"inherits", INHERITS},
{"initially", INITIALLY},
{"inner", INNER_P},
@ -219,6 +220,7 @@ static const ScanKeyword ScanKeywords[] = {
{"nocreatedb", NOCREATEDB},
{"nocreaterole", NOCREATEROLE},
{"nocreateuser", NOCREATEUSER},
{"noinherit", NOINHERIT},
{"nologin", NOLOGIN_P},
{"none", NONE},
{"nosuperuser", NOSUPERUSER},

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.121 2005/07/26 00:04:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.122 2005/07/26 16:38:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -39,15 +39,29 @@
* all the roles the "given role" is a member of, directly or indirectly.
* The cache is flushed whenever we detect a change in pg_auth_members.
*
* Possibly this mechanism should be generalized to allow caching membership
* info for more than one role?
* There are actually two caches, one computed under "has_privs" rules
* (do not recurse where rolinherit isn't true) and one computed under
* "is_member" rules (recurse regardless of rolinherit).
*
* cached_role is the role OID the cache is for.
* cached_memberships is an OID list of roles that cached_role is a member of.
* The cache is valid if cached_role is not InvalidOid.
* Possibly this mechanism should be generalized to allow caching membership
* info for multiple roles?
*
* The has_privs cache is:
* cached_privs_role is the role OID the cache is for.
* cached_privs_roles is an OID list of roles that cached_privs_role
* has the privileges of (always including itself).
* The cache is valid if cached_privs_role is not InvalidOid.
*
* The is_member cache is similarly:
* cached_member_role is the role OID the cache is for.
* cached_membership_roles is an OID list of roles that cached_member_role
* is a member of (always including itself).
* The cache is valid if cached_member_role is not InvalidOid.
*/
static Oid cached_role = InvalidOid;
static List *cached_memberships = NIL;
static Oid cached_privs_role = InvalidOid;
static List *cached_privs_roles = NIL;
static Oid cached_member_role = InvalidOid;
static List *cached_membership_roles = NIL;
static const char *getid(const char *s, char *n);
@ -999,7 +1013,7 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
result = 0;
/* Owner always implicitly has all grant options */
if (is_member_of_role(roleid, ownerId))
if (has_privs_of_role(roleid, ownerId))
{
result = mask & ACLITEM_ALL_GOPTION_BITS;
if (result == mask)
@ -1042,7 +1056,7 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
continue; /* already checked it */
if ((aidata->ai_privs & remaining) &&
is_member_of_role(roleid, aidata->ai_grantee))
has_privs_of_role(roleid, aidata->ai_grantee))
{
result |= aidata->ai_privs & mask;
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
@ -2653,8 +2667,10 @@ pg_has_role_id_id(PG_FUNCTION_ARGS)
* convert_role_priv_string
* Convert text string to AclMode value.
*
* There is only one interesting option, MEMBER, which we represent by
* ACL_USAGE since no formal ACL bit is defined for it. This convention
* We use USAGE to denote whether the privileges of the role are accessible
* (has_privs), MEMBER to denote is_member, and MEMBER WITH GRANT OPTION
* (or ADMIN OPTION) to denote is_admin. There is no ACL bit corresponding
* to MEMBER so we cheat and use ACL_CREATE for that. This convention
* is shared only with pg_role_aclcheck, below.
*/
static AclMode
@ -2668,12 +2684,15 @@ convert_role_priv_string(text *priv_type_text)
/*
* Return mode from priv_type string
*/
if (pg_strcasecmp(priv_type, "MEMBER") == 0)
if (pg_strcasecmp(priv_type, "USAGE") == 0)
return ACL_USAGE;
if (pg_strcasecmp(priv_type, "MEMBER WITH GRANT OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_USAGE);
if (pg_strcasecmp(priv_type, "MEMBER WITH ADMIN OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_USAGE);
if (pg_strcasecmp(priv_type, "MEMBER") == 0)
return ACL_CREATE;
if (pg_strcasecmp(priv_type, "USAGE WITH GRANT OPTION") == 0 ||
pg_strcasecmp(priv_type, "USAGE WITH ADMIN OPTION") == 0 ||
pg_strcasecmp(priv_type, "MEMBER WITH GRANT OPTION") == 0 ||
pg_strcasecmp(priv_type, "MEMBER WITH ADMIN OPTION") == 0)
return ACL_GRANT_OPTION_FOR(ACL_CREATE);
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@ -2688,20 +2707,22 @@ convert_role_priv_string(text *priv_type_text)
static AclResult
pg_role_aclcheck(Oid role_oid, Oid roleid, AclMode mode)
{
if (mode & ACL_GRANT_OPTION_FOR(ACL_USAGE))
if (mode & ACL_GRANT_OPTION_FOR(ACL_CREATE))
{
if (is_admin_of_role(roleid, role_oid))
return ACLCHECK_OK;
else
return ACLCHECK_NO_PRIV;
}
else
if (mode & ACL_CREATE)
{
if (is_member_of_role(roleid, role_oid))
return ACLCHECK_OK;
else
return ACLCHECK_NO_PRIV;
}
if (mode & ACL_USAGE)
{
if (has_privs_of_role(roleid, role_oid))
return ACLCHECK_OK;
}
return ACLCHECK_NO_PRIV;
}
@ -2730,23 +2751,47 @@ initialize_acl(void)
static void
RoleMembershipCacheCallback(Datum arg, Oid relid)
{
/* Force membership cache to be recomputed on next use */
cached_role = InvalidOid;
/* Force membership caches to be recomputed on next use */
cached_privs_role = InvalidOid;
cached_member_role = InvalidOid;
}
/* Check if specified role has rolinherit set */
static bool
has_rolinherit(Oid roleid)
{
bool result = false;
HeapTuple utup;
utup = SearchSysCache(AUTHOID,
ObjectIdGetDatum(roleid),
0, 0, 0);
if (HeapTupleIsValid(utup))
{
result = ((Form_pg_authid) GETSTRUCT(utup))->rolinherit;
ReleaseSysCache(utup);
}
return result;
}
/*
* Is member a member of role (directly or indirectly)?
* Does member have the privileges of role (directly or indirectly)?
*
* This is defined not to recurse through roles that don't have rolinherit
* set; for such roles, membership implies the ability to do SET ROLE, but
* the privileges are not available until you've done so.
*
* Since indirect membership testing is relatively expensive, we cache
* a list of memberships.
*/
bool
is_member_of_role(Oid member, Oid role)
has_privs_of_role(Oid member, Oid role)
{
List *roles_list;
ListCell *l;
List *new_cached_memberships;
List *new_cached_privs_roles;
MemoryContext oldctx;
/* Fast path for simple case */
@ -2758,8 +2803,100 @@ is_member_of_role(Oid member, Oid role)
return true;
/* If cache is already valid, just use the list */
if (OidIsValid(cached_role) && cached_role == member)
return list_member_oid(cached_memberships, role);
if (OidIsValid(cached_privs_role) && cached_privs_role == member)
return list_member_oid(cached_privs_roles, role);
/*
* Find all the roles that member is a member of,
* including multi-level recursion. The role itself will always
* be the first element of the resulting list.
*
* Each element of the list is scanned to see if it adds any indirect
* memberships. We can use a single list as both the record of
* already-found memberships and the agenda of roles yet to be scanned.
* This is a bit tricky but works because the foreach() macro doesn't
* fetch the next list element until the bottom of the loop.
*/
roles_list = list_make1_oid(member);
foreach(l, roles_list)
{
Oid memberid = lfirst_oid(l);
CatCList *memlist;
int i;
/* Ignore non-inheriting roles */
if (!has_rolinherit(memberid))
continue;
/* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList(AUTHMEMMEMROLE, 1,
ObjectIdGetDatum(memberid),
0, 0, 0);
for (i = 0; i < memlist->n_members; i++)
{
HeapTuple tup = &memlist->members[i]->tuple;
Oid otherid = ((Form_pg_auth_members) GETSTRUCT(tup))->roleid;
/*
* Even though there shouldn't be any loops in the membership
* graph, we must test for having already seen this role.
* It is legal for instance to have both A->B and A->C->B.
*/
if (!list_member_oid(roles_list, otherid))
roles_list = lappend_oid(roles_list, otherid);
}
ReleaseSysCacheList(memlist);
}
/*
* Copy the completed list into TopMemoryContext so it will persist.
*/
oldctx = MemoryContextSwitchTo(TopMemoryContext);
new_cached_privs_roles = list_copy(roles_list);
MemoryContextSwitchTo(oldctx);
list_free(roles_list);
/*
* Now safe to assign to state variable
*/
cached_privs_role = InvalidOid; /* just paranoia */
list_free(cached_privs_roles);
cached_privs_roles = new_cached_privs_roles;
cached_privs_role = member;
/* And now we can return the answer */
return list_member_oid(cached_privs_roles, role);
}
/*
* Is member a member of role (directly or indirectly)?
*
* This is defined to recurse through roles regardless of rolinherit.
*
* Since indirect membership testing is relatively expensive, we cache
* a list of memberships.
*/
bool
is_member_of_role(Oid member, Oid role)
{
List *roles_list;
ListCell *l;
List *new_cached_membership_roles;
MemoryContext oldctx;
/* Fast path for simple case */
if (member == role)
return true;
/* Superusers have every privilege, so are part of every role */
if (superuser_arg(member))
return true;
/* If cache is already valid, just use the list */
if (OidIsValid(cached_member_role) && cached_member_role == member)
return list_member_oid(cached_membership_roles, role);
/*
* Find all the roles that member is a member of,
@ -2804,20 +2941,20 @@ is_member_of_role(Oid member, Oid role)
* Copy the completed list into TopMemoryContext so it will persist.
*/
oldctx = MemoryContextSwitchTo(TopMemoryContext);
new_cached_memberships = list_copy(roles_list);
new_cached_membership_roles = list_copy(roles_list);
MemoryContextSwitchTo(oldctx);
list_free(roles_list);
/*
* Now safe to assign to state variable
*/
cached_role = InvalidOid; /* just paranoia */
list_free(cached_memberships);
cached_memberships = new_cached_memberships;
cached_role = member;
cached_member_role = InvalidOid; /* just paranoia */
list_free(cached_membership_roles);
cached_membership_roles = new_cached_membership_roles;
cached_member_role = member;
/* And now we can return the answer */
return list_member_oid(cached_memberships, role);
return list_member_oid(cached_membership_roles, role);
}
/*

View File

@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.291 2005/07/26 00:04:18 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.292 2005/07/26 16:38:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200507251
#define CATALOG_VERSION_NO 200507261
#endif

View File

@ -10,7 +10,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/catalog/pg_authid.h,v 1.1 2005/06/28 05:09:05 tgl Exp $
* $PostgreSQL: pgsql/src/include/catalog/pg_authid.h,v 1.2 2005/07/26 16:38:28 tgl Exp $
*
* NOTES
* the genbki.sh script reads this file and generates .bki
@ -44,6 +44,7 @@ CATALOG(pg_authid,1260) BKI_SHARED_RELATION
{
NameData rolname; /* name of role */
bool rolsuper; /* read this field via superuser() only! */
bool rolinherit; /* inherit privileges from other roles? */
bool rolcreaterole; /* allowed to create more roles? */
bool rolcreatedb; /* allowed to create databases? */
bool rolcatupdate; /* allowed to alter catalogs manually? */
@ -69,16 +70,17 @@ typedef FormData_pg_authid *Form_pg_authid;
* compiler constants for pg_authid
* ----------------
*/
#define Natts_pg_authid 9
#define Natts_pg_authid 10
#define Anum_pg_authid_rolname 1
#define Anum_pg_authid_rolsuper 2
#define Anum_pg_authid_rolcreaterole 3
#define Anum_pg_authid_rolcreatedb 4
#define Anum_pg_authid_rolcatupdate 5
#define Anum_pg_authid_rolcanlogin 6
#define Anum_pg_authid_rolpassword 7
#define Anum_pg_authid_rolvaliduntil 8
#define Anum_pg_authid_rolconfig 9
#define Anum_pg_authid_rolinherit 3
#define Anum_pg_authid_rolcreaterole 4
#define Anum_pg_authid_rolcreatedb 5
#define Anum_pg_authid_rolcatupdate 6
#define Anum_pg_authid_rolcanlogin 7
#define Anum_pg_authid_rolpassword 8
#define Anum_pg_authid_rolvaliduntil 9
#define Anum_pg_authid_rolconfig 10
/* ----------------
* initial contents of pg_authid
@ -87,7 +89,7 @@ typedef FormData_pg_authid *Form_pg_authid;
* user choices.
* ----------------
*/
DATA(insert OID = 10 ( "POSTGRES" t t t t t _null_ _null_ _null_ ));
DATA(insert OID = 10 ( "POSTGRES" t t t t t t _null_ _null_ _null_ ));
#define BOOTSTRAP_SUPERUSERID 10

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.285 2005/06/28 19:51:24 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.286 2005/07/26 16:38:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1139,11 +1139,24 @@ typedef struct DropPLangStmt
/* ----------------------
* Create/Alter/Drop Role Statements
*
* Note: these node types are also used for the backwards-compatible
* Create/Alter/Drop User/Group statements. In the ALTER and DROP cases
* there's really no need to distinguish what the original spelling was,
* but for CREATE we mark the type because the defaults vary.
* ----------------------
*/
typedef enum RoleStmtType
{
ROLESTMT_ROLE,
ROLESTMT_USER,
ROLESTMT_GROUP
} RoleStmtType;
typedef struct CreateRoleStmt
{
NodeTag type;
RoleStmtType stmt_type; /* ROLE/USER/GROUP */
char *role; /* role name */
List *options; /* List of DefElem nodes */
} CreateRoleStmt;

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.82 2005/07/14 21:46:30 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.83 2005/07/26 16:38:29 tgl Exp $
*
* NOTES
* An ACL array is simply an array of AclItems, representing the union
@ -210,6 +210,7 @@ extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId,
AclMode mask, AclMaskHow how);
extern int aclmembers(const Acl *acl, Oid **roleids);
extern bool has_privs_of_role(Oid member, Oid role);
extern bool is_member_of_role(Oid member, Oid role);
extern bool is_admin_of_role(Oid member, Oid role);
extern void check_is_member_of_role(Oid member, Oid role);

View File

@ -1281,7 +1281,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
pg_indexes | SELECT n.nspname AS schemaname, c.relname AS tablename, i.relname AS indexname, t.spcname AS "tablespace", pg_get_indexdef(i.oid) AS indexdef FROM ((((pg_index x JOIN pg_class c ON ((c.oid = x.indrelid))) JOIN pg_class i ON ((i.oid = x.indexrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN pg_tablespace t ON ((t.oid = i.reltablespace))) WHERE ((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char"));
pg_locks | SELECT l.locktype, l."database", l.relation, l.page, l.tuple, l.transactionid, l.classid, l.objid, l.objsubid, l."transaction", l.pid, l."mode", l."granted" FROM pg_lock_status() l(locktype text, "database" oid, relation oid, page integer, tuple smallint, transactionid xid, classid oid, objid oid, objsubid smallint, "transaction" xid, pid integer, "mode" text, "granted" boolean);
pg_prepared_xacts | SELECT p."transaction", p.gid, p."prepared", u.rolname AS "owner", d.datname AS "database" FROM ((pg_prepared_xact() p("transaction" xid, gid text, "prepared" timestamp with time zone, ownerid oid, dbid oid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid)));
pg_roles | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, '********'::text AS rolpassword, pg_authid.rolvaliduntil, pg_authid.rolconfig FROM pg_authid;
pg_roles | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolinherit, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, '********'::text AS rolpassword, pg_authid.rolvaliduntil, pg_authid.rolconfig, pg_authid.oid FROM pg_authid;
pg_rules | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name);
pg_settings | SELECT a.name, a.setting, a.category, a.short_desc, a.extra_desc, a.context, a.vartype, a.source, a.min_val, a.max_val FROM pg_show_all_settings() a(name text, setting text, category text, short_desc text, extra_desc text, context text, vartype text, source text, min_val text, max_val text);
pg_shadow | SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, pg_authid.rolcreatedb AS usecreatedb, pg_authid.rolsuper AS usesuper, pg_authid.rolcatupdate AS usecatupd, pg_authid.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, pg_authid.rolconfig AS useconfig FROM pg_authid WHERE pg_authid.rolcanlogin;