mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(users): Add entity type filter in list users and list roles API (#5997)
This commit is contained in:
@ -2,12 +2,12 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
|
|||||||
|
|
||||||
use crate::user_role::{
|
use crate::user_role::{
|
||||||
role::{
|
role::{
|
||||||
CreateRoleRequest, GetRoleRequest, ListRolesAtEntityLevelRequest, ListRolesResponse,
|
CreateRoleRequest, GetRoleRequest, ListRolesAtEntityLevelRequest, ListRolesRequest,
|
||||||
RoleInfoResponseNew, RoleInfoWithGroupsResponse, RoleInfoWithPermissionsResponse,
|
ListRolesResponse, RoleInfoResponseNew, RoleInfoWithGroupsResponse,
|
||||||
UpdateRoleRequest,
|
RoleInfoWithPermissionsResponse, UpdateRoleRequest,
|
||||||
},
|
},
|
||||||
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest,
|
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest,
|
||||||
MerchantSelectRequest, UpdateUserRoleRequest,
|
ListUsersInEntityRequest, MerchantSelectRequest, UpdateUserRoleRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
common_utils::impl_api_event_type!(
|
common_utils::impl_api_event_type!(
|
||||||
@ -25,6 +25,8 @@ common_utils::impl_api_event_type!(
|
|||||||
ListRolesResponse,
|
ListRolesResponse,
|
||||||
ListRolesAtEntityLevelRequest,
|
ListRolesAtEntityLevelRequest,
|
||||||
RoleInfoResponseNew,
|
RoleInfoResponseNew,
|
||||||
RoleInfoWithGroupsResponse
|
RoleInfoWithGroupsResponse,
|
||||||
|
ListUsersInEntityRequest,
|
||||||
|
ListRolesRequest
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -159,3 +159,8 @@ pub struct Entity {
|
|||||||
pub entity_id: String,
|
pub entity_id: String,
|
||||||
pub entity_type: common_enums::EntityType,
|
pub entity_type: common_enums::EntityType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ListUsersInEntityRequest {
|
||||||
|
pub entity_type: Option<common_enums::EntityType>,
|
||||||
|
}
|
||||||
|
|||||||
@ -35,6 +35,11 @@ pub struct RoleInfoWithGroupsResponse {
|
|||||||
pub role_scope: RoleScope,
|
pub role_scope: RoleScope,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct ListRolesRequest {
|
||||||
|
pub entity_type: Option<EntityType>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
pub struct RoleInfoResponseNew {
|
pub struct RoleInfoResponseNew {
|
||||||
pub role_id: String,
|
pub role_id: String,
|
||||||
|
|||||||
@ -673,6 +673,7 @@ pub async fn delete_user_role(
|
|||||||
pub async fn list_users_in_lineage(
|
pub async fn list_users_in_lineage(
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
user_from_token: auth::UserFromToken,
|
user_from_token: auth::UserFromToken,
|
||||||
|
request: user_role_api::ListUsersInEntityRequest,
|
||||||
) -> UserResponse<Vec<user_role_api::ListUsersInEntityResponse>> {
|
) -> UserResponse<Vec<user_role_api::ListUsersInEntityResponse>> {
|
||||||
let requestor_role_info = roles::RoleInfo::from_role_id_in_merchant_scope(
|
let requestor_role_info = roles::RoleInfo::from_role_id_in_merchant_scope(
|
||||||
&state,
|
&state,
|
||||||
@ -683,51 +684,55 @@ pub async fn list_users_in_lineage(
|
|||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
let user_roles_set: HashSet<_> = match requestor_role_info.get_entity_type() {
|
let user_roles_set: HashSet<_> = match utils::user_role::get_min_entity(
|
||||||
EntityType::Organization => state
|
requestor_role_info.get_entity_type(),
|
||||||
.store
|
request.entity_type,
|
||||||
.list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
|
)? {
|
||||||
user_id: None,
|
EntityType::Organization => {
|
||||||
org_id: &user_from_token.org_id,
|
utils::user_role::fetch_user_roles_by_payload(
|
||||||
merchant_id: None,
|
&state,
|
||||||
profile_id: None,
|
ListUserRolesByOrgIdPayload {
|
||||||
version: None,
|
user_id: None,
|
||||||
})
|
org_id: &user_from_token.org_id,
|
||||||
.await
|
merchant_id: None,
|
||||||
.change_context(UserErrors::InternalServerError)?
|
profile_id: None,
|
||||||
.into_iter()
|
version: None,
|
||||||
.collect(),
|
},
|
||||||
EntityType::Merchant => state
|
request.entity_type,
|
||||||
.store
|
)
|
||||||
.list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
|
.await?
|
||||||
user_id: None,
|
}
|
||||||
org_id: &user_from_token.org_id,
|
EntityType::Merchant => {
|
||||||
merchant_id: Some(&user_from_token.merchant_id),
|
utils::user_role::fetch_user_roles_by_payload(
|
||||||
profile_id: None,
|
&state,
|
||||||
version: None,
|
ListUserRolesByOrgIdPayload {
|
||||||
})
|
user_id: None,
|
||||||
.await
|
org_id: &user_from_token.org_id,
|
||||||
.change_context(UserErrors::InternalServerError)?
|
merchant_id: Some(&user_from_token.merchant_id),
|
||||||
.into_iter()
|
profile_id: None,
|
||||||
.collect(),
|
version: None,
|
||||||
|
},
|
||||||
|
request.entity_type,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
EntityType::Profile => {
|
EntityType::Profile => {
|
||||||
let Some(profile_id) = user_from_token.profile_id.as_ref() else {
|
let Some(profile_id) = user_from_token.profile_id.as_ref() else {
|
||||||
return Err(UserErrors::JwtProfileIdMissing.into());
|
return Err(UserErrors::JwtProfileIdMissing.into());
|
||||||
};
|
};
|
||||||
|
|
||||||
state
|
utils::user_role::fetch_user_roles_by_payload(
|
||||||
.store
|
&state,
|
||||||
.list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
|
ListUserRolesByOrgIdPayload {
|
||||||
user_id: None,
|
user_id: None,
|
||||||
org_id: &user_from_token.org_id,
|
org_id: &user_from_token.org_id,
|
||||||
merchant_id: Some(&user_from_token.merchant_id),
|
merchant_id: Some(&user_from_token.merchant_id),
|
||||||
profile_id: Some(profile_id),
|
profile_id: Some(profile_id),
|
||||||
version: None,
|
version: None,
|
||||||
})
|
},
|
||||||
.await
|
request.entity_type,
|
||||||
.change_context(UserErrors::InternalServerError)?
|
)
|
||||||
.into_iter()
|
.await?
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
EntityType::Internal => HashSet::new(),
|
EntityType::Internal => HashSet::new(),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -217,6 +217,7 @@ pub async fn update_role(
|
|||||||
pub async fn list_roles_with_info(
|
pub async fn list_roles_with_info(
|
||||||
state: SessionState,
|
state: SessionState,
|
||||||
user_from_token: UserFromToken,
|
user_from_token: UserFromToken,
|
||||||
|
request: role_api::ListRolesRequest,
|
||||||
) -> UserResponse<Vec<role_api::RoleInfoResponseNew>> {
|
) -> UserResponse<Vec<role_api::RoleInfoResponseNew>> {
|
||||||
let user_role_info = user_from_token
|
let user_role_info = user_from_token
|
||||||
.get_role_info_from_db(&state)
|
.get_role_info_from_db(&state)
|
||||||
@ -229,49 +230,57 @@ pub async fn list_roles_with_info(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let user_role_entity = user_role_info.get_entity_type();
|
let user_role_entity = user_role_info.get_entity_type();
|
||||||
let custom_roles = match user_role_entity {
|
let custom_roles =
|
||||||
EntityType::Organization => state
|
match utils::user_role::get_min_entity(user_role_entity, request.entity_type)? {
|
||||||
.store
|
EntityType::Organization => state
|
||||||
.list_roles_for_org_by_parameters(&user_from_token.org_id, None, None, None)
|
.store
|
||||||
.await
|
.list_roles_for_org_by_parameters(
|
||||||
.change_context(UserErrors::InternalServerError)
|
&user_from_token.org_id,
|
||||||
.attach_printable("Failed to get roles")?,
|
None,
|
||||||
EntityType::Merchant => state
|
request.entity_type,
|
||||||
.store
|
None,
|
||||||
.list_roles_for_org_by_parameters(
|
)
|
||||||
&user_from_token.org_id,
|
.await
|
||||||
Some(&user_from_token.merchant_id),
|
.change_context(UserErrors::InternalServerError)
|
||||||
None,
|
.attach_printable("Failed to get roles")?,
|
||||||
None,
|
EntityType::Merchant => state
|
||||||
)
|
.store
|
||||||
.await
|
.list_roles_for_org_by_parameters(
|
||||||
.change_context(UserErrors::InternalServerError)
|
&user_from_token.org_id,
|
||||||
.attach_printable("Failed to get roles")?,
|
Some(&user_from_token.merchant_id),
|
||||||
// TODO: Populate this from Db function when support for profile id and profile level custom roles is added
|
request.entity_type,
|
||||||
EntityType::Profile => Vec::new(),
|
None,
|
||||||
EntityType::Internal => {
|
)
|
||||||
return Err(UserErrors::InvalidRoleOperationWithMessage(
|
.await
|
||||||
"Internal roles are not allowed for this operation".to_string(),
|
.change_context(UserErrors::InternalServerError)
|
||||||
)
|
.attach_printable("Failed to get roles")?,
|
||||||
.into());
|
// TODO: Populate this from Db function when support for profile id and profile level custom roles is added
|
||||||
}
|
EntityType::Profile => Vec::new(),
|
||||||
};
|
EntityType::Internal => {
|
||||||
|
return Err(UserErrors::InvalidRoleOperationWithMessage(
|
||||||
|
"Internal roles are not allowed for this operation".to_string(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from));
|
role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from));
|
||||||
|
|
||||||
let list_role_info_response = role_info_vec
|
let list_role_info_response = role_info_vec
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|role_info| {
|
.filter_map(|role_info| {
|
||||||
if user_role_entity >= role_info.get_entity_type() {
|
let is_lower_entity = user_role_entity >= role_info.get_entity_type();
|
||||||
Some(role_api::RoleInfoResponseNew {
|
let request_filter = request.entity_type.map_or(true, |entity_type| {
|
||||||
role_id: role_info.get_role_id().to_string(),
|
entity_type == role_info.get_entity_type()
|
||||||
role_name: role_info.get_role_name().to_string(),
|
});
|
||||||
groups: role_info.get_permission_groups().to_vec(),
|
|
||||||
entity_type: role_info.get_entity_type(),
|
(is_lower_entity && request_filter).then_some(role_api::RoleInfoResponseNew {
|
||||||
scope: role_info.get_scope(),
|
role_id: role_info.get_role_id().to_string(),
|
||||||
})
|
role_name: role_info.get_role_name().to_string(),
|
||||||
} else {
|
groups: role_info.get_permission_groups().to_vec(),
|
||||||
None
|
entity_type: role_info.get_entity_type(),
|
||||||
}
|
scope: role_info.get_scope(),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|||||||
@ -291,16 +291,20 @@ pub async fn get_role_information(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_users_in_lineage(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
|
pub async fn list_users_in_lineage(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
query: web::Query<user_role_api::ListUsersInEntityRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
let flow = Flow::ListUsersInLineage;
|
let flow = Flow::ListUsersInLineage;
|
||||||
|
|
||||||
Box::pin(api::server_wrap(
|
Box::pin(api::server_wrap(
|
||||||
flow,
|
flow,
|
||||||
state.clone(),
|
state.clone(),
|
||||||
&req,
|
&req,
|
||||||
(),
|
query.into_inner(),
|
||||||
|state, user_from_token, _, _| {
|
|state, user_from_token, request, _| {
|
||||||
user_role_core::list_users_in_lineage(state, user_from_token)
|
user_role_core::list_users_in_lineage(state, user_from_token, request)
|
||||||
},
|
},
|
||||||
&auth::DashboardNoPermissionAuth,
|
&auth::DashboardNoPermissionAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
@ -308,15 +312,21 @@ pub async fn list_users_in_lineage(state: web::Data<AppState>, req: HttpRequest)
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_roles_with_info(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
|
pub async fn list_roles_with_info(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
req: HttpRequest,
|
||||||
|
query: web::Query<role_api::ListRolesRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
let flow = Flow::ListRolesV2;
|
let flow = Flow::ListRolesV2;
|
||||||
|
|
||||||
Box::pin(api::server_wrap(
|
Box::pin(api::server_wrap(
|
||||||
flow,
|
flow,
|
||||||
state.clone(),
|
state.clone(),
|
||||||
&req,
|
&req,
|
||||||
(),
|
query.into_inner(),
|
||||||
|state, user_from_token, _, _| role_core::list_roles_with_info(state, user_from_token),
|
|state, user_from_token, request, _| {
|
||||||
|
role_core::list_roles_with_info(state, user_from_token, request)
|
||||||
|
},
|
||||||
&auth::JWTAuth {
|
&auth::JWTAuth {
|
||||||
permission: Permission::UsersRead,
|
permission: Permission::UsersRead,
|
||||||
minimum_entity_level: EntityType::Profile,
|
minimum_entity_level: EntityType::Profile,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ use storage_impl::errors::StorageError;
|
|||||||
use crate::{
|
use crate::{
|
||||||
consts,
|
consts,
|
||||||
core::errors::{UserErrors, UserResult},
|
core::errors::{UserErrors, UserResult},
|
||||||
db::user_role::ListUserRolesByUserIdPayload,
|
db::user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload},
|
||||||
routes::SessionState,
|
routes::SessionState,
|
||||||
services::authorization::{self as authz, permissions::Permission, roles},
|
services::authorization::{self as authz, permissions::Permission, roles},
|
||||||
types::domain,
|
types::domain,
|
||||||
@ -398,3 +398,61 @@ pub async fn get_single_merchant_id_and_profile_id(
|
|||||||
|
|
||||||
Ok((merchant_id, profile_id))
|
Ok((merchant_id, profile_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_user_roles_by_payload(
|
||||||
|
state: &SessionState,
|
||||||
|
payload: ListUserRolesByOrgIdPayload<'_>,
|
||||||
|
request_entity_type: Option<EntityType>,
|
||||||
|
) -> UserResult<HashSet<UserRole>> {
|
||||||
|
Ok(state
|
||||||
|
.store
|
||||||
|
.list_user_roles_by_org_id(payload)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|user_role| {
|
||||||
|
let (_entity_id, entity_type) = user_role.get_entity_id_and_type()?;
|
||||||
|
request_entity_type
|
||||||
|
.map_or(true, |req_entity_type| entity_type == req_entity_type)
|
||||||
|
.then_some(user_role)
|
||||||
|
})
|
||||||
|
.collect::<HashSet<_>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_min_entity(
|
||||||
|
user_entity: EntityType,
|
||||||
|
filter_entity: Option<EntityType>,
|
||||||
|
) -> UserResult<EntityType> {
|
||||||
|
match (user_entity, filter_entity) {
|
||||||
|
(EntityType::Organization, None)
|
||||||
|
| (EntityType::Organization, Some(EntityType::Organization)) => {
|
||||||
|
Ok(EntityType::Organization)
|
||||||
|
}
|
||||||
|
|
||||||
|
(EntityType::Merchant, None)
|
||||||
|
| (EntityType::Organization, Some(EntityType::Merchant))
|
||||||
|
| (EntityType::Merchant, Some(EntityType::Merchant)) => Ok(EntityType::Merchant),
|
||||||
|
|
||||||
|
(EntityType::Profile, None)
|
||||||
|
| (EntityType::Organization, Some(EntityType::Profile))
|
||||||
|
| (EntityType::Merchant, Some(EntityType::Profile))
|
||||||
|
| (EntityType::Profile, Some(EntityType::Profile)) => Ok(EntityType::Profile),
|
||||||
|
|
||||||
|
(EntityType::Internal, _) => Ok(EntityType::Internal),
|
||||||
|
|
||||||
|
(EntityType::Organization, Some(EntityType::Internal))
|
||||||
|
| (EntityType::Merchant, Some(EntityType::Internal))
|
||||||
|
| (EntityType::Profile, Some(EntityType::Internal)) => {
|
||||||
|
Err(UserErrors::InvalidRoleOperation.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
(EntityType::Merchant, Some(EntityType::Organization))
|
||||||
|
| (EntityType::Profile, Some(EntityType::Organization))
|
||||||
|
| (EntityType::Profile, Some(EntityType::Merchant)) => {
|
||||||
|
Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
|
||||||
|
"{} level user requesting data for {:?} level",
|
||||||
|
user_entity, filter_entity
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user