]> granicus.if.org Git - postgresql/commitdiff
Refactor low-level aclcheck code to provide useful interfaces for multi-bit
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 11 May 2004 17:36:13 +0000 (17:36 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 11 May 2004 17:36:13 +0000 (17:36 +0000)
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.

src/backend/catalog/aclchk.c
src/backend/executor/execMain.c
src/include/utils/acl.h

index e40b5b310f3ff36fa26ac44b6e7a54cbd48d3a1d..8e27f10d39a8226f70b57ae67b6eccf77ec42f49 100644 (file)
@@ -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).
  */
index 13b38d3752994d3dd794625571774b36b0371f12..e8ed4ce5cc46f702a438610d2332d659eb15dc73 100644 (file)
@@ -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));
 }
 
 /*
index 4fcec230ccbe339332f2ac6a4085fb73190b09f1..59131122cbc0d057746dec45d5f07e420dd825d9 100644 (file)
@@ -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);