feat(users): custom role at profile read (#6875)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com>
This commit is contained in:
Riddhiagrawal001
2025-02-05 19:02:38 +05:30
committed by GitHub
parent 6f90b93cee
commit 899c207d58
19 changed files with 491 additions and 283 deletions

View File

@ -637,6 +637,7 @@ async fn handle_invitation(
&request.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
&user_from_token.profile_id,
user_from_token
.tenant_id
.as_ref()

View File

@ -127,6 +127,7 @@ pub async fn update_user_role(
&req.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
&user_from_token.profile_id,
user_from_token
.tenant_id
.as_ref()
@ -551,6 +552,7 @@ pub async fn delete_user_role(
&role_to_be_deleted.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
&user_from_token.profile_id,
user_from_token
.tenant_id
.as_ref()
@ -625,6 +627,7 @@ pub async fn delete_user_role(
&role_to_be_deleted.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
&user_from_token.profile_id,
user_from_token
.tenant_id
.as_ref()

View File

@ -1,9 +1,9 @@
use std::collections::HashSet;
use std::{cmp, collections::HashSet};
use api_models::user_role::role as role_api;
use common_enums::{EntityType, ParentGroup, PermissionGroup, RoleScope};
use common_enums::{EntityType, ParentGroup, PermissionGroup};
use common_utils::generate_id_with_default_len;
use diesel_models::role::{RoleNew, RoleUpdate};
use diesel_models::role::{ListRolesByEntityPayload, RoleNew, RoleUpdate};
use error_stack::{report, ResultExt};
use crate::{
@ -65,6 +65,43 @@ pub async fn create_role(
_req_state: ReqState,
) -> UserResponse<role_api::RoleInfoWithGroupsResponse> {
let now = common_utils::date_time::now();
let user_entity_type = user_from_token
.get_role_info_from_db(&state)
.await
.attach_printable("Invalid role_id in JWT")?
.get_entity_type();
let role_entity_type = req.entity_type.unwrap_or(EntityType::Merchant);
if matches!(role_entity_type, EntityType::Organization) {
return Err(report!(UserErrors::InvalidRoleOperation))
.attach_printable("User trying to create org level custom role");
}
// TODO: Remove in PR custom-role-write-pr
if matches!(role_entity_type, EntityType::Profile) {
return Err(report!(UserErrors::InvalidRoleOperation))
.attach_printable("User trying to create profile level custom role");
}
let requestor_entity_from_role_scope = EntityType::from(req.role_scope);
if requestor_entity_from_role_scope < role_entity_type {
return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
"User is trying to create role of type {} and scope {}",
role_entity_type, requestor_entity_from_role_scope
));
}
let max_from_scope_and_entity = cmp::max(requestor_entity_from_role_scope, role_entity_type);
if user_entity_type < max_from_scope_and_entity {
return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
"{} is trying to create of scope {} and of type {}",
user_entity_type, requestor_entity_from_role_scope, role_entity_type
));
}
let role_name = RoleName::new(req.role_name)?;
utils::user_role::validate_role_groups(&req.groups)?;
@ -77,33 +114,38 @@ pub async fn create_role(
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
&user_from_token.profile_id,
&role_entity_type,
)
.await?;
let user_role_info = user_from_token.get_role_info_from_db(&state).await?;
if matches!(req.role_scope, RoleScope::Organization)
&& user_role_info.get_entity_type() < EntityType::Organization
{
return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(
"User does not have sufficient privileges to perform organization-level role operation",
);
}
let (org_id, merchant_id, profile_id) = match role_entity_type {
EntityType::Organization | EntityType::Tenant => {
(user_from_token.org_id, user_from_token.merchant_id, None)
}
EntityType::Merchant => (user_from_token.org_id, user_from_token.merchant_id, None),
EntityType::Profile => (
user_from_token.org_id,
user_from_token.merchant_id,
Some(user_from_token.profile_id),
),
};
let role = state
.global_store
.insert_role(RoleNew {
role_id: generate_id_with_default_len("role"),
role_name: role_name.get_role_name(),
merchant_id: user_from_token.merchant_id,
org_id: user_from_token.org_id,
merchant_id,
org_id,
groups: req.groups,
scope: req.role_scope,
entity_type: EntityType::Merchant,
entity_type: role_entity_type,
created_by: user_from_token.user_id.clone(),
last_modified_by: user_from_token.user_id,
created_at: now,
last_modified_at: now,
profile_id,
tenant_id: user_from_token.tenant_id.unwrap_or(state.tenant.tenant_id),
})
.await
@ -115,6 +157,7 @@ pub async fn create_role(
role_id: role.role_id,
role_name: role.role_name,
role_scope: role.scope,
entity_type: role.entity_type,
},
))
}
@ -146,6 +189,7 @@ pub async fn get_role_with_groups(
role_id: role.role_id,
role_name: role_info.get_role_name().to_string(),
role_scope: role_info.get_scope(),
entity_type: role_info.get_entity_type(),
},
))
}
@ -207,29 +251,12 @@ pub async fn update_role(
) -> UserResponse<role_api::RoleInfoWithGroupsResponse> {
let role_name = req.role_name.map(RoleName::new).transpose()?;
if let Some(ref role_name) = role_name {
utils::user_role::validate_role_name(
&state,
role_name,
&user_from_token.merchant_id,
&user_from_token.org_id,
user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
)
.await?;
}
if let Some(ref groups) = req.groups {
utils::user_role::validate_role_groups(groups)?;
}
let role_info = roles::RoleInfo::from_role_id_in_lineage(
&state,
role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
&user_from_token.profile_id,
user_from_token
.tenant_id
.as_ref()
@ -240,11 +267,38 @@ pub async fn update_role(
let user_role_info = user_from_token.get_role_info_from_db(&state).await?;
if matches!(role_info.get_scope(), RoleScope::Organization)
&& user_role_info.get_entity_type() != EntityType::Organization
{
return Err(report!(UserErrors::InvalidRoleOperation))
.attach_printable("Non org admin user changing org level role");
let requested_entity_from_role_scope = EntityType::from(role_info.get_scope());
let requested_role_entity_type = role_info.get_entity_type();
let max_from_scope_and_entity =
cmp::max(requested_entity_from_role_scope, requested_role_entity_type);
if user_role_info.get_entity_type() < max_from_scope_and_entity {
return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
"{} is trying to update of scope {} and of type {}",
user_role_info.get_entity_type(),
requested_entity_from_role_scope,
requested_role_entity_type
));
}
if let Some(ref role_name) = role_name {
utils::user_role::validate_role_name(
&state,
role_name,
&user_from_token.merchant_id,
&user_from_token.org_id,
user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
&user_from_token.profile_id,
&role_info.get_entity_type(),
)
.await?;
}
if let Some(ref groups) = req.groups {
utils::user_role::validate_role_groups(groups)?;
}
let updated_role = state
@ -269,6 +323,7 @@ pub async fn update_role(
role_id: updated_role.role_id,
role_name: updated_role.role_name,
role_scope: updated_role.scope,
entity_type: updated_role.entity_type,
},
))
}
@ -296,40 +351,51 @@ pub async fn list_roles_with_info(
.collect::<Vec<_>>();
let user_role_entity = user_role_info.get_entity_type();
let is_lineage_data_required = request.entity_type.is_none();
let tenant_id = user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id)
.to_owned();
let custom_roles =
match utils::user_role::get_min_entity(user_role_entity, request.entity_type)? {
EntityType::Tenant | EntityType::Organization => state
.global_store
.list_roles_for_org_by_parameters(
user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
&user_from_token.org_id,
None,
request.entity_type,
None,
.generic_list_roles_by_entity_type(
ListRolesByEntityPayload::Organization,
is_lineage_data_required,
tenant_id,
user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
EntityType::Merchant => state
.global_store
.list_roles_for_org_by_parameters(
user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
&user_from_token.org_id,
Some(&user_from_token.merchant_id),
request.entity_type,
None,
.generic_list_roles_by_entity_type(
ListRolesByEntityPayload::Merchant(user_from_token.merchant_id),
is_lineage_data_required,
tenant_id,
user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
EntityType::Profile => state
.global_store
.generic_list_roles_by_entity_type(
ListRolesByEntityPayload::Profile(
user_from_token.merchant_id,
user_from_token.profile_id,
),
is_lineage_data_required,
tenant_id,
user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
// TODO: Populate this from Db function when support for profile id and profile level custom roles is added
EntityType::Profile => Vec::new(),
};
role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from));
@ -378,18 +444,21 @@ pub async fn list_roles_at_entity_level(
.map(|(_, role_info)| role_info.clone())
.collect::<Vec<_>>();
let tenant_id = user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id)
.to_owned();
let is_lineage_data_required = false;
let custom_roles = match req.entity_type {
EntityType::Tenant | EntityType::Organization => state
.global_store
.list_roles_for_org_by_parameters(
user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
&user_from_token.org_id,
None,
Some(req.entity_type),
None,
.generic_list_roles_by_entity_type(
ListRolesByEntityPayload::Organization,
is_lineage_data_required,
tenant_id,
user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)
@ -397,21 +466,30 @@ pub async fn list_roles_at_entity_level(
EntityType::Merchant => state
.global_store
.list_roles_for_org_by_parameters(
user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
&user_from_token.org_id,
Some(&user_from_token.merchant_id),
Some(req.entity_type),
None,
.generic_list_roles_by_entity_type(
ListRolesByEntityPayload::Merchant(user_from_token.merchant_id),
is_lineage_data_required,
tenant_id,
user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
EntityType::Profile => state
.global_store
.generic_list_roles_by_entity_type(
ListRolesByEntityPayload::Profile(
user_from_token.merchant_id,
user_from_token.profile_id,
),
is_lineage_data_required,
tenant_id,
user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
// TODO: Populate this from Db function when support for profile id and profile level custom roles is added
EntityType::Profile => Vec::new(),
};
role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from));

View File

@ -3611,28 +3611,16 @@ impl RoleInterface for KafkaStore {
self.diesel_store.find_role_by_role_id(role_id).await
}
//TODO:Remove once find_by_role_id_in_lineage is stable
async fn find_role_by_role_id_in_merchant_scope(
&self,
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> CustomResult<storage::Role, errors::StorageError> {
self.diesel_store
.find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id, tenant_id)
.await
}
async fn find_role_by_role_id_in_lineage(
&self,
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
profile_id: &id_type::ProfileId,
tenant_id: &id_type::TenantId,
) -> CustomResult<storage::Role, errors::StorageError> {
self.diesel_store
.find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, tenant_id)
.find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, profile_id, tenant_id)
.await
}
@ -3664,17 +3652,7 @@ impl RoleInterface for KafkaStore {
self.diesel_store.delete_role_by_role_id(role_id).await
}
async fn list_all_roles(
&self,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
self.diesel_store
.list_all_roles(merchant_id, org_id, tenant_id)
.await
}
//TODO: Remove once generic_list_roles_by_entity_type is stable
async fn list_roles_for_org_by_parameters(
&self,
tenant_id: &id_type::TenantId,
@ -3687,6 +3665,18 @@ impl RoleInterface for KafkaStore {
.list_roles_for_org_by_parameters(tenant_id, org_id, merchant_id, entity_type, limit)
.await
}
async fn generic_list_roles_by_entity_type(
&self,
payload: diesel_models::role::ListRolesByEntityPayload,
is_lineage_data_required: bool,
tenant_id: id_type::TenantId,
org_id: id_type::OrganizationId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
self.diesel_store
.generic_list_roles_by_entity_type(payload, is_lineage_data_required, tenant_id, org_id)
.await
}
}
#[async_trait::async_trait]

View File

@ -1,6 +1,8 @@
use common_enums::enums;
use common_utils::id_type;
use diesel_models::role as storage;
use diesel_models::{
enums::{EntityType, RoleScope},
role as storage,
};
use error_stack::report;
use router_env::{instrument, tracing};
@ -23,20 +25,12 @@ pub trait RoleInterface {
role_id: &str,
) -> CustomResult<storage::Role, errors::StorageError>;
//TODO:Remove once find_by_role_id_in_lineage is stable
async fn find_role_by_role_id_in_merchant_scope(
&self,
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> CustomResult<storage::Role, errors::StorageError>;
async fn find_role_by_role_id_in_lineage(
&self,
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
profile_id: &id_type::ProfileId,
tenant_id: &id_type::TenantId,
) -> CustomResult<storage::Role, errors::StorageError>;
@ -58,21 +52,23 @@ pub trait RoleInterface {
role_id: &str,
) -> CustomResult<storage::Role, errors::StorageError>;
async fn list_all_roles(
&self,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError>;
//TODO: Remove once generic_list_roles_by_entity_type is stable
async fn list_roles_for_org_by_parameters(
&self,
tenant_id: &id_type::TenantId,
org_id: &id_type::OrganizationId,
merchant_id: Option<&id_type::MerchantId>,
entity_type: Option<enums::EntityType>,
entity_type: Option<EntityType>,
limit: Option<u32>,
) -> CustomResult<Vec<storage::Role>, errors::StorageError>;
async fn generic_list_roles_by_entity_type(
&self,
payload: storage::ListRolesByEntityPayload,
is_lineage_data_required: bool,
tenant_id: id_type::TenantId,
org_id: id_type::OrganizationId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError>;
}
#[async_trait::async_trait]
@ -99,39 +95,26 @@ impl RoleInterface for Store {
.map_err(|error| report!(errors::StorageError::from(error)))
}
//TODO:Remove once find_by_role_id_in_lineage is stable
#[instrument(skip_all)]
async fn find_role_by_role_id_in_merchant_scope(
&self,
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> CustomResult<storage::Role, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::Role::find_by_role_id_in_merchant_scope(
&conn,
role_id,
merchant_id,
org_id,
tenant_id,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
async fn find_role_by_role_id_in_lineage(
&self,
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
profile_id: &id_type::ProfileId,
tenant_id: &id_type::TenantId,
) -> CustomResult<storage::Role, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::Role::find_by_role_id_in_lineage(&conn, role_id, merchant_id, org_id, tenant_id)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
storage::Role::find_by_role_id_in_lineage(
&conn,
role_id,
merchant_id,
org_id,
profile_id,
tenant_id,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
@ -170,26 +153,14 @@ impl RoleInterface for Store {
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
async fn list_all_roles(
&self,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::Role::list_roles(&conn, merchant_id, org_id, tenant_id)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
//TODO: Remove once generic_list_roles_by_entity_type is stable
#[instrument(skip_all)]
async fn list_roles_for_org_by_parameters(
&self,
tenant_id: &id_type::TenantId,
org_id: &id_type::OrganizationId,
merchant_id: Option<&id_type::MerchantId>,
entity_type: Option<enums::EntityType>,
entity_type: Option<EntityType>,
limit: Option<u32>,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
@ -204,6 +175,26 @@ impl RoleInterface for Store {
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
#[instrument(skip_all)]
async fn generic_list_roles_by_entity_type(
&self,
payload: storage::ListRolesByEntityPayload,
is_lineage_data_required: bool,
tenant_id: id_type::TenantId,
org_id: id_type::OrganizationId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::Role::generic_list_roles_by_entity_type(
&conn,
payload,
is_lineage_data_required,
tenant_id,
org_id,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
}
#[async_trait::async_trait]
@ -234,6 +225,7 @@ impl RoleInterface for MockDb {
created_at: role.created_at,
last_modified_at: role.last_modified_at,
last_modified_by: role.last_modified_by,
profile_id: role.profile_id,
tenant_id: role.tenant_id,
};
roles.push(role.clone());
@ -257,38 +249,12 @@ impl RoleInterface for MockDb {
)
}
// TODO: Remove once find_by_role_id_in_lineage is stable
async fn find_role_by_role_id_in_merchant_scope(
&self,
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> CustomResult<storage::Role, errors::StorageError> {
let roles = self.roles.lock().await;
roles
.iter()
.find(|role| {
role.role_id == role_id
&& (role.tenant_id == *tenant_id)
&& (role.merchant_id == *merchant_id
|| (role.org_id == *org_id && role.scope == enums::RoleScope::Organization))
})
.cloned()
.ok_or(
errors::StorageError::ValueNotFound(format!(
"No role available in merchant scope for role_id = {role_id}, \
merchant_id = {merchant_id:?} and org_id = {org_id:?}"
))
.into(),
)
}
async fn find_role_by_role_id_in_lineage(
&self,
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
profile_id: &id_type::ProfileId,
tenant_id: &id_type::TenantId,
) -> CustomResult<storage::Role, errors::StorageError> {
let roles = self.roles.lock().await;
@ -298,9 +264,15 @@ impl RoleInterface for MockDb {
role.role_id == role_id
&& (role.tenant_id == *tenant_id)
&& role.org_id == *org_id
&& ((role.scope == enums::RoleScope::Organization)
|| (role.merchant_id == *merchant_id
&& role.scope == enums::RoleScope::Merchant))
&& ((role.scope == RoleScope::Organization)
|| (role.merchant_id == *merchant_id && role.scope == RoleScope::Merchant)
|| (role
.profile_id
.as_ref()
.is_some_and(|profile_id_from_role| {
profile_id_from_role == profile_id
&& role.scope == RoleScope::Profile
})))
})
.cloned()
.ok_or(
@ -382,43 +354,14 @@ impl RoleInterface for MockDb {
Ok(roles.remove(role_index))
}
async fn list_all_roles(
&self,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
let roles = self.roles.lock().await;
let roles_list: Vec<_> = roles
.iter()
.filter(|role| {
role.tenant_id == *tenant_id
&& (role.merchant_id == *merchant_id
|| (role.org_id == *org_id
&& role.scope == diesel_models::enums::RoleScope::Organization))
})
.cloned()
.collect();
if roles_list.is_empty() {
return Err(errors::StorageError::ValueNotFound(format!(
"No role found for merchant id = {:?} and org_id = {:?}",
merchant_id, org_id
))
.into());
}
Ok(roles_list)
}
//TODO: Remove once generic_list_roles_by_entity_type is stable
#[instrument(skip_all)]
async fn list_roles_for_org_by_parameters(
&self,
tenant_id: &id_type::TenantId,
org_id: &id_type::OrganizationId,
merchant_id: Option<&id_type::MerchantId>,
entity_type: Option<enums::EntityType>,
entity_type: Option<EntityType>,
limit: Option<u32>,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
let roles = self.roles.lock().await;
@ -442,4 +385,72 @@ impl RoleInterface for MockDb {
Ok(roles_list)
}
#[instrument(skip_all)]
async fn generic_list_roles_by_entity_type(
&self,
payload: storage::ListRolesByEntityPayload,
is_lineage_data_required: bool,
tenant_id: id_type::TenantId,
org_id: id_type::OrganizationId,
) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
let roles = self.roles.lock().await;
let roles_list: Vec<_> = roles
.iter()
.filter(|role| match &payload {
storage::ListRolesByEntityPayload::Organization => {
let entity_in_vec = if is_lineage_data_required {
vec![
EntityType::Organization,
EntityType::Merchant,
EntityType::Profile,
]
} else {
vec![EntityType::Organization]
};
role.tenant_id == tenant_id
&& role.org_id == org_id
&& entity_in_vec.contains(&role.entity_type)
}
storage::ListRolesByEntityPayload::Merchant(merchant_id) => {
let entity_in_vec = if is_lineage_data_required {
vec![EntityType::Merchant, EntityType::Profile]
} else {
vec![EntityType::Merchant]
};
role.tenant_id == tenant_id
&& role.org_id == org_id
&& (role.scope == RoleScope::Organization
|| role.merchant_id == *merchant_id)
&& entity_in_vec.contains(&role.entity_type)
}
storage::ListRolesByEntityPayload::Profile(merchant_id, profile_id) => {
let entity_in_vec = [EntityType::Profile];
let matches_merchant =
role.merchant_id == *merchant_id && role.scope == RoleScope::Merchant;
let matches_profile =
role.profile_id
.as_ref()
.is_some_and(|profile_id_from_role| {
profile_id_from_role == profile_id
&& role.scope == RoleScope::Profile
});
role.tenant_id == tenant_id
&& role.org_id == org_id
&& (role.scope == RoleScope::Organization
|| matches_merchant
|| matches_profile)
&& entity_in_vec.contains(&role.entity_type)
}
})
.cloned()
.collect();
Ok(roles_list)
}
}

View File

@ -121,6 +121,7 @@ impl RoleInfo {
role_id: &str,
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
profile_id: &id_type::ProfileId,
tenant_id: &id_type::TenantId,
) -> CustomResult<Self, errors::StorageError> {
if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
@ -128,7 +129,13 @@ impl RoleInfo {
} else {
state
.global_store
.find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, tenant_id)
.find_role_by_role_id_in_lineage(
role_id,
merchant_id,
org_id,
profile_id,
tenant_id,
)
.await
.map(Self::from)
}

View File

@ -4,6 +4,7 @@ use common_enums::{EntityType, PermissionGroup};
use common_utils::id_type;
use diesel_models::{
enums::{UserRoleVersion, UserStatus},
role::ListRolesByEntityPayload,
user_role::{UserRole, UserRoleUpdate},
};
use error_stack::{report, Report, ResultExt};
@ -49,6 +50,8 @@ pub async fn validate_role_name(
merchant_id: &id_type::MerchantId,
org_id: &id_type::OrganizationId,
tenant_id: &id_type::TenantId,
profile_id: &id_type::ProfileId,
entity_type: &EntityType,
) -> UserResult<()> {
let role_name_str = role_name.clone().get_role_name();
@ -56,16 +59,37 @@ pub async fn validate_role_name(
.iter()
.any(|(_, role_info)| role_info.get_role_name() == role_name_str);
// TODO: Create and use find_by_role_name to make this efficient
let is_present_in_custom_roles = state
.global_store
.list_all_roles(merchant_id, org_id, tenant_id)
.await
.change_context(UserErrors::InternalServerError)?
.iter()
.any(|role| role.role_name == role_name_str);
let entity_type_for_role = match entity_type {
EntityType::Tenant | EntityType::Organization => ListRolesByEntityPayload::Organization,
EntityType::Merchant => ListRolesByEntityPayload::Merchant(merchant_id.to_owned()),
EntityType::Profile => {
ListRolesByEntityPayload::Profile(merchant_id.to_owned(), profile_id.to_owned())
}
};
if is_present_in_predefined_roles || is_present_in_custom_roles {
let is_present_in_custom_role = match state
.global_store
.generic_list_roles_by_entity_type(
entity_type_for_role,
false,
tenant_id.to_owned(),
org_id.to_owned(),
)
.await
{
Ok(roles_list) => roles_list
.iter()
.any(|role| role.role_name == role_name_str),
Err(e) => {
if e.current_context().is_db_not_found() {
false
} else {
return Err(UserErrors::InternalServerError.into());
}
}
};
if is_present_in_predefined_roles || is_present_in_custom_role {
return Err(UserErrors::RoleNameAlreadyExists.into());
}