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)] #[derive(Debug, serde::Serialize)]
pub struct RoleInfoWithParents { pub struct RoleInfoWithParents {
pub role_id: String, pub role_id: String,
pub parent_groups: Vec<ParentGroupInfo>, pub parent_groups: Vec<ParentGroupDescription>,
pub role_name: String, pub role_name: String,
pub role_scope: RoleScope, pub role_scope: RoleScope,
} }
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
pub struct ParentGroupInfo { pub struct ParentGroupDescription {
pub name: ParentGroup, pub name: ParentGroup,
pub description: String, pub description: String,
pub scopes: Vec<PermissionScope>, pub scopes: Vec<PermissionScope>,
@ -74,7 +74,7 @@ pub struct RoleInfoResponseWithParentsGroup {
pub role_id: String, pub role_id: String,
pub role_name: String, pub role_name: String,
pub entity_type: EntityType, pub entity_type: EntityType,
pub parent_groups: Vec<ParentGroupInfo>, pub parent_groups: Vec<ParentGroupDescription>,
pub role_scope: RoleScope, pub role_scope: RoleScope,
} }
@ -117,3 +117,10 @@ pub enum ListRolesResponse {
WithGroups(Vec<RoleInfoResponseNew>), WithGroups(Vec<RoleInfoResponseNew>),
WithParentGroups(Vec<RoleInfoResponseWithParentsGroup>), 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, state: SessionState,
user_from_token: auth::UserFromToken, user_from_token: auth::UserFromToken,
request: role_api::GetParentGroupsInfoQueryParams, 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( let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
&state, &state,
&user_from_token.role_id, &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()) ParentGroup::get_descriptions_for_groups(entity_type, PermissionGroup::iter().collect())
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.map(|(parent_group, description)| role_api::ParentGroupInfo { .map(
name: parent_group.clone(), |(parent_group, description)| role_api::ParentGroupDescription {
description, name: parent_group.clone(),
scopes: PermissionGroup::iter() description,
.filter_map(|group| (group.parent() == parent_group).then_some(group.scope())) scopes: PermissionGroup::iter()
// TODO: Remove this hashset conversion when merchant access .filter_map(|group| {
// and organization access groups are removed (group.parent() == parent_group).then_some(group.scope())
.collect::<HashSet<_>>() })
.into_iter() // TODO: Remove this hashset conversion when merchant access
.collect(), // and organization access groups are removed
}) .collect::<HashSet<_>>()
.into_iter()
.collect(),
},
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(ApplicationResponse::Json(parent_groups)) 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( pub async fn create_role(
state: SessionState, state: SessionState,
user_from_token: UserFromToken, user_from_token: UserFromToken,
@ -248,16 +267,31 @@ pub async fn create_role_v2(
.await .await
.to_duplicate_response(UserErrors::RoleNameAlreadyExists)?; .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); 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( Ok(ApplicationResponse::Json(
role_api::RoleInfoResponseWithParentsGroup { role_api::RoleInfoResponseWithParentsGroup {
role_id: role.role_id, role_id: role.role_id,
role_name: role.role_name, role_name: role.role_name,
role_scope: role.scope, role_scope: role.scope,
entity_type: role.entity_type, entity_type: role.entity_type,
parent_groups: response_parent_groups, parent_groups: parent_group_descriptions,
}, },
)) ))
} }
@ -325,19 +359,21 @@ pub async fn get_parent_info_for_role(
role.role_id role.role_id
))? ))?
.into_iter() .into_iter()
.map(|(parent_group, description)| role_api::ParentGroupInfo { .map(
name: parent_group.clone(), |(parent_group, description)| role_api::ParentGroupDescription {
description, name: parent_group.clone(),
scopes: role_info description,
.get_permission_groups() scopes: role_info
.iter() .get_permission_groups()
.filter_map(|group| (group.parent() == parent_group).then_some(group.scope())) .iter()
// TODO: Remove this hashset conversion when merchant access .filter_map(|group| (group.parent() == parent_group).then_some(group.scope()))
// and organization access groups are removed // TODO: Remove this hashset conversion when merchant access
.collect::<HashSet<_>>() // and organization access groups are removed
.into_iter() .collect::<HashSet<_>>()
.collect(), .into_iter()
}) .collect(),
},
)
.collect(); .collect();
Ok(ApplicationResponse::Json(role_api::RoleInfoWithParents { Ok(ApplicationResponse::Json(role_api::RoleInfoWithParents {
@ -517,16 +553,33 @@ pub async fn list_roles_with_info(
(is_lower_entity && request_filter).then_some({ (is_lower_entity && request_filter).then_some({
let permission_groups = role_info.get_permission_groups(); let permission_groups = role_info.get_permission_groups();
let parent_groups = utils::user_role::permission_groups_to_parent_group_info( let parent_group_details =
&permission_groups, utils::user_role::permission_groups_to_parent_group_info(
role_info.get_entity_type(), &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_api::RoleInfoResponseWithParentsGroup {
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(),
entity_type: role_info.get_entity_type(), entity_type: role_info.get_entity_type(),
parent_groups, parent_groups: parent_group_descriptions,
role_scope: role_info.get_scope(), role_scope: role_info.get_scope(),
} }
}) })

View File

@ -2779,51 +2779,54 @@ impl User {
} }
// Role information // Role information
route = route.service( route =
web::scope("/role") route.service(
.service( web::scope("/role")
web::resource("") .service(
.route(web::get().to(user_role::get_role_from_token)) web::resource("")
// TODO: To be deprecated .route(web::get().to(user_role::get_role_from_token))
.route(web::post().to(user_role::create_role)), // TODO: To be deprecated
) .route(web::post().to(user_role::create_role)),
.service( )
web::resource("/v2") .service(
.route(web::post().to(user_role::create_role_v2)) web::resource("/v2")
.route( .route(web::post().to(user_role::create_role_v2))
web::get().to(user_role::get_groups_and_resources_for_role_from_token), .route(
), web::get()
) .to(user_role::get_groups_and_resources_for_role_from_token),
// TODO: To be deprecated ),
.service( )
web::resource("/v2/list").route(web::get().to(user_role::list_roles_with_info)), .service(web::resource("/v3").route(
) web::get().to(user_role::get_parent_groups_info_for_role_from_token),
.service( ))
web::scope("/list") // TODO: To be deprecated
.service( .service(
web::resource("").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::resource("/invite").route( .service(
web::scope("/list")
.service(
web::resource("")
.route(web::get().to(user_role::list_roles_with_info)),
)
.service(web::resource("/invite").route(
web::get().to(user_role::list_invitable_roles_at_entity_level), 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), web::get().to(user_role::list_updatable_roles_at_entity_level),
), )),
), )
) .service(
.service( web::resource("/{role_id}")
web::resource("/{role_id}") .route(web::get().to(user_role::get_role))
.route(web::get().to(user_role::get_role)) .route(web::put().to(user_role::update_role)),
.route(web::put().to(user_role::update_role)), )
) .service(
.service( web::resource("/{role_id}/v2")
web::resource("/{role_id}/v2") .route(web::get().to(user_role::get_parent_info_for_role)),
.route(web::get().to(user_role::get_parent_info_for_role)), ),
), );
);
#[cfg(feature = "dummy_connector")] #[cfg(feature = "dummy_connector")]
{ {

View File

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

View File

@ -74,6 +74,27 @@ pub async fn get_groups_and_resources_for_role_from_token(
)) ))
.await .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 // TODO: To be deprecated
pub async fn create_role( pub async fn create_role(
state: web::Data<AppState>, 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 common_enums::{EntityType, ParentGroup, PermissionGroup, PermissionScope, Resource};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use super::permissions::{self, ResourceExt}; use super::permissions;
pub trait PermissionGroupExt { pub trait PermissionGroupExt {
fn scope(&self) -> PermissionScope; fn scope(&self) -> PermissionScope;
@ -147,15 +147,8 @@ impl ParentGroupExt for ParentGroup {
if !groups.iter().any(|group| group.parent() == parent) { if !groups.iter().any(|group| group.parent() == parent) {
return None; return None;
} }
let filtered_resources: Vec<_> = parent let filtered_resources =
.resources() permissions::filter_resources_by_entity_type(parent.resources(), entity_type)?;
.into_iter()
.filter(|res| res.entities().iter().any(|entity| entity <= &entity_type))
.collect();
if filtered_resources.is_empty() {
return None;
}
let description = filtered_resources let description = filtered_resources
.iter() .iter()

View File

@ -157,3 +157,15 @@ pub fn get_scope_name(scope: PermissionScope) -> &'static str {
PermissionScope::Write => "View and Manage", 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::{ services::authorization::{
self as authz, self as authz,
permission_groups::{ParentGroupExt, PermissionGroupExt}, permission_groups::{ParentGroupExt, PermissionGroupExt},
roles, permissions, roles,
}, },
types::domain, types::domain,
}; };
@ -570,15 +570,33 @@ pub fn permission_groups_to_parent_group_info(
.into_iter() .into_iter()
.collect(); .collect();
let description = let filtered_resources =
ParentGroup::get_descriptions_for_groups(entity_type, permission_groups.to_vec()) permissions::filter_resources_by_entity_type(name.resources(), entity_type)?;
.and_then(|descriptions| descriptions.get(&name).cloned())?;
Some(role_api::ParentGroupInfo { Some(role_api::ParentGroupInfo {
name, name,
description, resources: filtered_resources,
scopes: unique_scopes, scopes: unique_scopes,
}) })
}) })
.collect() .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, GetRoleFromToken,
/// Get resources and groups for role from token /// Get resources and groups for role from token
GetRoleFromTokenV2, GetRoleFromTokenV2,
/// Get parent groups info for role from token
GetParentGroupsInfoForRoleFromToken,
/// Update user role /// Update user role
UpdateUserRole, UpdateUserRole,
/// Create merchant account for user in a org /// Create merchant account for user in a org