]> granicus.if.org Git - postgresql/commitdiff
Fix the problem of GRANTs creating "dangling" privileges not directly
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 10 Oct 2005 18:49:04 +0000 (18:49 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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);
 
 /*