refactor(user_roles): implement parent group info based role APIs (#8896)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Kanika Bansal
2025-08-22 18:38:23 +05:30
committed by GitHub
parent 0ba5d54349
commit e3c46b7de7
11 changed files with 414 additions and 102 deletions

View File

@ -2,9 +2,10 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
use crate::user_role::{ use crate::user_role::{
role::{ role::{
CreateRoleRequest, GetRoleRequest, GroupsAndResources, ListRolesAtEntityLevelRequest, CreateRoleRequest, CreateRoleV2Request, GetParentGroupsInfoQueryParams, GetRoleRequest,
ListRolesRequest, RoleInfoResponseNew, RoleInfoWithGroupsResponse, RoleInfoWithParents, GroupsAndResources, ListRolesAtEntityLevelRequest, ListRolesQueryParams, ListRolesResponse,
UpdateRoleRequest, ParentGroupInfoRequest, RoleInfoResponseNew, RoleInfoResponseWithParentsGroup,
RoleInfoWithGroupsResponse, RoleInfoWithParents, UpdateRoleRequest,
}, },
AuthorizationInfoResponse, DeleteUserRoleRequest, ListUsersInEntityRequest, AuthorizationInfoResponse, DeleteUserRoleRequest, ListUsersInEntityRequest,
UpdateUserRoleRequest, UpdateUserRoleRequest,
@ -14,17 +15,22 @@ common_utils::impl_api_event_type!(
Miscellaneous, Miscellaneous,
( (
GetRoleRequest, GetRoleRequest,
GetParentGroupsInfoQueryParams,
AuthorizationInfoResponse, AuthorizationInfoResponse,
UpdateUserRoleRequest, UpdateUserRoleRequest,
DeleteUserRoleRequest, DeleteUserRoleRequest,
CreateRoleRequest, CreateRoleRequest,
CreateRoleV2Request,
UpdateRoleRequest, UpdateRoleRequest,
ListRolesAtEntityLevelRequest, ListRolesAtEntityLevelRequest,
RoleInfoResponseNew, RoleInfoResponseNew,
RoleInfoWithGroupsResponse, RoleInfoWithGroupsResponse,
ListUsersInEntityRequest, ListUsersInEntityRequest,
ListRolesRequest, ListRolesQueryParams,
GroupsAndResources, GroupsAndResources,
RoleInfoWithParents RoleInfoWithParents,
ParentGroupInfoRequest,
RoleInfoResponseWithParentsGroup,
ListRolesResponse
) )
); );

View File

@ -10,6 +10,14 @@ pub struct CreateRoleRequest {
pub entity_type: Option<EntityType>, pub entity_type: Option<EntityType>,
} }
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct CreateRoleV2Request {
pub role_name: String,
pub role_scope: RoleScope,
pub entity_type: Option<EntityType>,
pub parent_groups: Vec<ParentGroupInfoRequest>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct UpdateRoleRequest { pub struct UpdateRoleRequest {
pub groups: Option<Vec<PermissionGroup>>, pub groups: Option<Vec<PermissionGroup>>,
@ -41,8 +49,15 @@ pub struct ParentGroupInfo {
} }
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct ListRolesRequest { pub struct ParentGroupInfoRequest {
pub name: ParentGroup,
pub scopes: Vec<PermissionScope>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct ListRolesQueryParams {
pub entity_type: Option<EntityType>, pub entity_type: Option<EntityType>,
pub groups: Option<bool>,
} }
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
@ -54,6 +69,15 @@ pub struct RoleInfoResponseNew {
pub scope: RoleScope, pub scope: RoleScope,
} }
#[derive(Debug, serde::Serialize)]
pub struct RoleInfoResponseWithParentsGroup {
pub role_id: String,
pub role_name: String,
pub entity_type: EntityType,
pub parent_groups: Vec<ParentGroupInfo>,
pub role_scope: RoleScope,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct GetRoleRequest { pub struct GetRoleRequest {
pub role_id: String, pub role_id: String,
@ -64,6 +88,11 @@ pub struct ListRolesAtEntityLevelRequest {
pub entity_type: EntityType, pub entity_type: EntityType,
} }
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct GetParentGroupsInfoQueryParams {
pub entity_type: Option<EntityType>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub enum RoleCheckType { pub enum RoleCheckType {
Invite, Invite,
@ -81,3 +110,10 @@ pub struct GroupsAndResources {
pub groups: Vec<PermissionGroup>, pub groups: Vec<PermissionGroup>,
pub resources: Vec<Resource>, pub resources: Vec<Resource>,
} }
#[derive(Debug, serde::Serialize)]
#[serde(untagged)]
pub enum ListRolesResponse {
WithGroups(Vec<RoleInfoResponseNew>),
WithParentGroups(Vec<RoleInfoResponseWithParentsGroup>),
}

View File

@ -7708,7 +7708,9 @@ pub enum PermissionGroup {
ThemeManage, ThemeManage,
} }
#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, Hash, strum::EnumIter)] #[derive(
Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, strum::EnumIter,
)]
pub enum ParentGroup { pub enum ParentGroup {
Operations, Operations,
Connectors, Connectors,
@ -7722,7 +7724,7 @@ pub enum ParentGroup {
Theme, Theme,
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum Resource { pub enum Resource {
Payment, Payment,
@ -7753,7 +7755,9 @@ pub enum Resource {
Theme, Theme,
} }
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)] #[derive(
Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, serde::Deserialize, Hash,
)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum PermissionScope { pub enum PermissionScope {
Read = 0, Read = 0,

View File

@ -89,6 +89,7 @@ pub async fn get_authorization_info_with_group_tag(
pub async fn get_parent_group_info( 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,
) -> UserResponse<Vec<role_api::ParentGroupInfo>> { ) -> UserResponse<Vec<role_api::ParentGroupInfo>> {
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,
@ -102,10 +103,20 @@ pub async fn get_parent_group_info(
.await .await
.to_not_found_response(UserErrors::InvalidRoleId)?; .to_not_found_response(UserErrors::InvalidRoleId)?;
let parent_groups = ParentGroup::get_descriptions_for_groups( let entity_type = request
.entity_type
.unwrap_or_else(|| role_info.get_entity_type());
if role_info.get_entity_type() < entity_type {
return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
"Invalid operation, requestor entity type = {} cannot access entity type = {}",
role_info.get_entity_type(), role_info.get_entity_type(),
PermissionGroup::iter().collect(), entity_type
) ));
}
let parent_groups =
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(|(parent_group, description)| role_api::ParentGroupInfo {
@ -113,7 +124,7 @@ pub async fn get_parent_group_info(
description, description,
scopes: PermissionGroup::iter() 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 merhant access // TODO: Remove this hashset conversion when merchant access
// and organization access groups are removed // and organization access groups are removed
.collect::<HashSet<_>>() .collect::<HashSet<_>>()
.into_iter() .into_iter()

View File

@ -157,6 +157,111 @@ pub async fn create_role(
)) ))
} }
pub async fn create_role_v2(
state: SessionState,
user_from_token: UserFromToken,
req: role_api::CreateRoleV2Request,
_req_state: ReqState,
) -> UserResponse<role_api::RoleInfoResponseWithParentsGroup> {
let now = common_utils::date_time::now();
let user_entity_type = user_from_token
.get_role_info_from_db(&state)
.await
.attach_printable("Invalid role_id in JWT")?
.get_entity_type();
let role_entity_type = req.entity_type.unwrap_or(EntityType::Merchant);
if matches!(role_entity_type, EntityType::Organization) {
return Err(report!(UserErrors::InvalidRoleOperation))
.attach_printable("User trying to create org level custom role");
}
let requestor_entity_from_role_scope = EntityType::from(req.role_scope);
if requestor_entity_from_role_scope < role_entity_type {
return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
"User is trying to create role of type {role_entity_type} and scope {requestor_entity_from_role_scope}",
));
}
let max_from_scope_and_entity = cmp::max(requestor_entity_from_role_scope, role_entity_type);
if user_entity_type < max_from_scope_and_entity {
return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!(
"{user_entity_type} is trying to create of scope {requestor_entity_from_role_scope} and of type {role_entity_type}",
));
}
let role_name = RoleName::new(req.role_name.clone())?;
let permission_groups =
utils::user_role::parent_group_info_request_to_permission_groups(&req.parent_groups)?;
utils::user_role::validate_role_groups(&permission_groups)?;
utils::user_role::validate_role_name(
&state,
&role_name,
&user_from_token.merchant_id,
&user_from_token.org_id,
user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
&user_from_token.profile_id,
&role_entity_type,
)
.await?;
let (org_id, merchant_id, profile_id) = match role_entity_type {
EntityType::Organization | EntityType::Tenant => (user_from_token.org_id, None, None),
EntityType::Merchant => (
user_from_token.org_id,
Some(user_from_token.merchant_id),
None,
),
EntityType::Profile => (
user_from_token.org_id,
Some(user_from_token.merchant_id),
Some(user_from_token.profile_id),
),
};
let role = state
.global_store
.insert_role(RoleNew {
role_id: generate_id_with_default_len("role"),
role_name: role_name.get_role_name(),
merchant_id,
org_id,
groups: permission_groups,
scope: req.role_scope,
entity_type: role_entity_type,
created_by: user_from_token.user_id.clone(),
last_modified_by: user_from_token.user_id,
created_at: now,
last_modified_at: now,
profile_id,
tenant_id: user_from_token.tenant_id.unwrap_or(state.tenant.tenant_id),
})
.await
.to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;
let response_parent_groups =
utils::user_role::permission_groups_to_parent_group_info(&role.groups, role.entity_type);
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,
},
))
}
pub async fn get_role_with_groups( pub async fn get_role_with_groups(
state: SessionState, state: SessionState,
user_from_token: UserFromToken, user_from_token: UserFromToken,
@ -227,7 +332,7 @@ pub async fn get_parent_info_for_role(
.get_permission_groups() .get_permission_groups()
.iter() .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 merhant access // TODO: Remove this hashset conversion when merchant access
// and organization access groups are removed // and organization access groups are removed
.collect::<HashSet<_>>() .collect::<HashSet<_>>()
.into_iter() .into_iter()
@ -331,8 +436,8 @@ 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, request: role_api::ListRolesQueryParams,
) -> UserResponse<Vec<role_api::RoleInfoResponseNew>> { ) -> UserResponse<role_api::ListRolesResponse> {
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)
.await .await
@ -397,6 +502,39 @@ pub async fn list_roles_with_info(
role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from)); role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from));
if request.groups == Some(true) {
let list_role_info_response = role_info_vec
.into_iter()
.filter_map(|role_info| {
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({
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(),
);
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,
role_scope: role_info.get_scope(),
}
})
})
.collect::<Vec<_>>();
Ok(ApplicationResponse::Json(
role_api::ListRolesResponse::WithParentGroups(list_role_info_response),
))
}
// TODO: To be deprecated
else {
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| {
@ -415,7 +553,10 @@ pub async fn list_roles_with_info(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(ApplicationResponse::Json(list_role_info_response)) Ok(ApplicationResponse::Json(
role_api::ListRolesResponse::WithGroups(list_role_info_response),
))
}
} }
pub async fn list_roles_at_entity_level( pub async fn list_roles_at_entity_level(

View File

@ -2722,34 +2722,40 @@ impl User {
} }
// Role information // Role information
route = route = route.service(
route.service(
web::scope("/role") web::scope("/role")
.service( .service(
web::resource("") web::resource("")
.route(web::get().to(user_role::get_role_from_token)) .route(web::get().to(user_role::get_role_from_token))
// TODO: To be deprecated
.route(web::post().to(user_role::create_role)), .route(web::post().to(user_role::create_role)),
) )
.service(web::resource("/v2").route( .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), web::get().to(user_role::get_groups_and_resources_for_role_from_token),
)) ),
)
// TODO: To be deprecated // TODO: To be deprecated
.service( .service(
web::resource("/v2/list") web::resource("/v2/list").route(web::get().to(user_role::list_roles_with_info)),
.route(web::get().to(user_role::list_roles_with_info)),
) )
.service( .service(
web::scope("/list") web::scope("/list")
.service( .service(
web::resource("") web::resource("").route(web::get().to(user_role::list_roles_with_info)),
.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), 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}")

View File

@ -331,6 +331,7 @@ impl From<Flow> for ApiIdentifier {
| Flow::AcceptInvitationsPreAuth | Flow::AcceptInvitationsPreAuth
| Flow::DeleteUserRole | Flow::DeleteUserRole
| Flow::CreateRole | Flow::CreateRole
| Flow::CreateRoleV2
| Flow::UpdateRole | Flow::UpdateRole
| Flow::UserFromEmail | Flow::UserFromEmail
| Flow::ListUsersInLineage => Self::UserRole, | Flow::ListUsersInLineage => Self::UserRole,

View File

@ -74,7 +74,7 @@ pub async fn get_groups_and_resources_for_role_from_token(
)) ))
.await .await
} }
// TODO: To be deprecated
pub async fn create_role( pub async fn create_role(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
@ -95,6 +95,26 @@ pub async fn create_role(
.await .await
} }
pub async fn create_role_v2(
state: web::Data<AppState>,
req: HttpRequest,
json_payload: web::Json<role_api::CreateRoleV2Request>,
) -> HttpResponse {
let flow = Flow::CreateRoleV2;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
json_payload.into_inner(),
role_core::create_role_v2,
&auth::JWTAuth {
permission: Permission::MerchantUserWrite,
},
api_locking::LockAction::NotApplicable,
))
.await
}
pub async fn get_role( pub async fn get_role(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
@ -274,6 +294,7 @@ pub async fn get_role_information(
pub async fn get_parent_group_info( pub async fn get_parent_group_info(
state: web::Data<AppState>, state: web::Data<AppState>,
http_req: HttpRequest, http_req: HttpRequest,
query: web::Query<role_api::GetParentGroupsInfoQueryParams>,
) -> HttpResponse { ) -> HttpResponse {
let flow = Flow::GetParentGroupInfo; let flow = Flow::GetParentGroupInfo;
@ -281,9 +302,9 @@ pub async fn get_parent_group_info(
flow, flow,
state.clone(), state.clone(),
&http_req, &http_req,
(), query.into_inner(),
|state, user_from_token, _, _| async move { |state, user_from_token, request, _| async move {
user_role_core::get_parent_group_info(state, user_from_token).await user_role_core::get_parent_group_info(state, user_from_token, request).await
}, },
&auth::JWTAuth { &auth::JWTAuth {
permission: Permission::ProfileUserRead, permission: Permission::ProfileUserRead,
@ -317,7 +338,7 @@ pub async fn list_users_in_lineage(
pub async fn list_roles_with_info( pub async fn list_roles_with_info(
state: web::Data<AppState>, state: web::Data<AppState>,
req: HttpRequest, req: HttpRequest,
query: web::Query<role_api::ListRolesRequest>, query: web::Query<role_api::ListRolesQueryParams>,
) -> HttpResponse { ) -> HttpResponse {
let flow = Flow::ListRolesV2; let flow = Flow::ListRolesV2;

View File

@ -119,6 +119,7 @@ pub trait ParentGroupExt {
entity_type: EntityType, entity_type: EntityType,
groups: Vec<PermissionGroup>, groups: Vec<PermissionGroup>,
) -> Option<HashMap<ParentGroup, String>>; ) -> Option<HashMap<ParentGroup, String>>;
fn get_available_scopes(&self) -> Vec<PermissionScope>;
} }
impl ParentGroupExt for ParentGroup { impl ParentGroupExt for ParentGroup {
@ -143,24 +144,26 @@ impl ParentGroupExt for ParentGroup {
) -> Option<HashMap<Self, String>> { ) -> Option<HashMap<Self, String>> {
let descriptions_map = Self::iter() let descriptions_map = Self::iter()
.filter_map(|parent| { .filter_map(|parent| {
let scopes = groups if !groups.iter().any(|group| group.parent() == parent) {
.iter() return None;
.filter(|group| group.parent() == parent) }
.map(|group| group.scope()) let filtered_resources: Vec<_> = parent
.max()?;
let resources = parent
.resources() .resources()
.iter() .into_iter()
.filter(|res| res.entities().iter().any(|entity| entity <= &entity_type)) .filter(|res| res.entities().iter().any(|entity| entity <= &entity_type))
.collect();
if filtered_resources.is_empty() {
return None;
}
let description = filtered_resources
.iter()
.map(|res| permissions::get_resource_name(*res, entity_type)) .map(|res| permissions::get_resource_name(*res, entity_type))
.collect::<Option<Vec<_>>>()? .collect::<Option<Vec<_>>>()?
.join(", "); .join(", ");
Some(( Some((parent, description))
parent,
format!("{} {}", permissions::get_scope_name(scopes), resources),
))
}) })
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
@ -169,6 +172,13 @@ impl ParentGroupExt for ParentGroup {
.not() .not()
.then_some(descriptions_map) .then_some(descriptions_map)
} }
fn get_available_scopes(&self) -> Vec<PermissionScope> {
PermissionGroup::iter()
.filter(|group| group.parent() == *self)
.map(|group| group.scope())
.collect()
}
} }
pub static OPERATIONS: [Resource; 8] = [ pub static OPERATIONS: [Resource; 8] = [

View File

@ -1,6 +1,10 @@
use std::{cmp, collections::HashSet}; use std::{
cmp,
collections::{HashMap, HashSet},
};
use common_enums::{EntityType, PermissionGroup}; use api_models::user_role::role as role_api;
use common_enums::{EntityType, ParentGroup, PermissionGroup};
use common_utils::id_type; use common_utils::id_type;
use diesel_models::{ use diesel_models::{
enums::{UserRoleVersion, UserStatus}, enums::{UserRoleVersion, UserStatus},
@ -10,6 +14,7 @@ use diesel_models::{
use error_stack::{report, Report, ResultExt}; use error_stack::{report, Report, ResultExt};
use router_env::logger; use router_env::logger;
use storage_impl::errors::StorageError; use storage_impl::errors::StorageError;
use strum::IntoEnumIterator;
use crate::{ use crate::{
consts, consts,
@ -19,7 +24,11 @@ use crate::{
user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload}, user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload},
}, },
routes::SessionState, routes::SessionState,
services::authorization::{self as authz, roles}, services::authorization::{
self as authz,
permission_groups::{ParentGroupExt, PermissionGroupExt},
roles,
},
types::domain, types::domain,
}; };
@ -508,3 +517,68 @@ pub fn get_min_entity(
Ok(cmp::min(user_entity, filter_entity)) Ok(cmp::min(user_entity, filter_entity))
} }
pub fn parent_group_info_request_to_permission_groups(
parent_groups: &[role_api::ParentGroupInfoRequest],
) -> Result<Vec<PermissionGroup>, UserErrors> {
parent_groups
.iter()
.try_fold(Vec::new(), |mut permission_groups, parent_group| {
let scopes = &parent_group.scopes;
if scopes.is_empty() {
return Err(UserErrors::InvalidRoleOperation);
}
let available_scopes = parent_group.name.get_available_scopes();
if !scopes.iter().all(|scope| available_scopes.contains(scope)) {
return Err(UserErrors::InvalidRoleOperation);
}
let groups = PermissionGroup::iter()
.filter(|group| {
group.parent() == parent_group.name && scopes.contains(&group.scope())
})
.collect::<Vec<_>>();
permission_groups.extend(groups);
Ok(permission_groups)
})
}
pub fn permission_groups_to_parent_group_info(
permission_groups: &[PermissionGroup],
entity_type: EntityType,
) -> Vec<role_api::ParentGroupInfo> {
let parent_groups_map: HashMap<ParentGroup, Vec<common_enums::PermissionScope>> =
permission_groups
.iter()
.fold(HashMap::new(), |mut acc, group| {
let parent = group.parent();
let scope = group.scope();
acc.entry(parent).or_default().push(scope);
acc
});
parent_groups_map
.into_iter()
.filter_map(|(name, scopes)| {
let unique_scopes = scopes
.into_iter()
.collect::<HashSet<_>>()
.into_iter()
.collect();
let description =
ParentGroup::get_descriptions_for_groups(entity_type, permission_groups.to_vec())
.and_then(|descriptions| descriptions.get(&name).cloned())?;
Some(role_api::ParentGroupInfo {
name,
description,
scopes: unique_scopes,
})
})
.collect()
}

View File

@ -473,6 +473,8 @@ pub enum Flow {
PaymentsAuthorize, PaymentsAuthorize,
/// Create Role /// Create Role
CreateRole, CreateRole,
/// Create Role V2
CreateRoleV2,
/// Update Role /// Update Role
UpdateRole, UpdateRole,
/// User email flow start /// User email flow start