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::{
role::{
CreateRoleRequest, GetRoleRequest, ListRolesAtEntityLevelRequest, ListRolesResponse,
RoleInfoResponseNew, RoleInfoWithGroupsResponse, RoleInfoWithPermissionsResponse,
UpdateRoleRequest,
CreateRoleRequest, GetRoleRequest, ListRolesAtEntityLevelRequest, ListRolesRequest,
ListRolesResponse, RoleInfoResponseNew, RoleInfoWithGroupsResponse,
RoleInfoWithPermissionsResponse, UpdateRoleRequest,
},
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest,
MerchantSelectRequest, UpdateUserRoleRequest,
ListUsersInEntityRequest, MerchantSelectRequest, UpdateUserRoleRequest,
};
common_utils::impl_api_event_type!(
@ -25,6 +25,8 @@ common_utils::impl_api_event_type!(
ListRolesResponse,
ListRolesAtEntityLevelRequest,
RoleInfoResponseNew,
RoleInfoWithGroupsResponse
RoleInfoWithGroupsResponse,
ListUsersInEntityRequest,
ListRolesRequest
)
);

View File

@ -159,3 +159,8 @@ pub struct Entity {
pub entity_id: String,
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,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct ListRolesRequest {
pub entity_type: Option<EntityType>,
}
#[derive(Debug, serde::Serialize)]
pub struct RoleInfoResponseNew {
pub role_id: String,

View File

@ -673,6 +673,7 @@ pub async fn delete_user_role(
pub async fn list_users_in_lineage(
state: SessionState,
user_from_token: auth::UserFromToken,
request: user_role_api::ListUsersInEntityRequest,
) -> UserResponse<Vec<user_role_api::ListUsersInEntityResponse>> {
let requestor_role_info = roles::RoleInfo::from_role_id_in_merchant_scope(
&state,
@ -683,51 +684,55 @@ pub async fn list_users_in_lineage(
.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(),
let user_roles_set: HashSet<_> = match utils::user_role::get_min_entity(
requestor_role_info.get_entity_type(),
request.entity_type,
)? {
EntityType::Organization => {
utils::user_role::fetch_user_roles_by_payload(
&state,
ListUserRolesByOrgIdPayload {
user_id: None,
org_id: &user_from_token.org_id,
merchant_id: None,
profile_id: None,
version: None,
},
request.entity_type,
)
.await?
}
EntityType::Merchant => {
utils::user_role::fetch_user_roles_by_payload(
&state,
ListUserRolesByOrgIdPayload {
user_id: None,
org_id: &user_from_token.org_id,
merchant_id: Some(&user_from_token.merchant_id),
profile_id: None,
version: None,
},
request.entity_type,
)
.await?
}
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 {
utils::user_role::fetch_user_roles_by_payload(
&state,
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()
},
request.entity_type,
)
.await?
}
EntityType::Internal => HashSet::new(),
};

View File

@ -217,6 +217,7 @@ pub async fn update_role(
pub async fn list_roles_with_info(
state: SessionState,
user_from_token: UserFromToken,
request: role_api::ListRolesRequest,
) -> UserResponse<Vec<role_api::RoleInfoResponseNew>> {
let user_role_info = user_from_token
.get_role_info_from_db(&state)
@ -229,49 +230,57 @@ pub async fn list_roles_with_info(
.collect::<Vec<_>>();
let user_role_entity = user_role_info.get_entity_type();
let custom_roles = match user_role_entity {
EntityType::Organization => state
.store
.list_roles_for_org_by_parameters(&user_from_token.org_id, None, None, None)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
EntityType::Merchant => state
.store
.list_roles_for_org_by_parameters(
&user_from_token.org_id,
Some(&user_from_token.merchant_id),
None,
None,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
// 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());
}
};
let custom_roles =
match utils::user_role::get_min_entity(user_role_entity, request.entity_type)? {
EntityType::Organization => state
.store
.list_roles_for_org_by_parameters(
&user_from_token.org_id,
None,
request.entity_type,
None,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
EntityType::Merchant => state
.store
.list_roles_for_org_by_parameters(
&user_from_token.org_id,
Some(&user_from_token.merchant_id),
request.entity_type,
None,
)
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get roles")?,
// 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));
let list_role_info_response = role_info_vec
.into_iter()
.filter_map(|role_info| {
if user_role_entity >= role_info.get_entity_type() {
Some(role_api::RoleInfoResponseNew {
role_id: role_info.get_role_id().to_string(),
role_name: role_info.get_role_name().to_string(),
groups: role_info.get_permission_groups().to_vec(),
entity_type: role_info.get_entity_type(),
scope: role_info.get_scope(),
})
} else {
None
}
let is_lower_entity = user_role_entity >= role_info.get_entity_type();
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_name: role_info.get_role_name().to_string(),
groups: role_info.get_permission_groups().to_vec(),
entity_type: role_info.get_entity_type(),
scope: role_info.get_scope(),
})
})
.collect::<Vec<_>>();

View File

@ -291,16 +291,20 @@ pub async fn get_role_information(
.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;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
(),
|state, user_from_token, _, _| {
user_role_core::list_users_in_lineage(state, user_from_token)
query.into_inner(),
|state, user_from_token, request, _| {
user_role_core::list_users_in_lineage(state, user_from_token, request)
},
&auth::DashboardNoPermissionAuth,
api_locking::LockAction::NotApplicable,
@ -308,15 +312,21 @@ pub async fn list_users_in_lineage(state: web::Data<AppState>, req: HttpRequest)
.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;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
(),
|state, user_from_token, _, _| role_core::list_roles_with_info(state, user_from_token),
query.into_inner(),
|state, user_from_token, request, _| {
role_core::list_roles_with_info(state, user_from_token, request)
},
&auth::JWTAuth {
permission: Permission::UsersRead,
minimum_entity_level: EntityType::Profile,

View File

@ -14,7 +14,7 @@ use storage_impl::errors::StorageError;
use crate::{
consts,
core::errors::{UserErrors, UserResult},
db::user_role::ListUserRolesByUserIdPayload,
db::user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload},
routes::SessionState,
services::authorization::{self as authz, permissions::Permission, roles},
types::domain,
@ -398,3 +398,61 @@ pub async fn get_single_merchant_id_and_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
))
}
}
}