From f3029b7fec34b001912543e5c9dc9ddbe497d423 Mon Sep 17 00:00:00 2001 From: Egor Ivkov <e.o.ivkov@gmail.com> Date: Tue, 23 Jan 2024 20:55:00 +0300 Subject: [PATCH] feat: make alter and revoke privilege operations idempotent --- src/sql.rs | 22 ++------------------ src/storage.rs | 49 ++++++++++++++++++++++++++++++++++---------- test/int/test_sql.py | 8 ++++---- 3 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/sql.rs b/src/sql.rs index a271239772..fada1eb4fc 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -825,29 +825,11 @@ fn reenterable_schema_change_request( } AlterOptionParam::Login => { - if check_privilege_already_granted( - node, - grantee_id, - &object_type, - object_id, - &privilege, - )? { - // Login is already granted, no op needed. - return Ok(ConsumerResult { row_count: 0 }); - } + // It will be checked at a later stage whether login is already granted Op::Acl(OpAcl::GrantPrivilege { priv_def }) } AlterOptionParam::NoLogin => { - if !check_privilege_already_granted( - node, - grantee_id, - &object_type, - object_id, - &privilege, - )? { - // Login is not granted yet, no op needed. - return Ok(ConsumerResult { row_count: 0 }); - } + // It will be checked at a later stage whether login was not granted Op::Acl(OpAcl::RevokePrivilege { priv_def, initiator: current_user, diff --git a/src/storage.rs b/src/storage.rs index 579158267d..64eac060f4 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -2504,12 +2504,27 @@ impl Privileges { Ok(()) } + /// If `if_not_exists == true` will skip insertion if this privilege already exists. #[inline(always)] - pub fn insert(&self, priv_def: &PrivilegeDef) -> tarantool::Result<()> { + pub fn insert(&self, priv_def: &PrivilegeDef, if_not_exists: bool) -> tarantool::Result<()> { + if if_not_exists + && self + .space + .get(&( + priv_def.grantee_id(), + priv_def.object_type(), + priv_def.object_id_raw(), + priv_def.privilege(), + ))? + .is_some() + { + return Ok(()); + } self.space.insert(priv_def)?; Ok(()) } + /// If `if_exists == true` will skip deletion if this privilege does not exist. #[inline(always)] pub fn delete( &self, @@ -2517,7 +2532,16 @@ impl Privileges { object_type: &str, object_id: i64, privilege: &str, + if_exists: bool, ) -> tarantool::Result<()> { + if if_exists + && self + .space + .get(&(grantee_id, object_type, object_id, privilege))? + .is_none() + { + return Ok(()); + } self.space .delete(&(grantee_id, object_type, object_id, privilege))?; Ok(()) @@ -2532,6 +2556,7 @@ impl Privileges { &priv_def.object_type(), priv_def.object_id_raw(), &priv_def.privilege(), + false, )?; } Ok(()) @@ -2551,6 +2576,7 @@ impl Privileges { &priv_def.object_type(), priv_def.object_id_raw(), &priv_def.privilege(), + false, )?; } } @@ -2571,6 +2597,7 @@ impl Privileges { &priv_def.object_type(), object_id, &priv_def.privilege(), + false, )?; } Ok(()) @@ -3029,16 +3056,14 @@ pub mod acl { storage: &Clusterwide, priv_def: &PrivilegeDef, ) -> tarantool::Result<()> { - storage.privileges.insert(priv_def)?; + storage.privileges.insert(priv_def, true)?; let privilege = &priv_def.privilege(); let object = priv_def .resolve_object_name(storage) .expect("target object should exist"); let object_type = &priv_def.object_type(); - let (grantee_type, grantee) = priv_def.grantee_type_and_name(storage)?; - let initiator_def = user_by_id(priv_def.grantor_id())?; match (privilege.as_str(), object_type.as_str()) { @@ -3089,6 +3114,7 @@ pub mod acl { &priv_def.object_type(), priv_def.object_id_raw(), &priv_def.privilege(), + true, )?; let privilege = &priv_def.privilege(); @@ -3097,7 +3123,6 @@ pub mod acl { .expect("target object should exist"); let object_type = &priv_def.object_type(); let (grantee_type, grantee) = priv_def.grantee_type_and_name(storage)?; - let initiator_def = user_by_id(initiator)?; match (privilege.as_str(), object_type.as_str()) { @@ -3239,16 +3264,17 @@ pub mod acl { Ok(()) } - /// Grant a tarantool user some privilege defined by `priv_def`. + /// Grant a tarantool user or role the privilege defined by `priv_def`. + /// Is idempotent: will not return an error even if the privilege is already granted. pub fn on_master_grant_privilege(priv_def: &PrivilegeDef) -> tarantool::Result<()> { let lua = ::tarantool::lua_state(); lua.exec_with( "local grantee_id, privilege, object_type, object_id = ... local grantee_def = box.space._user:get(grantee_id) if grantee_def.type == 'user' then - box.schema.user.grant(grantee_id, privilege, object_type, object_id) + box.schema.user.grant(grantee_id, privilege, object_type, object_id, {if_not_exists=true}) else - box.schema.role.grant(grantee_id, privilege, object_type, object_id) + box.schema.role.grant(grantee_id, privilege, object_type, object_id, {if_not_exists=true}) end", ( priv_def.grantee_id(), @@ -3262,7 +3288,8 @@ pub mod acl { Ok(()) } - /// Revoke a privilege from a tarantool user. + /// Revoke a privilege from a tarantool user or role. + /// Is idempotent: will not return an error even if the privilege was not granted. pub fn on_master_revoke_privilege(priv_def: &PrivilegeDef) -> tarantool::Result<()> { let lua = ::tarantool::lua_state(); lua.exec_with( @@ -3273,9 +3300,9 @@ pub mod acl { return end if grantee_def.type == 'user' then - box.schema.user.revoke(grantee_id, privilege, object_type, object_id) + box.schema.user.revoke(grantee_id, privilege, object_type, object_id, {if_exists=true}) else - box.schema.role.revoke(grantee_id, privilege, object_type, object_id) + box.schema.role.revoke(grantee_id, privilege, object_type, object_id, {if_exists=true}) end", ( priv_def.grantee_id(), diff --git a/test/int/test_sql.py b/test/int/test_sql.py index 9c1a0a33f4..90f8f30b5b 100644 --- a/test/int/test_sql.py +++ b/test/int/test_sql.py @@ -1478,15 +1478,15 @@ def test_sql_alter_login(cluster: Cluster): acl = i1.sudo_sql(f"create user {username} with password '{password}'") assert acl["row_count"] == 1 - # Alter user with LOGIN option do nothing. + # Alter user with LOGIN option - opertaion is idempotent. acl = i1.sudo_sql(f""" alter user {username} with login """) - assert acl["row_count"] == 0 + assert acl["row_count"] == 1 # * Alter user with NOLOGIN option. acl = i1.sudo_sql(f""" alter user {username} with nologin """) assert acl["row_count"] == 1 - # * Alter user with NOLOGIN again do nothing. + # * Alter user with NOLOGIN again - operation is idempotent. acl = i1.sudo_sql(f""" alter user {username} with nologin """) - assert acl["row_count"] == 0 + assert acl["row_count"] == 1 # * TODO: Check SESSION privilege is removed. -- GitLab