diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2005-07-26 16:38:29 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2005-07-26 16:38:29 +0000 |
commit | af019fb9aec0274875a10a89c68c8fecb949349f (patch) | |
tree | 21f9b389c49ef4386bc8faf9adcd26199417a806 /src/backend | |
parent | f9fd1764615ed5d85fab703b0ffb0c323fe7dfd5 (diff) | |
download | postgresql-af019fb9aec0274875a10a89c68c8fecb949349f.tar.gz postgresql-af019fb9aec0274875a10a89c68c8fecb949349f.zip |
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.
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/aclchk.c | 20 | ||||
-rw-r--r-- | src/backend/catalog/system_views.sql | 6 | ||||
-rw-r--r-- | src/backend/commands/user.c | 54 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 3 | ||||
-rw-r--r-- | src/backend/nodes/equalfuncs.c | 3 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 25 | ||||
-rw-r--r-- | src/backend/parser/keywords.c | 4 | ||||
-rw-r--r-- | src/backend/utils/adt/acl.c | 203 |
8 files changed, 258 insertions, 60 deletions
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 8053ca73bbf..9e4b58e1298 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 d20a3b6d7f2..22e2c911107 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 5f8eeae30df..493a6bf7904 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 8d42cead085..283cf549514 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 e8c8a0cbc94..47e989dbc7d 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 3730068915f..4d21d4eb9f5 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 726e7fc01e3..5d4cab2124f 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 7517f2743f9..48a24db1826 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. * + * 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). + * * Possibly this mechanism should be generalized to allow caching membership - * info for more than one role? + * 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. * - * 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. + * 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,14 +2751,130 @@ 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; +} + + +/* + * 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 +has_privs_of_role(Oid member, Oid role) +{ + List *roles_list; + ListCell *l; + List *new_cached_privs_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_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. */ @@ -2746,7 +2883,7 @@ is_member_of_role(Oid member, Oid role) { List *roles_list; ListCell *l; - List *new_cached_memberships; + List *new_cached_membership_roles; MemoryContext oldctx; /* Fast path for simple case */ @@ -2758,8 +2895,8 @@ 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_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); } /* |