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.c293
1 files changed, 177 insertions, 116 deletions
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 2fe7fd39bca..5b65099696e 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.116 2005/06/28 19:51:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/acl.c,v 1.117 2005/06/29 20:34:14 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -32,18 +32,23 @@
#include "utils/syscache.h"
-#define ACL_IDTYPE_ROLE_KEYWORD "role"
-
-/* The rolmemcache is a possibly-empty list of role OIDs.
- * rolmemRole is the Role for which the cache was generated.
- * In the event of a Role change the cache will be regenerated.
+/*
+ * We frequently need to test whether a given role is a member of some other
+ * role. In most of these tests the "given role" is the same, namely the
+ * active current user. So we can optimize it by keeping a cached list of
+ * 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?
+ *
+ * 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.
*/
-static List *rolmemcache = NIL;
-static Oid rolmemRole = InvalidOid;
+static Oid cached_role = InvalidOid;
+static List *cached_memberships = NIL;
-/* rolmemcache and rolmemRole only valid when
- * rolmemcacheValid is true */
-static bool rolmemcacheValid = false;
static const char *getid(const char *s, char *n);
static void putid(char *p, const char *s);
@@ -70,8 +75,7 @@ static AclMode convert_schema_priv_string(text *priv_type_text);
static Oid convert_tablespace_name(text *tablespacename);
static AclMode convert_tablespace_priv_string(text *priv_type_text);
-static void RolMemCacheCallback(Datum arg, Oid relid);
-static void recomputeRolMemCache(Oid roleid);
+static void RoleMembershipCacheCallback(Datum arg, Oid relid);
/*
@@ -134,7 +138,7 @@ getid(const char *s, char *n)
}
/*
- * Write a user or group Name at *p, adding double quotes if needed.
+ * Write a role name at *p, adding double quotes if needed.
* There must be at least (2*NAMEDATALEN)+2 bytes available at *p.
* This needs to be kept in sync with copyAclUserName in pg_dump/dumputils.c
*/
@@ -175,6 +179,9 @@ putid(char *p, const char *s)
* between the optional id type keyword (group|user) and the actual
* ACL specification.
*
+ * The group|user decoration is unnecessary in the roles world,
+ * but we still accept it for backward compatibility.
+ *
* This routine is called by the parser as well as aclitemin(), hence
* the added generality.
*
@@ -202,17 +209,17 @@ aclparse(const char *s, AclItem *aip)
if (*s != '=')
{
/* we just read a keyword, not a name */
- if (strcmp(name, ACL_IDTYPE_ROLE_KEYWORD) != 0)
+ if (strcmp(name, "group") != 0 && strcmp(name, "user") != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("unrecognized key word: \"%s\"", name),
- errhint("ACL key word must be \"role\".")));
+ errhint("ACL key word must be \"group\" or \"user\".")));
s = getid(s, name); /* move s to the name beyond the keyword */
if (name[0] == '\0')
ereport(ERROR,
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
errmsg("missing name"),
- errhint("A name must follow the \"role\" key word.")));
+ errhint("A name must follow the \"group\" or \"user\" key word.")));
}
if (*s != '=')
@@ -378,7 +385,7 @@ aclitemout(PG_FUNCTION_ARGS)
HeapTuple htup;
unsigned i;
- out = palloc(strlen("group =/") +
+ out = palloc(strlen("=/") +
2 * N_ACL_RIGHTS +
2 * (2 * NAMEDATALEN + 2) +
1);
@@ -432,10 +439,6 @@ aclitemout(PG_FUNCTION_ARGS)
sprintf(p, "%u", aip->ai_grantor);
}
- while (*p)
- ++p;
- *p = '\0';
-
PG_RETURN_CSTRING(out);
}
@@ -968,13 +971,13 @@ restart:
*
* To determine exactly which of a set of privileges are held:
* heldprivs = aclmask(acl, roleid, ownerId, privs, ACLMASK_ALL);
- *
*/
AclMode
aclmask(const Acl *acl, Oid roleid, Oid ownerId,
AclMode mask, AclMaskHow how)
{
AclMode result;
+ AclMode remaining;
AclItem *aidat;
int i,
num;
@@ -993,7 +996,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 (is_member_of_role(roleid, ownerId))
{
result = mask & ACLITEM_ALL_GOPTION_BITS;
if (result == mask)
@@ -1004,15 +1007,14 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
aidat = ACL_DAT(acl);
/*
- * Check privileges granted directly to role, indirectly
- * via role membership or to public
+ * Check privileges granted directly to user or to public
*/
for (i = 0; i < num; i++)
{
AclItem *aidata = &aidat[i];
if (aidata->ai_grantee == ACL_ID_PUBLIC ||
- is_member_of_role(roleid, aidata->ai_grantee))
+ aidata->ai_grantee == roleid)
{
result |= (aidata->ai_privs & mask);
if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
@@ -1020,24 +1022,33 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
}
}
- return result;
-}
-
+ /*
+ * Check privileges granted indirectly via roles.
+ * We do this in a separate pass to minimize expensive indirect
+ * membership tests. In particular, it's worth testing whether
+ * a given ACL entry grants any privileges still of interest before
+ * we perform the is_member test.
+ */
+ remaining = (mask & ~result);
+ for (i = 0; i < num; i++)
+ {
+ AclItem *aidata = &aidat[i];
-/*
- * Is member a member of role?
- * relmemcache includes the role itself too
- */
-bool
-is_member_of_role(Oid member, Oid role)
-{
- /* Fast path for simple case */
- if (member == role)
- return true;
+ if (aidata->ai_grantee == ACL_ID_PUBLIC ||
+ aidata->ai_grantee == roleid)
+ continue; /* already checked it */
- recomputeRolMemCache(member);
+ if ((aidata->ai_privs & remaining) &&
+ is_member_of_role(roleid, aidata->ai_grantee))
+ {
+ result |= (aidata->ai_privs & mask);
+ if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0))
+ return result;
+ remaining = (mask & ~result);
+ }
+ }
- return list_member_oid(rolmemcache, role);
+ return result;
}
@@ -1092,20 +1103,20 @@ makeaclitem(PG_FUNCTION_ARGS)
Oid grantor = PG_GETARG_OID(1);
text *privtext = PG_GETARG_TEXT_P(2);
bool goption = PG_GETARG_BOOL(3);
- AclItem *aclitem;
+ AclItem *result;
AclMode priv;
priv = convert_priv_string(privtext);
- aclitem = (AclItem *) palloc(sizeof(AclItem));
+ result = (AclItem *) palloc(sizeof(AclItem));
- aclitem->ai_grantee = grantee;
- aclitem->ai_grantor = grantor;
+ result->ai_grantee = grantee;
+ result->ai_grantor = grantor;
- ACLITEM_SET_PRIVS_GOPTIONS(*aclitem, priv,
+ ACLITEM_SET_PRIVS_GOPTIONS(*result, priv,
(goption ? priv : ACL_NO_RIGHTS));
- PG_RETURN_ACLITEM_P(aclitem);
+ PG_RETURN_ACLITEM_P(result);
}
static AclMode
@@ -1175,7 +1186,6 @@ has_table_privilege_name_name(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*rolename));
-
tableoid = convert_table_name(tablename);
mode = convert_table_priv_string(priv_type_text);
@@ -1225,7 +1235,6 @@ has_table_privilege_name_id(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
mode = convert_table_priv_string(priv_type_text);
aclresult = pg_class_aclcheck(tableoid, roleid, mode);
@@ -1401,7 +1410,6 @@ has_database_privilege_name_name(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
databaseoid = convert_database_name(databasename);
mode = convert_database_priv_string(priv_type_text);
@@ -1451,7 +1459,6 @@ has_database_privilege_name_id(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
mode = convert_database_priv_string(priv_type_text);
aclresult = pg_database_aclcheck(databaseoid, roleid, mode);
@@ -1615,7 +1622,6 @@ has_function_privilege_name_name(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
functionoid = convert_function_name(functionname);
mode = convert_function_priv_string(priv_type_text);
@@ -1665,7 +1671,6 @@ has_function_privilege_name_id(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
mode = convert_function_priv_string(priv_type_text);
aclresult = pg_proc_aclcheck(functionoid, roleid, mode);
@@ -1821,7 +1826,6 @@ has_language_privilege_name_name(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
languageoid = convert_language_name(languagename);
mode = convert_language_priv_string(priv_type_text);
@@ -1871,7 +1875,6 @@ has_language_privilege_name_id(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
mode = convert_language_priv_string(priv_type_text);
aclresult = pg_language_aclcheck(languageoid, roleid, mode);
@@ -2027,7 +2030,6 @@ has_schema_privilege_name_name(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
schemaoid = convert_schema_name(schemaname);
mode = convert_schema_priv_string(priv_type_text);
@@ -2077,7 +2079,6 @@ has_schema_privilege_name_id(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
mode = convert_schema_priv_string(priv_type_text);
aclresult = pg_namespace_aclcheck(schemaoid, roleid, mode);
@@ -2237,7 +2238,6 @@ has_tablespace_privilege_name_name(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
tablespaceoid = convert_tablespace_name(tablespacename);
mode = convert_tablespace_priv_string(priv_type_text);
@@ -2287,7 +2287,6 @@ has_tablespace_privilege_name_id(PG_FUNCTION_ARGS)
AclResult aclresult;
roleid = get_roleid_checked(NameStr(*username));
-
mode = convert_tablespace_priv_string(priv_type_text);
aclresult = pg_tablespace_aclcheck(tablespaceoid, roleid, mode);
@@ -2413,6 +2412,9 @@ convert_tablespace_priv_string(text *priv_type_text)
return ACL_NO_RIGHTS; /* keep compiler quiet */
}
+/*
+ * initialization function (called by InitPostgres)
+ */
void
initialize_acl(void)
{
@@ -2423,99 +2425,158 @@ initialize_acl(void)
* invalidation of pg_auth_members rows
*/
CacheRegisterSyscacheCallback(AUTHMEMROLEMEM,
- RolMemCacheCallback,
+ RoleMembershipCacheCallback,
(Datum) 0);
-
- /* Force role/member cache to be recomputed on next use */
- rolmemcacheValid = false;
}
}
/*
- * RolMemCacheCallback
+ * RoleMembershipCacheCallback
* Syscache inval callback function
*/
static void
-RolMemCacheCallback(Datum arg, Oid relid)
+RoleMembershipCacheCallback(Datum arg, Oid relid)
{
- /* Force role/member cache to be recomputed on next use */
- rolmemcacheValid = false;
+ /* Force membership cache to be recomputed on next use */
+ cached_role = InvalidOid;
}
-/*
- * recomputeRolMemCache - recompute the role/member cache if needed
+/*
+ * Is member a member of role (directly or indirectly)?
+ *
+ * Since indirect membership testing is relatively expensive, we cache
+ * a list of memberships.
*/
-static void
-recomputeRolMemCache(Oid roleid)
-{
- int i;
- Oid memberOid;
- List *roles_list_hunt = NIL;
- List *roles_list = NIL;
- List *newrolmemcache;
- CatCList *memlist;
+bool
+is_member_of_role(Oid member, Oid role)
+{
+ List *roles_list;
+ ListCell *l;
+ List *new_cached_memberships;
MemoryContext oldctx;
- /* Do nothing if rolmemcache is already valid */
- if (rolmemcacheValid && rolmemRole == roleid)
- return;
+ /* Fast path for simple case */
+ if (member == role)
+ return true;
- if (rolmemRole != roleid)
- rolmemcacheValid = false;
+ /* If cache is already valid, just use the list */
+ if (OidIsValid(cached_role) && cached_role == member)
+ return list_member_oid(cached_memberships, role);
/*
- * Find all the roles which this role is a member of,
- * including multi-level recursion
+ * 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);
- /*
- * Include the current role itself to simplify checks
- * later on, also should be at the head so lookup should
- * be fast.
- */
- roles_list = lappend_oid(roles_list, roleid);
- roles_list_hunt = lappend_oid(roles_list_hunt, roleid);
-
- while (roles_list_hunt)
+ foreach(l, roles_list)
{
- memberOid = linitial_oid(roles_list_hunt);
+ Oid memberid = lfirst_oid(l);
+ CatCList *memlist;
+ int i;
+
+ /* Find roles that memberid is directly a member of */
memlist = SearchSysCacheList(AUTHMEMMEMROLE, 1,
- ObjectIdGetDatum(memberOid),
+ ObjectIdGetDatum(memberid),
0, 0, 0);
- for (i = 0; i < memlist->n_members; i++) {
- HeapTuple roletup = &memlist->members[i]->tuple;
- Form_pg_auth_members rolemem = (Form_pg_auth_members) GETSTRUCT(roletup);
-
- if (!list_member_oid(roles_list,rolemem->roleid)) {
- roles_list = lappend_oid(roles_list,rolemem->roleid);
- roles_list_hunt = lappend_oid(roles_list_hunt,rolemem->roleid);
- }
+ 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);
}
- roles_list_hunt = list_delete_oid(roles_list_hunt, memberOid);
ReleaseSysCacheList(memlist);
}
/*
- * Now that we've built the list of role Oids this
- * role is a member of, save it in permanent storage
+ * Copy the completed list into TopMemoryContext so it will persist.
*/
oldctx = MemoryContextSwitchTo(TopMemoryContext);
- newrolmemcache = list_copy(roles_list);
+ new_cached_memberships = list_copy(roles_list);
MemoryContextSwitchTo(oldctx);
+ list_free(roles_list);
/*
* Now safe to assign to state variable
*/
- list_free(rolmemcache);
- rolmemcache = newrolmemcache;
+ cached_role = InvalidOid; /* just paranoia */
+ list_free(cached_memberships);
+ cached_memberships = new_cached_memberships;
+ cached_role = member;
- /*
- * Mark as valid
+ /* And now we can return the answer */
+ return list_member_oid(cached_memberships, role);
+}
+
+
+/*
+ * Is member an admin of role (directly or indirectly)? That is, is it
+ * a member WITH ADMIN OPTION?
+ *
+ * We could cache the result as for is_member_of_role, but currently this
+ * is not used in any performance-critical paths, so we don't.
+ */
+bool
+is_admin_of_role(Oid member, Oid role)
+{
+ bool result = false;
+ List *roles_list;
+ ListCell *l;
+
+ /*
+ * Find all the roles that member is a member of,
+ * including multi-level recursion. We build a list in the same way
+ * that is_member_of_role does to track visited and unvisited roles.
*/
- rolmemRole = roleid;
- rolmemcacheValid = true;
+ roles_list = list_make1_oid(member);
+
+ foreach(l, roles_list)
+ {
+ Oid memberid = lfirst_oid(l);
+ CatCList *memlist;
+ int i;
+
+ /* 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;
+
+ if (otherid == role &&
+ ((Form_pg_auth_members) GETSTRUCT(tup))->admin_option)
+ {
+ /* Found what we came for, so can stop searching */
+ result = true;
+ break;
+ }
+
+ if (!list_member_oid(roles_list, otherid))
+ roles_list = lappend_oid(roles_list, otherid);
+ }
+ ReleaseSysCacheList(memlist);
+ if (result)
+ break;
+ }
- /* Clean up */
list_free(roles_list);
+
+ return result;
}