aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/utils/adt/acl.c')
-rw-r--r--src/backend/utils/adt/acl.c203
1 files changed, 170 insertions, 33 deletions
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);
}
/*