Thread

  1. [PATCH v1 1/1] GRANTED BY

    Nathan Bossart <nathan@postgresql.org> — 2025-11-13T15:29:35Z

    ---
     src/backend/catalog/aclchk.c             | 89 +++++++++++++++++-------
     src/backend/utils/adt/acl.c              |  2 +-
     src/include/utils/acl.h                  |  2 +
     src/include/utils/aclchk_internal.h      |  1 +
     src/test/regress/expected/privileges.out |  2 +-
     5 files changed, 68 insertions(+), 28 deletions(-)
    
    diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
    index cd139bd65a6..9ee3ff9e494 100644
    --- a/src/backend/catalog/aclchk.c
    +++ b/src/backend/catalog/aclchk.c
    @@ -397,19 +397,15 @@ ExecuteGrantStmt(GrantStmt *stmt)
     
     	if (stmt->grantor)
     	{
    -		Oid			grantor;
    -
    -		grantor = get_rolespec_oid(stmt->grantor, false);
    -
    -		/*
    -		 * Currently, this clause is only for SQL compatibility, not very
    -		 * interesting otherwise.
    -		 */
    -		if (grantor != GetUserId())
    +		istmt.grantor = get_rolespec_oid(stmt->grantor, false);
    +		if (!has_privs_of_role(GetUserId(), istmt.grantor))
     			ereport(ERROR,
    -					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    -					 errmsg("grantor must be current user")));
    +					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
    +					 errmsg("must inherit privileges of role \"%s\"",
    +							GetUserNameFromId(istmt.grantor, false))));
     	}
    +	else
    +		istmt.grantor = InvalidOid;
     
     	/*
     	 * Turn the regular GrantStmt into the InternalGrant form.
    @@ -1538,6 +1534,7 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid)
     		istmt.all_privs = true;
     		istmt.privileges = ACL_NO_RIGHTS;
     		istmt.col_privs = NIL;
    +		istmt.grantor = InvalidOid;
     		istmt.grantees = list_make1_oid(roleid);
     		istmt.grant_option = false;
     		istmt.behavior = DROP_CASCADE;
    @@ -1694,9 +1691,17 @@ ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname,
     	merged_acl = aclconcat(old_rel_acl, old_acl);
     
     	/* Determine ID to do the grant as, and available grant options */
    -	select_best_grantor(GetUserId(), col_privileges,
    -						merged_acl, ownerId,
    -						&grantorId, &avail_goptions);
    +	if (OidIsValid(istmt->grantor))
    +	{
    +		grantorId = istmt->grantor;
    +		avail_goptions = aclmask_direct(merged_acl, grantorId, ownerId,
    +										ACL_GRANT_OPTION_FOR(col_privileges),
    +										ACLMASK_ALL);
    +	}
    +	else
    +		select_best_grantor(GetUserId(), col_privileges,
    +							merged_acl, ownerId,
    +							&grantorId, &avail_goptions);
     
     	pfree(merged_acl);
     
    @@ -1967,9 +1972,17 @@ ExecGrant_Relation(InternalGrant *istmt)
     			ObjectType	objtype;
     
     			/* Determine ID to do the grant as, and available grant options */
    -			select_best_grantor(GetUserId(), this_privileges,
    -								old_acl, ownerId,
    -								&grantorId, &avail_goptions);
    +			if (OidIsValid(istmt->grantor))
    +			{
    +				grantorId = istmt->grantor;
    +				avail_goptions = aclmask_direct(old_acl, grantorId, ownerId,
    +												ACL_GRANT_OPTION_FOR(this_privileges),
    +												ACLMASK_ALL);
    +			}
    +			else
    +				select_best_grantor(GetUserId(), this_privileges,
    +									old_acl, ownerId,
    +									&grantorId, &avail_goptions);
     
     			switch (pg_class_tuple->relkind)
     			{
    @@ -2182,9 +2195,17 @@ ExecGrant_common(InternalGrant *istmt, Oid classid, AclMode default_privs,
     		}
     
     		/* Determine ID to do the grant as, and available grant options */
    -		select_best_grantor(GetUserId(), istmt->privileges,
    -							old_acl, ownerId,
    -							&grantorId, &avail_goptions);
    +		if (OidIsValid(istmt->grantor))
    +		{
    +			grantorId = istmt->grantor;
    +			avail_goptions = aclmask_direct(old_acl, grantorId, ownerId,
    +											ACL_GRANT_OPTION_FOR(istmt->privileges),
    +											ACLMASK_ALL);
    +		}
    +		else
    +			select_best_grantor(GetUserId(), istmt->privileges,
    +								old_acl, ownerId,
    +								&grantorId, &avail_goptions);
     
     		nameDatum = SysCacheGetAttrNotNull(cacheid, tuple,
     										   get_object_attnum_name(classid));
    @@ -2337,9 +2358,17 @@ ExecGrant_Largeobject(InternalGrant *istmt)
     		}
     
     		/* Determine ID to do the grant as, and available grant options */
    -		select_best_grantor(GetUserId(), istmt->privileges,
    -							old_acl, ownerId,
    -							&grantorId, &avail_goptions);
    +		if (OidIsValid(istmt->grantor))
    +		{
    +			grantorId = istmt->grantor;
    +			avail_goptions = aclmask_direct(old_acl, grantorId, ownerId,
    +											ACL_GRANT_OPTION_FOR(istmt->privileges),
    +											ACLMASK_ALL);
    +		}
    +		else
    +			select_best_grantor(GetUserId(), istmt->privileges,
    +								old_acl, ownerId,
    +								&grantorId, &avail_goptions);
     
     		/*
     		 * Restrict the privileges to what we can actually grant, and emit the
    @@ -2483,9 +2512,17 @@ ExecGrant_Parameter(InternalGrant *istmt)
     		}
     
     		/* Determine ID to do the grant as, and available grant options */
    -		select_best_grantor(GetUserId(), istmt->privileges,
    -							old_acl, ownerId,
    -							&grantorId, &avail_goptions);
    +		if (OidIsValid(istmt->grantor))
    +		{
    +			grantorId = istmt->grantor;
    +			avail_goptions = aclmask_direct(old_acl, grantorId, ownerId,
    +											ACL_GRANT_OPTION_FOR(istmt->privileges),
    +											ACLMASK_ALL);
    +		}
    +		else
    +			select_best_grantor(GetUserId(), istmt->privileges,
    +								old_acl, ownerId,
    +								&grantorId, &avail_goptions);
     
     		/*
     		 * Restrict the privileges to what we can actually grant, and emit the
    diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
    index fbcd64a2609..bf078888b0f 100644
    --- a/src/backend/utils/adt/acl.c
    +++ b/src/backend/utils/adt/acl.c
    @@ -1473,7 +1473,7 @@ aclmask(const Acl *acl, Oid roleid, Oid ownerId,
      * This is exactly like aclmask() except that we consider only privileges
      * held *directly* by roleid, not those inherited via role membership.
      */
    -static AclMode
    +AclMode
     aclmask_direct(const Acl *acl, Oid roleid, Oid ownerId,
     			   AclMode mask, AclMaskHow how)
     {
    diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
    index 01ae5b719fd..50d3b42bfcc 100644
    --- a/src/include/utils/acl.h
    +++ b/src/include/utils/acl.h
    @@ -207,6 +207,8 @@ extern bool aclequal(const Acl *left_acl, const Acl *right_acl);
     
     extern AclMode aclmask(const Acl *acl, Oid roleid, Oid ownerId,
     					   AclMode mask, AclMaskHow how);
    +extern AclMode aclmask_direct(const Acl *acl, Oid roleid, Oid ownerId,
    +							  AclMode mask, AclMaskHow how);
     extern int	aclmembers(const Acl *acl, Oid **roleids);
     
     extern bool has_privs_of_role(Oid member, Oid role);
    diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h
    index 62af290779a..051a9630256 100644
    --- a/src/include/utils/aclchk_internal.h
    +++ b/src/include/utils/aclchk_internal.h
    @@ -36,6 +36,7 @@ typedef struct
     	bool		all_privs;
     	AclMode		privileges;
     	List	   *col_privs;
    +	Oid			grantor;
     	List	   *grantees;
     	bool		grant_option;
     	DropBehavior behavior;
    diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
    index daafaa94fde..997c4b68f47 100644
    --- a/src/test/regress/expected/privileges.out
    +++ b/src/test/regress/expected/privileges.out
    @@ -321,7 +321,7 @@ SELECT pg_get_acl(0, 0, 0); -- null
     (1 row)
     
     GRANT TRUNCATE ON atest2 TO regress_priv_user4 GRANTED BY regress_priv_user5;  -- error
    -ERROR:  grantor must be current user
    +ERROR:  must inherit privileges of role "regress_priv_user5"
     SET SESSION AUTHORIZATION regress_priv_user2;
     SELECT session_user, current_user;
         session_user    |    current_user    
    -- 
    2.39.5 (Apple Git-154)
    
    
    --0180Z+uLTgRXM/m1--