</para>
<para>
- If <literal>GRANTED BY</literal> is specified, the specified grantor must
- be the current user. This clause is currently present in this form only
- for SQL compatibility.
+ If <literal>GRANTED BY</literal> is specified, the grant is recorded as
+ having been done by the specified role. A role can only attribute a grant
+ to another role if it inherits the privileges of that role.
</para>
<para>
<para>
If <literal>GRANTED BY</literal> is specified, the grant is recorded as
having been done by the specified role. A user can only attribute a grant
- to another role if they possess the privileges of that role. The role
+ to another role if it inherits the privileges of that role. The role
recorded as the grantor must have <literal>ADMIN OPTION</literal> on the
target role, unless it is the bootstrap superuser. When a grant is recorded
as having a grantor other than the bootstrap superuser, it depends on the
Otherwise, both the privilege and the grant option are revoked.
</para>
+ <para>
+ If <literal>GRANTED BY</literal> is specified, only privileges granted by
+ the specified role are revoked. A role can only revoke grants by another
+ role if it inherits the privileges of that role.
+ </para>
+
<para>
If a user holds a privilege with grant option and has granted it to
other users then the privileges held by those other users are
If the role executing <command>REVOKE</command> holds privileges
indirectly via more than one role membership path, it is unspecified
which containing role will be used to perform the command. In such cases
- it is best practice to use <command>SET ROLE</command> to become the specific
+ it is best practice to use <literal>GRANTED BY</literal> to specify which
role you want to do the <command>REVOKE</command> as. Failure to do so might
lead to revoking privileges other than the ones you intended, or not
revoking anything at all.
AclMode privileges;
List *grantees;
bool grant_option;
+ RoleSpec *grantor;
DropBehavior behavior;
} InternalDefaultACL;
const char *errormsg;
AclMode all_privileges;
- 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())
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("grantor must be current user")));
- }
-
/*
* Turn the regular GrantStmt into the InternalGrant form.
*/
istmt.col_privs = NIL; /* may get filled below */
istmt.grantees = NIL; /* filled below */
istmt.grant_option = stmt->grant_option;
+ istmt.grantor = stmt->grantor;
istmt.behavior = stmt->behavior;
/*
/* privileges to be filled below */
iacls.grantees = NIL; /* filled below */
iacls.grant_option = action->grant_option;
+ iacls.grantor = action->grantor;
iacls.behavior = action->behavior;
/*
iacls.privileges = ACL_NO_RIGHTS;
iacls.grantees = list_make1_oid(roleid);
iacls.grant_option = false;
+ iacls.grantor = NULL;
iacls.behavior = DROP_CASCADE;
/* Do it */
istmt.col_privs = NIL;
istmt.grantees = list_make1_oid(roleid);
istmt.grant_option = false;
+ istmt.grantor = NULL;
istmt.behavior = DROP_CASCADE;
ExecGrantStmt_oids(&istmt);
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,
+ select_best_grantor(istmt->grantor, col_privileges,
merged_acl, ownerId,
&grantorId, &avail_goptions);
ObjectType objtype;
/* Determine ID to do the grant as, and available grant options */
- select_best_grantor(GetUserId(), this_privileges,
+ select_best_grantor(istmt->grantor, this_privileges,
old_acl, ownerId,
&grantorId, &avail_goptions);
}
/* Determine ID to do the grant as, and available grant options */
- select_best_grantor(GetUserId(), istmt->privileges,
+ select_best_grantor(istmt->grantor, istmt->privileges,
old_acl, ownerId,
&grantorId, &avail_goptions);
}
/* Determine ID to do the grant as, and available grant options */
- select_best_grantor(GetUserId(), istmt->privileges,
+ select_best_grantor(istmt->grantor, istmt->privileges,
old_acl, ownerId,
&grantorId, &avail_goptions);
}
/* Determine ID to do the grant as, and available grant options */
- select_best_grantor(GetUserId(), istmt->privileges,
+ select_best_grantor(istmt->grantor, istmt->privileges,
old_acl, ownerId,
&grantorId, &avail_goptions);
/*
* Select the effective grantor ID for a GRANT or REVOKE operation.
*
+ * If the GRANT/REVOKE has an explicit GRANTED BY clause, we always use
+ * exactly that role (which may result in granting/revoking no privileges).
+ * Otherwise, we seek a "best" grantor, starting with the current user.
+ *
* 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
* 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
+ * grantedBy: the GRANTED BY clause of GRANT/REVOKE, or NULL if none
* 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.
+ * *grantOptions: receives grant options actually held by grantorId (maybe 0)
*/
void
-select_best_grantor(Oid roleId, AclMode privileges,
+select_best_grantor(const RoleSpec *grantedBy, AclMode privileges,
const Acl *acl, Oid ownerId,
Oid *grantorId, AclMode *grantOptions)
{
+ Oid roleId = GetUserId();
AclMode needed_goptions = ACL_GRANT_OPTION_FOR(privileges);
List *roles_list;
int nrights;
ListCell *l;
+ /*
+ * If we have GRANTED BY, resolve it and verify current user is allowed to
+ * specify that role.
+ */
+ if (grantedBy)
+ {
+ Oid grantor = get_rolespec_oid(grantedBy, false);
+
+ if (!has_privs_of_role(roleId, grantor))
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must inherit privileges of role \"%s\"",
+ GetUserNameFromId(grantor, false))));
+ /* Use exactly that grantor, whether it has privileges or not */
+ *grantorId = grantor;
+ *grantOptions = aclmask_direct(acl, grantor, ownerId,
+ needed_goptions, ACLMASK_ALL);
+ return;
+ }
+
/*
* 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
/* privileges == NIL denotes ALL PRIVILEGES */
List *grantees; /* list of RoleSpec nodes */
bool grant_option; /* grant or revoke grant option */
- RoleSpec *grantor;
+ RoleSpec *grantor; /* GRANTED BY clause, or NULL if none */
DropBehavior behavior; /* drop behavior (for REVOKE) */
} GrantStmt;
extern HeapTuple get_rolespec_tuple(const RoleSpec *role);
extern char *get_rolespec_name(const RoleSpec *role);
-extern void select_best_grantor(Oid roleId, AclMode privileges,
+extern void select_best_grantor(const RoleSpec *grantedBy, AclMode privileges,
const Acl *acl, Oid ownerId,
Oid *grantorId, AclMode *grantOptions);
List *col_privs;
List *grantees;
bool grant_option;
+ RoleSpec *grantor;
DropBehavior behavior;
} InternalGrant;
(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
DROP TABLE grantor_test1, grantor_test2, grantor_test3;
DROP ROLE regress_grantor1, regress_grantor2, regress_grantor3;
+-- GRANTED BY
+CREATE ROLE regress_grantor1;
+CREATE ROLE regress_grantor2 ROLE regress_grantor1;
+CREATE ROLE regress_grantor3 ROLE regress_grantor1;
+CREATE ROLE regress_grantor4 ROLE regress_grantor1;
+CREATE ROLE regress_grantor5;
+CREATE TABLE grantor_test ();
+GRANT SELECT ON grantor_test TO regress_grantor2 WITH GRANT OPTION;
+GRANT UPDATE ON grantor_test TO regress_grantor3 WITH GRANT OPTION;
+GRANT SELECT, UPDATE ON grantor_test TO regress_grantor4 WITH GRANT OPTION;
+SET ROLE regress_grantor1;
+GRANT SELECT, UPDATE ON grantor_test TO regress_grantor5;
+SELECT * FROM information_schema.table_privileges t
+ WHERE grantor LIKE 'regress_grantor%' ORDER BY ROW(t.*);
+ grantor | grantee | table_catalog | table_schema | table_name | privilege_type | is_grantable | with_hierarchy
+------------------+------------------+---------------+--------------+--------------+----------------+--------------+----------------
+ regress_grantor4 | regress_grantor5 | regression | public | grantor_test | SELECT | NO | YES
+ regress_grantor4 | regress_grantor5 | regression | public | grantor_test | UPDATE | NO | NO
+(2 rows)
+
+REVOKE SELECT, UPDATE ON grantor_test FROM regress_grantor5;
+GRANT SELECT, UPDATE ON grantor_test TO regress_grantor5 GRANTED BY regress_grantor2;
+WARNING: not all privileges were granted for "grantor_test"
+GRANT SELECT, UPDATE ON grantor_test TO regress_grantor5 GRANTED BY regress_grantor3;
+WARNING: not all privileges were granted for "grantor_test"
+SELECT * FROM information_schema.table_privileges t
+ WHERE grantor LIKE 'regress_grantor%' ORDER BY ROW(t.*);
+ grantor | grantee | table_catalog | table_schema | table_name | privilege_type | is_grantable | with_hierarchy
+------------------+------------------+---------------+--------------+--------------+----------------+--------------+----------------
+ regress_grantor2 | regress_grantor5 | regression | public | grantor_test | SELECT | NO | YES
+ regress_grantor3 | regress_grantor5 | regression | public | grantor_test | UPDATE | NO | NO
+(2 rows)
+
+REVOKE SELECT, UPDATE ON grantor_test FROM regress_grantor5 GRANTED BY regress_grantor2;
+WARNING: not all privileges could be revoked for "grantor_test"
+WARNING: not all privileges could be revoked for column "tableoid" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "cmax" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "xmax" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "cmin" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "xmin" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "ctid" of relation "grantor_test"
+REVOKE SELECT, UPDATE ON grantor_test FROM regress_grantor5 GRANTED BY regress_grantor3;
+WARNING: not all privileges could be revoked for "grantor_test"
+WARNING: not all privileges could be revoked for column "tableoid" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "cmax" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "xmax" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "cmin" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "xmin" of relation "grantor_test"
+WARNING: not all privileges could be revoked for column "ctid" of relation "grantor_test"
+SELECT * FROM information_schema.table_privileges t
+ WHERE grantor LIKE 'regress_grantor%' ORDER BY ROW(t.*);
+ grantor | grantee | table_catalog | table_schema | table_name | privilege_type | is_grantable | with_hierarchy
+---------+---------+---------------+--------------+------------+----------------+--------------+----------------
+(0 rows)
+
+RESET ROLE;
+DROP TABLE grantor_test;
+DROP ROLE regress_grantor1, regress_grantor2, regress_grantor3, regress_grantor4, regress_grantor5;
DROP TABLE grantor_test1, grantor_test2, grantor_test3;
DROP ROLE regress_grantor1, regress_grantor2, regress_grantor3;
+
+-- GRANTED BY
+CREATE ROLE regress_grantor1;
+CREATE ROLE regress_grantor2 ROLE regress_grantor1;
+CREATE ROLE regress_grantor3 ROLE regress_grantor1;
+CREATE ROLE regress_grantor4 ROLE regress_grantor1;
+CREATE ROLE regress_grantor5;
+CREATE TABLE grantor_test ();
+GRANT SELECT ON grantor_test TO regress_grantor2 WITH GRANT OPTION;
+GRANT UPDATE ON grantor_test TO regress_grantor3 WITH GRANT OPTION;
+GRANT SELECT, UPDATE ON grantor_test TO regress_grantor4 WITH GRANT OPTION;
+SET ROLE regress_grantor1;
+
+GRANT SELECT, UPDATE ON grantor_test TO regress_grantor5;
+
+SELECT * FROM information_schema.table_privileges t
+ WHERE grantor LIKE 'regress_grantor%' ORDER BY ROW(t.*);
+
+REVOKE SELECT, UPDATE ON grantor_test FROM regress_grantor5;
+GRANT SELECT, UPDATE ON grantor_test TO regress_grantor5 GRANTED BY regress_grantor2;
+GRANT SELECT, UPDATE ON grantor_test TO regress_grantor5 GRANTED BY regress_grantor3;
+
+SELECT * FROM information_schema.table_privileges t
+ WHERE grantor LIKE 'regress_grantor%' ORDER BY ROW(t.*);
+
+REVOKE SELECT, UPDATE ON grantor_test FROM regress_grantor5 GRANTED BY regress_grantor2;
+REVOKE SELECT, UPDATE ON grantor_test FROM regress_grantor5 GRANTED BY regress_grantor3;
+
+SELECT * FROM information_schema.table_privileges t
+ WHERE grantor LIKE 'regress_grantor%' ORDER BY ROW(t.*);
+
+RESET ROLE;
+DROP TABLE grantor_test;
+DROP ROLE regress_grantor1, regress_grantor2, regress_grantor3, regress_grantor4, regress_grantor5;