feat(users): add global support in user roles (#6458)

This commit is contained in:
Apoorv Dixit
2024-11-13 12:21:03 +05:30
committed by GitHub
parent 596c49e111
commit 98b141c6a0
18 changed files with 211 additions and 34 deletions

View File

@ -1343,6 +1343,8 @@ diesel::table! {
#[max_length = 64]
entity_type -> Nullable<Varchar>,
version -> UserRoleVersion,
#[max_length = 64]
tenant_id -> Varchar,
}
}

View File

@ -1289,6 +1289,8 @@ diesel::table! {
#[max_length = 64]
entity_type -> Nullable<Varchar>,
version -> UserRoleVersion,
#[max_length = 64]
tenant_id -> Varchar,
}
}

View File

@ -24,6 +24,7 @@ pub struct UserRole {
pub entity_id: Option<String>,
pub entity_type: Option<EntityType>,
pub version: enums::UserRoleVersion,
pub tenant_id: String,
}
impl UserRole {
@ -87,6 +88,7 @@ pub struct UserRoleNew {
pub entity_id: Option<String>,
pub entity_type: Option<EntityType>,
pub version: enums::UserRoleVersion,
pub tenant_id: String,
}
#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]

View File

@ -1853,7 +1853,7 @@ pub mod routes {
return Err(OpenSearchError::AccessForbiddenError)?;
}
let user_roles: HashSet<UserRole> = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &auth.user_id,
org_id: Some(&auth.org_id),
@ -1976,7 +1976,7 @@ pub mod routes {
return Err(OpenSearchError::AccessForbiddenError)?;
}
let user_roles: HashSet<UserRole> = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &auth.user_id,
org_id: Some(&auth.org_id),

View File

@ -94,6 +94,8 @@ pub enum UserErrors {
MaxTotpAttemptsReached,
#[error("Maximum attempts reached for Recovery Code")]
MaxRecoveryCodeAttemptsReached,
#[error("Forbidden tenant id")]
ForbiddenTenantId,
}
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
@ -239,6 +241,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
Self::MaxRecoveryCodeAttemptsReached => {
AER::BadRequest(ApiError::new(sub_code, 49, self.get_error_message(), None))
}
Self::ForbiddenTenantId => {
AER::BadRequest(ApiError::new(sub_code, 50, self.get_error_message(), None))
}
}
}
}
@ -289,6 +294,7 @@ impl UserErrors {
Self::AuthConfigParsingError => "Auth config parsing error",
Self::SSOFailed => "Invalid SSO request",
Self::JwtProfileIdMissing => "profile_id missing in JWT",
Self::ForbiddenTenantId => "Forbidden tenant id",
}
}
}

View File

@ -94,6 +94,7 @@ pub async fn generate_recon_token(
&state.conf,
user.org_id.clone(),
user.profile_id.clone(),
user.tenant_id,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)

View File

@ -598,7 +598,7 @@ async fn handle_existing_user_invitation(
let now = common_utils::date_time::now();
if state
.store
.global_store
.find_user_role_by_user_id_and_lineage(
invitee_user_from_db.get_user_id(),
&user_from_token.org_id,
@ -614,7 +614,7 @@ async fn handle_existing_user_invitation(
}
if state
.store
.global_store
.find_user_role_by_user_id_and_lineage(
invitee_user_from_db.get_user_id(),
&user_from_token.org_id,
@ -650,6 +650,10 @@ async fn handle_existing_user_invitation(
EntityType::Organization => {
user_role
.add_entity(domain::OrganizationLevel {
tenant_id: user_from_token
.tenant_id
.clone()
.unwrap_or(state.tenant.tenant_id.clone()),
org_id: user_from_token.org_id.clone(),
})
.insert_in_v2(state)
@ -658,6 +662,10 @@ async fn handle_existing_user_invitation(
EntityType::Merchant => {
user_role
.add_entity(domain::MerchantLevel {
tenant_id: user_from_token
.tenant_id
.clone()
.unwrap_or(state.tenant.tenant_id.clone()),
org_id: user_from_token.org_id.clone(),
merchant_id: user_from_token.merchant_id.clone(),
})
@ -671,6 +679,10 @@ async fn handle_existing_user_invitation(
.ok_or(UserErrors::InternalServerError)?;
user_role
.add_entity(domain::ProfileLevel {
tenant_id: user_from_token
.tenant_id
.clone()
.unwrap_or(state.tenant.tenant_id.clone()),
org_id: user_from_token.org_id.clone(),
merchant_id: user_from_token.merchant_id.clone(),
profile_id: profile_id.clone(),
@ -777,6 +789,10 @@ async fn handle_new_user_invitation(
EntityType::Organization => {
user_role
.add_entity(domain::OrganizationLevel {
tenant_id: user_from_token
.tenant_id
.clone()
.unwrap_or(state.tenant.tenant_id.clone()),
org_id: user_from_token.org_id.clone(),
})
.insert_in_v2(state)
@ -785,6 +801,10 @@ async fn handle_new_user_invitation(
EntityType::Merchant => {
user_role
.add_entity(domain::MerchantLevel {
tenant_id: user_from_token
.tenant_id
.clone()
.unwrap_or(state.tenant.tenant_id.clone()),
org_id: user_from_token.org_id.clone(),
merchant_id: user_from_token.merchant_id.clone(),
})
@ -798,6 +818,10 @@ async fn handle_new_user_invitation(
.ok_or(UserErrors::InternalServerError)?;
user_role
.add_entity(domain::ProfileLevel {
tenant_id: user_from_token
.tenant_id
.clone()
.unwrap_or(state.tenant.tenant_id.clone()),
org_id: user_from_token.org_id.clone(),
merchant_id: user_from_token.merchant_id.clone(),
profile_id: profile_id.clone(),
@ -864,6 +888,7 @@ async fn handle_new_user_invitation(
org_id: user_from_token.org_id.clone(),
role_id: request.role_id.clone(),
profile_id: None,
tenant_id: user_from_token.tenant_id.clone(),
};
let set_metadata_request = SetMetaDataRequest::IsChangePasswordRequired;
@ -909,7 +934,7 @@ pub async fn resend_invite(
.into();
let user_role = match state
.store
.global_store
.find_user_role_by_user_id_and_lineage(
user.get_user_id(),
&user_from_token.org_id,
@ -932,7 +957,7 @@ pub async fn resend_invite(
let user_role = match user_role {
Some(user_role) => user_role,
None => state
.store
.global_store
.find_user_role_by_user_id_and_lineage(
user.get_user_id(),
&user_from_token.org_id,
@ -1102,6 +1127,13 @@ pub async fn create_internal_user(
}
})?;
let default_tenant_id = common_utils::consts::DEFAULT_TENANT.to_string();
if state.tenant.tenant_id != default_tenant_id {
return Err(UserErrors::ForbiddenTenantId)
.attach_printable("Operation allowed only for the default tenant.");
}
let internal_merchant_id = common_utils::id_type::MerchantId::get_internal_user_merchant_id(
consts::user_role::INTERNAL_USER_MERCHANT_ID,
);
@ -1142,6 +1174,7 @@ pub async fn create_internal_user(
UserStatus::Active,
)
.add_entity(domain::MerchantLevel {
tenant_id: default_tenant_id,
org_id: internal_merchant.organization_id,
merchant_id: internal_merchant_id,
})
@ -1195,7 +1228,7 @@ pub async fn list_user_roles_details(
}
let user_roles_set = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: required_user.get_user_id(),
org_id: Some(&user_from_token.org_id),
@ -2372,7 +2405,7 @@ pub async fn list_orgs_for_user(
}
let orgs = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
org_id: None,
@ -2437,7 +2470,7 @@ pub async fn list_merchants_for_user_in_org(
.change_context(UserErrors::InternalServerError)?,
EntityType::Merchant | EntityType::Profile => {
let merchant_ids = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
org_id: Some(&user_from_token.org_id),
@ -2516,7 +2549,7 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
.change_context(UserErrors::InternalServerError)?,
EntityType::Profile => {
let profile_ids = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
org_id: Some(&user_from_token.org_id),
@ -2592,7 +2625,7 @@ pub async fn switch_org_for_user(
}
let user_role = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &user_from_token.user_id,
org_id: Some(&request.org_id),
@ -2621,6 +2654,7 @@ pub async fn switch_org_for_user(
request.org_id.clone(),
user_role.role_id.clone(),
profile_id.clone(),
user_from_token.tenant_id,
)
.await?;
@ -2764,7 +2798,7 @@ pub async fn switch_merchant_for_user_in_org(
EntityType::Merchant | EntityType::Profile => {
let user_role = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &user_from_token.user_id,
org_id: Some(&user_from_token.org_id),
@ -2805,6 +2839,7 @@ pub async fn switch_merchant_for_user_in_org(
org_id.clone(),
role_id.clone(),
profile_id,
user_from_token.tenant_id,
)
.await?;
@ -2879,7 +2914,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
EntityType::Profile => {
let user_role = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload{
user_id:&user_from_token.user_id,
org_id: Some(&user_from_token.org_id),
@ -2910,6 +2945,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
user_from_token.org_id.clone(),
role_id.clone(),
profile_id,
user_from_token.tenant_id,
)
.await?;

View File

@ -156,7 +156,7 @@ pub async fn update_user_role(
let mut is_updated = false;
let v2_user_role_to_be_updated = match state
.store
.global_store
.find_user_role_by_user_id_and_lineage(
user_to_be_updated.get_user_id(),
&user_from_token.org_id,
@ -210,7 +210,7 @@ pub async fn update_user_role(
}
state
.store
.global_store
.update_user_role_by_user_id_and_lineage(
user_to_be_updated.get_user_id(),
&user_from_token.org_id,
@ -229,7 +229,7 @@ pub async fn update_user_role(
}
let v1_user_role_to_be_updated = match state
.store
.global_store
.find_user_role_by_user_id_and_lineage(
user_to_be_updated.get_user_id(),
&user_from_token.org_id,
@ -283,7 +283,7 @@ pub async fn update_user_role(
}
state
.store
.global_store
.update_user_role_by_user_id_and_lineage(
user_to_be_updated.get_user_id(),
&user_from_token.org_id,
@ -470,7 +470,7 @@ pub async fn delete_user_role(
// Find in V2
let user_role_v2 = match state
.store
.global_store
.find_user_role_by_user_id_and_lineage(
user_from_db.get_user_id(),
&user_from_token.org_id,
@ -517,7 +517,7 @@ pub async fn delete_user_role(
user_role_deleted_flag = true;
state
.store
.global_store
.delete_user_role_by_user_id_and_lineage(
user_from_db.get_user_id(),
&user_from_token.org_id,
@ -532,7 +532,7 @@ pub async fn delete_user_role(
// Find in V1
let user_role_v1 = match state
.store
.global_store
.find_user_role_by_user_id_and_lineage(
user_from_db.get_user_id(),
&user_from_token.org_id,
@ -579,7 +579,7 @@ pub async fn delete_user_role(
user_role_deleted_flag = true;
state
.store
.global_store
.delete_user_role_by_user_id_and_lineage(
user_from_db.get_user_id(),
&user_from_token.org_id,
@ -599,7 +599,7 @@ pub async fn delete_user_role(
// Check if user has any more role associations
let remaining_roles = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_db.get_user_id(),
org_id: None,
@ -780,7 +780,7 @@ pub async fn list_invitations_for_user(
user_from_token: auth::UserIdFromAuth,
) -> UserResponse<Vec<user_role_api::ListInvitationForUserResponse>> {
let user_roles = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: &user_from_token.user_id,
org_id: None,

View File

@ -123,7 +123,6 @@ pub trait StorageInterface:
+ routing_algorithm::RoutingAlgorithmInterface
+ gsm::GsmInterface
+ unified_translations::UnifiedTranslationsInterface
+ user_role::UserRoleInterface
+ authorization::AuthorizationInterface
+ user::sample_data::BatchSampleDataInterface
+ health_check::HealthCheckDbInterface
@ -144,6 +143,7 @@ pub trait GlobalStorageInterface:
+ Sync
+ dyn_clone::DynClone
+ user::UserInterface
+ user_role::UserRoleInterface
+ user_key_store::UserKeyStoreInterface
+ 'static
{

View File

@ -234,6 +234,7 @@ impl UserRoleInterface for MockDb {
entity_id: None,
entity_type: None,
version: enums::UserRoleVersion::V1,
tenant_id: user_role.tenant_id,
};
db_user_roles.push(user_role.clone());
Ok(user_role)

View File

@ -179,6 +179,7 @@ pub struct UserFromSinglePurposeToken {
pub user_id: String,
pub origin: domain::Origin,
pub path: Vec<TokenPurpose>,
pub tenant_id: Option<String>,
}
#[cfg(feature = "olap")]
@ -189,6 +190,7 @@ pub struct SinglePurposeToken {
pub origin: domain::Origin,
pub path: Vec<TokenPurpose>,
pub exp: u64,
pub tenant_id: Option<String>,
}
#[cfg(feature = "olap")]
@ -199,6 +201,7 @@ impl SinglePurposeToken {
origin: domain::Origin,
settings: &Settings,
path: Vec<TokenPurpose>,
tenant_id: Option<String>,
) -> UserResult<String> {
let exp_duration =
std::time::Duration::from_secs(consts::SINGLE_PURPOSE_TOKEN_TIME_IN_SECS);
@ -209,6 +212,7 @@ impl SinglePurposeToken {
origin,
exp,
path,
tenant_id,
};
jwt::generate_jwt(&token_payload, settings).await
}
@ -222,6 +226,7 @@ pub struct AuthToken {
pub exp: u64,
pub org_id: id_type::OrganizationId,
pub profile_id: Option<id_type::ProfileId>,
pub tenant_id: Option<String>,
}
#[cfg(feature = "olap")]
@ -233,6 +238,7 @@ impl AuthToken {
settings: &Settings,
org_id: id_type::OrganizationId,
profile_id: Option<id_type::ProfileId>,
tenant_id: Option<String>,
) -> UserResult<String> {
let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS);
let exp = jwt::generate_exp(exp_duration)?.as_secs();
@ -243,6 +249,7 @@ impl AuthToken {
exp,
org_id,
profile_id,
tenant_id,
};
jwt::generate_jwt(&token_payload, settings).await
}
@ -255,6 +262,7 @@ pub struct UserFromToken {
pub role_id: String,
pub org_id: id_type::OrganizationId,
pub profile_id: Option<id_type::ProfileId>,
pub tenant_id: Option<String>,
}
pub struct UserIdFromAuth {
@ -268,6 +276,7 @@ pub struct SinglePurposeOrLoginToken {
pub role_id: Option<String>,
pub purpose: Option<TokenPurpose>,
pub exp: u64,
pub tenant_id: Option<String>,
}
pub trait AuthInfo {
@ -748,6 +757,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
if self.0 != payload.purpose {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
@ -758,6 +771,7 @@ where
user_id: payload.user_id.clone(),
origin: payload.origin.clone(),
path: payload.path,
tenant_id: payload.tenant_id,
},
AuthenticationType::SinglePurposeJwt {
user_id: payload.user_id,
@ -782,6 +796,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
if self.0 != payload.purpose {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
@ -792,6 +810,7 @@ where
user_id: payload.user_id.clone(),
origin: payload.origin.clone(),
path: payload.path,
tenant_id: payload.tenant_id,
}),
AuthenticationType::SinglePurposeJwt {
user_id: payload.user_id,
@ -821,6 +840,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let is_purpose_equal = payload
.purpose
@ -1448,6 +1471,11 @@ where
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.permission, &role_info)?;
@ -1476,6 +1504,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.permission, &role_info)?;
@ -1487,6 +1519,7 @@ where
org_id: payload.org_id,
role_id: payload.role_id,
profile_id: payload.profile_id,
tenant_id: payload.tenant_id,
},
AuthenticationType::MerchantJwt {
merchant_id: payload.merchant_id,
@ -1511,6 +1544,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.permission, &role_info)?;
@ -1570,6 +1607,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.required_permission, &role_info)?;
@ -1611,6 +1652,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.required_permission, &role_info)?;
@ -1647,6 +1692,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.required_permission, &role_info)?;
@ -1792,6 +1841,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.required_permission, &role_info)?;
@ -1825,6 +1878,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
if payload.merchant_id != self.merchant_id {
return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
@ -1964,6 +2021,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
if payload.merchant_id != self.merchant_id {
return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
@ -2039,6 +2100,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.required_permission, &role_info)?;
@ -2205,6 +2270,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.permission, &role_info)?;
@ -2262,6 +2331,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let profile_id = HeaderMapStruct::new(request_headers)
.get_id_type_from_header::<id_type::ProfileId>(headers::X_PROFILE_ID)?;
@ -2334,6 +2407,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.permission, &role_info)?;
@ -2393,6 +2470,11 @@ where
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
Ok((
UserFromToken {
user_id: payload.user_id.clone(),
@ -2400,6 +2482,7 @@ where
org_id: payload.org_id,
role_id: payload.role_id,
profile_id: payload.profile_id,
tenant_id: payload.tenant_id,
},
AuthenticationType::MerchantJwt {
merchant_id: payload.merchant_id,
@ -2424,6 +2507,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
Ok(((), AuthenticationType::NoAuth))
}
@ -2441,6 +2528,11 @@ where
state: &A,
) -> RouterResult<(AuthenticationData, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let key_manager_state = &(&state.session_state()).into();
let key_store = state
.store()
@ -2809,6 +2901,10 @@ where
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
authorization::check_tenant(
payload.tenant_id.clone(),
&state.session_state().tenant.tenant_id,
)?;
let role_info = authorization::get_role_info(state, &payload).await?;
authorization::check_permission(&self.permission, &role_info)?;

View File

@ -112,6 +112,18 @@ pub fn check_permission(
)
}
pub fn check_tenant(token_tenant_id: Option<String>, header_tenant_id: &str) -> RouterResult<()> {
if let Some(tenant_id) = token_tenant_id {
if tenant_id != header_tenant_id {
return Err(ApiErrorResponse::InvalidJwtToken).attach_printable(format!(
"Token tenant ID: '{}' does not match Header tenant ID: '{}'",
tenant_id, header_tenant_id
));
}
}
Ok(())
}
fn get_redis_connection<A: SessionStateInfo>(state: &A) -> RouterResult<Arc<RedisConnectionPool>> {
state
.store()

View File

@ -689,7 +689,10 @@ impl NewUser {
let org_user_role = self
.get_no_level_user_role(role_id, user_status)
.add_entity(OrganizationLevel { org_id });
.add_entity(OrganizationLevel {
tenant_id: state.tenant.tenant_id.clone(),
org_id,
});
org_user_role.insert_in_v2(&state).await
}
@ -1116,17 +1119,20 @@ pub struct NoLevel;
#[derive(Clone)]
pub struct OrganizationLevel {
pub tenant_id: String,
pub org_id: id_type::OrganizationId,
}
#[derive(Clone)]
pub struct MerchantLevel {
pub tenant_id: String,
pub org_id: id_type::OrganizationId,
pub merchant_id: id_type::MerchantId,
}
#[derive(Clone)]
pub struct ProfileLevel {
pub tenant_id: String,
pub org_id: id_type::OrganizationId,
pub merchant_id: id_type::MerchantId,
pub profile_id: id_type::ProfileId,
@ -1163,6 +1169,7 @@ impl NewUserRole<NoLevel> {
}
pub struct EntityInfo {
tenant_id: String,
org_id: id_type::OrganizationId,
merchant_id: Option<id_type::MerchantId>,
profile_id: Option<id_type::ProfileId>,
@ -1175,6 +1182,7 @@ impl From<OrganizationLevel> for EntityInfo {
Self {
entity_id: value.org_id.get_string_repr().to_owned(),
entity_type: EntityType::Organization,
tenant_id: value.tenant_id,
org_id: value.org_id,
merchant_id: None,
profile_id: None,
@ -1187,6 +1195,7 @@ impl From<MerchantLevel> for EntityInfo {
Self {
entity_id: value.merchant_id.get_string_repr().to_owned(),
entity_type: EntityType::Merchant,
tenant_id: value.tenant_id,
org_id: value.org_id,
profile_id: None,
merchant_id: Some(value.merchant_id),
@ -1199,6 +1208,7 @@ impl From<ProfileLevel> for EntityInfo {
Self {
entity_id: value.profile_id.get_string_repr().to_owned(),
entity_type: EntityType::Profile,
tenant_id: value.tenant_id,
org_id: value.org_id,
merchant_id: Some(value.merchant_id),
profile_id: Some(value.profile_id),
@ -1225,6 +1235,7 @@ where
entity_id: Some(entity.entity_id),
entity_type: Some(entity.entity_type),
version: UserRoleVersion::V2,
tenant_id: entity.tenant_id,
}
}
@ -1234,7 +1245,7 @@ where
let new_v2_role = self.convert_to_new_v2_role(entity.into());
state
.store
.global_store
.insert_user_role(new_v2_role)
.await
.change_context(UserErrors::InternalServerError)

View File

@ -65,7 +65,7 @@ impl SPTFlow {
.is_password_rotate_required(state)
.map(|rotate_required| rotate_required && !path.contains(&TokenPurpose::SSO)),
Self::MerchantSelect => Ok(state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user.get_user_id(),
org_id: None,
@ -93,6 +93,7 @@ impl SPTFlow {
next_flow.origin.clone(),
&state.conf,
next_flow.path.to_vec(),
Some(state.tenant.tenant_id.clone()),
)
.await
.map(|token| token.into())
@ -132,6 +133,7 @@ impl JWTFlow {
.ok_or(report!(UserErrors::InternalServerError))
.attach_printable("org_id not found")?,
Some(profile_id),
Some(user_role.tenant_id.clone()),
)
.await
.map(|token| token.into())
@ -299,7 +301,7 @@ impl NextFlow {
self.user.get_verification_days_left(state)?;
}
let user_role = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: self.user.get_user_id(),
org_id: None,

View File

@ -92,6 +92,7 @@ pub async fn generate_jwt_auth_token_with_attributes(
org_id: id_type::OrganizationId,
role_id: String,
profile_id: id_type::ProfileId,
tenant_id: Option<String>,
) -> UserResult<Secret<String>> {
let token = AuthToken::new_token(
user_id,
@ -100,6 +101,7 @@ pub async fn generate_jwt_auth_token_with_attributes(
&state.conf,
org_id,
Some(profile_id),
tenant_id,
)
.await?;
Ok(Secret::new(token))

View File

@ -142,7 +142,7 @@ pub async fn update_v1_and_v2_user_roles_in_db(
Result<UserRole, Report<StorageError>>,
) {
let updated_v1_role = state
.store
.global_store
.update_user_role_by_user_id_and_lineage(
user_id,
org_id,
@ -158,7 +158,7 @@ pub async fn update_v1_and_v2_user_roles_in_db(
});
let updated_v2_role = state
.store
.global_store
.update_user_role_by_user_id_and_lineage(
user_id,
org_id,
@ -228,7 +228,7 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite(
};
let user_roles = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id,
org_id: Some(&org_id),
@ -272,7 +272,7 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite(
};
let user_roles = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id,
org_id: None,
@ -317,7 +317,7 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite(
};
let user_roles = state
.store
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id,
org_id: None,
@ -407,7 +407,7 @@ pub async fn fetch_user_roles_by_payload(
request_entity_type: Option<EntityType>,
) -> UserResult<HashSet<UserRole>> {
Ok(state
.store
.global_store
.list_user_roles_by_org_id(payload)
.await
.change_context(UserErrors::InternalServerError)?

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
ALTER TABLE user_roles DROP COLUMN IF EXISTS tenant_id;

View File

@ -0,0 +1,2 @@
-- Your SQL goes here
ALTER TABLE user_roles ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) NOT NULL DEFAULT 'public';