diff --git a/src/box/alter.cc b/src/box/alter.cc index baec3df978936f3d0b64b6aa4469b5eeab68da86..0259139c303ab13db4529ea9e4821561dd24c951 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -3970,10 +3970,11 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) /** * Update a metadata cache object with the new access - * data. + * data. For the purpose of the rolled back statement, please refer to + * `user_reload_privs`. */ static int -grant_or_revoke(struct priv_def *priv) +grant_or_revoke(struct priv_def *priv, struct txn_stmt *rolled_back_stmt) { struct user *grantee = user_by_id(priv->grantee_id); if (grantee == NULL) @@ -3994,7 +3995,7 @@ grant_or_revoke(struct priv_def *priv) return -1; } } else { - if (priv_grant(grantee, priv) != 0) + if (priv_grant(grantee, priv, rolled_back_stmt) != 0) return -1; } return 0; @@ -4004,13 +4005,12 @@ grant_or_revoke(struct priv_def *priv) static int revoke_priv(struct trigger *trigger, void *event) { - (void) event; struct tuple *tuple = (struct tuple *)trigger->data; struct priv_def priv; if (priv_def_create_from_tuple(&priv, tuple) != 0) return -1; priv.access = 0; - if (grant_or_revoke(&priv) != 0) + if (grant_or_revoke(&priv, (struct txn_stmt *)event) != 0) return -1; return 0; } @@ -4019,11 +4019,10 @@ revoke_priv(struct trigger *trigger, void *event) static int modify_priv(struct trigger *trigger, void *event) { - (void) event; struct tuple *tuple = (struct tuple *)trigger->data; struct priv_def priv; if (priv_def_create_from_tuple(&priv, tuple) != 0 || - grant_or_revoke(&priv) != 0) + grant_or_revoke(&priv, (struct txn_stmt *)event) != 0) return -1; return 0; } @@ -4044,7 +4043,7 @@ on_replace_dd_priv(struct trigger * /* trigger */, void *event) if (new_tuple != NULL && old_tuple == NULL) { /* grant */ if (priv_def_create_from_tuple(&priv, new_tuple) != 0 || priv_def_check(&priv, PRIV_GRANT) != 0 || - grant_or_revoke(&priv) != 0) + grant_or_revoke(&priv, NULL) != 0) return -1; struct trigger *on_rollback = txn_alter_trigger_new(revoke_priv, new_tuple); @@ -4057,7 +4056,7 @@ on_replace_dd_priv(struct trigger * /* trigger */, void *event) priv_def_check(&priv, PRIV_REVOKE) != 0) return -1; priv.access = 0; - if (grant_or_revoke(&priv) != 0) + if (grant_or_revoke(&priv, NULL) != 0) return -1; struct trigger *on_rollback = txn_alter_trigger_new(modify_priv, old_tuple); @@ -4067,7 +4066,7 @@ on_replace_dd_priv(struct trigger * /* trigger */, void *event) } else { /* modify */ if (priv_def_create_from_tuple(&priv, new_tuple) != 0 || priv_def_check(&priv, PRIV_GRANT) != 0 || - grant_or_revoke(&priv) != 0) + grant_or_revoke(&priv, NULL) != 0) return -1; struct trigger *on_rollback = txn_alter_trigger_new(modify_priv, old_tuple); diff --git a/src/box/user.cc b/src/box/user.cc index a1e90ba63e0652751f54e29f40a6a3f29625b32d..abf6296f13f14c42150bacd822ed13d8f19ec22b 100644 --- a/src/box/user.cc +++ b/src/box/user.cc @@ -41,6 +41,7 @@ #include "sequence.h" #include "trivia/util.h" #include "tt_static.h" +#include "txn.h" struct universe universe; static struct user users[BOX_USER_MAX]; @@ -401,10 +402,31 @@ user_set_effective_access(struct user *user) } /** - * Reload user privileges and re-grant them. + * Reload a single user privilege from a privilege tuple. */ static int -user_reload_privs(struct user *user) +user_reload_priv(struct user *user, struct tuple *tuple) +{ + struct priv_def priv; + if (priv_def_create_from_tuple(&priv, tuple) != 0) + return -1; + /** + * Skip role grants, we're only + * interested in real objects. + */ + if (priv.object_type != SC_ROLE || !(priv.access & PRIV_X)) + user_grant_priv(user, &priv); + return 0; +} + +/** + * Reload user privileges and re-grant them. If a rolled back statement is + * passed, the privileges are reloaded as part of the rollback process, so we + * must account for the changes made by the statement and use the old data + * (which will be the actual data after rollback). + */ +static int +user_reload_privs(struct user *user, struct txn_stmt *rolled_back_stmt) { if (user->is_dirty == false) return 0; @@ -444,23 +466,42 @@ user_reload_privs(struct user *user) if (it == NULL) return -1; IteratorGuard iter_guard(it); - + bool in_rollback = rolled_back_stmt != NULL; + struct tuple *old_tuple = in_rollback ? + rolled_back_stmt->old_tuple : NULL; + struct tuple *new_tuple = in_rollback ? + rolled_back_stmt->new_tuple : NULL; + assert(!in_rollback || old_tuple != NULL || new_tuple != NULL); + struct key_def *key_def = index->def->key_def; + int cmp_old = old_tuple != NULL ? + tuple_compare_with_key(old_tuple, HINT_NONE, key, + 1, HINT_NONE, key_def) : 0; + int cmp_new = new_tuple != NULL && old_tuple == NULL ? + tuple_compare_with_key(new_tuple, HINT_NONE, key, + 1, HINT_NONE, key_def) : 0; + if (cmp_old != 0 || cmp_new != 0) + in_rollback = false; + assert(old_tuple == NULL || new_tuple == NULL || + tuple_compare(new_tuple, HINT_NONE, old_tuple, HINT_NONE, + key_def) == 0); struct tuple *tuple; if (iterator_next(it, &tuple) != 0) return -1; while (tuple != NULL) { - struct priv_def priv; - if (priv_def_create_from_tuple(&priv, tuple) != 0) + if (in_rollback && tuple == new_tuple) { + tuple = old_tuple; + if (tuple == NULL) + goto next_tuple; + } + if (user_reload_priv(user, tuple) != 0) return -1; - /** - * Skip role grants, we're only - * interested in real objects. - */ - if (priv.object_type != SC_ROLE || !(priv.access & PRIV_X)) - user_grant_priv(user, &priv); +next_tuple: if (iterator_next(it, &tuple) != 0) return -1; } + if (in_rollback && new_tuple == NULL && + user_reload_priv(user, old_tuple) != 0) + return -1; } { /* Take into account privs granted through roles. */ @@ -746,11 +787,13 @@ role_check(struct user *grantee, struct user *role) } /** - * Re-calculate effective grants of the linked subgraph - * this user/role is a part of. + * Re-calculate effective grants of the linked subgraph this user/role is a + * part of. For the purpose of the rolled back statement, please refer to + * `user_reload_privs`. */ int -rebuild_effective_grants(struct user *grantee) +rebuild_effective_grants(struct user *grantee, + struct txn_stmt *rolled_back_stmt) { /* * Recurse over all roles to which grantee is granted @@ -801,7 +844,8 @@ rebuild_effective_grants(struct user *grantee) struct user_map indirect_edges = user->roles; user_map_minus(&indirect_edges, &transitive_closure); if (user_map_is_empty(&indirect_edges)) { - if (user_reload_privs(user) != 0) + if (user_reload_privs(user, + rolled_back_stmt) != 0) return -1; user_map_union(&next_layer, &user->users); } else { @@ -837,7 +881,7 @@ role_grant(struct user *grantee, struct user *role) { user_map_set(&role->users, grantee->auth_token); user_map_set(&grantee->roles, role->auth_token); - if (rebuild_effective_grants(grantee) != 0) + if (rebuild_effective_grants(grantee, NULL) != 0) return -1; return 0; } @@ -851,13 +895,14 @@ role_revoke(struct user *grantee, struct user *role) { user_map_clear(&role->users, grantee->auth_token); user_map_clear(&grantee->roles, role->auth_token); - if (rebuild_effective_grants(grantee) != 0) + if (rebuild_effective_grants(grantee, NULL) != 0) return -1; return 0; } int -priv_grant(struct user *grantee, struct priv_def *priv) +priv_grant(struct user *grantee, struct priv_def *priv, + struct txn_stmt *rolled_back_stmt) { struct access *object = access_get(priv); if (object == NULL) @@ -872,7 +917,7 @@ priv_grant(struct user *grantee, struct priv_def *priv) struct access *access = &object[grantee->auth_token]; access->granted = priv->access; access_put(priv, object); - if (rebuild_effective_grants(grantee) != 0) + if (rebuild_effective_grants(grantee, rolled_back_stmt) != 0) return -1; return 0; } diff --git a/src/box/user.h b/src/box/user.h index 089b925e56bad0cefca204c32df8b62c5042789c..850ded9f5e4eb7b2e7fb5b32aa7aa9016d07bad1 100644 --- a/src/box/user.h +++ b/src/box/user.h @@ -232,12 +232,13 @@ int role_revoke(struct user *grantee, struct user *role); /** - * Grant or revoke a single privilege to a user or role - * and re-evaluate effective access of all users of this - * role if this role. + * Grant or revoke a single privilege to a user or role and re-evaluate + * effective access of all users of this role if this role. For the purpose of + * the rolled back statement, please refer to `user_reload_privs`. */ int -priv_grant(struct user *grantee, struct priv_def *priv); +priv_grant(struct user *grantee, struct priv_def *priv, + struct txn_stmt *rolled_back_stmt); int priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple); diff --git a/test/box-luatest/rollback_ddl_on__priv_space_test.lua b/test/box-luatest/rollback_ddl_on__priv_space_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..3b27fa86b45036b94d582a86577e9d6bb5bf5f54 --- /dev/null +++ b/test/box-luatest/rollback_ddl_on__priv_space_test.lua @@ -0,0 +1,79 @@ +local server = require('luatest.server') +local t = require('luatest') + +local g = t.group() + +g.before_all(function(cg) + cg.server = server:new() + cg.server:start() + cg.server:exec(function() + local t = box.schema.space.create('test') + t:create_index('pk') + end) +end) + +g.after_all(function(cg) + cg.server:drop() +end) + +-- Test that rollback of privilege grant works correctly. +g.test_rollback_grant = function(cg) + cg.server:exec(function() + box.schema.user.create('grant') + local msg = "Read access to space 'test' is denied for user 'grant'" + box.session.su('grant', function() + t.assert_error_msg_content_equals(msg, function() + box.space.test:select{} + end) + end) + box.begin() + box.schema.user.grant('grant', 'read', 'space', 'test') + box.rollback() + box.session.su('grant', function() + t.assert_error_msg_content_equals(msg, function() + box.space.test:select{} + end) + end) + end) +end + +-- Test that rollback of privilege revoke works correctly. +g.test_rollback_revoke = function(cg) + cg.server:exec(function() + box.schema.user.create('revoke') + box.schema.user.grant('revoke', 'read', 'space', 'test') + box.session.su('revoke', function() + t.assert_equals({}, box.space.test:select{}) + end) + box.begin() + box.schema.user.revoke('revoke', 'read', 'space', 'test') + box.rollback() + box.session.su('revoke', function() + t.assert_equals({}, box.space.test:select{}) + end) + end) +end + +-- Test that rollback of privilege modification works correctly. +g.test_rollback_modify = function(cg) + cg.server:exec(function() + box.schema.user.create('modify') + box.schema.user.grant('modify', 'read', 'space', 'test') + local msg = "Write access to space 'test' is denied for user 'modify'" + box.session.su('modify', function() + t.assert_equals({}, box.space.test:select{}) + t.assert_error_msg_content_equals(msg, function() + box.space.test:delete{0} + end) + end) + box.begin() + box.schema.user.grant('modify', 'write', 'space', 'test') + box.rollback() + box.session.su('modify', function() + t.assert_equals({}, box.space.test:select{}) + t.assert_error_msg_content_equals(msg, function() + box.space.test:delete{0} + end) + end) + end) +end