feat(users): Add API to list users in user lineage (#5722)

This commit is contained in:
Mani Chandra
2024-08-28 17:29:41 +05:30
committed by GitHub
parent 32dd3f97ad
commit 20f20da94e
18 changed files with 412 additions and 48 deletions

View File

@ -418,3 +418,15 @@ pub struct ListProfilesForUserInOrgAndMerchantAccountResponse {
pub profile_id: id_type::ProfileId,
pub profile_name: String,
}
#[derive(Debug, serde::Serialize)]
pub struct ListUsersInEntityResponse {
pub email: pii::Email,
pub roles: Vec<MinimalRoleInfo>,
}
#[derive(Debug, serde::Serialize, Clone)]
pub struct MinimalRoleInfo {
pub role_id: String,
pub role_name: String,
}

View File

@ -3094,6 +3094,7 @@ pub enum ApiVersion {
strum::Display,
strum::EnumString,
ToSchema,
Hash,
)]
#[router_derive::diesel_enum(storage_type = "text")]
#[strum(serialize_all = "snake_case")]

View File

@ -142,3 +142,10 @@ pub const MAX_ALLOWED_MERCHANT_NAME_LENGTH: usize = 64;
/// Default locale
pub const DEFAULT_LOCALE: &str = "en";
/// Role ID for Org Admin
pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin";
/// Role ID for Internal View Only
pub const ROLE_ID_INTERNAL_VIEW_ONLY_USER: &str = "internal_view_only";
/// Role ID for Internal Admin
pub const ROLE_ID_INTERNAL_ADMIN: &str = "internal_admin";

View File

@ -185,7 +185,7 @@ impl UserRole {
.await
}
pub async fn generic_user_roles_list(
pub async fn generic_user_roles_list_for_user(
conn: &PgPooledConn,
user_id: String,
org_id: Option<id_type::OrganizationId>,
@ -235,4 +235,50 @@ impl UserRole {
},
}
}
pub async fn generic_user_roles_list_for_org_and_extra(
conn: &PgPooledConn,
user_id: Option<String>,
org_id: id_type::OrganizationId,
merchant_id: Option<id_type::MerchantId>,
profile_id: Option<id_type::ProfileId>,
version: Option<UserRoleVersion>,
) -> StorageResult<Vec<Self>> {
let mut query = <Self as HasTable>::table()
.filter(dsl::org_id.eq(org_id))
.into_boxed();
if let Some(user_id) = user_id {
query = query.filter(dsl::user_id.eq(user_id));
}
if let Some(merchant_id) = merchant_id {
query = query.filter(dsl::merchant_id.eq(merchant_id));
}
if let Some(profile_id) = profile_id {
query = query.filter(dsl::profile_id.eq(profile_id));
}
if let Some(version) = version {
query = query.filter(dsl::version.eq(version));
}
router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());
match generics::db_metrics::track_database_call::<Self, _, _>(
query.get_results_async(conn),
generics::db_metrics::DatabaseOperation::Filter,
)
.await
{
Ok(value) => Ok(value),
Err(err) => match err {
DieselError::NotFound => {
Err(report!(err)).change_context(errors::DatabaseError::NotFound)
}
_ => Err(report!(err)).change_context(errors::DatabaseError::Others),
},
}
}
}

View File

@ -1,11 +1,13 @@
use std::hash::Hash;
use common_enums::EntityType;
use common_utils::id_type;
use common_utils::{consts, id_type};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use time::PrimitiveDateTime;
use crate::{enums, schema::user_roles};
#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
#[derive(Clone, Debug, Identifiable, Queryable, Selectable, Eq)]
#[diesel(table_name = user_roles, check_for_backend(diesel::pg::Pg))]
pub struct UserRole {
pub id: i32,
@ -24,6 +26,55 @@ pub struct UserRole {
pub version: enums::UserRoleVersion,
}
fn get_entity_id_and_type(user_role: &UserRole) -> (Option<String>, Option<EntityType>) {
match (user_role.version, user_role.role_id.as_str()) {
(enums::UserRoleVersion::V1, consts::ROLE_ID_ORGANIZATION_ADMIN) => (
user_role
.org_id
.clone()
.map(|org_id| org_id.get_string_repr().to_string()),
Some(EntityType::Organization),
),
(enums::UserRoleVersion::V1, consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER)
| (enums::UserRoleVersion::V1, consts::ROLE_ID_INTERNAL_ADMIN) => (
user_role
.merchant_id
.clone()
.map(|merchant_id| merchant_id.get_string_repr().to_string()),
Some(EntityType::Internal),
),
(enums::UserRoleVersion::V1, _) => (
user_role
.merchant_id
.clone()
.map(|merchant_id| merchant_id.get_string_repr().to_string()),
Some(EntityType::Merchant),
),
(enums::UserRoleVersion::V2, _) => (user_role.entity_id.clone(), user_role.entity_type),
}
}
impl Hash for UserRole {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let (entity_id, entity_type) = get_entity_id_and_type(self);
self.user_id.hash(state);
entity_id.hash(state);
entity_type.hash(state);
}
}
impl PartialEq for UserRole {
fn eq(&self, other: &Self) -> bool {
let (self_entity_id, self_entity_type) = get_entity_id_and_type(self);
let (other_entity_id, other_entity_type) = get_entity_id_and_type(other);
self.user_id == other.user_id
&& self_entity_id == other_entity_id
&& self_entity_type == other_entity_type
}
}
#[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
#[diesel(table_name = user_roles)]
pub struct UserRoleNew {

View File

@ -1,8 +1,5 @@
// User Roles
pub const ROLE_ID_INTERNAL_VIEW_ONLY_USER: &str = "internal_view_only";
pub const ROLE_ID_INTERNAL_ADMIN: &str = "internal_admin";
pub const ROLE_ID_MERCHANT_ADMIN: &str = "merchant_admin";
pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin";
pub const ROLE_ID_MERCHANT_VIEW_ONLY: &str = "merchant_view_only";
pub const ROLE_ID_MERCHANT_IAM_ADMIN: &str = "merchant_iam_admin";
pub const ROLE_ID_MERCHANT_DEVELOPER: &str = "merchant_developer";

View File

@ -88,6 +88,8 @@ pub enum UserErrors {
AuthConfigParsingError,
#[error("Invalid SSO request")]
SSOFailed,
#[error("profile_id missing in JWT")]
JwtProfileIdMissing,
}
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
@ -224,6 +226,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
Self::SSOFailed => {
AER::BadRequest(ApiError::new(sub_code, 46, self.get_error_message(), None))
}
Self::JwtProfileIdMissing => {
AER::Unauthorized(ApiError::new(sub_code, 47, self.get_error_message(), None))
}
}
}
}
@ -271,6 +276,7 @@ impl UserErrors {
Self::InvalidUserAuthMethodOperation => "Invalid user auth method operation",
Self::AuthConfigParsingError => "Auth config parsing error",
Self::SSOFailed => "Invalid SSO request",
Self::JwtProfileIdMissing => "profile_id missing in JWT",
}
}
}

View File

@ -62,7 +62,7 @@ pub async fn signup_with_merchant_id(
let user_role = new_user
.insert_user_role_in_db(
state.clone(),
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
UserStatus::Active,
)
.await?;
@ -134,7 +134,7 @@ pub async fn signup(
let user_role = new_user
.insert_user_role_in_db(
state.clone(),
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
UserStatus::Active,
)
.await?;
@ -165,7 +165,7 @@ pub async fn signup_token_only_flow(
let user_role = new_user
.insert_user_role_in_db(
state.clone(),
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
UserStatus::Active,
)
.await?;
@ -316,7 +316,7 @@ pub async fn connect_account(
let user_role = new_user
.insert_user_role_in_db(
state.clone(),
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
UserStatus::Active,
)
.await?;
@ -1310,7 +1310,7 @@ pub async fn create_internal_user(
new_user
.insert_user_role_in_db(
state,
consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
UserStatus::Active,
)
.await?;
@ -1389,7 +1389,7 @@ pub async fn switch_merchant_id(
} else {
let user_roles = state
.store
.list_user_roles_by_user_id(&user_from_token.user_id, UserRoleVersion::V1)
.list_user_roles_by_user_id_and_version(&user_from_token.user_id, UserRoleVersion::V1)
.await
.change_context(UserErrors::InternalServerError)?;
@ -1450,7 +1450,7 @@ pub async fn create_merchant_account(
let role_insertion_res = new_user
.insert_user_role_in_db(
state.clone(),
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
UserStatus::Active,
)
.await;
@ -1471,7 +1471,10 @@ pub async fn list_merchants_for_user(
) -> UserResponse<Vec<user_api::UserMerchantAccount>> {
let user_roles = state
.store
.list_user_roles_by_user_id(user_from_token.user_id.as_str(), UserRoleVersion::V1)
.list_user_roles_by_user_id_and_version(
user_from_token.user_id.as_str(),
UserRoleVersion::V1,
)
.await
.change_context(UserErrors::InternalServerError)?;
@ -2572,7 +2575,7 @@ pub async fn list_orgs_for_user(
) -> UserResponse<Vec<user_api::ListOrgsForUserResponse>> {
let orgs = state
.store
.list_user_roles(
.list_user_roles_by_user_id(
user_from_token.user_id.as_str(),
None,
None,
@ -2638,7 +2641,7 @@ pub async fn list_merchants_for_user_in_org(
} else {
let merchant_ids = state
.store
.list_user_roles(
.list_user_roles_by_user_id(
user_from_token.user_id.as_str(),
Some(&user_from_token.org_id),
None,
@ -2721,7 +2724,7 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
} else {
let profile_ids = state
.store
.list_user_roles(
.list_user_roles_by_user_id(
user_from_token.user_id.as_str(),
Some(&user_from_token.org_id),
Some(&user_from_token.merchant_id),
@ -2793,7 +2796,7 @@ pub async fn switch_org_for_user(
let user_role = state
.store
.list_user_roles(
.list_user_roles_by_user_id(
&user_from_token.user_id,
Some(&request.org_id),
None,
@ -3012,7 +3015,7 @@ pub async fn switch_merchant_for_user_in_org(
EntityType::Merchant | EntityType::Profile => {
let user_role = state
.store
.list_user_roles(
.list_user_roles_by_user_id(
&user_from_token.user_id,
Some(&user_from_token.org_id),
Some(&request.merchant_id),
@ -3152,7 +3155,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
EntityType::Profile => {
let user_role = state
.store
.list_user_roles(
.list_user_roles_by_user_id(
&user_from_token.user_id,
Some(&user_from_token.org_id),
Some(&user_from_token.merchant_id),

View File

@ -1,4 +1,4 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use api_models::{user as user_api, user_role as user_role_api};
use diesel_models::{
@ -10,6 +10,7 @@ use once_cell::sync::Lazy;
use crate::{
core::errors::{StorageErrorExt, UserErrors, UserResponse},
db::user_role::ListUserRolesByOrgIdPayload,
routes::{app::ReqState, SessionState},
services::{
authentication as auth,
@ -20,7 +21,7 @@ use crate::{
utils,
};
pub mod role;
use common_enums::PermissionGroup;
use common_enums::{EntityType, PermissionGroup};
use strum::IntoEnumIterator;
// TODO: To be deprecated once groups are stable
@ -618,13 +619,13 @@ pub async fn delete_user_role(
// 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)
.list_user_roles_by_user_id_and_version(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)
.list_user_roles_by_user_id_and_version(user_from_db.get_user_id(), UserRoleVersion::V1)
.await
.change_context(UserErrors::InternalServerError)?;
@ -641,3 +642,135 @@ pub async fn delete_user_role(
auth::blacklist::insert_user_in_blacklist(&state, user_from_db.get_user_id()).await?;
Ok(ApplicationResponse::StatusOk)
}
pub async fn list_users_in_lineage(
state: SessionState,
user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_api::ListUsersInEntityResponse>> {
let 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 user_roles_set: HashSet<_> = match requestor_role_info.get_entity_type() {
EntityType::Organization => state
.store
.list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
user_id: None,
org_id: &user_from_token.org_id,
merchant_id: None,
profile_id: None,
version: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.collect(),
EntityType::Merchant => state
.store
.list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
user_id: None,
org_id: &user_from_token.org_id,
merchant_id: Some(&user_from_token.merchant_id),
profile_id: None,
version: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.collect(),
EntityType::Profile => {
let Some(profile_id) = user_from_token.profile_id.as_ref() else {
return Err(UserErrors::JwtProfileIdMissing.into());
};
state
.store
.list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
user_id: None,
org_id: &user_from_token.org_id,
merchant_id: Some(&user_from_token.merchant_id),
profile_id: Some(profile_id),
version: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.collect()
}
EntityType::Internal => HashSet::new(),
};
let mut email_map = state
.global_store
.find_users_by_user_ids(
user_roles_set
.iter()
.map(|user_role| user_role.user_id.clone())
.collect(),
)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(|user| (user.user_id.clone(), user.email))
.collect::<HashMap<_, _>>();
let role_info_map =
futures::future::try_join_all(user_roles_set.iter().map(|user_role| async {
roles::RoleInfo::from_role_id(
&state,
&user_role.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.map(|role_info| {
(
user_role.role_id.clone(),
user_api::MinimalRoleInfo {
role_id: user_role.role_id.clone(),
role_name: role_info.get_role_name().to_string(),
},
)
})
}))
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.collect::<HashMap<_, _>>();
let user_role_map = user_roles_set
.into_iter()
.fold(HashMap::new(), |mut map, user_role| {
map.entry(user_role.user_id)
.or_insert(Vec::with_capacity(1))
.push(user_role.role_id);
map
});
Ok(ApplicationResponse::Json(
user_role_map
.into_iter()
.map(|(user_id, role_id_vec)| {
Ok::<_, error_stack::Report<UserErrors>>(user_api::ListUsersInEntityResponse {
email: email_map
.remove(&user_id)
.ok_or(UserErrors::InternalServerError)?,
roles: role_id_vec
.into_iter()
.map(|role_id| {
role_info_map
.get(&role_id)
.cloned()
.ok_or(UserErrors::InternalServerError)
})
.collect::<Result<Vec<_>, _>>()?,
})
})
.collect::<Result<Vec<_>, _>>()?,
))
}

View File

@ -5,7 +5,6 @@ use diesel_models::role::{RoleNew, RoleUpdate};
use error_stack::{report, ResultExt};
use crate::{
consts,
core::errors::{StorageErrorExt, UserErrors, UserResponse},
routes::{app::ReqState, SessionState},
services::{
@ -72,7 +71,7 @@ pub async fn create_role(
.await?;
if matches!(req.role_scope, RoleScope::Organization)
&& user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN
&& user_from_token.role_id != common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN
{
return Err(report!(UserErrors::InvalidRoleOperation))
.attach_printable("Non org admin user creating org level role");
@ -292,7 +291,7 @@ pub async fn update_role(
.to_not_found_response(UserErrors::InvalidRoleOperation)?;
if matches!(role_info.get_scope(), RoleScope::Organization)
&& user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN
&& user_from_token.role_id != common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN
{
return Err(report!(UserErrors::InvalidRoleOperation))
.attach_printable("Non org admin user changing org level role");

View File

@ -35,7 +35,7 @@ use super::{
user::{sample_data::BatchSampleDataInterface, UserInterface},
user_authentication_method::UserAuthenticationMethodInterface,
user_key_store::UserKeyStoreInterface,
user_role::UserRoleInterface,
user_role::{ListUserRolesByOrgIdPayload, UserRoleInterface},
};
#[cfg(feature = "payouts")]
use crate::services::kafka::payout::KafkaPayout;
@ -2792,13 +2792,13 @@ impl UserRoleInterface for KafkaStore {
.await
}
async fn list_user_roles_by_user_id(
async fn list_user_roles_by_user_id_and_version(
&self,
user_id: &str,
version: enums::UserRoleVersion,
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
self.diesel_store
.list_user_roles_by_user_id(user_id, version)
.list_user_roles_by_user_id_and_version(user_id, version)
.await
}
@ -2871,7 +2871,7 @@ impl UserRoleInterface for KafkaStore {
.await
}
async fn list_user_roles(
async fn list_user_roles_by_user_id(
&self,
user_id: &str,
org_id: Option<&id_type::OrganizationId>,
@ -2881,9 +2881,23 @@ impl UserRoleInterface for KafkaStore {
version: Option<enums::UserRoleVersion>,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
self.diesel_store
.list_user_roles(user_id, org_id, merchant_id, profile_id, entity_id, version)
.list_user_roles_by_user_id(
user_id,
org_id,
merchant_id,
profile_id,
entity_id,
version,
)
.await
}
async fn list_user_roles_by_org_id<'a>(
&self,
payload: ListUserRolesByOrgIdPayload<'a>,
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
self.diesel_store.list_user_roles_by_org_id(payload).await
}
}
#[async_trait::async_trait]

View File

@ -30,7 +30,7 @@ pub trait UserRoleInterface {
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError>;
async fn list_user_roles_by_user_id(
async fn list_user_roles_by_user_id_and_version(
&self,
user_id: &str,
version: enums::UserRoleVersion,
@ -70,7 +70,7 @@ pub trait UserRoleInterface {
version: enums::UserRoleVersion,
) -> CustomResult<storage::UserRole, errors::StorageError>;
async fn list_user_roles(
async fn list_user_roles_by_user_id(
&self,
user_id: &str,
org_id: Option<&id_type::OrganizationId>,
@ -79,6 +79,19 @@ pub trait UserRoleInterface {
entity_id: Option<&String>,
version: Option<enums::UserRoleVersion>,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
async fn list_user_roles_by_org_id<'a>(
&self,
payload: ListUserRolesByOrgIdPayload<'a>,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
}
pub struct ListUserRolesByOrgIdPayload<'a> {
pub user_id: Option<&'a String>,
pub org_id: &'a id_type::OrganizationId,
pub merchant_id: Option<&'a id_type::MerchantId>,
pub profile_id: Option<&'a id_type::ProfileId>,
pub version: Option<enums::UserRoleVersion>,
}
#[async_trait::async_trait]
@ -126,7 +139,7 @@ impl UserRoleInterface for Store {
}
#[instrument(skip_all)]
async fn list_user_roles_by_user_id(
async fn list_user_roles_by_user_id_and_version(
&self,
user_id: &str,
version: enums::UserRoleVersion,
@ -217,7 +230,7 @@ impl UserRoleInterface for Store {
.map_err(|error| report!(errors::StorageError::from(error)))
}
async fn list_user_roles(
async fn list_user_roles_by_user_id(
&self,
user_id: &str,
org_id: Option<&id_type::OrganizationId>,
@ -227,7 +240,7 @@ impl UserRoleInterface for Store {
version: Option<enums::UserRoleVersion>,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::UserRole::generic_user_roles_list(
storage::UserRole::generic_user_roles_list_for_user(
&conn,
user_id.to_owned(),
org_id.cloned(),
@ -239,6 +252,23 @@ impl UserRoleInterface for Store {
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
async fn list_user_roles_by_org_id<'a>(
&self,
payload: ListUserRolesByOrgIdPayload<'a>,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
let conn = connection::pg_connection_read(self).await?;
storage::UserRole::generic_user_roles_list_for_org_and_extra(
&conn,
payload.user_id.cloned(),
payload.org_id.to_owned(),
payload.merchant_id.cloned(),
payload.profile_id.cloned(),
payload.version,
)
.await
.map_err(|error| report!(errors::StorageError::from(error)))
}
}
#[async_trait::async_trait]
@ -328,7 +358,7 @@ impl UserRoleInterface for MockDb {
.into())
}
async fn list_user_roles_by_user_id(
async fn list_user_roles_by_user_id_and_version(
&self,
user_id: &str,
version: enums::UserRoleVersion,
@ -505,7 +535,7 @@ impl UserRoleInterface for MockDb {
}
}
async fn list_user_roles(
async fn list_user_roles_by_user_id(
&self,
user_id: &str,
org_id: Option<&id_type::OrganizationId>,
@ -551,4 +581,48 @@ impl UserRoleInterface for MockDb {
Ok(filtered_roles)
}
async fn list_user_roles_by_org_id<'a>(
&self,
payload: ListUserRolesByOrgIdPayload<'a>,
) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
let user_roles = self.user_roles.lock().await;
let mut filtered_roles = Vec::new();
for role in user_roles.iter() {
let role_org_id = role
.org_id
.as_ref()
.ok_or(report!(errors::StorageError::MockDbError))?;
let mut filter_condition = role_org_id == payload.org_id;
if let Some(user_id) = payload.user_id {
filter_condition = filter_condition && user_id == &role.user_id
}
role.merchant_id.as_ref().zip(payload.merchant_id).inspect(
|(role_merchant_id, merchant_id)| {
filter_condition = filter_condition && role_merchant_id == merchant_id
},
);
role.profile_id.as_ref().zip(payload.profile_id).inspect(
|(role_profile_id, profile_id)| {
filter_condition = filter_condition && role_profile_id == profile_id
},
);
payload
.version
.inspect(|ver| filter_condition = filter_condition && ver == &role.version);
if filter_condition {
filtered_roles.push(role.clone())
}
}
Ok(filtered_roles)
}
}

View File

@ -1799,6 +1799,7 @@ impl User {
.service(
web::resource("/list").route(web::get().to(list_users_for_merchant_account)),
)
.service(web::resource("/v2/list").route(web::get().to(list_users_in_lineage)))
.service(
web::resource("/invite_multiple").route(web::post().to(invite_multiple_user)),
)

View File

@ -263,7 +263,8 @@ impl From<Flow> for ApiIdentifier {
| Flow::DeleteUserRole
| Flow::CreateRole
| Flow::UpdateRole
| Flow::UserFromEmail => Self::UserRole,
| Flow::UserFromEmail
| Flow::ListUsersInLineage => Self::UserRole,
Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => {
Self::ConnectorOnboarding

View File

@ -269,3 +269,20 @@ pub async fn get_role_information(
))
.await
}
pub async fn list_users_in_lineage(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
let flow = Flow::ListUsersInLineage;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
(),
|state, user_from_token, _, _| {
user_role_core::list_users_in_lineage(state, user_from_token)
},
&auth::DashboardNoPermissionAuth,
api_locking::LockAction::NotApplicable,
))
.await
}

View File

@ -9,7 +9,7 @@ use crate::consts;
pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|| {
let mut roles = HashMap::new();
roles.insert(
consts::user_role::ROLE_ID_INTERNAL_ADMIN,
common_utils::consts::ROLE_ID_INTERNAL_ADMIN,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
@ -25,7 +25,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsManage,
PermissionGroup::OrganizationManage,
],
role_id: consts::user_role::ROLE_ID_INTERNAL_ADMIN.to_string(),
role_id: common_utils::consts::ROLE_ID_INTERNAL_ADMIN.to_string(),
role_name: "internal_admin".to_string(),
scope: RoleScope::Organization,
entity_type: EntityType::Internal,
@ -36,7 +36,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
},
);
roles.insert(
consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER,
common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
@ -46,7 +46,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::UsersView,
PermissionGroup::MerchantDetailsView,
],
role_id: consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
role_id: common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
role_name: "internal_view_only".to_string(),
scope: RoleScope::Organization,
entity_type: EntityType::Internal,
@ -58,7 +58,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
);
roles.insert(
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN,
common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
@ -74,7 +74,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsManage,
PermissionGroup::OrganizationManage,
],
role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
role_name: "organization_admin".to_string(),
scope: RoleScope::Organization,
entity_type: EntityType::Organization,

View File

@ -868,7 +868,7 @@ impl UserFromStorage {
pub async fn get_roles_from_db(&self, state: &SessionState) -> UserResult<Vec<UserRole>> {
state
.store
.list_user_roles_by_user_id(&self.0.user_id, UserRoleVersion::V1)
.list_user_roles_by_user_id_and_version(&self.0.user_id, UserRoleVersion::V1)
.await
.change_context(UserErrors::InternalServerError)
}
@ -949,7 +949,7 @@ impl UserFromStorage {
} else {
state
.store
.list_user_roles_by_user_id(&self.0.user_id, UserRoleVersion::V1)
.list_user_roles_by_user_id_and_version(&self.0.user_id, UserRoleVersion::V1)
.await?
.into_iter()
.find(|role| role.status == UserStatus::Active)

View File

@ -466,6 +466,8 @@ pub enum Flow {
ListMerchantsForUserInOrg,
/// List Profile for user in org and merchant
ListProfileForUserInOrgAndMerchant,
/// List Users in Org
ListUsersInLineage,
/// List initial webhook delivery attempts
WebhookEventInitialDeliveryAttemptList,
/// List delivery attempts for a webhook event