diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index fa0abca950..0c8ed68195 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,6 +1,6 @@ @@ -976,6 +976,14 @@ Role has superuser privileges + + rolinherit + bool + + Role automatically inherits privileges of roles it is a + member of + + rolcreaterole bool @@ -4728,6 +4736,11 @@ that blanks out the password field. + + This view explicitly exposes the OID column of the underlying table, + since that is needed to do joins to other catalogs. + + <structname>pg_roles</> Columns @@ -4756,6 +4769,14 @@ Role has superuser privileges + + rolinherit + bool + + Role automatically inherits privileges of roles it is a + member of + + rolcreaterole bool @@ -4811,6 +4832,13 @@ Session defaults for run-time configuration variables + + + oid + oid + pg_authid.oid + ID of role +
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index b5ce30105b..0fdcb1d0df 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -1,5 +1,5 @@ @@ -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 has_table_privilege. The desired access privilege type must evaluate to - MEMBER. + MEMBER or + USAGE. + MEMBER denotes direct or indirect membership in + the role (that is, the right to do SET ROLE), while + USAGE denotes whether the privileges of the role + are immediately available without doing SET ROLE. diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 8053ca73bb..9e4b58e129 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -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); } diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index d20a3b6d7f..22e2c91110 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -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 diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index 5f8eeae30d..493a6bf790 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.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); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 8d42cead08..283cf54951 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -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); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index e8c8a0cbc9..47e989dbc7 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -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); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3730068915..4d21d4eb9f 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.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 diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index 726e7fc01e..5d4cab2124 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -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}, diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 7517f2743f..48a24db182 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -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); } /* diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 592ea17b11..38f31b114b 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -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 diff --git a/src/include/catalog/pg_authid.h b/src/include/catalog/pg_authid.h index 2ea15fea8a..6672138d86 100644 --- a/src/include/catalog/pg_authid.h +++ b/src/include/catalog/pg_authid.h @@ -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 diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 32f9b03c58..6d388b07d3 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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; diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index d3ef003198..1f21600909 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -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); diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 41cc113b6d..e204864c3c 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -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;