diff --git a/crates/api_models/src/user_role/role.rs b/crates/api_models/src/user_role/role.rs index 0ab853273e..b0cc9aa855 100644 --- a/crates/api_models/src/user_role/role.rs +++ b/crates/api_models/src/user_role/role.rs @@ -36,13 +36,13 @@ pub struct RoleInfoWithGroupsResponse { #[derive(Debug, serde::Serialize)] pub struct RoleInfoWithParents { pub role_id: String, - pub parent_groups: Vec, + pub parent_groups: Vec, 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, @@ -74,7 +74,7 @@ pub struct RoleInfoResponseWithParentsGroup { pub role_id: String, pub role_name: String, pub entity_type: EntityType, - pub parent_groups: Vec, + pub parent_groups: Vec, pub role_scope: RoleScope, } @@ -117,3 +117,10 @@ pub enum ListRolesResponse { WithGroups(Vec), WithParentGroups(Vec), } + +#[derive(Debug, serde::Serialize)] +pub struct ParentGroupInfo { + pub name: ParentGroup, + pub resources: Vec, + pub scopes: Vec, +} diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index 1b4870f05e..d6a55ffdbf 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -90,7 +90,7 @@ pub async fn get_parent_group_info( state: SessionState, user_from_token: auth::UserFromToken, request: role_api::GetParentGroupsInfoQueryParams, -) -> UserResponse> { +) -> UserResponse> { 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 { - name: parent_group.clone(), - description, - scopes: PermissionGroup::iter() - .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::>() - .into_iter() - .collect(), - }) + .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()) + }) + // TODO: Remove this hashset conversion when merchant access + // and organization access groups are removed + .collect::>() + .into_iter() + .collect(), + }, + ) .collect::>(); Ok(ApplicationResponse::Json(parent_groups)) diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs index 697d0213d8..81d5153d9b 100644 --- a/crates/router/src/core/user_role/role.rs +++ b/crates/router/src/core/user_role/role.rs @@ -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> { + let role_info = user_from_token.get_role_info_from_db(&state).await?; + + let groups = role_info + .get_permission_groups() + .into_iter() + .collect::>(); + + 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 = 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,19 +359,21 @@ pub async fn get_parent_info_for_role( role.role_id ))? .into_iter() - .map(|(parent_group, description)| role_api::ParentGroupInfo { - name: parent_group.clone(), - description, - scopes: role_info - .get_permission_groups() - .iter() - .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::>() - .into_iter() - .collect(), - }) + .map( + |(parent_group, description)| role_api::ParentGroupDescription { + name: parent_group.clone(), + description, + scopes: role_info + .get_permission_groups() + .iter() + .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::>() + .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( - &permission_groups, - role_info.get_entity_type(), - ); + 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 = + 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(), } }) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 57a9aeb1f7..20ae7ddce0 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -2779,51 +2779,54 @@ impl User { } // Role information - route = route.service( - web::scope("/role") - .service( - web::resource("") - .route(web::get().to(user_role::get_role_from_token)) - // TODO: To be deprecated - .route(web::post().to(user_role::create_role)), - ) - .service( - 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), - ), - ) - // TODO: To be deprecated - .service( - 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)), - ) - .service( - web::resource("/invite").route( + route = + route.service( + web::scope("/role") + .service( + web::resource("") + .route(web::get().to(user_role::get_role_from_token)) + // TODO: To be deprecated + .route(web::post().to(user_role::create_role)), + ) + .service( + 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), + ), + ) + .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)), + ) + .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), - ), - ) - .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}") - .route(web::get().to(user_role::get_role)) - .route(web::put().to(user_role::update_role)), - ) - .service( - web::resource("/{role_id}/v2") - .route(web::get().to(user_role::get_parent_info_for_role)), - ), - ); + )), + ) + .service( + web::resource("/{role_id}") + .route(web::get().to(user_role::get_role)) + .route(web::put().to(user_role::update_role)), + ) + .service( + web::resource("/{role_id}/v2") + .route(web::get().to(user_role::get_parent_info_for_role)), + ), + ); #[cfg(feature = "dummy_connector")] { diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 713b51a9d7..87513e6e23 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -306,6 +306,7 @@ impl From for ApiIdentifier { | Flow::GetRoleV2 | Flow::GetRoleFromToken | Flow::GetRoleFromTokenV2 + | Flow::GetParentGroupsInfoForRoleFromToken | Flow::UpdateUserRole | Flow::GetAuthorizationInfo | Flow::GetRolesInfo diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs index 91cc92cb3e..6247761d61 100644 --- a/crates/router/src/routes/user_role.rs +++ b/crates/router/src/routes/user_role.rs @@ -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, + 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, diff --git a/crates/router/src/services/authorization/permission_groups.rs b/crates/router/src/services/authorization/permission_groups.rs index 774368b18d..6a2ce1c839 100644 --- a/crates/router/src/services/authorization/permission_groups.rs +++ b/crates/router/src/services/authorization/permission_groups.rs @@ -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() diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 6fb4b09cd5..2181d9d0eb 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -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, + entity_type: EntityType, +) -> Option> { + let filtered: Vec = resources + .into_iter() + .filter(|res| res.entities().iter().any(|entity| entity <= &entity_type)) + .collect(); + + (!filtered.is_empty()).then_some(filtered) +} diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 7b5bd81678..f9a5f77807 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -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, + entity_type: EntityType, +) -> Option { + 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::>>()? + .join(", "); + + Some(description) +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 95582124cc..5200047db5 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -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