feat(user_roles): add parent group info based API to fetch permissions for user role (#9487)

This commit is contained in:
Kanika Bansal
2025-10-07 19:25:31 +05:30
committed by GitHub
parent 916444b018
commit c44c3ed27d
10 changed files with 207 additions and 93 deletions

View File

@ -36,13 +36,13 @@ pub struct RoleInfoWithGroupsResponse {
#[derive(Debug, serde::Serialize)]
pub struct RoleInfoWithParents {
pub role_id: String,
pub parent_groups: Vec<ParentGroupInfo>,
pub parent_groups: Vec<ParentGroupDescription>,
pub role_name: String,
pub role_scope: RoleScope,
}
#[derive(Debug, serde::Serialize)]
pub struct ParentGroupInfo {
pub struct ParentGroupDescription {
pub name: ParentGroup,
pub description: String,
pub scopes: Vec<PermissionScope>,
@ -74,7 +74,7 @@ pub struct RoleInfoResponseWithParentsGroup {
pub role_id: String,
pub role_name: String,
pub entity_type: EntityType,
pub parent_groups: Vec<ParentGroupInfo>,
pub parent_groups: Vec<ParentGroupDescription>,
pub role_scope: RoleScope,
}
@ -117,3 +117,10 @@ pub enum ListRolesResponse {
WithGroups(Vec<RoleInfoResponseNew>),
WithParentGroups(Vec<RoleInfoResponseWithParentsGroup>),
}
#[derive(Debug, serde::Serialize)]
pub struct ParentGroupInfo {
pub name: ParentGroup,
pub resources: Vec<Resource>,
pub scopes: Vec<PermissionScope>,
}

View File

@ -90,7 +90,7 @@ pub async fn get_parent_group_info(
state: SessionState,
user_from_token: auth::UserFromToken,
request: role_api::GetParentGroupsInfoQueryParams,
) -> UserResponse<Vec<role_api::ParentGroupInfo>> {
) -> UserResponse<Vec<role_api::ParentGroupDescription>> {
let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
&state,
&user_from_token.role_id,
@ -119,17 +119,21 @@ pub async fn get_parent_group_info(
ParentGroup::get_descriptions_for_groups(entity_type, PermissionGroup::iter().collect())
.unwrap_or_default()
.into_iter()
.map(|(parent_group, description)| role_api::ParentGroupInfo {
.map(
|(parent_group, description)| role_api::ParentGroupDescription {
name: parent_group.clone(),
description,
scopes: PermissionGroup::iter()
.filter_map(|group| (group.parent() == parent_group).then_some(group.scope()))
.filter_map(|group| {
(group.parent() == parent_group).then_some(group.scope())
})
// TODO: Remove this hashset conversion when merchant access
// and organization access groups are removed
.collect::<HashSet<_>>()
.into_iter()
.collect(),
})
},
)
.collect::<Vec<_>>();
Ok(ApplicationResponse::Json(parent_groups))

View File

@ -58,6 +58,25 @@ pub async fn get_groups_and_resources_for_role_from_token(
}))
}
pub async fn get_parent_groups_info_for_role_from_token(
state: SessionState,
user_from_token: UserFromToken,
) -> UserResponse<Vec<role_api::ParentGroupInfo>> {
let role_info = user_from_token.get_role_info_from_db(&state).await?;
let groups = role_info
.get_permission_groups()
.into_iter()
.collect::<Vec<_>>();
let parent_groups = utils::user_role::permission_groups_to_parent_group_info(
&groups,
role_info.get_entity_type(),
);
Ok(ApplicationResponse::Json(parent_groups))
}
pub async fn create_role(
state: SessionState,
user_from_token: UserFromToken,
@ -248,16 +267,31 @@ pub async fn create_role_v2(
.await
.to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;
let response_parent_groups =
let parent_group_details =
utils::user_role::permission_groups_to_parent_group_info(&role.groups, role.entity_type);
let parent_group_descriptions: Vec<role_api::ParentGroupDescription> = parent_group_details
.into_iter()
.filter_map(|group_details| {
let description = utils::user_role::resources_to_description(
group_details.resources,
role.entity_type,
)?;
Some(role_api::ParentGroupDescription {
name: group_details.name,
description,
scopes: group_details.scopes,
})
})
.collect();
Ok(ApplicationResponse::Json(
role_api::RoleInfoResponseWithParentsGroup {
role_id: role.role_id,
role_name: role.role_name,
role_scope: role.scope,
entity_type: role.entity_type,
parent_groups: response_parent_groups,
parent_groups: parent_group_descriptions,
},
))
}
@ -325,7 +359,8 @@ pub async fn get_parent_info_for_role(
role.role_id
))?
.into_iter()
.map(|(parent_group, description)| role_api::ParentGroupInfo {
.map(
|(parent_group, description)| role_api::ParentGroupDescription {
name: parent_group.clone(),
description,
scopes: role_info
@ -337,7 +372,8 @@ pub async fn get_parent_info_for_role(
.collect::<HashSet<_>>()
.into_iter()
.collect(),
})
},
)
.collect();
Ok(ApplicationResponse::Json(role_api::RoleInfoWithParents {
@ -517,16 +553,33 @@ pub async fn list_roles_with_info(
(is_lower_entity && request_filter).then_some({
let permission_groups = role_info.get_permission_groups();
let parent_groups = utils::user_role::permission_groups_to_parent_group_info(
let parent_group_details =
utils::user_role::permission_groups_to_parent_group_info(
&permission_groups,
role_info.get_entity_type(),
);
let parent_group_descriptions: Vec<role_api::ParentGroupDescription> =
parent_group_details
.into_iter()
.filter_map(|group_details| {
let description = utils::user_role::resources_to_description(
group_details.resources,
role_info.get_entity_type(),
)?;
Some(role_api::ParentGroupDescription {
name: group_details.name,
description,
scopes: group_details.scopes,
})
})
.collect();
role_api::RoleInfoResponseWithParentsGroup {
role_id: role_info.get_role_id().to_string(),
role_name: role_info.get_role_name().to_string(),
entity_type: role_info.get_entity_type(),
parent_groups,
parent_groups: parent_group_descriptions,
role_scope: role_info.get_scope(),
}
})

View File

@ -2779,7 +2779,8 @@ impl User {
}
// Role information
route = route.service(
route =
route.service(
web::scope("/role")
.service(
web::resource("")
@ -2791,28 +2792,30 @@ impl User {
web::resource("/v2")
.route(web::post().to(user_role::create_role_v2))
.route(
web::get().to(user_role::get_groups_and_resources_for_role_from_token),
web::get()
.to(user_role::get_groups_and_resources_for_role_from_token),
),
)
.service(web::resource("/v3").route(
web::get().to(user_role::get_parent_groups_info_for_role_from_token),
))
// TODO: To be deprecated
.service(
web::resource("/v2/list").route(web::get().to(user_role::list_roles_with_info)),
web::resource("/v2/list")
.route(web::get().to(user_role::list_roles_with_info)),
)
.service(
web::scope("/list")
.service(
web::resource("").route(web::get().to(user_role::list_roles_with_info)),
web::resource("")
.route(web::get().to(user_role::list_roles_with_info)),
)
.service(
web::resource("/invite").route(
.service(web::resource("/invite").route(
web::get().to(user_role::list_invitable_roles_at_entity_level),
),
)
.service(
web::resource("/update").route(
))
.service(web::resource("/update").route(
web::get().to(user_role::list_updatable_roles_at_entity_level),
),
),
)),
)
.service(
web::resource("/{role_id}")

View File

@ -306,6 +306,7 @@ impl From<Flow> for ApiIdentifier {
| Flow::GetRoleV2
| Flow::GetRoleFromToken
| Flow::GetRoleFromTokenV2
| Flow::GetParentGroupsInfoForRoleFromToken
| Flow::UpdateUserRole
| Flow::GetAuthorizationInfo
| Flow::GetRolesInfo

View File

@ -74,6 +74,27 @@ pub async fn get_groups_and_resources_for_role_from_token(
))
.await
}
pub async fn get_parent_groups_info_for_role_from_token(
state: web::Data<AppState>,
req: HttpRequest,
) -> HttpResponse {
let flow = Flow::GetParentGroupsInfoForRoleFromToken;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
(),
|state, user, _, _| async move {
role_core::get_parent_groups_info_for_role_from_token(state, user).await
},
&auth::DashboardNoPermissionAuth,
api_locking::LockAction::NotApplicable,
))
.await
}
// TODO: To be deprecated
pub async fn create_role(
state: web::Data<AppState>,

View File

@ -3,7 +3,7 @@ use std::{collections::HashMap, ops::Not};
use common_enums::{EntityType, ParentGroup, PermissionGroup, PermissionScope, Resource};
use strum::IntoEnumIterator;
use super::permissions::{self, ResourceExt};
use super::permissions;
pub trait PermissionGroupExt {
fn scope(&self) -> PermissionScope;
@ -147,15 +147,8 @@ impl ParentGroupExt for ParentGroup {
if !groups.iter().any(|group| group.parent() == parent) {
return None;
}
let filtered_resources: Vec<_> = parent
.resources()
.into_iter()
.filter(|res| res.entities().iter().any(|entity| entity <= &entity_type))
.collect();
if filtered_resources.is_empty() {
return None;
}
let filtered_resources =
permissions::filter_resources_by_entity_type(parent.resources(), entity_type)?;
let description = filtered_resources
.iter()

View File

@ -157,3 +157,15 @@ pub fn get_scope_name(scope: PermissionScope) -> &'static str {
PermissionScope::Write => "View and Manage",
}
}
pub fn filter_resources_by_entity_type(
resources: Vec<Resource>,
entity_type: EntityType,
) -> Option<Vec<Resource>> {
let filtered: Vec<Resource> = resources
.into_iter()
.filter(|res| res.entities().iter().any(|entity| entity <= &entity_type))
.collect();
(!filtered.is_empty()).then_some(filtered)
}

View File

@ -27,7 +27,7 @@ use crate::{
services::authorization::{
self as authz,
permission_groups::{ParentGroupExt, PermissionGroupExt},
roles,
permissions, roles,
},
types::domain,
};
@ -570,15 +570,33 @@ pub fn permission_groups_to_parent_group_info(
.into_iter()
.collect();
let description =
ParentGroup::get_descriptions_for_groups(entity_type, permission_groups.to_vec())
.and_then(|descriptions| descriptions.get(&name).cloned())?;
let filtered_resources =
permissions::filter_resources_by_entity_type(name.resources(), entity_type)?;
Some(role_api::ParentGroupInfo {
name,
description,
resources: filtered_resources,
scopes: unique_scopes,
})
})
.collect()
}
pub fn resources_to_description(
resources: Vec<common_enums::Resource>,
entity_type: EntityType,
) -> Option<String> {
if resources.is_empty() {
return None;
}
let filtered_resources = permissions::filter_resources_by_entity_type(resources, entity_type)?;
let description = filtered_resources
.iter()
.map(|res| permissions::get_resource_name(*res, entity_type))
.collect::<Option<Vec<_>>>()?
.join(", ");
Some(description)
}

View File

@ -429,6 +429,8 @@ pub enum Flow {
GetRoleFromToken,
/// Get resources and groups for role from token
GetRoleFromTokenV2,
/// Get parent groups info for role from token
GetParentGroupsInfoForRoleFromToken,
/// Update user role
UpdateUserRole,
/// Create merchant account for user in a org