From 91783061517b740e07f7dbf24f980e4641ba7d83 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 10 Oct 2005 18:49:04 +0000 Subject: [PATCH] Fix the problem of GRANTs creating "dangling" privileges not directly traceable to grant options. As per my earlier proposal, a GRANT made by a role member has to be recorded as being granted by the role that actually holds the grant option, and not the member. --- src/backend/catalog/aclchk.c | 359 ++++++++++++++++------------------- src/backend/utils/adt/acl.c | 286 ++++++++++++++++++++++++---- src/include/utils/acl.h | 6 +- 3 files changed, 415 insertions(+), 236 deletions(-) diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 97da34243c..689a2ff819 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.118 2005/08/17 19:45:51 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.119 2005/10/10 18:49:01 tgl Exp $ * * NOTES * See acl.h. @@ -70,32 +70,6 @@ dumpacl(Acl *acl) #endif /* ACLDEBUG */ -/* - * Determine the effective grantor ID for a GRANT or REVOKE operation. - * - * Ordinarily this is just the current user, but when a superuser does - * GRANT or REVOKE, we pretend he is the object owner. This ensures that - * all granted privileges appear to flow from the object owner, and there - * are never multiple "original sources" of a privilege. - */ -static Oid -select_grantor(Oid ownerId) -{ - Oid grantorId; - - grantorId = GetUserId(); - - /* fast path if no difference */ - if (grantorId == ownerId) - return grantorId; - - if (superuser()) - grantorId = ownerId; - - return grantorId; -} - - /* * If is_grant is true, adds the given privileges for the list of * grantees to the existing old_acl. If is_grant is false, the @@ -243,7 +217,7 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) Form_pg_class pg_class_tuple; Datum aclDatum; bool isNull; - AclMode my_goptions; + AclMode avail_goptions; AclMode this_privileges; Acl *old_acl; Acl *new_acl; @@ -282,28 +256,36 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) errmsg("\"%s\" is a composite type", relvar->relname))); + /* + * Get owner ID and working copy of existing ACL. + * If there's no ACL, substitute the proper default. + */ ownerId = pg_class_tuple->relowner; - grantorId = select_grantor(ownerId); + aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl, + &isNull); + if (isNull) + old_acl = acldefault(ACL_OBJECT_RELATION, ownerId); + else + old_acl = DatumGetAclPCopy(aclDatum); + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); /* - * Must be owner or have some privilege on the object (per spec, - * any privilege will get you by here). The owner is always - * treated as having all grant options. + * If we found no grant options, consider whether to issue a hard + * error. Per spec, having any privilege at all on the object + * will get you by here. */ - if (pg_class_ownercheck(relOid, GetUserId())) - my_goptions = ACL_ALL_RIGHTS_RELATION; - else + if (avail_goptions == ACL_NO_RIGHTS) { - AclMode my_rights; - - my_rights = pg_class_aclmask(relOid, - GetUserId(), - ACL_ALL_RIGHTS_RELATION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_RELATION), - ACLMASK_ALL); - if (my_rights == ACL_NO_RIGHTS) + if (pg_class_aclmask(relOid, + grantorId, + ACL_ALL_RIGHTS_RELATION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_RELATION), + ACLMASK_ANY) == ACL_NO_RIGHTS) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, relvar->relname); - my_goptions = ACL_OPTION_TO_PRIVS(my_rights); } /* @@ -314,7 +296,7 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) * In practice that behavior seems much too noisy, as well as * inconsistent with the GRANT case.) */ - this_privileges = privileges & my_goptions; + this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions); if (stmt->is_grant) { if (this_privileges == 0) @@ -339,17 +321,8 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt) } /* - * If there's no ACL, substitute the proper default. - */ - aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl, - &isNull); - if (isNull) - old_acl = acldefault(ACL_OBJECT_RELATION, ownerId); - else - /* get a detoasted copy of the ACL */ - old_acl = DatumGetAclPCopy(aclDatum); - - /* + * Generate new ACL. + * * We need the members of both old and new ACLs so we can correct * the shared dependency information. */ @@ -434,7 +407,7 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) Form_pg_database pg_database_tuple; Datum aclDatum; bool isNull; - AclMode my_goptions; + AclMode avail_goptions; AclMode this_privileges; Acl *old_acl; Acl *new_acl; @@ -462,28 +435,36 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) errmsg("database \"%s\" does not exist", dbname))); pg_database_tuple = (Form_pg_database) GETSTRUCT(tuple); + /* + * Get owner ID and working copy of existing ACL. + * If there's no ACL, substitute the proper default. + */ ownerId = pg_database_tuple->datdba; - grantorId = select_grantor(ownerId); + aclDatum = heap_getattr(tuple, Anum_pg_database_datacl, + RelationGetDescr(relation), &isNull); + if (isNull) + old_acl = acldefault(ACL_OBJECT_DATABASE, ownerId); + else + old_acl = DatumGetAclPCopy(aclDatum); + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); /* - * Must be owner or have some privilege on the object (per spec, - * any privilege will get you by here). The owner is always - * treated as having all grant options. + * If we found no grant options, consider whether to issue a hard + * error. Per spec, having any privilege at all on the object + * will get you by here. */ - if (pg_database_ownercheck(HeapTupleGetOid(tuple), GetUserId())) - my_goptions = ACL_ALL_RIGHTS_DATABASE; - else + if (avail_goptions == ACL_NO_RIGHTS) { - AclMode my_rights; - - my_rights = pg_database_aclmask(HeapTupleGetOid(tuple), - GetUserId(), - ACL_ALL_RIGHTS_DATABASE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_DATABASE), - ACLMASK_ALL); - if (my_rights == ACL_NO_RIGHTS) + if (pg_database_aclmask(HeapTupleGetOid(tuple), + grantorId, + ACL_ALL_RIGHTS_DATABASE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_DATABASE), + ACLMASK_ANY) == ACL_NO_RIGHTS) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_DATABASE, NameStr(pg_database_tuple->datname)); - my_goptions = ACL_OPTION_TO_PRIVS(my_rights); } /* @@ -494,7 +475,7 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) * In practice that behavior seems much too noisy, as well as * inconsistent with the GRANT case.) */ - this_privileges = privileges & my_goptions; + this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions); if (stmt->is_grant) { if (this_privileges == 0) @@ -519,17 +500,8 @@ ExecuteGrantStmt_Database(GrantStmt *stmt) } /* - * If there's no ACL, substitute the proper default. - */ - aclDatum = heap_getattr(tuple, Anum_pg_database_datacl, - RelationGetDescr(relation), &isNull); - if (isNull) - old_acl = acldefault(ACL_OBJECT_DATABASE, ownerId); - else - /* get a detoasted copy of the ACL */ - old_acl = DatumGetAclPCopy(aclDatum); - - /* + * Generate new ACL. + * * We need the members of both old and new ACLs so we can correct * the shared dependency information. */ @@ -613,7 +585,7 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) Form_pg_proc pg_proc_tuple; Datum aclDatum; bool isNull; - AclMode my_goptions; + AclMode avail_goptions; AclMode this_privileges; Acl *old_acl; Acl *new_acl; @@ -638,28 +610,36 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) elog(ERROR, "cache lookup failed for function %u", oid); pg_proc_tuple = (Form_pg_proc) GETSTRUCT(tuple); + /* + * Get owner ID and working copy of existing ACL. + * If there's no ACL, substitute the proper default. + */ ownerId = pg_proc_tuple->proowner; - grantorId = select_grantor(ownerId); + aclDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proacl, + &isNull); + if (isNull) + old_acl = acldefault(ACL_OBJECT_FUNCTION, ownerId); + else + old_acl = DatumGetAclPCopy(aclDatum); + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); /* - * Must be owner or have some privilege on the object (per spec, - * any privilege will get you by here). The owner is always - * treated as having all grant options. + * If we found no grant options, consider whether to issue a hard + * error. Per spec, having any privilege at all on the object + * will get you by here. */ - if (pg_proc_ownercheck(oid, GetUserId())) - my_goptions = ACL_ALL_RIGHTS_FUNCTION; - else + if (avail_goptions == ACL_NO_RIGHTS) { - AclMode my_rights; - - my_rights = pg_proc_aclmask(oid, - GetUserId(), - ACL_ALL_RIGHTS_FUNCTION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_FUNCTION), - ACLMASK_ALL); - if (my_rights == ACL_NO_RIGHTS) + if (pg_proc_aclmask(oid, + grantorId, + ACL_ALL_RIGHTS_FUNCTION | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_FUNCTION), + ACLMASK_ANY) == ACL_NO_RIGHTS) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_PROC, NameStr(pg_proc_tuple->proname)); - my_goptions = ACL_OPTION_TO_PRIVS(my_rights); } /* @@ -670,7 +650,7 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) * In practice that behavior seems much too noisy, as well as * inconsistent with the GRANT case.) */ - this_privileges = privileges & my_goptions; + this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions); if (stmt->is_grant) { if (this_privileges == 0) @@ -695,17 +675,8 @@ ExecuteGrantStmt_Function(GrantStmt *stmt) } /* - * If there's no ACL, substitute the proper default. - */ - aclDatum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proacl, - &isNull); - if (isNull) - old_acl = acldefault(ACL_OBJECT_FUNCTION, ownerId); - else - /* get a detoasted copy of the ACL */ - old_acl = DatumGetAclPCopy(aclDatum); - - /* + * Generate new ACL. + * * We need the members of both old and new ACLs so we can correct * the shared dependency information. */ @@ -788,7 +759,7 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) Form_pg_language pg_language_tuple; Datum aclDatum; bool isNull; - AclMode my_goptions; + AclMode avail_goptions; AclMode this_privileges; Acl *old_acl; Acl *new_acl; @@ -820,31 +791,38 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) errhint("Only superusers may use untrusted languages."))); /* + * Get owner ID and working copy of existing ACL. + * If there's no ACL, substitute the proper default. + * * Note: for now, languages are treated as owned by the bootstrap * user. We should add an owner column to pg_language instead. */ ownerId = BOOTSTRAP_SUPERUSERID; - grantorId = select_grantor(ownerId); + aclDatum = SysCacheGetAttr(LANGNAME, tuple, Anum_pg_language_lanacl, + &isNull); + if (isNull) + old_acl = acldefault(ACL_OBJECT_LANGUAGE, ownerId); + else + old_acl = DatumGetAclPCopy(aclDatum); + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); /* - * Must be owner or have some privilege on the object (per spec, - * any privilege will get you by here). The owner is always - * treated as having all grant options. + * If we found no grant options, consider whether to issue a hard + * error. Per spec, having any privilege at all on the object + * will get you by here. */ - if (superuser()) /* XXX no ownercheck() available */ - my_goptions = ACL_ALL_RIGHTS_LANGUAGE; - else + if (avail_goptions == ACL_NO_RIGHTS) { - AclMode my_rights; - - my_rights = pg_language_aclmask(HeapTupleGetOid(tuple), - GetUserId(), - ACL_ALL_RIGHTS_LANGUAGE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_LANGUAGE), - ACLMASK_ALL); - if (my_rights == ACL_NO_RIGHTS) + if (pg_language_aclmask(HeapTupleGetOid(tuple), + grantorId, + ACL_ALL_RIGHTS_LANGUAGE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_LANGUAGE), + ACLMASK_ANY) == ACL_NO_RIGHTS) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_LANGUAGE, NameStr(pg_language_tuple->lanname)); - my_goptions = ACL_OPTION_TO_PRIVS(my_rights); } /* @@ -855,7 +833,7 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) * In practice that behavior seems much too noisy, as well as * inconsistent with the GRANT case.) */ - this_privileges = privileges & my_goptions; + this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions); if (stmt->is_grant) { if (this_privileges == 0) @@ -880,17 +858,8 @@ ExecuteGrantStmt_Language(GrantStmt *stmt) } /* - * If there's no ACL, substitute the proper default. - */ - aclDatum = SysCacheGetAttr(LANGNAME, tuple, Anum_pg_language_lanacl, - &isNull); - if (isNull) - old_acl = acldefault(ACL_OBJECT_LANGUAGE, ownerId); - else - /* get a detoasted copy of the ACL */ - old_acl = DatumGetAclPCopy(aclDatum); - - /* + * Generate new ACL. + * * We need the members of both old and new ACLs so we can correct * the shared dependency information. */ @@ -973,7 +942,7 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) Form_pg_namespace pg_namespace_tuple; Datum aclDatum; bool isNull; - AclMode my_goptions; + AclMode avail_goptions; AclMode this_privileges; Acl *old_acl; Acl *new_acl; @@ -998,28 +967,37 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) errmsg("schema \"%s\" does not exist", nspname))); pg_namespace_tuple = (Form_pg_namespace) GETSTRUCT(tuple); + /* + * Get owner ID and working copy of existing ACL. + * If there's no ACL, substitute the proper default. + */ ownerId = pg_namespace_tuple->nspowner; - grantorId = select_grantor(ownerId); + aclDatum = SysCacheGetAttr(NAMESPACENAME, tuple, + Anum_pg_namespace_nspacl, + &isNull); + if (isNull) + old_acl = acldefault(ACL_OBJECT_NAMESPACE, ownerId); + else + old_acl = DatumGetAclPCopy(aclDatum); + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); /* - * Must be owner or have some privilege on the object (per spec, - * any privilege will get you by here). The owner is always - * treated as having all grant options. + * If we found no grant options, consider whether to issue a hard + * error. Per spec, having any privilege at all on the object + * will get you by here. */ - if (pg_namespace_ownercheck(HeapTupleGetOid(tuple), GetUserId())) - my_goptions = ACL_ALL_RIGHTS_NAMESPACE; - else + if (avail_goptions == ACL_NO_RIGHTS) { - AclMode my_rights; - - my_rights = pg_namespace_aclmask(HeapTupleGetOid(tuple), - GetUserId(), - ACL_ALL_RIGHTS_NAMESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_NAMESPACE), - ACLMASK_ALL); - if (my_rights == ACL_NO_RIGHTS) + if (pg_namespace_aclmask(HeapTupleGetOid(tuple), + grantorId, + ACL_ALL_RIGHTS_NAMESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_NAMESPACE), + ACLMASK_ANY) == ACL_NO_RIGHTS) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_NAMESPACE, nspname); - my_goptions = ACL_OPTION_TO_PRIVS(my_rights); } /* @@ -1030,7 +1008,7 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) * In practice that behavior seems much too noisy, as well as * inconsistent with the GRANT case.) */ - this_privileges = privileges & my_goptions; + this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions); if (stmt->is_grant) { if (this_privileges == 0) @@ -1055,18 +1033,8 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt) } /* - * If there's no ACL, substitute the proper default. - */ - aclDatum = SysCacheGetAttr(NAMESPACENAME, tuple, - Anum_pg_namespace_nspacl, - &isNull); - if (isNull) - old_acl = acldefault(ACL_OBJECT_NAMESPACE, ownerId); - else - /* get a detoasted copy of the ACL */ - old_acl = DatumGetAclPCopy(aclDatum); - - /* + * Generate new ACL. + * * We need the members of both old and new ACLs so we can correct * the shared dependency information. */ @@ -1151,7 +1119,7 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt) Form_pg_tablespace pg_tablespace_tuple; Datum aclDatum; bool isNull; - AclMode my_goptions; + AclMode avail_goptions; AclMode this_privileges; Acl *old_acl; Acl *new_acl; @@ -1179,28 +1147,36 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt) errmsg("tablespace \"%s\" does not exist", spcname))); pg_tablespace_tuple = (Form_pg_tablespace) GETSTRUCT(tuple); + /* + * Get owner ID and working copy of existing ACL. + * If there's no ACL, substitute the proper default. + */ ownerId = pg_tablespace_tuple->spcowner; - grantorId = select_grantor(ownerId); + aclDatum = heap_getattr(tuple, Anum_pg_tablespace_spcacl, + RelationGetDescr(relation), &isNull); + if (isNull) + old_acl = acldefault(ACL_OBJECT_TABLESPACE, ownerId); + else + old_acl = DatumGetAclPCopy(aclDatum); + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); /* - * Must be owner or have some privilege on the object (per spec, - * any privilege will get you by here). The owner is always - * treated as having all grant options. + * If we found no grant options, consider whether to issue a hard + * error. Per spec, having any privilege at all on the object + * will get you by here. */ - if (pg_tablespace_ownercheck(HeapTupleGetOid(tuple), GetUserId())) - my_goptions = ACL_ALL_RIGHTS_TABLESPACE; - else + if (avail_goptions == ACL_NO_RIGHTS) { - AclMode my_rights; - - my_rights = pg_tablespace_aclmask(HeapTupleGetOid(tuple), - GetUserId(), - ACL_ALL_RIGHTS_TABLESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_TABLESPACE), - ACLMASK_ALL); - if (my_rights == ACL_NO_RIGHTS) + if (pg_tablespace_aclmask(HeapTupleGetOid(tuple), + grantorId, + ACL_ALL_RIGHTS_TABLESPACE | ACL_GRANT_OPTION_FOR(ACL_ALL_RIGHTS_TABLESPACE), + ACLMASK_ANY) == ACL_NO_RIGHTS) aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_TABLESPACE, spcname); - my_goptions = ACL_OPTION_TO_PRIVS(my_rights); } /* @@ -1211,7 +1187,7 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt) * In practice that behavior seems much too noisy, as well as * inconsistent with the GRANT case.) */ - this_privileges = privileges & my_goptions; + this_privileges = privileges & ACL_OPTION_TO_PRIVS(avail_goptions); if (stmt->is_grant) { if (this_privileges == 0) @@ -1236,17 +1212,8 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt) } /* - * If there's no ACL, substitute the proper default. - */ - aclDatum = heap_getattr(tuple, Anum_pg_tablespace_spcacl, - RelationGetDescr(relation), &isNull); - if (isNull) - old_acl = acldefault(ACL_OBJECT_TABLESPACE, ownerId); - else - /* get a detoasted copy of the ACL */ - old_acl = DatumGetAclPCopy(aclDatum); - - /* + * Generate new ACL. + * * We need the members of both old and new ACLs so we can correct * the shared dependency information. */ diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index bc3a32a0d7..9909640ad4 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.124 2005/10/07 19:59:34 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.125 2005/10/10 18:49:03 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1070,6 +1070,65 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId, } +/* + * aclmask_direct --- compute bitmask of all privileges held by roleid. + * + * This is exactly like aclmask() except that we consider only privileges + * held *directly* by roleid, not those inherited via role membership. + */ +static AclMode +aclmask_direct(const Acl *acl, Oid roleid, Oid ownerId, + AclMode mask, AclMaskHow how) +{ + AclMode result; + AclItem *aidat; + int i, + num; + + /* + * Null ACL should not happen, since caller should have inserted + * appropriate default + */ + if (acl == NULL) + elog(ERROR, "null ACL"); + + /* Quick exit for mask == 0 */ + if (mask == 0) + return 0; + + result = 0; + + /* Owner always implicitly has all grant options */ + if ((mask & ACLITEM_ALL_GOPTION_BITS) && + roleid == ownerId) + { + result = mask & ACLITEM_ALL_GOPTION_BITS; + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; + } + + num = ACL_NUM(acl); + aidat = ACL_DAT(acl); + + /* + * Check privileges granted directly to roleid (and not to public) + */ + for (i = 0; i < num; i++) + { + AclItem *aidata = &aidat[i]; + + if (aidata->ai_grantee == roleid) + { + result |= aidata->ai_privs & mask; + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; + } + } + + return result; +} + + /* * aclmembers * Find out all the roleids mentioned in an Acl. @@ -2778,37 +2837,33 @@ has_rolinherit(Oid roleid) /* - * Does member have the privileges of role (directly or indirectly)? + * Get a list of roles that the specified roleid has the privileges of * * 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. + * a list of memberships. Hence, the result is only guaranteed good until + * the next call of roles_has_privs_of()! + * + * For the benefit of select_best_grantor, the result is defined to be + * in breadth-first order, ie, closer relationships earlier. */ -bool -has_privs_of_role(Oid member, Oid role) +static List * +roles_has_privs_of(Oid roleid) { 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); + /* If cache is already valid, just return the list */ + if (OidIsValid(cached_privs_role) && cached_privs_role == roleid) + return cached_privs_roles; /* - * Find all the roles that member is a member of, + * Find all the roles that roleid is a member of, * including multi-level recursion. The role itself will always * be the first element of the resulting list. * @@ -2818,7 +2873,7 @@ has_privs_of_role(Oid member, Oid role) * 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); + roles_list = list_make1_oid(roleid); foreach(l, roles_list) { @@ -2863,43 +2918,36 @@ has_privs_of_role(Oid member, Oid role) cached_privs_role = InvalidOid; /* just paranoia */ list_free(cached_privs_roles); cached_privs_roles = new_cached_privs_roles; - cached_privs_role = member; + cached_privs_role = roleid; /* And now we can return the answer */ - return list_member_oid(cached_privs_roles, role); + return cached_privs_roles; } /* - * Is member a member of role (directly or indirectly)? + * Get a list of roles that the specified roleid is a member of * * This is defined to recurse through roles regardless of rolinherit. * * Since indirect membership testing is relatively expensive, we cache - * a list of memberships. + * a list of memberships. Hence, the result is only guaranteed good until + * the next call of roles_is_member_of()! */ -bool -is_member_of_role(Oid member, Oid role) +static List * +roles_is_member_of(Oid roleid) { 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); + /* If cache is already valid, just return the list */ + if (OidIsValid(cached_member_role) && cached_member_role == roleid) + return cached_membership_roles; /* - * Find all the roles that member is a member of, + * Find all the roles that roleid is a member of, * including multi-level recursion. The role itself will always * be the first element of the resulting list. * @@ -2909,7 +2957,7 @@ is_member_of_role(Oid member, Oid role) * 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); + roles_list = list_make1_oid(roleid); foreach(l, roles_list) { @@ -2950,10 +2998,60 @@ is_member_of_role(Oid member, Oid role) cached_member_role = InvalidOid; /* just paranoia */ list_free(cached_membership_roles); cached_membership_roles = new_cached_membership_roles; - cached_member_role = member; + cached_member_role = roleid; /* And now we can return the answer */ - return list_member_oid(cached_membership_roles, role); + return cached_membership_roles; +} + + +/* + * 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. + */ +bool +has_privs_of_role(Oid member, Oid role) +{ + /* 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; + + /* + * Find all the roles that member has the privileges of, including + * multi-level recursion, then see if target role is any one of them. + */ + return list_member_oid(roles_has_privs_of(member), role); +} + + +/* + * Is member a member of role (directly or indirectly)? + * + * This is defined to recurse through roles regardless of rolinherit. + */ +bool +is_member_of_role(Oid member, Oid role) +{ + /* 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; + + /* + * Find all the roles that member is a member of, including multi-level + * recursion, then see if target role is any one of them. + */ + return list_member_oid(roles_is_member_of(member), role); } /* @@ -3034,3 +3132,113 @@ is_admin_of_role(Oid member, Oid role) return result; } + + +/* does what it says ... */ +static int +count_one_bits(AclMode mask) +{ + int nbits = 0; + + /* this code relies on AclMode being an unsigned type */ + while (mask) + { + if (mask & 1) + nbits++; + mask >>= 1; + } + return nbits; +} + + +/* + * Select the effective grantor ID for a GRANT or REVOKE operation. + * + * The grantor must always be either the object owner or some role that has + * been explicitly granted grant options. This ensures that all granted + * privileges appear to flow from the object owner, and there are never + * multiple "original sources" of a privilege. Therefore, if the would-be + * grantor is a member of a role that has the needed grant options, we have + * to do the grant as that role instead. + * + * It is possible that the would-be grantor is a member of several roles + * that have different subsets of the desired grant options, but no one + * role has 'em all. In this case we pick a role with the largest number + * of desired options. Ties are broken in favor of closer ancestors. + * + * roleId: the role attempting to do the GRANT/REVOKE + * privileges: the privileges to be granted/revoked + * acl: the ACL of the object in question + * ownerId: the role owning the object in question + * *grantorId: receives the OID of the role to do the grant as + * *grantOptions: receives the grant options actually held by grantorId + * + * If no grant options exist, we set grantorId to roleId, grantOptions to 0. + */ +void +select_best_grantor(Oid roleId, AclMode privileges, + const Acl *acl, Oid ownerId, + Oid *grantorId, AclMode *grantOptions) +{ + AclMode needed_goptions = ACL_GRANT_OPTION_FOR(privileges); + List *roles_list; + int nrights; + ListCell *l; + + /* + * The object owner is always treated as having all grant options, + * so if roleId is the owner it's easy. Also, if roleId is a superuser + * it's easy: superusers are implicitly members of every role, so they + * act as the object owner. + */ + if (roleId == ownerId || superuser_arg(roleId)) + { + *grantorId = ownerId; + *grantOptions = needed_goptions; + return; + } + + /* + * Otherwise we have to do a careful search to see if roleId has the + * privileges of any suitable role. Note: we can hang onto the result + * of roles_has_privs_of() throughout this loop, because aclmask_direct() + * doesn't query any role memberships. + */ + roles_list = roles_has_privs_of(roleId); + + /* initialize candidate result as default */ + *grantorId = roleId; + *grantOptions = ACL_NO_RIGHTS; + nrights = 0; + + foreach(l, roles_list) + { + Oid otherrole = lfirst_oid(l); + AclMode otherprivs; + + otherprivs = aclmask_direct(acl, otherrole, ownerId, + needed_goptions, ACLMASK_ALL); + if (otherprivs == needed_goptions) + { + /* Found a suitable grantor */ + *grantorId = otherrole; + *grantOptions = otherprivs; + return; + } + /* + * If it has just some of the needed privileges, remember best + * candidate. + */ + if (otherprivs != ACL_NO_RIGHTS) + { + int nnewrights = count_one_bits(otherprivs); + + if (nnewrights > nrights) + { + *grantorId = otherrole; + *grantOptions = otherprivs; + nrights = nnewrights; + } + } + } +} diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 1f21600909..9fd551f28c 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.83 2005/07/26 16:38:29 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.84 2005/10/10 18:49:04 tgl Exp $ * * NOTES * An ACL array is simply an array of AclItems, representing the union @@ -215,6 +215,10 @@ 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); +extern void select_best_grantor(Oid roleId, AclMode privileges, + const Acl *acl, Oid ownerId, + Oid *grantorId, AclMode *grantOptions); + extern void initialize_acl(void); /*