feat(core): use profile_id passed from auth layer within core functions (#5553)

This commit is contained in:
Hrithikesh
2024-08-09 12:28:47 +05:30
committed by GitHub
parent fc581e08ff
commit 9fa631d2b9
12 changed files with 233 additions and 32 deletions

View File

@ -2817,7 +2817,7 @@ async fn validate_pm_auth(
pub async fn retrieve_connector(
state: SessionState,
merchant_id: id_type::MerchantId,
_profile_id: Option<String>,
profile_id: Option<String>,
merchant_connector_id: String,
) -> RouterResponse<api_models::admin::MerchantConnectorResponse> {
let store = state.store.as_ref();
@ -2847,6 +2847,7 @@ pub async fn retrieve_connector(
.to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
id: merchant_connector_id.clone(),
})?;
core_utils::validate_profile_id_from_auth_layer(profile_id, &mca)?;
Ok(service_api::ApplicationResponse::Json(
mca.foreign_try_into()?,
@ -2897,7 +2898,7 @@ pub async fn retrieve_connector(
pub async fn list_payment_connectors(
state: SessionState,
merchant_id: id_type::MerchantId,
_profile_id_list: Option<Vec<String>>,
profile_id_list: Option<Vec<String>>,
) -> RouterResponse<Vec<api_models::admin::MerchantConnectorListResponse>> {
let store = state.store.as_ref();
let key_manager_state = &(&state).into();
@ -2925,6 +2926,10 @@ pub async fn list_payment_connectors(
)
.await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;
let merchant_connector_accounts = core_utils::filter_objects_based_on_profile_id_list(
profile_id_list,
merchant_connector_accounts,
);
let mut response = vec![];
// The can be eliminated once [#79711](https://github.com/rust-lang/rust/issues/79711) is stabilized
@ -2938,7 +2943,7 @@ pub async fn list_payment_connectors(
pub async fn update_connector(
state: SessionState,
merchant_id: &id_type::MerchantId,
_profile_id: Option<String>,
profile_id: Option<String>,
merchant_connector_id: &str,
req: api_models::admin::MerchantConnectorUpdate,
) -> RouterResponse<api_models::admin::MerchantConnectorResponse> {
@ -2968,6 +2973,7 @@ pub async fn update_connector(
key_manager_state,
)
.await?;
core_utils::validate_profile_id_from_auth_layer(profile_id, &mca)?;
let payment_connector = req
.clone()

View File

@ -26,7 +26,7 @@ use crate::{
pub async fn retrieve_dispute(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id: Option<String>,
profile_id: Option<String>,
req: disputes::DisputeId,
) -> RouterResponse<api_models::disputes::DisputeResponse> {
let dispute = state
@ -36,6 +36,7 @@ pub async fn retrieve_dispute(
.to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
dispute_id: req.dispute_id,
})?;
core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
let dispute_response = api_models::disputes::DisputeResponse::foreign_from(dispute);
Ok(services::ApplicationResponse::Json(dispute_response))
}
@ -64,7 +65,7 @@ pub async fn retrieve_disputes_list(
pub async fn accept_dispute(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id: Option<String>,
profile_id: Option<String>,
key_store: domain::MerchantKeyStore,
req: disputes::DisputeId,
) -> RouterResponse<dispute_models::DisputeResponse> {
@ -76,6 +77,7 @@ pub async fn accept_dispute(
.to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
dispute_id: req.dispute_id,
})?;
core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
let dispute_id = dispute.dispute_id.clone();
common_utils::fp_utils::when(
!(dispute.dispute_stage == storage_enums::DisputeStage::Dispute
@ -167,7 +169,7 @@ pub async fn accept_dispute(
pub async fn submit_evidence(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id: Option<String>,
profile_id: Option<String>,
key_store: domain::MerchantKeyStore,
req: dispute_models::SubmitEvidenceRequest,
) -> RouterResponse<dispute_models::DisputeResponse> {
@ -179,6 +181,7 @@ pub async fn submit_evidence(
.to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
dispute_id: req.dispute_id.clone(),
})?;
core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
let dispute_id = dispute.dispute_id.clone();
common_utils::fp_utils::when(
!(dispute.dispute_stage == storage_enums::DisputeStage::Dispute
@ -333,7 +336,7 @@ pub async fn submit_evidence(
pub async fn attach_evidence(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id: Option<String>,
profile_id: Option<String>,
key_store: domain::MerchantKeyStore,
attach_evidence_request: api::AttachEvidenceRequest,
) -> RouterResponse<files_api_models::CreateFileResponse> {
@ -349,6 +352,7 @@ pub async fn attach_evidence(
.to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
dispute_id: dispute_id.clone(),
})?;
core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
common_utils::fp_utils::when(
!(dispute.dispute_stage == storage_enums::DisputeStage::Dispute
&& dispute.dispute_status == storage_enums::DisputeStatus::DisputeOpened),
@ -411,7 +415,7 @@ pub async fn attach_evidence(
pub async fn retrieve_dispute_evidence(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id: Option<String>,
profile_id: Option<String>,
req: disputes::DisputeId,
) -> RouterResponse<Vec<api_models::disputes::DisputeEvidenceBlock>> {
let dispute = state
@ -421,6 +425,7 @@ pub async fn retrieve_dispute_evidence(
.to_not_found_response(errors::ApiErrorResponse::DisputeNotFound {
dispute_id: req.dispute_id,
})?;
core_utils::validate_profile_id_from_auth_layer(profile_id, &dispute)?;
let dispute_evidence: api::DisputeEvidence = dispute
.evidence
.clone()

View File

@ -103,6 +103,7 @@ pub async fn payments_operation_core<F, Req, Op, FData>(
state: &SessionState,
req_state: ReqState,
merchant_account: domain::MerchantAccount,
profile_id_from_auth_layer: Option<String>,
key_store: domain::MerchantKeyStore,
operation: Op,
req: Req,
@ -162,6 +163,11 @@ where
)
.await?;
utils::validate_profile_id_from_auth_layer(
profile_id_from_auth_layer,
&payment_data.payment_intent,
)?;
let (operation, customer) = operation
.to_domain()?
.get_or_create_customer_details(
@ -788,7 +794,7 @@ pub async fn payments_core<F, Res, Req, Op, FData>(
state: SessionState,
req_state: ReqState,
merchant_account: domain::MerchantAccount,
_profile_id: Option<String>,
profile_id: Option<String>,
key_store: domain::MerchantKeyStore,
operation: Op,
req: Req,
@ -825,6 +831,7 @@ where
&state,
req_state,
merchant_account,
profile_id,
key_store,
operation.clone(),
req,
@ -2877,7 +2884,7 @@ pub fn is_operation_complete_authorize<Op: Debug>(operation: &Op) -> bool {
pub async fn list_payments(
state: SessionState,
merchant: domain::MerchantAccount,
_profile_id_list: Option<Vec<String>>,
profile_id_list: Option<Vec<String>>,
key_store: domain::MerchantKeyStore,
constraints: api::PaymentListConstraints,
) -> RouterResponse<api::PaymentListResponse> {
@ -2894,6 +2901,8 @@ pub async fn list_payments(
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
let payment_intents =
utils::filter_objects_based_on_profile_id_list(profile_id_list, payment_intents);
let collected_futures = payment_intents.into_iter().map(|pi| {
async {
@ -2950,7 +2959,7 @@ pub async fn list_payments(
pub async fn apply_filters_on_payments(
state: SessionState,
merchant: domain::MerchantAccount,
_profile_id_list: Option<Vec<String>>,
profile_id_list: Option<Vec<String>>,
merchant_key_store: domain::MerchantKeyStore,
constraints: api::PaymentListFilterConstraints,
) -> RouterResponse<api::PaymentListResponseV2> {
@ -2967,7 +2976,7 @@ pub async fn apply_filters_on_payments(
)
.await
.to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
let list = utils::filter_objects_based_on_profile_id_list(profile_id_list, list);
let data: Vec<api::PaymentsResponse> =
list.into_iter().map(ForeignFrom::foreign_from).collect();
@ -3048,10 +3057,11 @@ pub async fn get_filters_for_payments(
pub async fn get_payment_filters(
state: SessionState,
merchant: domain::MerchantAccount,
_profile_id_list: Option<Vec<String>>,
profile_id_list: Option<Vec<String>>,
) -> RouterResponse<api::PaymentListFiltersV2> {
let merchant_connector_accounts = if let services::ApplicationResponse::Json(data) =
super::admin::list_payment_connectors(state, merchant.get_id().to_owned(), None).await?
super::admin::list_payment_connectors(state, merchant.get_id().to_owned(), profile_id_list)
.await?
{
data
} else {

View File

@ -358,6 +358,7 @@ pub async fn payouts_confirm_core(
let mut payout_data = make_payout_data(
&state,
&merchant_account,
None,
&key_store,
&payouts::PayoutRequest::PayoutCreateRequest(req.to_owned()),
)
@ -422,6 +423,7 @@ pub async fn payouts_update_core(
let mut payout_data = make_payout_data(
&state,
&merchant_account,
None,
&key_store,
&payouts::PayoutRequest::PayoutCreateRequest(req.to_owned()),
)
@ -482,18 +484,18 @@ pub async fn payouts_update_core(
pub async fn payouts_retrieve_core(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id: Option<String>,
profile_id: Option<String>,
key_store: domain::MerchantKeyStore,
req: payouts::PayoutRetrieveRequest,
) -> RouterResponse<payouts::PayoutCreateResponse> {
let mut payout_data = make_payout_data(
&state,
&merchant_account,
profile_id,
&key_store,
&payouts::PayoutRequest::PayoutRetrieveRequest(req.to_owned()),
)
.await?;
let payout_attempt = payout_data.payout_attempt.to_owned();
let status = payout_attempt.status;
@ -533,6 +535,7 @@ pub async fn payouts_cancel_core(
let mut payout_data = make_payout_data(
&state,
&merchant_account,
None,
&key_store,
&payouts::PayoutRequest::PayoutActionRequest(req.to_owned()),
)
@ -628,6 +631,7 @@ pub async fn payouts_fulfill_core(
let mut payout_data = make_payout_data(
&state,
&merchant_account,
None,
&key_store,
&payouts::PayoutRequest::PayoutActionRequest(req.to_owned()),
)
@ -708,7 +712,7 @@ pub async fn payouts_fulfill_core(
pub async fn payouts_list_core(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id_list: Option<Vec<String>>,
profile_id_list: Option<Vec<String>>,
key_store: domain::MerchantKeyStore,
constraints: payouts::PayoutListConstraints,
) -> RouterResponse<payouts::PayoutListResponse> {
@ -723,6 +727,7 @@ pub async fn payouts_list_core(
)
.await
.to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
let payouts = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, payouts);
let collected_futures = payouts.into_iter().map(|payouts| async {
match db
@ -807,7 +812,7 @@ pub async fn payouts_list_core(
pub async fn payouts_filtered_list_core(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id_list: Option<Vec<String>>,
profile_id_list: Option<Vec<String>>,
key_store: domain::MerchantKeyStore,
filters: payouts::PayoutListFilterConstraints,
) -> RouterResponse<payouts::PayoutListResponse> {
@ -826,7 +831,7 @@ pub async fn payouts_filtered_list_core(
)
.await
.to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
let list = core_utils::filter_objects_based_on_profile_id_list(profile_id_list, list);
let data: Vec<api::PayoutCreateResponse> = join_all(list.into_iter().map(|(p, pa, c)| async {
match domain::Customer::convert_back(
&(&state).into(),
@ -2327,6 +2332,7 @@ pub async fn payout_create_db_entries(
pub async fn make_payout_data(
state: &SessionState,
merchant_account: &domain::MerchantAccount,
auth_profile_id: Option<String>,
key_store: &domain::MerchantKeyStore,
req: &payouts::PayoutRequest,
) -> RouterResult<PayoutData> {
@ -2346,6 +2352,7 @@ pub async fn make_payout_data(
)
.await
.to_not_found_response(errors::ApiErrorResponse::PayoutNotFound)?;
core_utils::validate_profile_id_from_auth_layer(auth_profile_id, &payouts)?;
let payout_attempt_id = utils::get_payment_attempt_id(payout_id, payouts.attempt_count);

View File

@ -401,7 +401,7 @@ where
pub async fn refund_retrieve_core(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id: Option<String>,
profile_id: Option<String>,
key_store: domain::MerchantKeyStore,
request: refunds::RefundsRetrieveRequest,
) -> RouterResult<storage::Refund> {
@ -419,7 +419,7 @@ pub async fn refund_retrieve_core(
)
.await
.to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;
core_utils::validate_profile_id_from_auth_layer(profile_id, &refund)?;
let payment_id = refund.payment_id.as_str();
payment_intent = db
.find_payment_intent_by_payment_id_merchant_id(
@ -987,10 +987,14 @@ pub async fn refund_manual_update(
pub async fn get_filters_for_refunds(
state: SessionState,
merchant_account: domain::MerchantAccount,
_profile_id_list: Option<Vec<String>>,
profile_id_list: Option<Vec<String>>,
) -> RouterResponse<api_models::refunds::RefundListFilters> {
let merchant_connector_accounts = if let services::ApplicationResponse::Json(data) =
super::admin::list_payment_connectors(state, merchant_account.get_id().to_owned(), None)
super::admin::list_payment_connectors(
state,
merchant_account.get_id().to_owned(),
profile_id_list,
)
.await?
{
data

View File

@ -1,4 +1,4 @@
use std::{marker::PhantomData, str::FromStr};
use std::{collections::HashSet, marker::PhantomData, str::FromStr};
use api_models::enums::{DisputeStage, DisputeStatus};
#[cfg(feature = "payouts")]
@ -8,7 +8,10 @@ use common_enums::{IntentStatus, RequestIncrementalAuthorization};
use common_utils::{crypto::Encryptable, pii::Email};
use common_utils::{errors::CustomResult, ext_traits::AsyncExt, types::MinorUnit};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{payment_address::PaymentAddress, router_data::ErrorResponse};
use hyperswitch_domain_models::{
merchant_connector_account::MerchantConnectorAccount, payment_address::PaymentAddress,
router_data::ErrorResponse,
};
#[cfg(feature = "payouts")]
use masking::{ExposeInterface, PeekInterface};
use maud::{html, PreEscaped};
@ -450,6 +453,64 @@ mod tests {
let generated_id = generate_id(consts::ID_LENGTH, "ref");
assert_eq!(generated_id.len(), consts::ID_LENGTH + 4)
}
#[test]
fn test_filter_objects_based_on_profile_id_list() {
#[derive(PartialEq, Debug, Clone)]
struct Object {
profile_id: Option<String>,
}
impl Object {
pub fn new(profile_id: &str) -> Self {
Self {
profile_id: Some(profile_id.to_string()),
}
}
}
impl GetProfileId for Object {
fn get_profile_id(&self) -> Option<&String> {
self.profile_id.as_ref()
}
}
// non empty object_list and profile_id_list
let object_list = vec![
Object::new("p1"),
Object::new("p2"),
Object::new("p2"),
Object::new("p4"),
Object::new("p5"),
];
let profile_id_list = vec!["p1".to_string(), "p2".to_string(), "p3".to_string()];
let filtered_list =
filter_objects_based_on_profile_id_list(Some(profile_id_list), object_list.clone());
let expected_result = vec![Object::new("p1"), Object::new("p2"), Object::new("p2")];
assert_eq!(filtered_list, expected_result);
// non empty object_list and empty profile_id_list
let empty_profile_id_list = vec![];
let filtered_list = filter_objects_based_on_profile_id_list(
Some(empty_profile_id_list),
object_list.clone(),
);
let expected_result = vec![];
assert_eq!(filtered_list, expected_result);
// non empty object_list and None profile_id_list
let profile_id_list_as_none = None;
let filtered_list =
filter_objects_based_on_profile_id_list(profile_id_list_as_none, object_list);
let expected_result = vec![
Object::new("p1"),
Object::new("p2"),
Object::new("p2"),
Object::new("p4"),
Object::new("p5"),
];
assert_eq!(filtered_list, expected_result);
}
}
// Dispute Stage can move linearly from PreDispute -> Dispute -> PreArbitration
@ -1267,3 +1328,100 @@ pub fn get_incremental_authorization_allowed_value(
incremental_authorization_allowed
}
}
pub(super) trait GetProfileId {
fn get_profile_id(&self) -> Option<&String>;
}
impl GetProfileId for MerchantConnectorAccount {
fn get_profile_id(&self) -> Option<&String> {
self.profile_id.as_ref()
}
}
impl GetProfileId for storage::PaymentIntent {
fn get_profile_id(&self) -> Option<&String> {
self.profile_id.as_ref()
}
}
impl<A> GetProfileId for (storage::PaymentIntent, A) {
fn get_profile_id(&self) -> Option<&String> {
self.0.get_profile_id()
}
}
impl GetProfileId for diesel_models::Dispute {
fn get_profile_id(&self) -> Option<&String> {
self.profile_id.as_ref()
}
}
impl GetProfileId for diesel_models::Refund {
fn get_profile_id(&self) -> Option<&String> {
self.profile_id.as_ref()
}
}
#[cfg(feature = "payouts")]
impl GetProfileId for storage::Payouts {
fn get_profile_id(&self) -> Option<&String> {
Some(&self.profile_id)
}
}
#[cfg(feature = "payouts")]
impl<T, F> GetProfileId for (storage::Payouts, T, F) {
fn get_profile_id(&self) -> Option<&String> {
self.0.get_profile_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<String>>,
object_list: Vec<T>,
) -> Vec<T> {
if let Some(profile_id_list) = profile_id_list_auth_layer {
let profile_ids_to_filter: HashSet<_> = profile_id_list.iter().collect();
object_list
.into_iter()
.filter_map(|item| {
if item
.get_profile_id()
.is_some_and(|profile_id| profile_ids_to_filter.contains(profile_id))
{
Some(item)
} else {
None
}
})
.collect()
} else {
object_list
}
}
pub(super) fn validate_profile_id_from_auth_layer<T: GetProfileId + std::fmt::Debug>(
profile_id_auth_layer: Option<String>,
object: &T,
) -> RouterResult<()> {
match (profile_id_auth_layer, object.get_profile_id()) {
(Some(auth_profile_id), Some(object_profile_id)) => {
auth_profile_id.eq(object_profile_id).then_some(()).ok_or(
errors::ApiErrorResponse::PreconditionFailed {
message: "Profile id authentication failed. Please use the correct JWT token"
.to_string(),
}
.into(),
)
}
(Some(_auth_profile_id), None) => RouterResult::Err(
errors::ApiErrorResponse::PreconditionFailed {
message: "Couldn't find profile_id in record for authentication".to_string(),
}
.into(),
)
.attach_printable(format!("Couldn't find profile_id in entity {:?}", object)),
(None, None) | (None, Some(_)) => Ok(()),
}
}

View File

@ -12,7 +12,7 @@ pub async fn verify_merchant_creds_for_applepay(
state: SessionState,
body: verifications::ApplepayMerchantVerificationRequest,
merchant_id: common_utils::id_type::MerchantId,
_profile_id: Option<String>,
profile_id: Option<String>,
) -> CustomResult<services::ApplicationResponse<ApplepayMerchantResponse>, errors::ApiErrorResponse>
{
let applepay_merchant_configs = state.conf.applepay_merchant_configs.get_inner();
@ -62,6 +62,7 @@ pub async fn verify_merchant_creds_for_applepay(
utils::check_existence_and_add_domain_to_db(
&state,
merchant_id,
profile_id,
body.merchant_connector_account_id.clone(),
body.domain_names.clone(),
)

View File

@ -2,7 +2,10 @@ use common_utils::errors::CustomResult;
use error_stack::{Report, ResultExt};
use crate::{
core::errors::{self, utils::StorageErrorExt},
core::{
errors::{self, utils::StorageErrorExt},
utils,
},
logger,
routes::SessionState,
types,
@ -12,6 +15,7 @@ use crate::{
pub async fn check_existence_and_add_domain_to_db(
state: &SessionState,
merchant_id: common_utils::id_type::MerchantId,
profile_id_from_auth_layer: Option<String>,
merchant_connector_id: String,
domain_from_req: Vec<String>,
) -> CustomResult<Vec<String>, errors::ApiErrorResponse> {
@ -48,7 +52,10 @@ pub async fn check_existence_and_add_domain_to_db(
let _ = domain_from_req;
todo!()
};
utils::validate_profile_id_from_auth_layer(
profile_id_from_auth_layer,
&merchant_connector_account,
)?;
let mut already_verified_domains = merchant_connector_account
.applepay_verified_domains
.clone()

View File

@ -706,7 +706,8 @@ async fn payouts_incoming_webhook_flow(
});
let payout_data =
payouts::make_payout_data(&state, &merchant_account, &key_store, &action_req).await?;
payouts::make_payout_data(&state, &merchant_account, None, &key_store, &action_req)
.await?;
let updated_payout_attempt = db
.update_payout_attempt(

View File

@ -47,7 +47,7 @@ impl ProcessTrackerWorkflow<SessionState> for AttachPayoutAccountWorkflow {
let request = api::payouts::PayoutRequest::PayoutRetrieveRequest(tracking_data);
let mut payout_data =
payouts::make_payout_data(state, &merchant_account, &key_store, &request).await?;
payouts::make_payout_data(state, &merchant_account, None, &key_store, &request).await?;
payouts::payouts_core(
state,

View File

@ -505,7 +505,8 @@ async fn get_outgoing_webhook_content_and_event_type(
);
let payout_data =
payouts::make_payout_data(&state, &merchant_account, &key_store, &request).await?;
payouts::make_payout_data(&state, &merchant_account, None, &key_store, &request)
.await?;
let router_response =
payouts::response_handler(&merchant_account, &payout_data).await?;

View File

@ -67,6 +67,7 @@ impl ProcessTrackerWorkflow<SessionState> for PaymentsSyncWorkflow {
state,
state.get_req_state(),
merchant_account.clone(),
None,
key_store.clone(),
operations::PaymentStatus,
tracking_data.clone(),