refactor(roles): Add more checks in create, update role APIs and change the response type (#3896)

This commit is contained in:
Mani Chandra
2024-02-29 19:26:46 +05:30
committed by GitHub
parent 7db499d8a9
commit 0136523f38
4 changed files with 81 additions and 53 deletions

View File

@ -3,7 +3,8 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};
use crate::user_role::{ use crate::user_role::{
role::{ role::{
CreateRoleRequest, GetRoleFromTokenResponse, GetRoleRequest, ListRolesResponse, CreateRoleRequest, GetRoleFromTokenResponse, GetRoleRequest, ListRolesResponse,
RoleInfoResponse, RoleInfoWithPermissionsResponse, UpdateRoleRequest, RoleInfoResponse, RoleInfoWithGroupsResponse, RoleInfoWithPermissionsResponse,
UpdateRoleRequest,
}, },
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest, AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest,
TransferOrgOwnershipRequest, UpdateUserRoleRequest, TransferOrgOwnershipRequest, UpdateUserRoleRequest,
@ -21,5 +22,6 @@ common_utils::impl_misc_api_event_type!(
UpdateRoleRequest, UpdateRoleRequest,
ListRolesResponse, ListRolesResponse,
RoleInfoResponse, RoleInfoResponse,
GetRoleFromTokenResponse GetRoleFromTokenResponse,
RoleInfoWithGroupsResponse
); );

View File

@ -57,14 +57,18 @@ pub async fn create_role(
state: AppState, state: AppState,
user_from_token: UserFromToken, user_from_token: UserFromToken,
req: role_api::CreateRoleRequest, req: role_api::CreateRoleRequest,
) -> UserResponse<()> { ) -> UserResponse<role_api::RoleInfoWithGroupsResponse> {
let now = common_utils::date_time::now(); let now = common_utils::date_time::now();
let role_name = RoleName::new(req.role_name)?.get_role_name(); let role_name = RoleName::new(req.role_name)?;
if req.groups.is_empty() { utils::user_role::validate_role_groups(&req.groups)?;
return Err(UserErrors::InvalidRoleOperation.into()) utils::user_role::validate_role_name(
.attach_printable("Role groups cannot be empty"); &state,
} &role_name,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await?;
if matches!(req.role_scope, RoleScope::Organization) if matches!(req.role_scope, RoleScope::Organization)
&& user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN && user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN
@ -73,19 +77,11 @@ pub async fn create_role(
.attach_printable("Non org admin user creating org level role"); .attach_printable("Non org admin user creating org level role");
} }
utils::user_role::is_role_name_already_present_for_merchant( let role = state
&state,
&role_name,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await?;
state
.store .store
.insert_role(RoleNew { .insert_role(RoleNew {
role_id: generate_id_with_default_len("role"), role_id: generate_id_with_default_len("role"),
role_name, role_name: role_name.get_role_name(),
merchant_id: user_from_token.merchant_id, merchant_id: user_from_token.merchant_id,
org_id: user_from_token.org_id, org_id: user_from_token.org_id,
groups: req.groups, groups: req.groups,
@ -98,7 +94,14 @@ pub async fn create_role(
.await .await
.to_duplicate_response(UserErrors::RoleNameAlreadyExists)?; .to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;
Ok(ApplicationResponse::StatusOk) Ok(ApplicationResponse::Json(
role_api::RoleInfoWithGroupsResponse {
groups: role.groups,
role_id: role.role_id,
role_name: role.role_name,
role_scope: role.scope,
},
))
} }
// TODO: To be deprecated once groups are stable // TODO: To be deprecated once groups are stable
@ -260,15 +263,11 @@ pub async fn update_role(
user_from_token: UserFromToken, user_from_token: UserFromToken,
req: role_api::UpdateRoleRequest, req: role_api::UpdateRoleRequest,
role_id: &str, role_id: &str,
) -> UserResponse<()> { ) -> UserResponse<role_api::RoleInfoWithGroupsResponse> {
let role_name = req let role_name = req.role_name.map(RoleName::new).transpose()?;
.role_name
.map(RoleName::new)
.transpose()?
.map(RoleName::get_role_name);
if let Some(ref role_name) = role_name { if let Some(ref role_name) = role_name {
utils::user_role::is_role_name_already_present_for_merchant( utils::user_role::validate_role_name(
&state, &state,
role_name, role_name,
&user_from_token.merchant_id, &user_from_token.merchant_id,
@ -277,6 +276,10 @@ pub async fn update_role(
.await?; .await?;
} }
if let Some(ref groups) = req.groups {
utils::user_role::validate_role_groups(groups)?;
}
let role_info = roles::RoleInfo::from_role_id( let role_info = roles::RoleInfo::from_role_id(
&state, &state,
role_id, role_id,
@ -290,23 +293,16 @@ pub async fn update_role(
&& user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN && user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN
{ {
return Err(UserErrors::InvalidRoleOperation.into()) return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("Non org admin user creating org level role"); .attach_printable("Non org admin user changing org level role");
} }
if let Some(ref groups) = req.groups { let updated_role = state
if groups.is_empty() {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("role groups cannot be empty");
}
}
state
.store .store
.update_role_by_role_id( .update_role_by_role_id(
role_id, role_id,
RoleUpdate::UpdateDetails { RoleUpdate::UpdateDetails {
groups: req.groups, groups: req.groups,
role_name, role_name: role_name.map(RoleName::get_role_name),
last_modified_at: common_utils::date_time::now(), last_modified_at: common_utils::date_time::now(),
last_modified_by: user_from_token.user_id, last_modified_by: user_from_token.user_id,
}, },
@ -316,5 +312,12 @@ pub async fn update_role(
blacklist::insert_role_in_blacklist(&state, role_id).await?; blacklist::insert_role_in_blacklist(&state, role_id).await?;
Ok(ApplicationResponse::StatusOk) Ok(ApplicationResponse::Json(
role_api::RoleInfoWithGroupsResponse {
groups: updated_role.groups,
role_id: updated_role.role_id,
role_name: updated_role.role_name,
role_scope: updated_role.scope,
},
))
} }

View File

@ -26,7 +26,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::OrganizationManage, PermissionGroup::OrganizationManage,
], ],
role_id: consts::user_role::ROLE_ID_INTERNAL_ADMIN.to_string(), role_id: consts::user_role::ROLE_ID_INTERNAL_ADMIN.to_string(),
role_name: "Internal Admin".to_string(), role_name: "internal_admin".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: false, is_invitable: false,
is_deletable: false, is_deletable: false,
@ -46,7 +46,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsView, PermissionGroup::MerchantDetailsView,
], ],
role_id: consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(), role_id: consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
role_name: "Internal View Only".to_string(), role_name: "internal_view_only".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: false, is_invitable: false,
is_deletable: false, is_deletable: false,
@ -73,7 +73,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::OrganizationManage, PermissionGroup::OrganizationManage,
], ],
role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(), role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
role_name: "Organization Admin".to_string(), role_name: "organization_admin".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: false, is_invitable: false,
is_deletable: false, is_deletable: false,
@ -100,7 +100,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsManage, PermissionGroup::MerchantDetailsManage,
], ],
role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(), role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(),
role_name: "Admin".to_string(), role_name: "admin".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: true, is_invitable: true,
is_deletable: true, is_deletable: true,
@ -120,7 +120,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsView, PermissionGroup::MerchantDetailsView,
], ],
role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(), role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(),
role_name: "View Only".to_string(), role_name: "view_only".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: true, is_invitable: true,
is_deletable: true, is_deletable: true,
@ -139,7 +139,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsView, PermissionGroup::MerchantDetailsView,
], ],
role_id: consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN.to_string(), role_id: consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN.to_string(),
role_name: "IAM".to_string(), role_name: "iam".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: true, is_invitable: true,
is_deletable: true, is_deletable: true,
@ -159,7 +159,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsManage, PermissionGroup::MerchantDetailsManage,
], ],
role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(), role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(),
role_name: "Developer".to_string(), role_name: "developer".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: true, is_invitable: true,
is_deletable: true, is_deletable: true,
@ -180,7 +180,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsView, PermissionGroup::MerchantDetailsView,
], ],
role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(), role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(),
role_name: "Operator".to_string(), role_name: "operator".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: true, is_invitable: true,
is_deletable: true, is_deletable: true,
@ -198,7 +198,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
PermissionGroup::MerchantDetailsView, PermissionGroup::MerchantDetailsView,
], ],
role_id: consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT.to_string(), role_id: consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT.to_string(),
role_name: "Customer Support".to_string(), role_name: "customer_support".to_string(),
scope: RoleScope::Organization, scope: RoleScope::Organization,
is_invitable: true, is_invitable: true,
is_deletable: true, is_deletable: true,

View File

@ -1,10 +1,12 @@
use api_models::user_role as user_role_api; use api_models::user_role as user_role_api;
use common_enums::PermissionGroup;
use error_stack::ResultExt; use error_stack::ResultExt;
use crate::{ use crate::{
core::errors::{UserErrors, UserResult}, core::errors::{UserErrors, UserResult},
routes::AppState, routes::AppState,
services::authorization::permissions::Permission, services::authorization::{permissions::Permission, roles},
types::domain,
}; };
impl From<Permission> for user_role_api::Permission { impl From<Permission> for user_role_api::Permission {
@ -40,23 +42,44 @@ impl From<Permission> for user_role_api::Permission {
} }
} }
pub async fn is_role_name_already_present_for_merchant( pub fn validate_role_groups(groups: &[PermissionGroup]) -> UserResult<()> {
if groups.is_empty() {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("Role groups cannot be empty");
}
if groups.contains(&PermissionGroup::OrganizationManage) {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("Organization manage group cannot be added to role");
}
Ok(())
}
pub async fn validate_role_name(
state: &AppState, state: &AppState,
role_name: &str, role_name: &domain::RoleName,
merchant_id: &str, merchant_id: &str,
org_id: &str, org_id: &str,
) -> UserResult<()> { ) -> UserResult<()> {
let role_name_list: Vec<String> = state let role_name_str = role_name.clone().get_role_name();
let is_present_in_predefined_roles = roles::predefined_roles::PREDEFINED_ROLES
.iter()
.any(|(_, role_info)| role_info.get_role_name() == role_name_str);
// TODO: Create and use find_by_role_name to make this efficient
let is_present_in_custom_roles = state
.store .store
.list_all_roles(merchant_id, org_id) .list_all_roles(merchant_id, org_id)
.await .await
.change_context(UserErrors::InternalServerError)? .change_context(UserErrors::InternalServerError)?
.iter() .iter()
.map(|role| role.role_name.to_owned()) .any(|role| role.role_name == role_name_str);
.collect();
if role_name_list.contains(&role_name.to_string()) { if is_present_in_predefined_roles || is_present_in_custom_roles {
return Err(UserErrors::RoleNameAlreadyExists.into()); return Err(UserErrors::RoleNameAlreadyExists.into());
} }
Ok(()) Ok(())
} }