feat(users): add support for tenant level users (#6708)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Apoorv Dixit
2024-12-06 17:31:55 +05:30
committed by GitHub
parent b5d3d49cea
commit 357e8a007a
22 changed files with 701 additions and 128 deletions

View File

@ -25,9 +25,13 @@ use router_env::logger;
#[cfg(not(feature = "email"))]
use user_api::dashboard_metadata::SetMetaDataRequest;
#[cfg(feature = "v1")]
use super::admin;
use super::errors::{StorageErrorExt, UserErrors, UserResponse, UserResult};
#[cfg(feature = "email")]
use crate::services::email::types as email_types;
#[cfg(feature = "v1")]
use crate::types::transformers::ForeignFrom;
use crate::{
consts,
core::encryption::send_request_to_key_service_for_user,
@ -648,6 +652,12 @@ async fn handle_existing_user_invitation(
}
let (org_id, merchant_id, profile_id) = match role_info.get_entity_type() {
EntityType::Tenant => {
return Err(UserErrors::InvalidRoleOperationWithMessage(
"Tenant roles are not allowed for this operation".to_string(),
)
.into());
}
EntityType::Organization => (Some(&user_from_token.org_id), None, None),
EntityType::Merchant => (
Some(&user_from_token.org_id),
@ -701,6 +711,12 @@ async fn handle_existing_user_invitation(
};
let _user_role = match role_info.get_entity_type() {
EntityType::Tenant => {
return Err(UserErrors::InvalidRoleOperationWithMessage(
"Tenant roles are not allowed for this operation".to_string(),
)
.into());
}
EntityType::Organization => {
user_role
.add_entity(domain::OrganizationLevel {
@ -747,6 +763,12 @@ 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::Tenant => {
return Err(UserErrors::InvalidRoleOperationWithMessage(
"Tenant roles are not allowed for this operation".to_string(),
)
.into());
}
EntityType::Organization => email_types::Entity {
entity_id: user_from_token.org_id.get_string_repr().to_owned(),
entity_type: EntityType::Organization,
@ -830,6 +852,12 @@ async fn handle_new_user_invitation(
};
let _user_role = match role_info.get_entity_type() {
EntityType::Tenant => {
return Err(UserErrors::InvalidRoleOperationWithMessage(
"Tenant roles are not allowed for this operation".to_string(),
)
.into());
}
EntityType::Organization => {
user_role
.add_entity(domain::OrganizationLevel {
@ -880,6 +908,12 @@ 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::Tenant => {
return Err(UserErrors::InvalidRoleOperationWithMessage(
"Tenant roles are not allowed for this operation".to_string(),
)
.into());
}
EntityType::Organization => email_types::Entity {
entity_id: user_from_token.org_id.get_string_repr().to_owned(),
entity_type: EntityType::Organization,
@ -1235,6 +1269,83 @@ pub async fn create_internal_user(
Ok(ApplicationResponse::StatusOk)
}
pub async fn create_tenant_user(
state: SessionState,
request: user_api::CreateTenantUserRequest,
) -> UserResponse<()> {
let key_manager_state = &(&state).into();
let (merchant_id, org_id) = state
.store
.list_merchant_and_org_ids(key_manager_state, 1, None)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get merchants list for org")?
.pop()
.ok_or(UserErrors::InternalServerError)
.attach_printable("No merchants found in the tenancy")?;
let new_user = domain::NewUser::try_from((
request,
domain::MerchantAccountIdentifier {
merchant_id,
org_id,
},
))?;
let mut store_user: storage_user::UserNew = new_user.clone().try_into()?;
store_user.set_is_verified(true);
state
.global_store
.insert_user(store_user)
.await
.map_err(|e| {
if e.current_context().is_db_unique_violation() {
e.change_context(UserErrors::UserExists)
} else {
e.change_context(UserErrors::InternalServerError)
}
})
.map(domain::user::UserFromStorage::from)?;
new_user
.get_no_level_user_role(
common_utils::consts::ROLE_ID_TENANT_ADMIN.to_string(),
UserStatus::Active,
)
.add_entity(domain::TenantLevel {
tenant_id: state.tenant.tenant_id.clone(),
})
.insert_in_v2(&state)
.await
.change_context(UserErrors::InternalServerError)?;
Ok(ApplicationResponse::StatusOk)
}
#[cfg(feature = "v1")]
pub async fn create_org_merchant_for_user(
state: SessionState,
req: user_api::UserOrgMerchantCreateRequest,
) -> UserResponse<()> {
let db_organization = ForeignFrom::foreign_from(req.clone());
let org: diesel_models::organization::Organization = state
.store
.insert_organization(db_organization)
.await
.change_context(UserErrors::InternalServerError)?;
let merchant_account_create_request =
utils::user::create_merchant_account_request_for_org(req, org)?;
admin::create_merchant_account(state.clone(), merchant_account_create_request)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Error while creating a merchant")?;
Ok(ApplicationResponse::StatusOk)
}
pub async fn create_merchant_account(
state: SessionState,
user_from_token: auth::UserFromToken,
@ -1352,7 +1463,7 @@ pub async fn list_user_roles_details(
merchant.push(merchant_id.clone());
merchant_profile.push((merchant_id, profile_id))
}
EntityType::Organization => (),
EntityType::Tenant | EntityType::Organization => (),
};
Ok::<_, error_stack::Report<UserErrors>>((merchant, merchant_profile))
@ -1438,7 +1549,7 @@ pub async fn list_user_roles_details(
.ok_or(UserErrors::InternalServerError)?;
let (merchant, profile) = match entity_type {
EntityType::Organization => (None, None),
EntityType::Tenant | EntityType::Organization => (None, None),
EntityType::Merchant => {
let merchant_id = &user_role
.merchant_id
@ -2437,28 +2548,44 @@ pub async fn list_orgs_for_user(
)
.into());
}
let orgs = state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
tenant_id: user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
org_id: None,
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| user_role.org_id)
.collect::<HashSet<_>>();
let orgs = match role_info.get_entity_type() {
EntityType::Tenant => {
let key_manager_state = &(&state).into();
state
.store
.list_merchant_and_org_ids(
key_manager_state,
consts::user::ORG_LIST_LIMIT_FOR_TENANT,
None,
)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(|(_, org_id)| org_id)
.collect::<HashSet<_>>()
}
EntityType::Organization | EntityType::Merchant | EntityType::Profile => state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
tenant_id: user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
org_id: None,
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| user_role.org_id)
.collect::<HashSet<_>>(),
};
let resp = futures::future::try_join_all(
orgs.iter()
@ -2501,7 +2628,7 @@ pub async fn list_merchants_for_user_in_org(
}
let merchant_accounts = match role_info.get_entity_type() {
EntityType::Organization => state
EntityType::Tenant | EntityType::Organization => state
.store
.list_merchant_accounts_by_organization_id(&(&state).into(), &user_from_token.org_id)
.await
@ -2580,7 +2707,7 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
.await
.change_context(UserErrors::InternalServerError)?;
let profiles = match role_info.get_entity_type() {
EntityType::Organization | EntityType::Merchant => state
EntityType::Tenant | EntityType::Organization | EntityType::Merchant => state
.store
.list_profile_by_merchant_id(
key_manager_state,
@ -2670,39 +2797,80 @@ pub async fn switch_org_for_user(
.into());
}
let user_role = state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &user_from_token.user_id,
tenant_id: user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
org_id: Some(&request.org_id),
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: Some(1),
})
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to list user roles by user_id and org_id")?
.pop()
.ok_or(UserErrors::InvalidRoleOperationWithMessage(
"No user role found for the requested org_id".to_string(),
))?;
let (merchant_id, profile_id, role_id) = match role_info.get_entity_type() {
EntityType::Tenant => {
let merchant_id = state
.store
.list_merchant_accounts_by_organization_id(&(&state).into(), &request.org_id)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get merchant list for org")?
.pop()
.ok_or(UserErrors::InvalidRoleOperation)
.attach_printable("No merchants found for the org id")?
.get_id()
.to_owned();
let (merchant_id, profile_id) =
utils::user_role::get_single_merchant_id_and_profile_id(&state, &user_role).await?;
let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
&(&state).into(),
&merchant_id,
&state.store.get_master_key().to_vec().into(),
)
.await
.change_context(UserErrors::InternalServerError)?;
let profile_id = state
.store
.list_profile_by_merchant_id(&(&state).into(), &key_store, &merchant_id)
.await
.change_context(UserErrors::InternalServerError)?
.pop()
.ok_or(UserErrors::InternalServerError)?
.get_id()
.to_owned();
(merchant_id, profile_id, user_from_token.role_id)
}
EntityType::Organization | EntityType::Merchant | EntityType::Profile => {
let user_role = state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &user_from_token.user_id,
tenant_id: user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
org_id: Some(&request.org_id),
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: Some(1),
})
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to list user roles by user_id and org_id")?
.pop()
.ok_or(UserErrors::InvalidRoleOperationWithMessage(
"No user role found for the requested org_id".to_string(),
))?;
let (merchant_id, profile_id) =
utils::user_role::get_single_merchant_id_and_profile_id(&state, &user_role).await?;
(merchant_id, profile_id, user_role.role_id)
}
};
let token = utils::user::generate_jwt_auth_token_with_attributes(
&state,
user_from_token.user_id,
merchant_id.clone(),
request.org_id.clone(),
user_role.role_id.clone(),
role_id.clone(),
profile_id.clone(),
user_from_token.tenant_id,
)
@ -2710,7 +2878,7 @@ pub async fn switch_org_for_user(
utils::user_role::set_role_permissions_in_cache_by_role_id_merchant_id_org_id(
&state,
&user_role.role_id,
&role_id,
&merchant_id,
&request.org_id,
)
@ -2794,7 +2962,7 @@ pub async fn switch_merchant_for_user_in_org(
} else {
// Match based on the other entity types
match role_info.get_entity_type() {
EntityType::Organization => {
EntityType::Tenant | EntityType::Organization => {
let merchant_key_store = state
.store
.get_merchant_key_store_by_merchant_id(
@ -2937,7 +3105,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
.attach_printable("Failed to retrieve role information")?;
let (profile_id, role_id) = match role_info.get_entity_type() {
EntityType::Organization | EntityType::Merchant => {
EntityType::Tenant | EntityType::Organization | EntityType::Merchant => {
let merchant_key_store = state
.store
.get_merchant_key_store_by_merchant_id(