Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3085,6 +3085,8 @@ pub enum ApiVersion {
Debug,
Eq,
PartialEq,
Ord,
PartialOrd,
serde::Deserialize,
serde::Serialize,
strum::Display,
Expand All @@ -3095,10 +3097,10 @@ pub enum ApiVersion {
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum EntityType {
Internal,
Organization,
Merchant,
Profile,
Internal = 3,
Organization = 2,
Merchant = 1,
Profile = 0,
}

#[derive(Clone, Debug, serde::Serialize)]
Expand Down
67 changes: 67 additions & 0 deletions crates/diesel_models/src/query/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,71 @@ impl UserRole {
)
.await
}

pub async fn find_by_user_id_org_id_merchant_id_profile_id(
conn: &PgPooledConn,
user_id: String,
org_id: id_type::OrganizationId,
merchant_id: id_type::MerchantId,
profile_id: Option<String>,
version: UserRoleVersion,
) -> StorageResult<Self> {
// Checking in user roles, for a user in token hierarchy, only one of the relation will be true, either org level, merchant level or profile level
// (org_id = ? && merchant_id = null && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = ?)
let check_lineage = dsl::org_id
.eq(org_id.clone())
.and(dsl::merchant_id.is_null().and(dsl::profile_id.is_null()))
.or(dsl::org_id.eq(org_id.clone()).and(
dsl::merchant_id
.eq(merchant_id.clone())
.and(dsl::profile_id.is_null()),
))
.or(dsl::org_id.eq(org_id).and(
dsl::merchant_id
.eq(merchant_id)
//TODO: In case of None, profile_id = NULL its unexpected behaviour, after V1 profile id will not be option
.and(dsl::profile_id.eq(profile_id)),
));

let predicate = dsl::user_id
.eq(user_id)
.and(check_lineage)
.and(dsl::version.eq(version));

generics::generic_find_one::<<Self as HasTable>::Table, _, _>(conn, predicate).await
}

pub async fn delete_by_user_id_org_id_merchant_id_profile_id(
conn: &PgPooledConn,
user_id: String,
org_id: id_type::OrganizationId,
merchant_id: id_type::MerchantId,
profile_id: Option<String>,
version: UserRoleVersion,
) -> StorageResult<Self> {
// Checking in user roles, for a user in token hierarchy, only one of the relation will be true, either org level, merchant level or profile level
// (org_id = ? && merchant_id = null && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = ?)
let check_lineage = dsl::org_id
.eq(org_id.clone())
.and(dsl::merchant_id.is_null().and(dsl::profile_id.is_null()))
.or(dsl::org_id.eq(org_id.clone()).and(
dsl::merchant_id
.eq(merchant_id.clone())
.and(dsl::profile_id.is_null()),
))
.or(dsl::org_id.eq(org_id).and(
dsl::merchant_id
.eq(merchant_id)
//TODO: In case of None, profile_id = NULL its unexpected behaviour, after V1 profile id will not be option
.and(dsl::profile_id.eq(profile_id)),
));

let predicate = dsl::user_id
.eq(user_id)
.and(check_lineage)
.and(dsl::version.eq(version));

generics::generic_delete_one_with_result::<<Self as HasTable>::Table, _, _>(conn, predicate)
.await
}
}
169 changes: 132 additions & 37 deletions crates/router/src/core/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,68 +342,163 @@ 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
.list_user_roles_by_user_id(user_from_db.get_user_id(), UserRoleVersion::V1)
.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)?;

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 !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 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,
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)?;
if !role_info.is_deletable() {
return Err(report!(UserErrors::InvalidDeleteOperation))
.attach_printable(format!("role_id = {} is not deletable", user_role.role_id));
.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());
}
} else {
return Err(report!(UserErrors::InvalidDeleteOperation))
.attach_printable("User is not associated with the merchant");
}
}
};

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)?;

let deleted_user_role = if user_roles.len() > 1 {
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_merchant_id(
.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")?
} else {
.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");
Comment on lines +480 to +482
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

500 is not handled.

}

// Check if user has any more role associations
let user_roles = state
.store
.list_user_roles_by_user_id(user_from_db.get_user_id(), UserRoleVersion::V1)
.await
.change_context(UserErrors::InternalServerError)?;

// If user has no more role associated with him then deleting user
if user_roles.is_empty() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can check if v2 roles are also 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)
}
36 changes: 36 additions & 0 deletions crates/router/src/db/kafka_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2666,6 +2666,42 @@ 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.find_user_role_by_user_id_and_lineage(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.find_user_role_by_user_id_and_lineage(
self.diesel_store.find_user_role_by_user_id_and_lineage(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In kakfaStore if we can self.functions will causes stack_overflow. So avoid following that.

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.delete_user_role_by_user_id_and_lineage(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.delete_user_role_by_user_id_and_lineage(
self.diesel_store.delete_user_role_by_user_id_and_lineage(

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In kakfaStore if we can self.functions will causes stack_overflow. So avoid following that.

user_id,
org_id,
merchant_id,
profile_id,
version,
)
.await
}

async fn transfer_org_ownership_between_users(
&self,
from_user_id: &str,
Expand Down
Loading
Loading