这个问题是从被问到information_schema.user_privilege的IS_GRANTABLE字段问题开始查起的,就先从user_privilege表的显示规则说起。
1、 IS_GRANTABLE字段
用root账号执行如下语句(本文中grant后都接flush privileges, 不赘述)
a) grant all privileges on *.* to `myuser`@localhost with grant option; b) select user,select_priv from mysql.user where user='myuser'; +------------------------------+----------------------------+ | user | select_priv | +------------------------------+----------------------------+ | myuser | Y | +------------------------------+----------------------------+ c) select PRIVILEGE_TYPE, IS_GRANTABLE from information_schema.user_privileges where grantee like '\'myuser\'@\'localhost\'' and PRIVILEGE_TYPE = ‘SELECT’; +------------------------------+----------------------------+ | PRIVILEGE_TYPE | IS_GRANTABLE | +------------------------------+----------------------------+ | SELECT | NO | +------------------------------+----------------------------+
|
说明: 从b)看myuser已经有了select_priv。而c) 中 IS_GRANTABLE是NO,这不是显示错误。 实际上,IS_GRANTABLE并非表示用户是否“拥有此权限”,而是表示用户是否拥有“将此权限赋予其他用户”的权限。它对应的是mysql.user表中的grant_priv字段,此时为NO。
2、user_privileges的显示规则
当我们创建一个新用户create user myuser2; 时,在mysql.user中看到这个用户的所有权限都为NO,此时user_privileges增加一行
+---------------------+-----------------------+--------------------------+---------------------+ | GRANTEE | TABLE_CATALOG | PRIVILEGE_TYPE | IS_GRANTABLE | | 'myuser2'@'%' | NULL | USAGE | NO |. +---------------------+-----------------------+--------------------------+---------------------+ |
什么时候显示USAGE? 从这篇文章中我们知道对应的显示控制代吗在sql/sql_show.cc。 对应的函数为fill_schema_schema_privileges。
简单分析源码得到规则如下:
PRIVILEGE_TYPE规则:
a) 当该用户没有权限,或只有grant_priv的时候,PRIVILEGE_TYPE显示为USAGE;
b) 否则按顺序显示被赋予的权限,每行一个,这些权限包括
(UPDATE_ACL | SELECT_ACL | INSERT_ACL | DELETE_ACL | CREATE_ACL | DROP_ACL | GRANT_ACL | REFERENCES_ACL | INDEX_ACL | ALTER_ACL | CREATE_TMP_ACL | LOCK_TABLES_ACL | EXECUTE_ACL | CREATE_VIEW_ACL | SHOW_VIEW_ACL | CREATE_PROC_ACL | ALTER_PROC_ACL | EVENT_ACL | TRIGGER_ACL), 但GRANT权限不显示。
IS_GRANTABLE规则:若该用户有grant_priv权限,则在列出的所有行的IS_GRANTABLE都显示YES,否则显示NO。
================================标题党的分割线================================
3、权限控制问题
从上面的分析中我们知道,用户是否拥有给其他用户赋权的权限,取决于这个用户本身是否拥有grant_priv权限。用一个字段控制一批权限,这样就联想到可能有一个“权限混乱“的现象。
首先,授权必然是要有范围限制的。用户A赋权给用户B,这些赋予的权限不能超过A的权限范围。
看以下的操作序列。使用root账户登录。
mysql> grant select,insert,delete,update on *.* to `grant_u2`@localhost ; Query OK, 0 rows affected (0.00 sec) mysql> flush privileges; Query OK, 0 rows affected (0.00 sec) mysql> select * from information_schema.user_privileges where grantee like '%gran_u%'; Empty set (0.01 sec) mysql> select GRANTEE, PRIVILEGE_TYPE, IS_GRANTABLE from information_schema.user_privileges where grantee like '%grant_u%'; +--------------------------------+----------------------------+--------------------+ | GRANTEE | PRIVILEGE_TYPE | IS_GRANTABLE | +--------------------------------+----------------------------+--------------------+ | 'grant_u1'@'localhost' | SELECT | YES | | 'grant_u2'@'localhost' | SELECT | NO | | 'grant_u2'@'localhost' | INSERT | NO | | 'grant_u2'@'localhost' | UPDATE | NO | | 'grant_u2'@'localhost' | DELETE | NO | +-------------------------------+-------------------------------+-------------------+ |
说明:上面的操作中,我们给grant_u1赋了查询权限且with grant option. 给grant_u2赋了增删改查权限,但没有grant权限。
从information_schema.user_privileges看出目前权限状态正常。
之后用grant_u1登录, 将select的“赋权”权限赋给grant_u2.
mysql> grant select on *.* to `grant_u2`@localhost with grant option ; Query OK, 0 rows affected (0.00 sec) mysql> select GRANTEE, PRIVILEGE_TYPE, IS_GRANTABLE from information_schema.user_privileges where grantee like '%grant_u%'; +--------------------------------+----------------------------+--------------------+ | GRANTEE | PRIVILEGE_TYPE | IS_GRANTABLE | +--------------------------------+----------------------------+--------------------+ | 'grant_u1'@'localhost' | SELECT | YES | | 'grant_u2'@'localhost' | SELECT | YES | | 'grant_u2'@'localhost' | INSERT | YES | | 'grant_u2'@'localhost' | UPDATE | YES | | 'grant_u2'@'localhost' | DELETE | YES | +-------------------------------+-------------------------------+-------------------+ 5 rows in set (0.00 sec) |
从结果看出,grant_u2用户拥有了对增删改查的赋权权限。这个已经超出了grant_u1的权限范围。
进一步的,再用grant_u2登录,执行grant select,insert,delete,update on *.* to `grant_u1`@localhost with grant option ; 则grant_u1用户也拥有了增删改查的赋权权限。
实际上,root账号设置的权限中,grant_u1、grant_u2都没有对增删改的赋权权限,但经过上述操作后,这两个用户的权限都扩大了,且超过了原有权限的并集。
4、分析
实际上这个问题的根源,就在于MySQL在设计上用一个grant_priv来控制是否有赋权权限,而每个概念上将每个权限分开。导致在grant_u1将“查询赋权”权限赋给grant_u2的时候,附带的将其他权限也带进去了。