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 { )? {
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(),
}; };

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,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<_>>();

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