diff --git a/mysql-test/r/acl_roles_admin.result b/mysql-test/r/acl_roles_admin.result index 591d155003e..ad3d4125252 100644 --- a/mysql-test/r/acl_roles_admin.result +++ b/mysql-test/r/acl_roles_admin.result @@ -19,7 +19,7 @@ ERROR 42000: You have an error in your SQL syntax; check the manual that corresp grant role1 to foo@localhost with admin option; grant role2 to foo@localhost; grant role2 to role1; -grant role3 to role4 with admin option; +grant role4 to role3 with admin option; grant select on *.* to foo@localhost with admin option; ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'admin option' at line 1 show grants for foo@localhost; @@ -33,18 +33,18 @@ Grants for role1 GRANT USAGE ON *.* TO 'role1' GRANT USAGE ON *.* TO 'role2' GRANT USAGE ON *.* TO 'role3' +GRANT USAGE ON *.* TO 'role4' GRANT role2 TO 'role1' GRANT role3 TO 'role1' WITH ADMIN OPTION +GRANT role4 TO 'role3' WITH ADMIN OPTION show grants for role4; Grants for role4 -GRANT USAGE ON *.* TO 'role3' GRANT USAGE ON *.* TO 'role4' -GRANT role3 TO 'role4' WITH ADMIN OPTION select * from mysql.roles_mapping; Host User Role Admin_option role1 role2 N role1 role3 Y - role4 role3 Y + role3 role4 Y bar foo role6 Y localhost foo role1 Y localhost foo role2 N @@ -64,25 +64,26 @@ Grants for role1 GRANT USAGE ON *.* TO 'role1' GRANT USAGE ON *.* TO 'role2' GRANT USAGE ON *.* TO 'role3' +GRANT USAGE ON *.* TO 'role4' GRANT role2 TO 'role1' GRANT role3 TO 'role1' WITH ADMIN OPTION +GRANT role4 TO 'role3' WITH ADMIN OPTION show grants for role4; Grants for role4 -GRANT USAGE ON *.* TO 'role3' GRANT USAGE ON *.* TO 'role4' -GRANT role3 TO 'role4' WITH ADMIN OPTION select * from information_schema.applicable_roles; GRANTEE ROLE_NAME IS_GRANTABLE role1 role2 NO role1 role3 YES -role4 role3 YES +role3 role4 YES root@localhost role1 YES root@localhost role2 YES root@localhost role4 YES grant role2 to role1 with admin option; revoke role1 from foo@localhost; -revoke admin option for role3 from role4; +revoke admin option for role4 from role3; revoke admin option for role2 from foo@localhost; +revoke admin option for role1 from root@localhost; show grants for foo@localhost; Grants for foo@localhost GRANT CREATE USER ON *.* TO 'foo'@'localhost' @@ -93,22 +94,22 @@ Grants for role1 GRANT USAGE ON *.* TO 'role1' GRANT USAGE ON *.* TO 'role2' GRANT USAGE ON *.* TO 'role3' +GRANT USAGE ON *.* TO 'role4' GRANT role2 TO 'role1' WITH ADMIN OPTION GRANT role3 TO 'role1' WITH ADMIN OPTION +GRANT role4 TO 'role3' show grants for role4; Grants for role4 -GRANT USAGE ON *.* TO 'role3' GRANT USAGE ON *.* TO 'role4' -GRANT role3 TO 'role4' select * from mysql.roles_mapping; Host User Role Admin_option role1 role2 Y role1 role3 Y - role4 role3 N + role3 role4 N bar foo role6 Y localhost foo role2 N localhost foo role5 Y -localhost root role1 Y +localhost root role1 N localhost root role2 Y localhost root role4 Y flush privileges; @@ -122,20 +123,30 @@ Grants for role1 GRANT USAGE ON *.* TO 'role1' GRANT USAGE ON *.* TO 'role2' GRANT USAGE ON *.* TO 'role3' +GRANT USAGE ON *.* TO 'role4' GRANT role2 TO 'role1' WITH ADMIN OPTION GRANT role3 TO 'role1' WITH ADMIN OPTION +GRANT role4 TO 'role3' show grants for role4; Grants for role4 -GRANT USAGE ON *.* TO 'role3' GRANT USAGE ON *.* TO 'role4' -GRANT role3 TO 'role4' select * from information_schema.applicable_roles; GRANTEE ROLE_NAME IS_GRANTABLE role1 role2 YES role1 role3 YES -role4 role3 NO -root@localhost role1 YES +role3 role4 NO +root@localhost role1 NO root@localhost role2 YES root@localhost role4 YES +grant role1 to role4; +ERROR 28000: Access denied for user 'root'@'localhost' +grant role1 to role4 with admin option; +ERROR 28000: Access denied for user 'root'@'localhost' +grant role3 to role2; +revoke role3 from role2; +grant role4 to role2 with admin option; +revoke role2 from current_user; +revoke role4 from current_user; +grant role4 to current_user; drop role role1, role2, role3, role4, role5, role6; drop user foo@localhost; diff --git a/mysql-test/t/acl_roles_admin.test b/mysql-test/t/acl_roles_admin.test index 9568547eaeb..14f63c5ced8 100644 --- a/mysql-test/t/acl_roles_admin.test +++ b/mysql-test/t/acl_roles_admin.test @@ -30,7 +30,7 @@ create user bar with admin current_user; grant role1 to foo@localhost with admin option; grant role2 to foo@localhost; grant role2 to role1; -grant role3 to role4 with admin option; +grant role4 to role3 with admin option; --error ER_PARSE_ERROR grant select on *.* to foo@localhost with admin option; @@ -54,8 +54,9 @@ select * from information_schema.applicable_roles; grant role2 to role1 with admin option; revoke role1 from foo@localhost; -revoke admin option for role3 from role4; +revoke admin option for role4 from role3; revoke admin option for role2 from foo@localhost; +revoke admin option for role1 from root@localhost; --sorted_result show grants for foo@localhost; @@ -75,6 +76,22 @@ show grants for role4; --sorted_result select * from information_schema.applicable_roles; +# Now, root@localhost don't have admin option for role1: +--error ER_ACCESS_DENIED_NO_PASSWORD_ERROR +grant role1 to role4; +--error ER_ACCESS_DENIED_NO_PASSWORD_ERROR +grant role1 to role4 with admin option; +# but role3 is grantable +grant role3 to role2; +revoke role3 from role2; + +# now, a diamond +grant role4 to role2 with admin option; +revoke role2 from current_user; +revoke role4 from current_user; +grant role4 to current_user; + + ######################################## # cleanup ######################################## diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index 842d3cf8275..19863d009c1 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -5322,6 +5322,67 @@ static void append_user(String *str, const char *u, const char *h) str->append('\''); } +struct IS_GRANTABLE_DATA +{ + ACL_ROLE *role; + bool grantable; +}; + +static void can_grant_role_callback(ACL_ROLE *unuser __attribute__((unused)), + ACL_ROLE *grantee, void *context_data) +{ + IS_GRANTABLE_DATA *data= (IS_GRANTABLE_DATA*)context_data; + for (uint i= 0; i < grantee->role_grants.elements; i++) + { + ACL_ROLE *r= *(dynamic_element(&grantee->role_grants, i, ACL_ROLE**)); + + if (r == data->role) + { + ROLE_GRANT_PAIR *pair= + find_role_grant_pair(&grantee->user, &empty_lex_str, &r->user); + if (pair->with_admin) + data->grantable= true; + } + } +} + + +/* + One can only grant a role if SELECT * FROM I_S.APPLICABLE_ROLES shows this + role as grantable. + + What this really means - we need to traverse role graph for the current user + looking for our role being granted with the admin option. +*/ +static bool can_grant_role(THD *thd, ACL_ROLE *role) +{ + Security_context *sctx= thd->security_ctx; + ACL_USER *grantee= find_user_no_anon(sctx->priv_host, sctx->priv_user, true); + if (!grantee) + return false; + + LEX_STRING host= { grantee->host.hostname, grantee->hostname_length }; + IS_GRANTABLE_DATA data= { role, false }; + + for (uint i= 0; i < grantee->role_grants.elements; i++) + { + ACL_ROLE *r= *(dynamic_element(&grantee->role_grants, i, ACL_ROLE**)); + + if (r == role) + { + ROLE_GRANT_PAIR *pair= + find_role_grant_pair(&grantee->user, &host, &r->user); + if (pair->with_admin) + return true; + } + + traverse_role_graph(r, &data, NULL, NULL, NULL, can_grant_role_callback); + if (data.grantable) + return true; + } + return false; +} + bool mysql_grant_role(THD *thd, List &list, bool revoke) { @@ -5371,6 +5432,15 @@ bool mysql_grant_role(THD *thd, List &list, bool revoke) DBUG_RETURN(TRUE); } + if (!can_grant_role(thd, role)) + { + mysql_mutex_unlock(&acl_cache->lock); + mysql_rwlock_unlock(&LOCK_grant); + my_error(ER_ACCESS_DENIED_NO_PASSWORD_ERROR, MYF(0), + thd->security_ctx->priv_user, thd->security_ctx->priv_host); + DBUG_RETURN(TRUE); + } + if (open_and_lock_tables(thd, &tables, FALSE, MYSQL_LOCK_IGNORE_TIMEOUT)) { // Should never happen mysql_mutex_unlock(&acl_cache->lock);