From 2bc21cfc5e3e6d8403bec82fde14cfd01536f406 Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:47:52 +0530 Subject: [PATCH] fix(users): Allow accepting invites for `org_admin`s (#6253) --- crates/diesel_models/src/query/user_role.rs | 2 +- crates/router/src/core/user.rs | 21 +++++-- crates/router/src/core/user_role.rs | 8 +-- crates/router/src/db/kafka_store.rs | 2 +- crates/router/src/db/user_role.rs | 12 ++-- crates/router/src/utils/user_role.rs | 61 ++++++++++++++++++--- 6 files changed, 83 insertions(+), 23 deletions(-) diff --git a/crates/diesel_models/src/query/user_role.rs b/crates/diesel_models/src/query/user_role.rs index f4142842e2..5ef0971521 100644 --- a/crates/diesel_models/src/query/user_role.rs +++ b/crates/diesel_models/src/query/user_role.rs @@ -119,7 +119,7 @@ impl UserRole { conn: &PgPooledConn, user_id: String, org_id: id_type::OrganizationId, - merchant_id: id_type::MerchantId, + merchant_id: Option, profile_id: Option, update: UserRoleUpdate, version: UserRoleVersion, diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 91c86a7405..046675f520 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -647,7 +647,14 @@ async fn handle_existing_user_invitation( }; let _user_role = match role_info.get_entity_type() { - EntityType::Organization => return Err(UserErrors::InvalidRoleId.into()), + EntityType::Organization => { + user_role + .add_entity(domain::OrganizationLevel { + org_id: user_from_token.org_id.clone(), + }) + .insert_in_v2(state) + .await? + } EntityType::Merchant => { user_role .add_entity(domain::MerchantLevel { @@ -678,7 +685,10 @@ async fn handle_existing_user_invitation( { let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?; let entity = match role_info.get_entity_type() { - EntityType::Organization => return Err(UserErrors::InvalidRoleId.into()), + EntityType::Organization => email_types::Entity { + entity_id: user_from_token.org_id.get_string_repr().to_owned(), + entity_type: EntityType::Organization, + }, EntityType::Merchant => email_types::Entity { entity_id: user_from_token.merchant_id.get_string_repr().to_owned(), entity_type: EntityType::Merchant, @@ -806,7 +816,10 @@ async fn handle_new_user_invitation( let _ = req_state.clone(); let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?; let entity = match role_info.get_entity_type() { - EntityType::Organization => return Err(UserErrors::InvalidRoleId.into()), + EntityType::Organization => email_types::Entity { + entity_id: user_from_token.org_id.get_string_repr().to_owned(), + entity_type: EntityType::Organization, + }, EntityType::Merchant => email_types::Entity { entity_id: user_from_token.merchant_id.get_string_repr().to_owned(), entity_type: EntityType::Merchant, @@ -1012,7 +1025,7 @@ pub async fn accept_invite_from_email_token_only_flow( &state, user_from_db.get_user_id(), &org_id, - &merchant_id, + merchant_id.as_ref(), profile_id.as_ref(), UserRoleUpdate::UpdateStatus { status: UserStatus::Active, diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index 9145706c10..2cd715ac35 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -174,7 +174,7 @@ pub async fn update_user_role( .update_user_role_by_user_id_and_lineage( user_to_be_updated.get_user_id(), &user_from_token.org_id, - &user_from_token.merchant_id, + Some(&user_from_token.merchant_id), user_from_token.profile_id.as_ref(), UserRoleUpdate::UpdateRole { role_id: req.role_id.clone(), @@ -247,7 +247,7 @@ pub async fn update_user_role( .update_user_role_by_user_id_and_lineage( user_to_be_updated.get_user_id(), &user_from_token.org_id, - &user_from_token.merchant_id, + Some(&user_from_token.merchant_id), user_from_token.profile_id.as_ref(), UserRoleUpdate::UpdateRole { role_id: req.role_id.clone(), @@ -296,7 +296,7 @@ pub async fn accept_invitations_v2( &state, user_from_token.user_id.as_str(), org_id, - merchant_id, + merchant_id.as_ref(), profile_id.as_ref(), UserRoleUpdate::UpdateStatus { status: UserStatus::Active, @@ -348,7 +348,7 @@ pub async fn accept_invitations_pre_auth( &state, user_token.user_id.as_str(), org_id, - merchant_id, + merchant_id.as_ref(), profile_id.as_ref(), UserRoleUpdate::UpdateStatus { status: UserStatus::Active, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 33f93ecc69..9f237c226b 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -3043,7 +3043,7 @@ impl UserRoleInterface for KafkaStore { &self, user_id: &str, org_id: &id_type::OrganizationId, - merchant_id: &id_type::MerchantId, + merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, update: user_storage::UserRoleUpdate, version: enums::UserRoleVersion, diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs index d8ef488193..b589d37597 100644 --- a/crates/router/src/db/user_role.rs +++ b/crates/router/src/db/user_role.rs @@ -52,7 +52,7 @@ pub trait UserRoleInterface { &self, user_id: &str, org_id: &id_type::OrganizationId, - merchant_id: &id_type::MerchantId, + merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, update: storage::UserRoleUpdate, version: enums::UserRoleVersion, @@ -120,7 +120,7 @@ impl UserRoleInterface for Store { &self, user_id: &str, org_id: &id_type::OrganizationId, - merchant_id: &id_type::MerchantId, + merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, update: storage::UserRoleUpdate, version: enums::UserRoleVersion, @@ -130,7 +130,7 @@ impl UserRoleInterface for Store { &conn, user_id.to_owned(), org_id.to_owned(), - merchant_id.to_owned(), + merchant_id.cloned(), profile_id.cloned(), update, version, @@ -280,7 +280,7 @@ impl UserRoleInterface for MockDb { &self, user_id: &str, org_id: &id_type::OrganizationId, - merchant_id: &id_type::MerchantId, + merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, update: storage::UserRoleUpdate, version: enums::UserRoleVersion, @@ -293,11 +293,11 @@ impl UserRoleInterface for MockDb { && user_role.profile_id.is_none(); let merchant_level_check = user_role.org_id.as_ref() == Some(org_id) - && user_role.merchant_id.as_ref() == Some(merchant_id) + && user_role.merchant_id.as_ref() == merchant_id && user_role.profile_id.is_none(); let profile_level_check = user_role.org_id.as_ref() == Some(org_id) - && user_role.merchant_id.as_ref() == Some(merchant_id) + && user_role.merchant_id.as_ref() == merchant_id && user_role.profile_id.as_ref() == profile_id; // Check if the user role matches the conditions and the version matches diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index b6db834077..0ea423989f 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -174,7 +174,7 @@ pub async fn update_v1_and_v2_user_roles_in_db( state: &SessionState, user_id: &str, org_id: &id_type::OrganizationId, - merchant_id: &id_type::MerchantId, + merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, update: UserRoleUpdate, ) -> ( @@ -255,12 +255,57 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( ) -> UserResult< Option<( id_type::OrganizationId, - id_type::MerchantId, + Option, Option, )>, > { match entity_type { - EntityType::Organization => Err(UserErrors::InvalidRoleOperation.into()), + EntityType::Organization => { + let Ok(org_id) = + id_type::OrganizationId::try_from(std::borrow::Cow::from(entity_id.clone())) + else { + return Ok(None); + }; + + let user_roles = state + .store + .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { + user_id, + org_id: Some(&org_id), + merchant_id: None, + profile_id: None, + entity_id: None, + version: None, + status: Some(UserStatus::InvitationSent), + limit: None, + }) + .await + .change_context(UserErrors::InternalServerError)? + .into_iter() + .collect::>(); + + if user_roles.len() > 1 { + return Ok(None); + } + + if let Some(user_role) = user_roles.into_iter().next() { + let (_entity_id, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + + if entity_type != EntityType::Organization { + return Ok(None); + } + + return Ok(Some(( + user_role.org_id.ok_or(UserErrors::InternalServerError)?, + None, + None, + ))); + } + + Ok(None) + } EntityType::Merchant => { let Ok(merchant_id) = id_type::MerchantId::wrap(entity_id) else { return Ok(None); @@ -298,7 +343,7 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( return Ok(Some(( user_role.org_id.ok_or(UserErrors::InternalServerError)?, - merchant_id, + Some(merchant_id), None, ))); } @@ -343,9 +388,11 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( return Ok(Some(( user_role.org_id.ok_or(UserErrors::InternalServerError)?, - user_role - .merchant_id - .ok_or(UserErrors::InternalServerError)?, + Some( + user_role + .merchant_id + .ok_or(UserErrors::InternalServerError)?, + ), Some(profile_id), ))); }