feat: add profile_id authentication for business profile update and list (#5673)

This commit is contained in:
Hrithikesh
2024-09-03 11:37:06 +05:30
committed by GitHub
parent f822730979
commit e3a9fb16c5
7 changed files with 148 additions and 10 deletions

View File

@ -3717,6 +3717,7 @@ pub async fn create_business_profile(
pub async fn list_business_profile( pub async fn list_business_profile(
state: SessionState, state: SessionState,
merchant_id: id_type::MerchantId, merchant_id: id_type::MerchantId,
profile_id_list: Option<Vec<id_type::ProfileId>>,
) -> RouterResponse<Vec<api_models::admin::BusinessProfileResponse>> { ) -> RouterResponse<Vec<api_models::admin::BusinessProfileResponse>> {
let db = state.store.as_ref(); let db = state.store.as_ref();
let key_store = db let key_store = db
@ -3732,6 +3733,7 @@ pub async fn list_business_profile(
.await .await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)? .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?
.clone(); .clone();
let profiles = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, profiles);
let mut business_profiles = Vec::new(); let mut business_profiles = Vec::new();
for profile in profiles { for profile in profiles {
let business_profile = let business_profile =

View File

@ -1334,7 +1334,7 @@ pub fn get_incremental_authorization_allowed_value(
} }
} }
pub(super) trait GetProfileId { pub(crate) trait GetProfileId {
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId>; fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId>;
} }
@ -1381,6 +1381,12 @@ impl<T, F> GetProfileId for (storage::Payouts, T, F) {
} }
} }
impl GetProfileId for domain::BusinessProfile {
fn get_profile_id(&self) -> Option<&common_utils::id_type::ProfileId> {
Some(self.get_id())
}
}
/// Filter Objects based on profile ids /// Filter Objects based on profile ids
pub(super) fn filter_objects_based_on_profile_id_list<T: GetProfileId>( pub(super) fn filter_objects_based_on_profile_id_list<T: GetProfileId>(
profile_id_list_auth_layer: Option<Vec<common_utils::id_type::ProfileId>>, profile_id_list_auth_layer: Option<Vec<common_utils::id_type::ProfileId>>,
@ -1406,7 +1412,7 @@ pub(super) fn filter_objects_based_on_profile_id_list<T: GetProfileId>(
} }
} }
pub(super) fn validate_profile_id_from_auth_layer<T: GetProfileId + std::fmt::Debug>( pub(crate) fn validate_profile_id_from_auth_layer<T: GetProfileId + std::fmt::Debug>(
profile_id_auth_layer: Option<common_utils::id_type::ProfileId>, profile_id_auth_layer: Option<common_utils::id_type::ProfileId>,
object: &T, object: &T,
) -> RouterResult<()> { ) -> RouterResult<()> {

View File

@ -119,7 +119,9 @@ pub fn mk_app(
{ {
// This is a more specific route as compared to `MerchantConnectorAccount` // This is a more specific route as compared to `MerchantConnectorAccount`
// so it is registered before `MerchantConnectorAccount`. // so it is registered before `MerchantConnectorAccount`.
server_app = server_app.service(routes::BusinessProfile::server(state.clone())) server_app = server_app
.service(routes::BusinessProfileNew::server(state.clone()))
.service(routes::BusinessProfile::server(state.clone()))
} }
server_app = server_app server_app = server_app
.service(routes::Payments::server(state.clone())) .service(routes::Payments::server(state.clone()))

View File

@ -59,10 +59,10 @@ pub use self::app::Forex;
#[cfg(all(feature = "olap", feature = "recon"))] #[cfg(all(feature = "olap", feature = "recon"))]
pub use self::app::Recon; pub use self::app::Recon;
pub use self::app::{ pub use self::app::{
ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, Cache, Cards, Configs, ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, BusinessProfileNew, Cache,
ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health, Mandates, Cards, Configs, ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health,
MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments,
Refunds, SessionState, User, Webhooks, Poll, Refunds, SessionState, User, Webhooks,
}; };
#[cfg(feature = "olap")] #[cfg(feature = "olap")]
pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents};

View File

@ -892,8 +892,9 @@ pub async fn business_profile_update(
}, },
auth::auth_type( auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()), &auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
&auth::JWTAuthMerchantFromRoute { &auth::JWTAuthMerchantAndProfileFromRoute {
merchant_id: merchant_id.clone(), merchant_id: merchant_id.clone(),
profile_id: profile_id.clone(),
required_permission: Permission::MerchantAccountWrite, required_permission: Permission::MerchantAccountWrite,
}, },
req.headers(), req.headers(),
@ -971,9 +972,43 @@ pub async fn business_profiles_list(
state, state,
&req, &req,
merchant_id.clone(), merchant_id.clone(),
|state, _, merchant_id, _| list_business_profile(state, merchant_id), |state, _auth, merchant_id, _| list_business_profile(state, merchant_id, None),
auth::auth_type( auth::auth_type(
&auth::AdminApiAuth, &auth::AdminApiAuthWithMerchantIdFromHeader,
&auth::JWTAuthMerchantFromRoute {
merchant_id,
required_permission: Permission::MerchantAccountRead,
},
req.headers(),
),
api_locking::LockAction::NotApplicable,
))
.await
}
#[instrument(skip_all, fields(flow = ?Flow::BusinessProfileList))]
pub async fn business_profiles_list_at_profile_level(
state: web::Data<AppState>,
req: HttpRequest,
path: web::Path<common_utils::id_type::MerchantId>,
) -> HttpResponse {
let flow = Flow::BusinessProfileList;
let merchant_id = path.into_inner();
Box::pin(api::server_wrap(
flow,
state,
&req,
merchant_id.clone(),
|state, auth, merchant_id, _| {
list_business_profile(
state,
merchant_id,
auth.profile_id.map(|profile_id| vec![profile_id]),
)
},
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromHeader,
&auth::JWTAuthMerchantFromRoute { &auth::JWTAuthMerchantFromRoute {
merchant_id, merchant_id,
required_permission: Permission::MerchantAccountRead, required_permission: Permission::MerchantAccountRead,

View File

@ -1639,6 +1639,19 @@ impl BusinessProfile {
} }
} }
pub struct BusinessProfileNew;
#[cfg(feature = "olap")]
impl BusinessProfileNew {
pub fn server(state: AppState) -> Scope {
web::scope("/account/{account_id}/profile")
.app_data(web::Data::new(state))
.service(
web::resource("").route(web::get().to(business_profiles_list_at_profile_level)),
)
}
}
pub struct Gsm; pub struct Gsm;
#[cfg(feature = "olap")] #[cfg(feature = "olap")]

View File

@ -88,6 +88,11 @@ pub enum AuthenticationType {
merchant_id: id_type::MerchantId, merchant_id: id_type::MerchantId,
user_id: Option<String>, user_id: Option<String>,
}, },
MerchantJwtWithProfileId {
merchant_id: id_type::MerchantId,
profile_id: Option<id_type::ProfileId>,
user_id: String,
},
UserJwt { UserJwt {
user_id: String, user_id: String,
}, },
@ -137,6 +142,7 @@ impl AuthenticationType {
merchant_id, merchant_id,
user_id: _, user_id: _,
} }
| Self::MerchantJwtWithProfileId { merchant_id, .. }
| Self::WebhookAuth { merchant_id } => Some(merchant_id), | Self::WebhookAuth { merchant_id } => Some(merchant_id),
Self::AdminApiKey Self::AdminApiKey
| Self::UserJwt { .. } | Self::UserJwt { .. }
@ -1324,6 +1330,80 @@ where
} }
} }
pub struct JWTAuthMerchantAndProfileFromRoute {
pub merchant_id: id_type::MerchantId,
pub profile_id: id_type::ProfileId,
pub required_permission: Permission,
}
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for JWTAuthMerchantAndProfileFromRoute
where
A: SessionStateInfo + Sync,
{
async fn authenticate_and_fetch(
&self,
request_headers: &HeaderMap,
state: &A,
) -> RouterResult<(AuthenticationData, AuthenticationType)> {
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
if payload.check_in_blacklist(state).await? {
return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
}
if payload.merchant_id != self.merchant_id {
return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
}
if payload
.profile_id
.as_ref()
.is_some_and(|profile_id| *profile_id != self.profile_id)
{
return Err(report!(errors::ApiErrorResponse::InvalidJwtToken));
}
let permissions = authorization::get_permissions(state, &payload).await?;
authorization::check_authorization(&self.required_permission, &permissions)?;
let key_manager_state = &(&state.session_state()).into();
let key_store = state
.store()
.get_merchant_key_store_by_merchant_id(
key_manager_state,
&payload.merchant_id,
&state.store().get_master_key().to_vec().into(),
)
.await
.to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
.attach_printable("Failed to fetch merchant key store for the merchant id")?;
let merchant = state
.store()
.find_merchant_account_by_merchant_id(
key_manager_state,
&payload.merchant_id,
&key_store,
)
.await
.to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken)
.attach_printable("Failed to fetch merchant account for the merchant id")?;
let auth = AuthenticationData {
merchant_account: merchant,
key_store,
profile_id: payload.profile_id,
};
Ok((
auth.clone(),
AuthenticationType::MerchantJwtWithProfileId {
merchant_id: auth.merchant_account.get_id().clone(),
profile_id: auth.profile_id.clone(),
user_id: payload.user_id,
},
))
}
}
pub async fn parse_jwt_payload<A, T>(headers: &HeaderMap, state: &A) -> RouterResult<T> pub async fn parse_jwt_payload<A, T>(headers: &HeaderMap, state: &A) -> RouterResult<T>
where where
T: serde::de::DeserializeOwned, T: serde::de::DeserializeOwned,