feat(users): add support for profile user delete (#5541)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Apoorv Dixit
2024-08-14 20:04:11 +05:30
committed by GitHub
parent 34f648e29b
commit 19a9180925
5 changed files with 404 additions and 134 deletions

View File

@ -342,68 +342,169 @@ pub async fn delete_user_role(
.attach_printable("User deleting himself");
}
let user_roles = state
let deletion_requestor_role_info = roles::RoleInfo::from_role_id(
&state,
&user_from_token.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;
let mut user_role_deleted_flag = false;
// Find in V2
let user_role_v2 = match state
.store
.find_user_role_by_user_id_and_lineage(
user_from_db.get_user_id(),
&user_from_token.org_id,
&user_from_token.merchant_id,
user_from_token.profile_id.as_ref(),
UserRoleVersion::V2,
)
.await
{
Ok(user_role) => Some(user_role),
Err(e) => {
if e.current_context().is_db_not_found() {
None
} else {
return Err(UserErrors::InternalServerError.into());
}
}
};
if let Some(role_to_be_deleted) = user_role_v2 {
let target_role_info = roles::RoleInfo::from_role_id(
&state,
&role_to_be_deleted.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;
if !target_role_info.is_deletable() {
return Err(report!(UserErrors::InvalidDeleteOperation)).attach_printable(format!(
"Invalid operation, role_id = {} is not deletable",
role_to_be_deleted.role_id
));
}
if deletion_requestor_role_info.get_entity_type() < target_role_info.get_entity_type() {
return Err(report!(UserErrors::InvalidDeleteOperation)).attach_printable(format!(
"Invalid operation, deletion requestor = {} cannot delete target = {}",
deletion_requestor_role_info.get_entity_type(),
target_role_info.get_entity_type()
));
}
user_role_deleted_flag = true;
state
.store
.delete_user_role_by_user_id_and_lineage(
user_from_db.get_user_id(),
&user_from_token.org_id,
&user_from_token.merchant_id,
user_from_token.profile_id.as_ref(),
UserRoleVersion::V2,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Error while deleting user role")?;
}
// Find in V1
let user_role_v1 = match state
.store
.find_user_role_by_user_id_and_lineage(
user_from_db.get_user_id(),
&user_from_token.org_id,
&user_from_token.merchant_id,
user_from_token.profile_id.as_ref(),
UserRoleVersion::V1,
)
.await
{
Ok(user_role) => Some(user_role),
Err(e) => {
if e.current_context().is_db_not_found() {
None
} else {
return Err(UserErrors::InternalServerError.into());
}
}
};
if let Some(role_to_be_deleted) = user_role_v1 {
let target_role_info = roles::RoleInfo::from_role_id(
&state,
&role_to_be_deleted.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;
if !target_role_info.is_deletable() {
return Err(report!(UserErrors::InvalidDeleteOperation)).attach_printable(format!(
"Invalid operation, role_id = {} is not deletable",
role_to_be_deleted.role_id
));
}
if deletion_requestor_role_info.get_entity_type() < target_role_info.get_entity_type() {
return Err(report!(UserErrors::InvalidDeleteOperation)).attach_printable(format!(
"Invalid operation, deletion requestor = {} cannot delete target = {}",
deletion_requestor_role_info.get_entity_type(),
target_role_info.get_entity_type()
));
}
user_role_deleted_flag = true;
state
.store
.delete_user_role_by_user_id_and_lineage(
user_from_db.get_user_id(),
&user_from_token.org_id,
&user_from_token.merchant_id,
user_from_token.profile_id.as_ref(),
UserRoleVersion::V1,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Error while deleting user role")?;
}
if !user_role_deleted_flag {
return Err(report!(UserErrors::InvalidDeleteOperation))
.attach_printable("User is not associated with the merchant");
}
// Check if user has any more role associations
let user_roles_v2 = state
.store
.list_user_roles_by_user_id(user_from_db.get_user_id(), UserRoleVersion::V2)
.await
.change_context(UserErrors::InternalServerError)?;
let user_roles_v1 = state
.store
.list_user_roles_by_user_id(user_from_db.get_user_id(), UserRoleVersion::V1)
.await
.change_context(UserErrors::InternalServerError)?;
for user_role in user_roles.iter() {
let Some(merchant_id) = user_role.merchant_id.as_ref() else {
return Err(report!(UserErrors::InternalServerError))
.attach_printable("merchant_id not found for user_role");
};
if merchant_id == &user_from_token.merchant_id {
let role_info = roles::RoleInfo::from_role_id(
&state,
&user_role.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;
if !role_info.is_deletable() {
return Err(report!(UserErrors::InvalidDeleteOperation))
.attach_printable(format!("role_id = {} is not deletable", user_role.role_id));
}
} else {
return Err(report!(UserErrors::InvalidDeleteOperation))
.attach_printable("User is not associated with the merchant");
}
}
let deleted_user_role = if user_roles.len() > 1 {
state
.store
.delete_user_role_by_user_id_merchant_id(
user_from_db.get_user_id(),
&user_from_token.merchant_id,
UserRoleVersion::V1,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Error while deleting user role")?
} else {
// If user has no more role associated with him then deleting user
if user_roles_v2.is_empty() && user_roles_v1.is_empty() {
state
.global_store
.delete_user_by_user_id(user_from_db.get_user_id())
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Error while deleting user entry")?;
}
state
.store
.delete_user_role_by_user_id_merchant_id(
user_from_db.get_user_id(),
&user_from_token.merchant_id,
UserRoleVersion::V1,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Error while deleting user role")?
};
auth::blacklist::insert_user_in_blacklist(&state, &deleted_user_role.user_id).await?;
auth::blacklist::insert_user_in_blacklist(&state, user_from_db.get_user_id()).await?;
Ok(ApplicationResponse::StatusOk)
}

View File

@ -2761,17 +2761,6 @@ impl UserRoleInterface for KafkaStore {
.await
}
async fn delete_user_role_by_user_id_merchant_id(
&self,
user_id: &str,
merchant_id: &id_type::MerchantId,
version: enums::UserRoleVersion,
) -> CustomResult<user_storage::UserRole, errors::StorageError> {
self.diesel_store
.delete_user_role_by_user_id_merchant_id(user_id, merchant_id, version)
.await
}
async fn list_user_roles_by_user_id(
&self,
user_id: &str,
@ -2782,6 +2771,44 @@ impl UserRoleInterface for KafkaStore {
.await
}
async fn find_user_role_by_user_id_and_lineage(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
merchant_id: &id_type::MerchantId,
profile_id: Option<&String>,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError> {
self.diesel_store
.find_user_role_by_user_id_and_lineage(
user_id,
org_id,
merchant_id,
profile_id,
version,
)
.await
}
async fn delete_user_role_by_user_id_and_lineage(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
merchant_id: &id_type::MerchantId,
profile_id: Option<&String>,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError> {
self.diesel_store
.delete_user_role_by_user_id_and_lineage(
user_id,
org_id,
merchant_id,
profile_id,
version,
)
.await
}
async fn transfer_org_ownership_between_users(
&self,
from_user_id: &str,

View File

@ -49,13 +49,6 @@ pub trait UserRoleInterface {
version: enums::UserRoleVersion,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
async fn delete_user_role_by_user_id_merchant_id(
&self,
user_id: &str,
merchant_id: &id_type::MerchantId,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError>;
async fn list_user_roles_by_user_id(
&self,
user_id: &str,
@ -68,6 +61,24 @@ pub trait UserRoleInterface {
version: enums::UserRoleVersion,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
async fn find_user_role_by_user_id_and_lineage(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
merchant_id: &id_type::MerchantId,
profile_id: Option<&String>,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError>;
async fn delete_user_role_by_user_id_and_lineage(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
merchant_id: &id_type::MerchantId,
profile_id: Option<&String>,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError>;
async fn transfer_org_ownership_between_users(
&self,
from_user_id: &str,
@ -161,25 +172,6 @@ impl UserRoleInterface for Store {
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
async fn delete_user_role_by_user_id_merchant_id(
&self,
user_id: &str,
merchant_id: &id_type::MerchantId,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
storage::UserRole::delete_by_user_id_merchant_id(
&conn,
user_id.to_owned(),
merchant_id.to_owned(),
version,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
async fn list_user_roles_by_user_id(
&self,
@ -204,6 +196,50 @@ impl UserRoleInterface for Store {
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
async fn find_user_role_by_user_id_and_lineage(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
merchant_id: &id_type::MerchantId,
profile_id: Option<&String>,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
storage::UserRole::find_by_user_id_org_id_merchant_id_profile_id(
&conn,
user_id.to_owned(),
org_id.to_owned(),
merchant_id.to_owned(),
profile_id.cloned(),
version,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
async fn delete_user_role_by_user_id_and_lineage(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
merchant_id: &id_type::MerchantId,
profile_id: Option<&String>,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError> {
let conn = connection::pg_connection_write(self).await?;
storage::UserRole::delete_by_user_id_org_id_merchant_id_profile_id(
&conn,
user_id.to_owned(),
org_id.to_owned(),
merchant_id.to_owned(),
profile_id.cloned(),
version,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
async fn transfer_org_ownership_between_users(
&self,
@ -565,32 +601,6 @@ impl UserRoleInterface for MockDb {
Ok(())
}
async fn delete_user_role_by_user_id_merchant_id(
&self,
user_id: &str,
merchant_id: &id_type::MerchantId,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError> {
let mut user_roles = self.user_roles.lock().await;
let index = user_roles.iter().position(|role| {
role.user_id == user_id
&& role.version == version
&& match role.merchant_id {
Some(ref mid) => mid == merchant_id,
None => false,
}
});
match index {
Some(idx) => Ok(user_roles.remove(idx)),
None => Err(errors::StorageError::ValueNotFound(
"Cannot find user role to delete".to_string(),
)
.into()),
}
}
async fn list_user_roles_by_user_id(
&self,
user_id: &str,
@ -634,4 +644,83 @@ impl UserRoleInterface for MockDb {
Ok(filtered_roles)
}
async fn find_user_role_by_user_id_and_lineage(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
merchant_id: &id_type::MerchantId,
profile_id: Option<&String>,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError> {
let user_roles = self.user_roles.lock().await;
for user_role in user_roles.iter() {
let org_level_check = user_role.org_id.as_ref() == Some(org_id)
&& user_role.merchant_id.is_none()
&& 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.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.profile_id.as_ref() == profile_id;
// Check if any condition matches and the version matches
if user_role.user_id == user_id
&& (org_level_check || merchant_level_check || profile_level_check)
&& user_role.version == version
{
return Ok(user_role.clone());
}
}
Err(errors::StorageError::ValueNotFound(format!(
"No user role available for user_id = {} in the current token hierarchy",
user_id
))
.into())
}
async fn delete_user_role_by_user_id_and_lineage(
&self,
user_id: &str,
org_id: &id_type::OrganizationId,
merchant_id: &id_type::MerchantId,
profile_id: Option<&String>,
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError> {
let mut user_roles = self.user_roles.lock().await;
// Find the position of the user role to delete
let index = user_roles.iter().position(|role| {
let org_level_check = role.org_id.as_ref() == Some(org_id)
&& role.merchant_id.is_none()
&& role.profile_id.is_none();
let merchant_level_check = role.org_id.as_ref() == Some(org_id)
&& role.merchant_id.as_ref() == Some(merchant_id)
&& role.profile_id.is_none();
let profile_level_check = role.org_id.as_ref() == Some(org_id)
&& role.merchant_id.as_ref() == Some(merchant_id)
&& role.profile_id.as_ref() == profile_id;
// Check if the user role matches the conditions and the version matches
role.user_id == user_id
&& (org_level_check || merchant_level_check || profile_level_check)
&& role.version == version
});
// Remove and return the user role if found
match index {
Some(idx) => Ok(user_roles.remove(idx)),
None => Err(errors::StorageError::ValueNotFound(
"Cannot find user role to delete".to_string(),
)
.into()),
}
}
}