From daf0345a7bafdbf4d7d4c4ab27b2c8342e28c743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vicen=C8=9Biu=20Ciorbaru?= Date: Fri, 18 Oct 2013 06:00:48 -0700 Subject: [PATCH] Added recursive database roles privilege propagation. The privileges are not correctly updated via grant commands yet. --- ...l_roles_set_role-database-recursive.result | 55 ++++++++++ ...acl_roles_set_role-database-recursive.test | 51 +++++++++ sql/sql_acl.cc | 102 ++++++++++++++++-- 3 files changed, 201 insertions(+), 7 deletions(-) create mode 100644 mysql-test/r/acl_roles_set_role-database-recursive.result create mode 100644 mysql-test/t/acl_roles_set_role-database-recursive.test diff --git a/mysql-test/r/acl_roles_set_role-database-recursive.result b/mysql-test/r/acl_roles_set_role-database-recursive.result new file mode 100644 index 00000000000..cbfb50cdf5a --- /dev/null +++ b/mysql-test/r/acl_roles_set_role-database-recursive.result @@ -0,0 +1,55 @@ +create user 'test_user'@'localhost'; +create user 'test_role1'@''; +create user 'test_role2'@''; +update mysql.user set is_role='Y' where user='test_role1'; +update mysql.user set is_role='Y' where user='test_role2'; +insert into mysql.roles_mapping (HostFk, UserFk, RoleFk) values ('localhost', +'test_user', +'test_role1'); +insert into mysql.roles_mapping (HostFk, UserFk, RoleFk) values ('localhost', +'test_user', +'test_role2'); +insert into mysql.roles_mapping (HostFk, UserFk, RoleFk) values ('', +'test_role1', +'test_role2'); +select user, host from mysql.user where user not like 'root'; +user host +test_role1 +test_role2 +test_user localhost +select * from mysql.roles_mapping; +HostFk UserFk RoleFk + test_role1 test_role2 +localhost test_user test_role1 +localhost test_user test_role2 +flush privileges; +select user, host from mysql.db; +user host + % + % +grant select on mysql.* to test_role2@''; +flush privileges; +select * from mysql.roles_mapping; +ERROR 42000: SELECT command denied to user 'test_user'@'localhost' for table 'roles_mapping' +set role test_role1; +select * from mysql.roles_mapping; +HostFk UserFk RoleFk + test_role1 test_role2 +localhost test_user test_role1 +localhost test_user test_role2 +set role none; +select * from mysql.roles_mapping; +ERROR 42000: SELECT command denied to user 'test_user'@'localhost' for table 'roles_mapping' +set role test_role2; +select * from mysql.roles_mapping; +HostFk UserFk RoleFk + test_role1 test_role2 +localhost test_user test_role1 +localhost test_user test_role2 +drop user 'test_user'@'localhost'; +revoke select on mysql.* from test_role2@''; +delete from mysql.user where user='test_role1'; +delete from mysql.user where user='test_role2'; +delete from mysql.roles_mapping where RoleFk='test_role1'; +delete from mysql.roles_mapping where RoleFk='test_role2'; +flush privileges; diff --git a/mysql-test/t/acl_roles_set_role-database-recursive.test b/mysql-test/t/acl_roles_set_role-database-recursive.test new file mode 100644 index 00000000000..c91c7fc5d3f --- /dev/null +++ b/mysql-test/t/acl_roles_set_role-database-recursive.test @@ -0,0 +1,51 @@ +#create a user with no privileges +create user 'test_user'@'localhost'; +create user 'test_role1'@''; +create user 'test_role2'@''; + +update mysql.user set is_role='Y' where user='test_role1'; +update mysql.user set is_role='Y' where user='test_role2'; +insert into mysql.roles_mapping (HostFk, UserFk, RoleFk) values ('localhost', + 'test_user', + 'test_role1'); +insert into mysql.roles_mapping (HostFk, UserFk, RoleFk) values ('localhost', + 'test_user', + 'test_role2'); +insert into mysql.roles_mapping (HostFk, UserFk, RoleFk) values ('', + 'test_role1', + 'test_role2'); +--sorted_result +select user, host from mysql.user where user not like 'root'; +--sorted_result +select * from mysql.roles_mapping; +flush privileges; + +--sorted_result +select user, host from mysql.db; + +grant select on mysql.* to test_role2@''; +flush privileges; + +change_user 'test_user'; + +--error ER_TABLEACCESS_DENIED_ERROR +select * from mysql.roles_mapping; + +set role test_role1; +--sorted_result +select * from mysql.roles_mapping; +set role none; +--error ER_TABLEACCESS_DENIED_ERROR +select * from mysql.roles_mapping; +set role test_role2; +--sorted_result +select * from mysql.roles_mapping; + +change_user 'root'; +drop user 'test_user'@'localhost'; +revoke select on mysql.* from test_role2@''; +delete from mysql.user where user='test_role1'; +delete from mysql.user where user='test_role2'; +delete from mysql.roles_mapping where RoleFk='test_role1'; +delete from mysql.roles_mapping where RoleFk='test_role2'; +flush privileges; diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index d606af6d83a..5d50b10bfdb 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -299,6 +299,7 @@ class ACL_DB :public ACL_ACCESS public: acl_host_and_ip host; char *user,*db; + ulong initial_access; /* access bits present in the table */ }; #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -712,6 +713,7 @@ static my_bool acl_role_propagate_grants(ACL_ROLE *role, void * not_used __attribute__((unused))); static int add_role_user_mapping(ROLE_GRANT_PAIR *mapping); static my_bool get_role_access(ACL_ROLE *role, ulong *access); +static void merge_role_grant_privileges(ACL_ROLE *target, ACL_ROLE *source); /* Enumeration of various ACL's and Hashes used in handle_grant_struct() @@ -1279,6 +1281,7 @@ static my_bool acl_load(THD *thd, TABLE_LIST *tables) } db.access=get_access(table,3); db.access=fix_rights_for_db(db.access); + db.initial_access= db.access; if (lower_case_table_names) { /* @@ -2079,7 +2082,7 @@ static void acl_insert_user(const char *user, const char *host, static void acl_update_db(const char *user, const char *host, const char *db, - ulong privileges) + ulong privileges, bool set_initial_access) { mysql_mutex_assert_owner(&acl_cache->lock); @@ -2099,7 +2102,11 @@ static void acl_update_db(const char *user, const char *host, const char *db, { if (privileges) - acl_db->access=privileges; + { + acl_db->access= privileges; + if (set_initial_access) + acl_db->initial_access= acl_db->access; + } else delete_dynamic_element(&acl_dbs,i); } @@ -2118,13 +2125,15 @@ static void acl_update_db(const char *user, const char *host, const char *db, host Host name db Database name privileges Bitmap of privileges + set_initial_access If marked true, will set the initial_access field + to be set to the same value as privileges. NOTES acl_cache->lock must be locked when calling this */ static void acl_insert_db(const char *user, const char *host, const char *db, - ulong privileges) + ulong privileges, bool set_initial_access) { ACL_DB acl_db; mysql_mutex_assert_owner(&acl_cache->lock); @@ -2132,6 +2141,10 @@ static void acl_insert_db(const char *user, const char *host, const char *db, update_hostname(&acl_db.host, *host ? strdup_root(&mem,host) : 0); acl_db.db=strdup_root(&mem,db); acl_db.access=privileges; + if (set_initial_access) + acl_db.initial_access= acl_db.access; + else + acl_db.initial_access= 0; acl_db.sort=get_sort(3,acl_db.host.hostname,acl_db.db,acl_db.user); (void) push_dynamic(&acl_dbs,(uchar*) &acl_db); my_qsort((uchar*) dynamic_element(&acl_dbs,0,ACL_DB*),acl_dbs.elements, @@ -2343,6 +2356,81 @@ my_bool acl_user_reset_grant(ACL_USER *user, return 0; } +/* + The function merges access bits from a granted role to a grantee. + + It creates data structures if they don't exist for the grantee. + This includes data structures related to database privileges, tables + privileges, column privileges, function and procedures privileges +*/ + +void merge_role_grant_privileges(ACL_ROLE *target, ACL_ROLE *source) +{ + DBUG_ASSERT(source->flags & ROLE_GRANTS_FINAL); + + /* Merge global access rights */ + target->access|= source->access; + + /* Merge database privileges */ + DYNAMIC_ARRAY target_dbs, source_dbs; /* arrays of pointers to ACL_DB elements */ + ACL_DB *target_db, *source_db; /* elements of the arrays */ + + (void) my_init_dynamic_array(&target_dbs,sizeof(ACL_DB *), 50, 100, MYF(0)); + (void) my_init_dynamic_array(&source_dbs,sizeof(ACL_DB *), 50, 100, MYF(0)); + + /* get all acl_db elements for the source and the target */ + for (uint i=0 ; i < acl_dbs.elements ; i++) + { + ACL_DB *acl_db= dynamic_element(&acl_dbs,i,ACL_DB*); + if (acl_db->user && !acl_db->host.hostname) + { + if (!strcmp(target->user.str, acl_db->user)) + push_dynamic(&target_dbs, (uchar*)&acl_db); + if (!strcmp(source->user.str, acl_db->user)) + push_dynamic(&source_dbs, (uchar*)&acl_db); + } + } + + for (uint i=0 ; i < source_dbs.elements; i++) + { + source_db= *dynamic_element(&source_dbs, i, ACL_DB **); + target_db= NULL; + for (uint j=0; j < target_dbs.elements; j++) + { + ACL_DB *acl_db= *dynamic_element(&target_dbs, i, ACL_DB **); + if (!strcmp(source_db->db, acl_db->db)) /* only need to compare DB here */ + target_db= acl_db; + } + + /* acl_db element found for the target, only need to update acces bits */ + if (target_db) + { + target_db->access|= source_db->access; + } + else + { + /* + Need to create an acl_db element as this role inherits database + privileges + */ + acl_insert_db(target->user.str, "", source_db->db, + source_db->access, FALSE); + } + } + + delete_dynamic(&source_dbs); + delete_dynamic(&target_dbs); + + /* Merge table privileges */ + /* TODO */ + + /* Merge column privileges */ + /* TODO */ + + /* Merge function and procedure privileges */ + /* TODO */ +} + /* The function scans through all roles granted to the role passed as argument and places the permissions in the access variable. @@ -2380,7 +2468,7 @@ my_bool get_role_access(ACL_ROLE *role, ulong *access) It uses a DYNAMIC_ARRAY to reduce the number of malloc calls to a minimum */ - DYNAMIC_ARRAY stack; + DYNAMIC_ARRAY stack; NODE_STATE state; /* variable used to insert elements in the stack */ state.neigh_idx= 0; @@ -2427,7 +2515,7 @@ my_bool get_role_access(ACL_ROLE *role, ulong *access) if (neighbour->flags & ROLE_GRANTS_FINAL) { DBUG_PRINT("info", ("Neighbour access is final, merging")); - current->access|= neighbour->access; + merge_role_grant_privileges(current, neighbour); continue; } @@ -3474,10 +3562,10 @@ static int replace_db_table(TABLE *table, const char *db, acl_cache->clear(1); // Clear privilege cache if (old_row_exists) - acl_update_db(combo.user.str,combo.host.str,db,rights); + acl_update_db(combo.user.str,combo.host.str,db,rights, TRUE); else if (rights) - acl_insert_db(combo.user.str,combo.host.str,db,rights); + acl_insert_db(combo.user.str,combo.host.str,db,rights, TRUE); DBUG_RETURN(0); /* This could only happen if the grant tables got corrupted */