mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +08:00
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:
@ -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
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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>),
|
||||||
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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}")
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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] = [
|
||||||
|
|||||||
@ -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()
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user