Skip to content

Commit

Permalink
feat: add profile_id authentication for business profile update and l…
Browse files Browse the repository at this point in the history
…ist (#5673)
  • Loading branch information
hrithikesh026 authored Sep 3, 2024
1 parent f822730 commit e3a9fb1
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 10 deletions.
2 changes: 2 additions & 0 deletions crates/router/src/core/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3717,6 +3717,7 @@ pub async fn create_business_profile(
pub async fn list_business_profile(
state: SessionState,
merchant_id: id_type::MerchantId,
profile_id_list: Option<Vec<id_type::ProfileId>>,
) -> RouterResponse<Vec<api_models::admin::BusinessProfileResponse>> {
let db = state.store.as_ref();
let key_store = db
Expand All @@ -3732,6 +3733,7 @@ pub async fn list_business_profile(
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?
.clone();
let profiles = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, profiles);
let mut business_profiles = Vec::new();
for profile in profiles {
let business_profile =
Expand Down
10 changes: 8 additions & 2 deletions crates/router/src/core/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
}

Expand Down Expand Up @@ -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
pub(super) fn filter_objects_based_on_profile_id_list<T: GetProfileId>(
profile_id_list_auth_layer: Option<Vec<common_utils::id_type::ProfileId>>,
Expand All @@ -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>,
object: &T,
) -> RouterResult<()> {
Expand Down
4 changes: 3 additions & 1 deletion crates/router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ pub fn mk_app(
{
// This is a more specific route as compared to `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
.service(routes::Payments::server(state.clone()))
Expand Down
8 changes: 4 additions & 4 deletions crates/router/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ pub use self::app::Forex;
#[cfg(all(feature = "olap", feature = "recon"))]
pub use self::app::Recon;
pub use self::app::{
ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, Cache, Cards, Configs,
ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health, Mandates,
MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll,
Refunds, SessionState, User, Webhooks,
ApiKeys, AppState, ApplePayCertificatesMigration, BusinessProfile, BusinessProfileNew, Cache,
Cards, Configs, ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Gsm, Health,
Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments,
Poll, Refunds, SessionState, User, Webhooks,
};
#[cfg(feature = "olap")]
pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents};
Expand Down
41 changes: 38 additions & 3 deletions crates/router/src/routes/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,9 @@ pub async fn business_profile_update(
},
auth::auth_type(
&auth::AdminApiAuthWithMerchantIdFromRoute(merchant_id.clone()),
&auth::JWTAuthMerchantFromRoute {
&auth::JWTAuthMerchantAndProfileFromRoute {
merchant_id: merchant_id.clone(),
profile_id: profile_id.clone(),
required_permission: Permission::MerchantAccountWrite,
},
req.headers(),
Expand Down Expand Up @@ -971,9 +972,43 @@ pub async fn business_profiles_list(
state,
&req,
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::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 {
merchant_id,
required_permission: Permission::MerchantAccountRead,
Expand Down
13 changes: 13 additions & 0 deletions crates/router/src/routes/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

#[cfg(feature = "olap")]
Expand Down
80 changes: 80 additions & 0 deletions crates/router/src/services/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ pub enum AuthenticationType {
merchant_id: id_type::MerchantId,
user_id: Option<String>,
},
MerchantJwtWithProfileId {
merchant_id: id_type::MerchantId,
profile_id: Option<id_type::ProfileId>,
user_id: String,
},
UserJwt {
user_id: String,
},
Expand Down Expand Up @@ -137,6 +142,7 @@ impl AuthenticationType {
merchant_id,
user_id: _,
}
| Self::MerchantJwtWithProfileId { merchant_id, .. }
| Self::WebhookAuth { merchant_id } => Some(merchant_id),
Self::AdminApiKey
| Self::UserJwt { .. }
Expand Down Expand Up @@ -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>
where
T: serde::de::DeserializeOwned,
Expand Down

0 comments on commit e3a9fb1

Please sign in to comment.