mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +08:00
feat(users): Add API to list users in user lineage (#5722)
This commit is contained in:
@ -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,
|
||||
}
|
||||
|
||||
@ -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")]
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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<_>, _>>()?,
|
||||
))
|
||||
}
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)),
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user