From: Tom Lane Date: Tue, 11 May 2004 17:36:13 +0000 (+0000) Subject: Refactor low-level aclcheck code to provide useful interfaces for multi-bit X-Git-Tag: REL8_0_0BETA1~661 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=5ddbe904c0f6d0dc67597b43b7823cc54300b638;p=postgresql Refactor low-level aclcheck code to provide useful interfaces for multi-bit permissions tests in about the same amount of code as before. Exactly what the GRANT/REVOKE code ought to be doing is still up for debate, but this should be helpful in any case, and it already solves an efficiency problem in executor startup. --- diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index e40b5b310f..8e27f10d39 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.97 2004/01/14 03:44:53 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/aclchk.c,v 1.98 2004/05/11 17:36:12 tgl Exp $ * * NOTES * See acl.h. @@ -48,7 +48,8 @@ static void ExecuteGrantStmt_Namespace(GrantStmt *stmt); static const char *privilege_to_string(AclMode privilege); -static AclResult aclcheck(Acl *acl, AclId userid, AclMode mode); +static AclMode aclmask(Acl *acl, AclId userid, + AclMode mask, AclMaskHow how); #ifdef ACLDEBUG @@ -869,15 +870,33 @@ in_group(AclId uid, AclId gid) /* - * aclcheck + * aclmask --- compute bitmask of all privileges held by userid. * - * Returns ACLCHECK_OK if the 'userid' has ACL entries in 'acl' to - * satisfy any one of the requirements of 'mode'. Returns an - * appropriate ACLCHECK_* error code otherwise. + * When 'how' = ACLMASK_ALL, this simply returns the privilege bits + * held by the given userid according to the given ACL list, ANDed + * with 'mask'. (The point of passing 'mask' is to let the routine + * exit early if all privileges of interest have been found.) + * + * When 'how' = ACLMASK_ANY, returns as soon as any bit in the mask + * is known true. (This lets us exit soonest in cases where the + * caller is only going to test for zero or nonzero result.) + * + * Usage patterns: + * + * To see if any of a set of privileges are held: + * if (aclmask(acl, userid, privs, ACLMASK_ANY) != 0) + * + * To see if all of a set of privileges are held: + * if (aclmask(acl, userid, privs, ACLMASK_ALL) == privs) + * + * To determine exactly which of a set of privileges are held: + * heldprivs = aclmask(acl, userid, privs, ACLMASK_ALL); */ -static AclResult -aclcheck(Acl *acl, AclId userid, AclMode mode) +static AclMode +aclmask(Acl *acl, AclId userid, AclMode mask, AclMaskHow how) { + AclMode result; + AclMode remaining; AclItem *aidat; int i, num; @@ -887,38 +906,55 @@ aclcheck(Acl *acl, AclId userid, AclMode mode) * appropriate default */ if (acl == NULL) - { elog(ERROR, "null ACL"); - return ACLCHECK_NO_PRIV; - } + + /* Quick exit for mask == 0 */ + if (mask == 0) + return 0; num = ACL_NUM(acl); aidat = ACL_DAT(acl); + result = 0; + /* - * See if privilege is granted directly to user or to public + * Check privileges granted directly to user or to public */ for (i = 0; i < num; i++) - if (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_WORLD - || (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_UID - && aidat[i].ai_grantee == userid)) + { + AclItem *aidata = &aidat[i]; + + if (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_WORLD + || (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_UID + && aidata->ai_grantee == userid)) { - if (aidat[i].ai_privs & mode) - return ACLCHECK_OK; + result |= (aidata->ai_privs & mask); + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; } + } /* - * See if he has the permission via any group (do this in a separate - * pass to avoid expensive(?) lookups in pg_group) + * Check privileges granted via groups. We do this in a separate + * pass to minimize expensive lookups in pg_group. */ + remaining = (mask & ~result); for (i = 0; i < num; i++) - if (ACLITEM_GET_IDTYPE(aidat[i]) == ACL_IDTYPE_GID - && aidat[i].ai_privs & mode - && in_group(userid, aidat[i].ai_grantee)) - return ACLCHECK_OK; + { + AclItem *aidata = &aidat[i]; + + if (ACLITEM_GET_IDTYPE(*aidata) == ACL_IDTYPE_GID + && (aidata->ai_privs & remaining) + && in_group(userid, aidata->ai_grantee)) + { + result |= (aidata->ai_privs & mask); + if ((how == ACLMASK_ALL) ? (result == mask) : (result != 0)) + return result; + remaining = (mask & ~result); + } + } - /* If here, doesn't have the privilege. */ - return ACLCHECK_NO_PRIV; + return result; } @@ -1001,17 +1037,20 @@ aclcheck_error(AclResult aclerr, AclObjectKind objectkind, /* - * Exported routine for checking a user's access privileges to a table + * Exported routine for examining a user's privileges for a table + * + * See aclmask() for a description of the API. * * Note: we give lookup failure the full ereport treatment because the * has_table_privilege() family of functions allow users to pass * any random OID to this function. Likewise for the sibling functions * below. */ -AclResult -pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) +AclMode +pg_class_aclmask(Oid table_oid, AclId userid, + AclMode mask, AclMaskHow how) { - AclResult result; + AclMode result; bool usesuper, usecatupd; HeapTuple tuple; @@ -1046,7 +1085,8 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("relation with OID %u does not exist", table_oid))); + errmsg("relation with OID %u does not exist", + table_oid))); classForm = (Form_pg_class) GETSTRUCT(tuple); /* @@ -1058,7 +1098,7 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) * be protected in this way. Assume the view rules can take care * of themselves. */ - if ((mode & (ACL_INSERT | ACL_UPDATE | ACL_DELETE)) && + if ((mask & (ACL_INSERT | ACL_UPDATE | ACL_DELETE)) && IsSystemClass(classForm) && classForm->relkind != RELKIND_VIEW && !usecatupd && @@ -1067,8 +1107,7 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) #ifdef ACLDEBUG elog(DEBUG2, "permission denied for system catalog update"); #endif - ReleaseSysCache(tuple); - return ACLCHECK_NO_PRIV; + mask &= ~(ACL_INSERT | ACL_UPDATE | ACL_DELETE); } /* @@ -1080,7 +1119,7 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) elog(DEBUG2, "%u is superuser, home free", userid); #endif ReleaseSysCache(tuple); - return ACLCHECK_OK; + return mask; } /* @@ -1102,7 +1141,7 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, mode); + result = aclmask(acl, userid, mask, how); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1114,12 +1153,13 @@ pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) } /* - * Exported routine for checking a user's access privileges to a database + * Exported routine for examining a user's privileges for a database */ -AclResult -pg_database_aclcheck(Oid db_oid, AclId userid, AclMode mode) +AclMode +pg_database_aclmask(Oid db_oid, AclId userid, + AclMode mask, AclMaskHow how) { - AclResult result; + AclMode result; Relation pg_database; ScanKeyData entry[1]; HeapScanDesc scan; @@ -1130,7 +1170,7 @@ pg_database_aclcheck(Oid db_oid, AclId userid, AclMode mode) /* Superusers bypass all permission checking. */ if (superuser_arg(userid)) - return ACLCHECK_OK; + return mask; /* * Get the database's ACL from pg_database @@ -1167,7 +1207,7 @@ pg_database_aclcheck(Oid db_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, mode); + result = aclmask(acl, userid, mask, how); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1180,12 +1220,13 @@ pg_database_aclcheck(Oid db_oid, AclId userid, AclMode mode) } /* - * Exported routine for checking a user's access privileges to a function + * Exported routine for examining a user's privileges for a function */ -AclResult -pg_proc_aclcheck(Oid proc_oid, AclId userid, AclMode mode) +AclMode +pg_proc_aclmask(Oid proc_oid, AclId userid, + AclMode mask, AclMaskHow how) { - AclResult result; + AclMode result; HeapTuple tuple; Datum aclDatum; bool isNull; @@ -1193,7 +1234,7 @@ pg_proc_aclcheck(Oid proc_oid, AclId userid, AclMode mode) /* Superusers bypass all permission checking. */ if (superuser_arg(userid)) - return ACLCHECK_OK; + return mask; /* * Get the function's ACL from pg_proc @@ -1223,7 +1264,7 @@ pg_proc_aclcheck(Oid proc_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, mode); + result = aclmask(acl, userid, mask, how); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1235,12 +1276,13 @@ pg_proc_aclcheck(Oid proc_oid, AclId userid, AclMode mode) } /* - * Exported routine for checking a user's access privileges to a language + * Exported routine for examining a user's privileges for a language */ -AclResult -pg_language_aclcheck(Oid lang_oid, AclId userid, AclMode mode) +AclMode +pg_language_aclmask(Oid lang_oid, AclId userid, + AclMode mask, AclMaskHow how) { - AclResult result; + AclMode result; HeapTuple tuple; Datum aclDatum; bool isNull; @@ -1248,7 +1290,7 @@ pg_language_aclcheck(Oid lang_oid, AclId userid, AclMode mode) /* Superusers bypass all permission checking. */ if (superuser_arg(userid)) - return ACLCHECK_OK; + return mask; /* * Get the language's ACL from pg_language @@ -1276,7 +1318,7 @@ pg_language_aclcheck(Oid lang_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, mode); + result = aclmask(acl, userid, mask, how); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1288,12 +1330,13 @@ pg_language_aclcheck(Oid lang_oid, AclId userid, AclMode mode) } /* - * Exported routine for checking a user's access privileges to a namespace + * Exported routine for examining a user's privileges for a namespace */ -AclResult -pg_namespace_aclcheck(Oid nsp_oid, AclId userid, AclMode mode) +AclMode +pg_namespace_aclmask(Oid nsp_oid, AclId userid, + AclMode mask, AclMaskHow how) { - AclResult result; + AclMode result; HeapTuple tuple; Datum aclDatum; bool isNull; @@ -1304,11 +1347,11 @@ pg_namespace_aclcheck(Oid nsp_oid, AclId userid, AclMode mode) * we have all grantable privileges on it. */ if (isTempNamespace(nsp_oid)) - return ACLCHECK_OK; + return mask; /* Superusers bypass all permission checking. */ if (superuser_arg(userid)) - return ACLCHECK_OK; + return mask; /* * Get the schema's ACL from pg_namespace @@ -1338,7 +1381,7 @@ pg_namespace_aclcheck(Oid nsp_oid, AclId userid, AclMode mode) acl = DatumGetAclP(aclDatum); } - result = aclcheck(acl, userid, mode); + result = aclmask(acl, userid, mask, how); /* if we have a detoasted copy, free it */ if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) @@ -1350,6 +1393,71 @@ pg_namespace_aclcheck(Oid nsp_oid, AclId userid, AclMode mode) } +/* + * Exported routine for checking a user's access privileges to a table + * + * Returns ACLCHECK_OK if the user has any of the privileges identified by + * 'mode'; otherwise returns a suitable error code (in practice, always + * ACLCHECK_NO_PRIV). + */ +AclResult +pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode) +{ + if (pg_class_aclmask(table_oid, userid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + +/* + * Exported routine for checking a user's access privileges to a database + */ +AclResult +pg_database_aclcheck(Oid db_oid, AclId userid, AclMode mode) +{ + if (pg_database_aclmask(db_oid, userid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + +/* + * Exported routine for checking a user's access privileges to a function + */ +AclResult +pg_proc_aclcheck(Oid proc_oid, AclId userid, AclMode mode) +{ + if (pg_proc_aclmask(proc_oid, userid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + +/* + * Exported routine for checking a user's access privileges to a language + */ +AclResult +pg_language_aclcheck(Oid lang_oid, AclId userid, AclMode mode) +{ + if (pg_language_aclmask(lang_oid, userid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + +/* + * Exported routine for checking a user's access privileges to a namespace + */ +AclResult +pg_namespace_aclcheck(Oid nsp_oid, AclId userid, AclMode mode) +{ + if (pg_namespace_aclmask(nsp_oid, userid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + + /* * Ownership check for a relation (specified by OID). */ diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 13b38d3752..e8ed4ce5cc 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.230 2004/03/23 19:35:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.231 2004/05/11 17:36:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -412,28 +412,13 @@ ExecCheckRTEPerms(RangeTblEntry *rte) userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); /* - * For each bit in requiredPerms, apply the required check. (We can't - * do this in one aclcheck call because aclcheck treats multiple bits - * as OR semantics, when we want AND.) - * - * We use a well-known cute trick for isolating the rightmost one-bit - * in a nonzero word. See nodes/bitmapset.c for commentary. + * We must have *all* the requiredPerms bits, so use aclmask not + * aclcheck. */ -#define RIGHTMOST_ONE(x) ((int32) (x) & -((int32) (x))) - - while (requiredPerms != 0) - { - AclMode thisPerm; - AclResult aclcheck_result; - - thisPerm = RIGHTMOST_ONE(requiredPerms); - requiredPerms &= ~thisPerm; - - aclcheck_result = pg_class_aclcheck(relOid, userid, thisPerm); - if (aclcheck_result != ACLCHECK_OK) - aclcheck_error(aclcheck_result, ACL_KIND_CLASS, - get_rel_name(relOid)); - } + if (pg_class_aclmask(relOid, userid, requiredPerms, ACLMASK_ALL) + != requiredPerms) + aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, + get_rel_name(relOid)); } /* diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 4fcec230cc..59131122cb 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.68 2004/05/02 13:38:28 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.69 2004/05/11 17:36:13 tgl Exp $ * * NOTES * An ACL array is simply an array of AclItems, representing the union @@ -177,6 +177,12 @@ typedef ArrayType IdList; #define ACL_ALL_RIGHTS_LANGUAGE (ACL_USAGE) #define ACL_ALL_RIGHTS_NAMESPACE (ACL_USAGE|ACL_CREATE) +/* operation codes for pg_*_aclmask */ +typedef enum +{ + ACLMASK_ALL, /* normal case: compute all bits */ + ACLMASK_ANY /* return when result is known nonzero */ +} AclMaskHow; /* result codes for pg_*_aclcheck */ typedef enum @@ -228,6 +234,17 @@ extern void ExecuteGrantStmt(GrantStmt *stmt); extern AclId get_grosysid(char *groname); extern char *get_groname(AclId grosysid); +extern AclMode pg_class_aclmask(Oid table_oid, AclId userid, + AclMode mask, AclMaskHow how); +extern AclMode pg_database_aclmask(Oid db_oid, AclId userid, + AclMode mask, AclMaskHow how); +extern AclMode pg_proc_aclmask(Oid proc_oid, AclId userid, + AclMode mask, AclMaskHow how); +extern AclMode pg_language_aclmask(Oid lang_oid, AclId userid, + AclMode mask, AclMaskHow how); +extern AclMode pg_namespace_aclmask(Oid nsp_oid, AclId userid, + AclMode mask, AclMaskHow how); + extern AclResult pg_class_aclcheck(Oid table_oid, AclId userid, AclMode mode); extern AclResult pg_database_aclcheck(Oid db_oid, AclId userid, AclMode mode); extern AclResult pg_proc_aclcheck(Oid proc_oid, AclId userid, AclMode mode);