feat(authz): Add custom role checks in authorization (#3719)

Co-authored-by: Apoorv Dixit <apoorv.dixit@juspay.in>
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Mani Chandra
2024-02-21 19:14:36 +05:30
committed by GitHub
parent 5952017260
commit ada6a32276
15 changed files with 669 additions and 148 deletions

View File

@ -1,3 +1,4 @@
use common_enums::RoleScope;
use common_utils::pii;
use crate::user::DashboardEntryResponse;
@ -7,9 +8,10 @@ pub struct ListRolesResponse(pub Vec<RoleInfoResponse>);
#[derive(Debug, serde::Serialize)]
pub struct RoleInfoResponse {
pub role_id: &'static str,
pub role_id: String,
pub permissions: Vec<Permission>,
pub role_name: &'static str,
pub role_name: String,
pub role_scope: RoleScope,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]

View File

@ -497,7 +497,7 @@ pub mod routes {
.await
.map(ApplicationResponse::Json)
},
&auth::JWTAuth(Permission::Analytics),
&auth::JWTAuth(Permission::PaymentWrite),
api_locking::LockAction::NotApplicable,
))
.await

View File

@ -18,10 +18,8 @@ use crate::services::email::types as email_types;
use crate::{
consts,
routes::AppState,
services::{
authentication as auth, authorization::predefined_permissions, ApplicationResponse,
},
types::domain,
services::{authentication as auth, authorization::roles, ApplicationResponse},
types::{domain, transformers::ForeignInto},
utils,
};
pub mod dashboard_metadata;
@ -444,7 +442,16 @@ pub async fn invite_user(
.into());
}
if !predefined_permissions::is_role_invitable(request.role_id.as_str())? {
let role_info = roles::get_role_info_from_role_id(
&state,
&request.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.to_not_found_response(UserErrors::InvalidRoleId)?;
if !role_info.is_invitable() {
return Err(UserErrors::InvalidRoleId.into())
.attach_printable(format!("role_id = {} is not invitable", request.role_id));
}
@ -652,7 +659,16 @@ async fn handle_invitation(
.into());
}
if !predefined_permissions::is_role_invitable(request.role_id.as_str())? {
let role_info = roles::get_role_info_from_role_id(
state,
&request.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.to_not_found_response(UserErrors::InvalidRoleId)?;
if !role_info.is_invitable() {
return Err(UserErrors::InvalidRoleId.into())
.attach_printable(format!("role_id = {} is not invitable", request.role_id));
}
@ -1030,20 +1046,18 @@ pub async fn switch_merchant_id(
.into());
}
let user_roles = state
.store
.list_user_roles_by_user_id(&user_from_token.user_id)
.await
.change_context(UserErrors::InternalServerError)?;
let active_user_roles = user_roles
.into_iter()
.filter(|role| role.status == UserStatus::Active)
.collect::<Vec<_>>();
let user = user_from_token.get_user_from_db(&state).await?;
let (token, role_id) = if utils::user_role::is_internal_role(&user_from_token.role_id) {
let role_info = roles::get_role_info_from_role_id(
&state,
&user_from_token.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.to_not_found_response(UserErrors::InternalServerError)?;
let (token, role_id) = if role_info.is_internal() {
let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
@ -1082,6 +1096,17 @@ pub async fn switch_merchant_id(
.await?;
(token, user_from_token.role_id)
} else {
let user_roles = state
.store
.list_user_roles_by_user_id(&user_from_token.user_id)
.await
.change_context(UserErrors::InternalServerError)?;
let active_user_roles = user_roles
.into_iter()
.filter(|role| role.status == UserStatus::Active)
.collect::<Vec<_>>();
let user_role = active_user_roles
.iter()
.find(|role| role.merchant_id == request.merchant_id)
@ -1166,17 +1191,47 @@ pub async fn get_users_for_merchant_account(
state: AppState,
user_from_token: auth::UserFromToken,
) -> UserResponse<user_api::GetUsersResponse> {
let users = state
let users_and_user_roles = state
.store
.find_users_and_roles_by_merchant_id(user_from_token.merchant_id.as_str())
.await
.change_context(UserErrors::InternalServerError)
.attach_printable("No users for given merchant id")?
.attach_printable("No users for given merchant id")?;
let users_user_roles_and_roles =
futures::future::try_join_all(users_and_user_roles.into_iter().map(
|(user, user_role)| async {
roles::get_role_info_from_role_id(
&state,
&user_role.role_id,
&user_role.merchant_id,
&user_role.org_id,
)
.await
.map(|role_info| (user, user_role, role_info))
.to_not_found_response(UserErrors::InternalServerError)
},
))
.await?;
let user_details_vec = users_user_roles_and_roles
.into_iter()
.filter_map(|(user, role)| domain::UserAndRoleJoined(user, role).try_into().ok())
.map(|(user, user_role, role_info)| {
let user = domain::UserFromStorage::from(user);
user_api::UserDetails {
email: user.get_email(),
name: user.get_name(),
role_id: user_role.role_id,
role_name: role_info.get_role_name().to_string(),
status: user_role.status.foreign_into(),
last_modified_at: user_role.last_modified,
}
})
.collect();
Ok(ApplicationResponse::Json(user_api::GetUsersResponse(users)))
Ok(ApplicationResponse::Json(user_api::GetUsersResponse(
user_details_vec,
)))
}
#[cfg(feature = "email")]

View File

@ -10,7 +10,7 @@ use crate::{
routes::AppState,
services::{
authentication::{self as auth},
authorization::{info, predefined_permissions},
authorization::{info, roles},
ApplicationResponse,
},
types::domain,
@ -30,58 +30,96 @@ pub async fn get_authorization_info(
))
}
pub async fn list_roles(_state: AppState) -> UserResponse<user_role_api::ListRolesResponse> {
pub async fn list_invitable_roles(
state: AppState,
user_from_token: auth::UserFromToken,
) -> UserResponse<user_role_api::ListRolesResponse> {
let predefined_roles_map = roles::predefined_roles::PREDEFINED_ROLES
.iter()
.filter(|(_, role_info)| role_info.is_invitable())
.map(|(role_id, role_info)| user_role_api::RoleInfoResponse {
permissions: role_info
.get_permissions_set()
.into_iter()
.map(Into::into)
.collect(),
role_id: role_id.to_string(),
role_name: role_info.get_role_name().to_string(),
role_scope: role_info.get_scope(),
});
let custom_roles_map = state
.store
.list_all_roles(&user_from_token.merchant_id, &user_from_token.org_id)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(roles::RoleInfo::from)
.filter(|role_info| role_info.is_invitable())
.map(|role_info| user_role_api::RoleInfoResponse {
permissions: role_info
.get_permissions_set()
.into_iter()
.map(Into::into)
.collect(),
role_id: role_info.get_role_id().to_string(),
role_name: role_info.get_role_name().to_string(),
role_scope: role_info.get_scope(),
});
Ok(ApplicationResponse::Json(user_role_api::ListRolesResponse(
predefined_permissions::PREDEFINED_PERMISSIONS
.iter()
.filter(|(_, role_info)| role_info.is_invitable())
.filter_map(|(role_id, role_info)| {
utils::user_role::get_role_name_and_permission_response(role_info).map(
|(permissions, role_name)| user_role_api::RoleInfoResponse {
permissions,
role_id,
role_name,
},
)
})
.collect(),
predefined_roles_map.chain(custom_roles_map).collect(),
)))
}
pub async fn get_role(
_state: AppState,
state: AppState,
user_from_token: auth::UserFromToken,
role: user_role_api::GetRoleRequest,
) -> UserResponse<user_role_api::RoleInfoResponse> {
let info = predefined_permissions::PREDEFINED_PERMISSIONS
.get_key_value(role.role_id.as_str())
.and_then(|(role_id, role_info)| {
utils::user_role::get_role_name_and_permission_response(role_info).map(
|(permissions, role_name)| user_role_api::RoleInfoResponse {
permissions,
role_id,
role_name,
},
)
})
.ok_or(UserErrors::InvalidRoleId)?;
let role_info = roles::get_role_info_from_role_id(
&state,
&role.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.to_not_found_response(UserErrors::InvalidRoleId)?;
Ok(ApplicationResponse::Json(info))
if role_info.is_internal() {
return Err(UserErrors::InvalidRoleId.into());
}
let permissions = role_info
.get_permissions_set()
.into_iter()
.map(Into::into)
.collect();
Ok(ApplicationResponse::Json(user_role_api::RoleInfoResponse {
permissions,
role_id: role.role_id,
role_name: role_info.get_role_name().to_string(),
role_scope: role_info.get_scope(),
}))
}
pub async fn get_role_from_token(
_state: AppState,
user: auth::UserFromToken,
state: AppState,
user_from_token: auth::UserFromToken,
) -> UserResponse<Vec<user_role_api::Permission>> {
Ok(ApplicationResponse::Json(
predefined_permissions::PREDEFINED_PERMISSIONS
.get(user.role_id.as_str())
.ok_or(UserErrors::InternalServerError.into())
.attach_printable("Invalid Role Id in JWT")?
.get_permissions()
.iter()
.map(|&per| per.into())
.collect(),
))
let role_info = user_from_token
.get_role_info_from_db(&state)
.await
.attach_printable("Invalid role_id in JWT")?;
let permissions = role_info
.get_permissions_set()
.into_iter()
.map(Into::into)
.collect();
Ok(ApplicationResponse::Json(permissions))
}
pub async fn update_user_role(
@ -89,7 +127,16 @@ pub async fn update_user_role(
user_from_token: auth::UserFromToken,
req: user_role_api::UpdateUserRoleRequest,
) -> UserResponse<()> {
if !predefined_permissions::is_role_updatable(&req.role_id)? {
let role_info = roles::get_role_info_from_role_id(
&state,
&req.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.to_not_found_response(UserErrors::InvalidRoleId)?;
if !role_info.is_updatable() {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable(format!("User role cannot be updated to {}", req.role_id));
}
@ -110,10 +157,19 @@ pub async fn update_user_role(
.await
.to_not_found_response(UserErrors::InvalidRoleOperation)?;
if !predefined_permissions::is_role_updatable(&user_role_to_be_updated.role_id)? {
let role_to_be_updated = roles::get_role_info_from_role_id(
&state,
&user_role_to_be_updated.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;
if !role_to_be_updated.is_updatable() {
return Err(UserErrors::InvalidRoleOperation.into()).attach_printable(format!(
"User role cannot be updated from {}",
user_role_to_be_updated.role_id
role_to_be_updated.get_role_id()
));
}
@ -270,7 +326,15 @@ pub async fn delete_user_role(
.find(|&role| role.merchant_id == user_from_token.merchant_id.as_str())
{
Some(user_role) => {
if !predefined_permissions::is_role_deletable(&user_role.role_id)? {
let role_info = roles::get_role_info_from_role_id(
&state,
&user_role.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;
if !role_info.is_deletable() {
return Err(UserErrors::InvalidDeleteOperation.into())
.attach_printable(format!("role_id = {} is not deletable", user_role.role_id));
}

View File

@ -291,7 +291,7 @@ pub async fn delete_sample_data(
&http_req,
payload.into_inner(),
sample_data::delete_sample_data_for_user,
&auth::JWTAuth(Permission::PaymentWrite),
&auth::JWTAuth(Permission::MerchantAccountWrite),
api_locking::LockAction::NotApplicable,
))
.await

View File

@ -36,7 +36,7 @@ pub async fn list_all_roles(state: web::Data<AppState>, req: HttpRequest) -> Htt
state.clone(),
&req,
(),
|state, _: (), _| user_role_core::list_roles(state),
|state, user, _| user_role_core::list_invitable_roles(state, user),
&auth::JWTAuth(Permission::UsersRead),
api_locking::LockAction::NotApplicable,
))
@ -57,7 +57,7 @@ pub async fn get_role(
state.clone(),
&req,
request_payload,
|state, _: (), req| user_role_core::get_role(state, req),
user_role_core::get_role,
&auth::JWTAuth(Permission::UsersRead),
api_locking::LockAction::NotApplicable,
))

View File

@ -503,8 +503,8 @@ where
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
let permissions = authorization::get_permissions(&payload.role_id)?;
authorization::check_authorization(&self.0, permissions)?;
let permissions = authorization::get_permissions(state, &payload).await?;
authorization::check_authorization(&self.0, &permissions)?;
Ok((
(),
@ -532,8 +532,8 @@ where
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
let permissions = authorization::get_permissions(&payload.role_id)?;
authorization::check_authorization(&self.0, permissions)?;
let permissions = authorization::get_permissions(state, &payload).await?;
authorization::check_authorization(&self.0, &permissions)?;
Ok((
UserFromToken {
@ -570,8 +570,8 @@ where
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
let permissions = authorization::get_permissions(&payload.role_id)?;
authorization::check_authorization(&self.required_permission, permissions)?;
let permissions = authorization::get_permissions(state, &payload).await?;
authorization::check_authorization(&self.required_permission, &permissions)?;
// Check if token has access to MerchantId that has been requested through query param
if payload.merchant_id != self.merchant_id {
@ -613,8 +613,8 @@ where
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
let permissions = authorization::get_permissions(&payload.role_id)?;
authorization::check_authorization(&self.0, permissions)?;
let permissions = authorization::get_permissions(state, &payload).await?;
authorization::check_authorization(&self.0, &permissions)?;
let key_store = state
.store()
@ -663,8 +663,8 @@ where
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
let permissions = authorization::get_permissions(&payload.role_id)?;
authorization::check_authorization(&self.0, permissions)?;
let permissions = authorization::get_permissions(state, &payload).await?;
authorization::check_authorization(&self.0, &permissions)?;
let key_store = state
.store()

View File

@ -1,14 +1,50 @@
use crate::core::errors::{ApiErrorResponse, RouterResult};
use common_enums::PermissionGroup;
use super::authentication::AuthToken;
use crate::{
core::errors::{ApiErrorResponse, RouterResult, StorageErrorExt},
routes::app::AppStateInfo,
};
pub mod info;
pub mod permission_groups;
pub mod permissions;
pub mod predefined_permissions;
pub mod roles;
pub fn get_permissions(role: &str) -> RouterResult<&Vec<permissions::Permission>> {
predefined_permissions::PREDEFINED_PERMISSIONS
.get(role)
.map(|role_info| role_info.get_permissions())
.ok_or(ApiErrorResponse::InvalidJwtToken.into())
pub async fn get_permissions<A>(
state: &A,
token: &AuthToken,
) -> RouterResult<Vec<permissions::Permission>>
where
A: AppStateInfo + Sync,
{
if let Some(role_info) = roles::predefined_roles::PREDEFINED_ROLES.get(token.role_id.as_str()) {
Ok(get_permissions_from_groups(
role_info.get_permission_groups(),
))
} else {
state
.store()
.find_role_by_role_id_in_merchant_scope(
&token.role_id,
&token.merchant_id,
&token.org_id,
)
.await
.map(|role| get_permissions_from_groups(&role.groups))
.to_not_found_response(ApiErrorResponse::InvalidJwtToken)
}
}
pub fn get_permissions_from_groups(groups: &[PermissionGroup]) -> Vec<permissions::Permission> {
groups
.iter()
.flat_map(|group| {
permission_groups::get_permissions_vec(group)
.iter()
.cloned()
})
.collect()
}
pub fn check_authorization(

View File

@ -0,0 +1,86 @@
use common_enums::PermissionGroup;
use super::permissions::Permission;
pub fn get_permissions_vec(permission_group: &PermissionGroup) -> &[Permission] {
match permission_group {
PermissionGroup::OperationsView => &OPERATIONS_VIEW,
PermissionGroup::OperationsManage => &OPERATIONS_MANAGE,
PermissionGroup::ConnectorsView => &CONNECTORS_VIEW,
PermissionGroup::ConnectorsManage => &CONNECTORS_MANAGE,
PermissionGroup::WorkflowsView => &WORKFLOWS_VIEW,
PermissionGroup::WorkflowsManage => &WORKFLOWS_MANAGE,
PermissionGroup::AnalyticsView => &ANALYTICS_VIEW,
PermissionGroup::UsersView => &USERS_VIEW,
PermissionGroup::UsersManage => &USERS_MANAGE,
PermissionGroup::MerchantDetailsView => &MERCHANT_DETAILS_VIEW,
PermissionGroup::MerchantDetailsManage => &MERCHANT_DETAILS_MANAGE,
PermissionGroup::OrganizationManage => &ORGANIZATION_MANAGE,
}
}
pub static OPERATIONS_VIEW: [Permission; 6] = [
Permission::PaymentRead,
Permission::RefundRead,
Permission::MandateRead,
Permission::DisputeRead,
Permission::CustomerRead,
Permission::MerchantAccountRead,
];
pub static OPERATIONS_MANAGE: [Permission; 6] = [
Permission::PaymentWrite,
Permission::RefundWrite,
Permission::MandateWrite,
Permission::DisputeWrite,
Permission::CustomerWrite,
Permission::MerchantAccountRead,
];
pub static CONNECTORS_VIEW: [Permission; 2] = [
Permission::MerchantConnectorAccountRead,
Permission::MerchantAccountRead,
];
pub static CONNECTORS_MANAGE: [Permission; 2] = [
Permission::MerchantConnectorAccountWrite,
Permission::MerchantAccountRead,
];
pub static WORKFLOWS_VIEW: [Permission; 5] = [
Permission::RoutingRead,
Permission::ThreeDsDecisionManagerRead,
Permission::SurchargeDecisionManagerRead,
Permission::MerchantConnectorAccountRead,
Permission::MerchantAccountRead,
];
pub static WORKFLOWS_MANAGE: [Permission; 5] = [
Permission::RoutingWrite,
Permission::ThreeDsDecisionManagerWrite,
Permission::SurchargeDecisionManagerWrite,
Permission::MerchantConnectorAccountRead,
Permission::MerchantAccountRead,
];
pub static ANALYTICS_VIEW: [Permission; 2] =
[Permission::Analytics, Permission::MerchantAccountRead];
pub static USERS_VIEW: [Permission; 2] = [Permission::UsersRead, Permission::MerchantAccountRead];
pub static USERS_MANAGE: [Permission; 2] =
[Permission::UsersWrite, Permission::MerchantAccountRead];
pub static MERCHANT_DETAILS_VIEW: [Permission; 1] = [Permission::MerchantAccountRead];
pub static MERCHANT_DETAILS_MANAGE: [Permission; 4] = [
Permission::MerchantAccountWrite,
Permission::ApiKeyRead,
Permission::ApiKeyWrite,
Permission::MerchantAccountRead,
];
pub static ORGANIZATION_MANAGE: [Permission; 2] = [
Permission::MerchantAccountCreate,
Permission::MerchantAccountRead,
];

View File

@ -1,6 +1,6 @@
use strum::Display;
#[derive(PartialEq, Display, Clone, Debug, Copy)]
#[derive(PartialEq, Display, Clone, Debug, Copy, Eq, Hash)]
pub enum Permission {
PaymentRead,
PaymentWrite,

View File

@ -0,0 +1,100 @@
use std::collections::HashSet;
use common_enums::{PermissionGroup, RoleScope};
use common_utils::errors::CustomResult;
use super::{permission_groups::get_permissions_vec, permissions::Permission};
use crate::{core::errors, routes::AppState};
pub mod predefined_roles;
#[derive(Clone)]
pub struct RoleInfo {
role_id: String,
role_name: String,
groups: Vec<PermissionGroup>,
scope: RoleScope,
is_invitable: bool,
is_deletable: bool,
is_updatable: bool,
is_internal: bool,
}
impl RoleInfo {
pub fn get_role_id(&self) -> &str {
&self.role_id
}
pub fn get_role_name(&self) -> &str {
&self.role_name
}
pub fn get_permission_groups(&self) -> &Vec<PermissionGroup> {
&self.groups
}
pub fn get_scope(&self) -> RoleScope {
self.scope
}
pub fn is_invitable(&self) -> bool {
self.is_invitable
}
pub fn is_deletable(&self) -> bool {
self.is_deletable
}
pub fn is_internal(&self) -> bool {
self.is_internal
}
pub fn is_updatable(&self) -> bool {
self.is_updatable
}
pub fn get_permissions_set(&self) -> HashSet<Permission> {
self.groups
.iter()
.flat_map(|group| get_permissions_vec(group).iter().copied())
.collect()
}
pub fn check_permission_exists(&self, required_permission: &Permission) -> bool {
self.groups
.iter()
.any(|group| get_permissions_vec(group).contains(required_permission))
}
}
pub async fn get_role_info_from_role_id(
state: &AppState,
role_id: &str,
merchant_id: &str,
org_id: &str,
) -> CustomResult<RoleInfo, errors::StorageError> {
if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
Ok(role.clone())
} else {
state
.store
.find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id)
.await
.map(RoleInfo::from)
}
}
impl From<diesel_models::role::Role> for RoleInfo {
fn from(role: diesel_models::role::Role) -> Self {
Self {
role_id: role.role_id,
role_name: role.role_name,
groups: role.groups.into_iter().map(Into::into).collect(),
scope: role.scope,
is_invitable: true,
is_deletable: true,
is_updatable: true,
is_internal: false,
}
}
}

View File

@ -0,0 +1,210 @@
use std::collections::HashMap;
use common_enums::{PermissionGroup, RoleScope};
use once_cell::sync::Lazy;
use super::RoleInfo;
use crate::consts;
pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|| {
let mut roles = HashMap::new();
roles.insert(
consts::user_role::ROLE_ID_INTERNAL_ADMIN,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::OperationsManage,
PermissionGroup::ConnectorsView,
PermissionGroup::ConnectorsManage,
PermissionGroup::WorkflowsView,
PermissionGroup::WorkflowsManage,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::UsersManage,
PermissionGroup::MerchantDetailsView,
PermissionGroup::MerchantDetailsManage,
PermissionGroup::OrganizationManage,
],
role_id: consts::user_role::ROLE_ID_INTERNAL_ADMIN.to_string(),
role_name: "Internal Admin".to_string(),
scope: RoleScope::Organization,
is_invitable: false,
is_deletable: false,
is_updatable: false,
is_internal: true,
},
);
roles.insert(
consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::ConnectorsView,
PermissionGroup::WorkflowsView,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::MerchantDetailsView,
],
role_id: consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
role_name: "Internal View Only".to_string(),
scope: RoleScope::Organization,
is_invitable: false,
is_deletable: false,
is_updatable: false,
is_internal: true,
},
);
roles.insert(
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::OperationsManage,
PermissionGroup::ConnectorsView,
PermissionGroup::ConnectorsManage,
PermissionGroup::WorkflowsView,
PermissionGroup::WorkflowsManage,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::UsersManage,
PermissionGroup::MerchantDetailsView,
PermissionGroup::MerchantDetailsManage,
PermissionGroup::OrganizationManage,
],
role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
role_name: "Organization Admin".to_string(),
scope: RoleScope::Organization,
is_invitable: false,
is_deletable: false,
is_updatable: false,
is_internal: false,
},
);
// MERCHANT ROLES
roles.insert(
consts::user_role::ROLE_ID_MERCHANT_ADMIN,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::OperationsManage,
PermissionGroup::ConnectorsView,
PermissionGroup::ConnectorsManage,
PermissionGroup::WorkflowsView,
PermissionGroup::WorkflowsManage,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::UsersManage,
PermissionGroup::MerchantDetailsView,
PermissionGroup::MerchantDetailsManage,
],
role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(),
role_name: "Admin".to_string(),
scope: RoleScope::Organization,
is_invitable: true,
is_deletable: true,
is_updatable: true,
is_internal: false,
},
);
roles.insert(
consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::ConnectorsView,
PermissionGroup::WorkflowsView,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::MerchantDetailsView,
],
role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(),
role_name: "View Only".to_string(),
scope: RoleScope::Organization,
is_invitable: true,
is_deletable: true,
is_updatable: true,
is_internal: false,
},
);
roles.insert(
consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::UsersManage,
PermissionGroup::MerchantDetailsView,
],
role_id: consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN.to_string(),
role_name: "IAM".to_string(),
scope: RoleScope::Organization,
is_invitable: true,
is_deletable: true,
is_updatable: true,
is_internal: false,
},
);
roles.insert(
consts::user_role::ROLE_ID_MERCHANT_DEVELOPER,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::ConnectorsView,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::MerchantDetailsView,
PermissionGroup::MerchantDetailsManage,
],
role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(),
role_name: "Developer".to_string(),
scope: RoleScope::Organization,
is_invitable: true,
is_deletable: true,
is_updatable: true,
is_internal: false,
},
);
roles.insert(
consts::user_role::ROLE_ID_MERCHANT_OPERATOR,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::OperationsManage,
PermissionGroup::ConnectorsView,
PermissionGroup::WorkflowsView,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::MerchantDetailsView,
],
role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(),
role_name: "Operator".to_string(),
scope: RoleScope::Organization,
is_invitable: true,
is_deletable: true,
is_updatable: true,
is_internal: false,
},
);
roles.insert(
consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT,
RoleInfo {
groups: vec![
PermissionGroup::OperationsView,
PermissionGroup::AnalyticsView,
PermissionGroup::UsersView,
PermissionGroup::MerchantDetailsView,
],
role_id: consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT.to_string(),
role_name: "Customer Support".to_string(),
scope: RoleScope::Organization,
is_invitable: true,
is_deletable: true,
is_updatable: true,
is_internal: false,
},
);
roles
});

View File

@ -25,11 +25,7 @@ use crate::{
},
db::StorageInterface,
routes::AppState,
services::{
authentication as auth,
authentication::UserFromToken,
authorization::{info, predefined_permissions},
},
services::{authentication as auth, authentication::UserFromToken, authorization::info},
types::transformers::ForeignFrom,
utils::{self, user::password},
};
@ -824,32 +820,6 @@ impl From<info::PermissionInfo> for user_role_api::PermissionInfo {
}
}
pub struct UserAndRoleJoined(pub storage_user::User, pub UserRole);
impl TryFrom<UserAndRoleJoined> for user_api::UserDetails {
type Error = ();
fn try_from(user_and_role: UserAndRoleJoined) -> Result<Self, Self::Error> {
let status = match user_and_role.1.status {
UserStatus::Active => user_role_api::UserStatus::Active,
UserStatus::InvitationSent => user_role_api::UserStatus::InvitationSent,
};
let role_id = user_and_role.1.role_id;
let role_name = predefined_permissions::get_role_name_from_id(role_id.as_str())
.ok_or(())?
.to_string();
Ok(Self {
email: user_and_role.0.email,
name: user_and_role.0.name,
role_id,
status,
role_name,
last_modified_at: user_and_role.0.last_modified_at,
})
}
}
pub enum SignInWithRoleStrategyType {
SingleRole(SignInWithSingleRoleStrategy),
MultipleRoles(SignInWithMultipleRolesStrategy),
@ -947,3 +917,12 @@ impl SignInWithMultipleRolesStrategy {
))
}
}
impl ForeignFrom<UserStatus> for user_role_api::UserStatus {
fn foreign_from(value: UserStatus) -> Self {
match value {
UserStatus::Active => Self::Active,
UserStatus::InvitationSent => Self::InvitationSent,
}
}
}

View File

@ -9,7 +9,10 @@ use masking::{ExposeInterface, Secret};
use crate::{
core::errors::{StorageError, UserErrors, UserResult},
routes::AppState,
services::authentication::{AuthToken, UserFromToken},
services::{
authentication::{AuthToken, UserFromToken},
authorization::roles::{self, RoleInfo},
},
types::domain::{self, MerchantAccount, UserFromStorage},
};
@ -19,7 +22,10 @@ pub mod password;
pub mod sample_data;
impl UserFromToken {
pub async fn get_merchant_account(&self, state: AppState) -> UserResult<MerchantAccount> {
pub async fn get_merchant_account_from_db(
&self,
state: AppState,
) -> UserResult<MerchantAccount> {
let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
@ -56,6 +62,12 @@ impl UserFromToken {
.change_context(UserErrors::InternalServerError)?;
Ok(user.into())
}
pub async fn get_role_info_from_db(&self, state: &AppState) -> UserResult<RoleInfo> {
roles::get_role_info_from_role_id(state, &self.role_id, &self.merchant_id, &self.org_id)
.await
.change_context(UserErrors::InternalServerError)
}
}
pub async fn generate_jwt_auth_token(

View File

@ -1,29 +1,6 @@
use api_models::user_role as user_role_api;
use crate::{
consts,
services::authorization::{permissions::Permission, predefined_permissions::RoleInfo},
};
pub fn is_internal_role(role_id: &str) -> bool {
role_id == consts::user_role::ROLE_ID_INTERNAL_ADMIN
|| role_id == consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER
}
pub fn get_role_name_and_permission_response(
role_info: &RoleInfo,
) -> Option<(Vec<user_role_api::Permission>, &'static str)> {
role_info.get_name().map(|name| {
(
role_info
.get_permissions()
.iter()
.map(|&per| per.into())
.collect::<Vec<user_role_api::Permission>>(),
name,
)
})
}
use crate::services::authorization::permissions::Permission;
impl From<Permission> for user_role_api::Permission {
fn from(value: Permission) -> Self {