Fix the problem of GRANTs creating "dangling" privileges not directly
authorTom Lane
Mon, 10 Oct 2005 18:49:04 +0000 (18:49 +0000)
committerTom Lane
Mon, 10 Oct 2005 18:49:04 +0000 (18:49 +0000)
traceable to grant options.  As per my earlier proposal, a GRANT made by
a role member has to be recorded as being granted by the role that actually
holds the grant option, and not the member.

src/backend/catalog/aclchk.c
src/backend/utils/adt/acl.c
src/include/utils/acl.h

index 97da34243c9897a47db00ee26d055129795a9bae..689a2ff819699e78d00da2bcf6434d418589d825 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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.
@@ -70,32 +70,6 @@ dumpacl(Acl *acl)
 #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
@@ -243,7 +217,7 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
        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;
@@ -282,28 +256,36 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
                     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);
        }
 
        /*
@@ -314,7 +296,7 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
         * 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)
@@ -339,17 +321,8 @@ ExecuteGrantStmt_Relation(GrantStmt *stmt)
        }
 
        /*
-        * 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.
         */
@@ -434,7 +407,7 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
        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;
@@ -462,28 +435,36 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
                     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);
        }
 
        /*
@@ -494,7 +475,7 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
         * 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)
@@ -519,17 +500,8 @@ ExecuteGrantStmt_Database(GrantStmt *stmt)
        }
 
        /*
-        * 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.
         */
@@ -613,7 +585,7 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
        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;
@@ -638,28 +610,36 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
            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);
        }
 
        /*
@@ -670,7 +650,7 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
         * 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)
@@ -695,17 +675,8 @@ ExecuteGrantStmt_Function(GrantStmt *stmt)
        }
 
        /*
-        * 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.
         */
@@ -788,7 +759,7 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
        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;
@@ -820,31 +791,38 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
               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);
        }
 
        /*
@@ -855,7 +833,7 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
         * 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)
@@ -880,17 +858,8 @@ ExecuteGrantStmt_Language(GrantStmt *stmt)
        }
 
        /*
-        * 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.
         */
@@ -973,7 +942,7 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
        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;
@@ -998,28 +967,37 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
                     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);
        }
 
        /*
@@ -1030,7 +1008,7 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
         * 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)
@@ -1055,18 +1033,8 @@ ExecuteGrantStmt_Namespace(GrantStmt *stmt)
        }
 
        /*
-        * 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.
         */
@@ -1151,7 +1119,7 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt)
        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;
@@ -1179,28 +1147,36 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt)
                   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);
        }
 
        /*
@@ -1211,7 +1187,7 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt)
         * 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)
@@ -1236,17 +1212,8 @@ ExecuteGrantStmt_Tablespace(GrantStmt *stmt)
        }
 
        /*
-        * 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.
         */
index bc3a32a0d76e57b3d4a98103bf574d5d1736b0da..9909640ad4a2d938fe484972b9a25d3023eaae16 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * 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 $
  *
  *-------------------------------------------------------------------------
  */
@@ -1070,6 +1070,65 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
 }
 
 
+/*
+ * 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.
@@ -2778,37 +2837,33 @@ has_rolinherit(Oid roleid)
 
 
 /*
- * 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.
     *
@@ -2818,7 +2873,7 @@ has_privs_of_role(Oid member, Oid role)
     * 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)
    {
@@ -2863,43 +2918,36 @@ has_privs_of_role(Oid member, Oid role)
    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.
     *
@@ -2909,7 +2957,7 @@ is_member_of_role(Oid member, Oid role)
     * 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)
    {
@@ -2950,10 +2998,60 @@ is_member_of_role(Oid member, Oid role)
    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);
 }
 
 /*
@@ -3034,3 +3132,113 @@ is_admin_of_role(Oid member, Oid 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;
+           }
+       }
+   }
+}
index 1f216009098fdfd30b14e2d9bf7afa618a60c19a..9fd551f28cac30de4297356eba9c201496f14c46 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.83 2005/07/26 16:38:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/acl.h,v 1.84 2005/10/10 18:49:04 tgl Exp $
  *
  * NOTES
  *   An ACL array is simply an array of AclItems, representing the union
@@ -215,6 +215,10 @@ extern bool is_member_of_role(Oid member, Oid role);
 extern bool is_admin_of_role(Oid member, Oid role);
 extern void check_is_member_of_role(Oid member, Oid role);
 
+extern void select_best_grantor(Oid roleId, AclMode privileges,
+                               const Acl *acl, Oid ownerId,
+                               Oid *grantorId, AclMode *grantOptions);
+
 extern void initialize_acl(void);
 
 /*