*
*
* 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.
#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
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;
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);
}
/*
* 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)
}
/*
- * 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.
*/
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;
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);
}
/*
* 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)
}
/*
- * 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.
*/
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;
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);
}
/*
* 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)
}
/*
- * 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.
*/
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;
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);
}
/*
* 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)
}
/*
- * 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.
*/
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;
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);
}
/*
* 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)
}
/*
- * 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.
*/
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;
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);
}
/*
* 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)
}
/*
- * 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.
*/
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
}
+/*
+ * 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.
/*
- * 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.
*
* 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)
{
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.
*
* 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)
{
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);
}
/*
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;
+ }
+ }
+ }
+}