feat(users): Add entity type filter in list users and list roles API (#5997)

This commit is contained in:
Mani Chandra
2024-09-24 19:48:39 +05:30
committed by GitHub
parent aae2343910
commit 3ddfe53838
7 changed files with 180 additions and 86 deletions

View File

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

View File

@ -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>,
}

View File

@ -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,

View File

@ -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 { )? {
EntityType::Organization => {
utils::user_role::fetch_user_roles_by_payload(
&state,
ListUserRolesByOrgIdPayload {
user_id: None, user_id: None,
org_id: &user_from_token.org_id, org_id: &user_from_token.org_id,
merchant_id: None, merchant_id: None,
profile_id: None, profile_id: None,
version: None, version: None,
}) },
.await request.entity_type,
.change_context(UserErrors::InternalServerError)? )
.into_iter() .await?
.collect(), }
EntityType::Merchant => state EntityType::Merchant => {
.store utils::user_role::fetch_user_roles_by_payload(
.list_user_roles_by_org_id(ListUserRolesByOrgIdPayload { &state,
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: None, profile_id: None,
version: None, version: None,
}) },
.await request.entity_type,
.change_context(UserErrors::InternalServerError)? )
.into_iter() .await?
.collect(), }
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(),
}; };

View File

@ -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,10 +230,16 @@ 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 =
match utils::user_role::get_min_entity(user_role_entity, request.entity_type)? {
EntityType::Organization => state EntityType::Organization => state
.store .store
.list_roles_for_org_by_parameters(&user_from_token.org_id, None, None, None) .list_roles_for_org_by_parameters(
&user_from_token.org_id,
None,
request.entity_type,
None,
)
.await .await
.change_context(UserErrors::InternalServerError) .change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?, .attach_printable("Failed to get roles")?,
@ -241,7 +248,7 @@ pub async fn list_roles_with_info(
.list_roles_for_org_by_parameters( .list_roles_for_org_by_parameters(
&user_from_token.org_id, &user_from_token.org_id,
Some(&user_from_token.merchant_id), Some(&user_from_token.merchant_id),
None, request.entity_type,
None, None,
) )
.await .await
@ -258,20 +265,22 @@ pub async fn list_roles_with_info(
}; };
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| {
entity_type == role_info.get_entity_type()
});
(is_lower_entity && request_filter).then_some(role_api::RoleInfoResponseNew {
role_id: role_info.get_role_id().to_string(), role_id: role_info.get_role_id().to_string(),
role_name: role_info.get_role_name().to_string(), role_name: role_info.get_role_name().to_string(),
groups: role_info.get_permission_groups().to_vec(), groups: role_info.get_permission_groups().to_vec(),
entity_type: role_info.get_entity_type(), entity_type: role_info.get_entity_type(),
scope: role_info.get_scope(), scope: role_info.get_scope(),
}) })
} else {
None
}
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@ -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,

View File

@ -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
))
}
}
}