fix: batch encrypt/decrypt on merchant connector account (#6206)

This commit is contained in:
Kartikeya Hegde
2024-10-04 18:55:20 +05:30
committed by GitHub
parent 939483cebe
commit b7139483bb
3 changed files with 312 additions and 268 deletions

View File

@ -8,12 +8,15 @@ use common_utils::{
date_time,
ext_traits::{AsyncExt, Encode, OptionExt, ValueExt},
id_type, pii, type_name,
types::keymanager::{self as km_types, KeyManagerState},
types::keymanager::{self as km_types, KeyManagerState, ToEncryptable},
};
use diesel_models::configs;
#[cfg(all(any(feature = "v1", feature = "v2"), feature = "olap"))]
use diesel_models::organization::OrganizationBridge;
use error_stack::{report, FutureExt, ResultExt};
use hyperswitch_domain_models::merchant_connector_account::{
McaFromRequest, McaFromRequestfromUpdate,
};
use masking::{ExposeInterface, PeekInterface, Secret};
use pm_auth::{connector::plaid::transformers::PlaidAuthType, types as pm_auth_types};
use regex::Regex;
@ -2089,25 +2092,37 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to serialize MerchantRecipientData")?;
let encrypted_data = domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::BatchEncrypt(McaFromRequestfromUpdate::to_encryptable(
McaFromRequestfromUpdate {
connector_account_details: self.connector_account_details,
connector_wallets_details:
helpers::get_connector_wallets_details_with_apple_pay_certificates(
&self.metadata,
&self.connector_wallets_details,
)
.await?,
additional_merchant_data: merchant_recipient_data.map(Secret::new),
},
)),
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_batchoperation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while decrypting connector account details".to_string())?;
let encrypted_data = McaFromRequestfromUpdate::from_encryptable(encrypted_data)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while decrypting connector account details")?;
Ok(storage::MerchantConnectorAccountUpdate::Update {
connector_type: Some(self.connector_type),
connector_label: self.connector_label.clone(),
connector_account_details: self
.connector_account_details
.async_lift(|inner| async {
domain_types::crypto_operation(
key_manager_state,
type_name!(storage::MerchantConnectorAccount),
domain_types::CryptoOperation::EncryptOptional(inner),
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.get_inner().peek(),
)
.await
.and_then(|val| val.try_into_optionaloperation())
})
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting data")?,
connector_account_details: encrypted_data.connector_account_details,
disabled,
payment_methods_enabled,
metadata: self.metadata,
@ -2123,31 +2138,8 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect
applepay_verified_domains: None,
pm_auth_config: self.pm_auth_config,
status: Some(connector_status),
additional_merchant_data: if let Some(mcd) = merchant_recipient_data {
Some(
domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::Encrypt(Secret::new(mcd)),
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_operation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt additional_merchant_data")?,
)
} else {
None
},
connector_wallets_details:
helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates(
state,
&key_store,
&metadata,
&self.connector_wallets_details,
)
.await?,
additional_merchant_data: encrypted_data.additional_merchant_data,
connector_wallets_details: encrypted_data.connector_wallets_details,
})
}
}
@ -2267,27 +2259,39 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to serialize MerchantRecipientData")?;
let encrypted_data = domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::BatchEncrypt(McaFromRequestfromUpdate::to_encryptable(
McaFromRequestfromUpdate {
connector_account_details: self.connector_account_details,
connector_wallets_details:
helpers::get_connector_wallets_details_with_apple_pay_certificates(
&self.metadata,
&self.connector_wallets_details,
)
.await?,
additional_merchant_data: merchant_recipient_data.map(Secret::new),
},
)),
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_batchoperation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while decrypting connector account details".to_string())?;
let encrypted_data = McaFromRequestfromUpdate::from_encryptable(encrypted_data)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while decrypting connector account details")?;
Ok(storage::MerchantConnectorAccountUpdate::Update {
connector_type: Some(self.connector_type),
connector_name: None,
merchant_connector_id: None,
connector_label: self.connector_label.clone(),
connector_account_details: self
.connector_account_details
.async_lift(|inner| async {
domain_types::crypto_operation(
key_manager_state,
type_name!(storage::MerchantConnectorAccount),
domain_types::CryptoOperation::EncryptOptional(inner),
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.get_inner().peek(),
)
.await
.and_then(|val| val.try_into_optionaloperation())
})
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting data")?,
connector_account_details: encrypted_data.connector_account_details,
test_mode: self.test_mode,
disabled,
payment_methods_enabled,
@ -2304,31 +2308,8 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect
applepay_verified_domains: None,
pm_auth_config: self.pm_auth_config,
status: Some(connector_status),
additional_merchant_data: if let Some(mcd) = merchant_recipient_data {
Some(
domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::Encrypt(Secret::new(mcd)),
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_operation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt additional_merchant_data")?,
)
} else {
None
},
connector_wallets_details:
helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates(
state,
&key_store,
&metadata,
&self.connector_wallets_details,
)
.await?,
additional_merchant_data: encrypted_data.additional_merchant_data,
connector_wallets_details: encrypted_data.connector_wallets_details,
})
}
}
@ -2417,25 +2398,43 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate {
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to serialize MerchantRecipientData")?;
let encrypted_data = domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::BatchEncrypt(McaFromRequest::to_encryptable(
McaFromRequest {
connector_account_details: self.connector_account_details.ok_or(
errors::ApiErrorResponse::MissingRequiredField {
field_name: "connector_account_details",
},
)?,
connector_wallets_details:
helpers::get_connector_wallets_details_with_apple_pay_certificates(
&self.metadata,
&self.connector_wallets_details,
)
.await?,
additional_merchant_data: merchant_recipient_data.map(Secret::new),
},
)),
identifier.clone(),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_batchoperation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while decrypting connector account details".to_string())?;
let encrypted_data = McaFromRequest::from_encryptable(encrypted_data)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while decrypting connector account details")?;
Ok(domain::MerchantConnectorAccount {
merchant_id: business_profile.merchant_id.clone(),
connector_type: self.connector_type,
connector_name: self.connector_name.to_string(),
connector_account_details: domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::Encrypt(self.connector_account_details.ok_or(
errors::ApiErrorResponse::MissingRequiredField {
field_name: "connector_account_details",
},
)?),
identifier.clone(),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_operation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt connector account details")?,
connector_account_details: encrypted_data.connector_account_details,
payment_methods_enabled,
disabled,
metadata: self.metadata.clone(),
@ -2459,22 +2458,8 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate {
applepay_verified_domains: None,
pm_auth_config: self.pm_auth_config.clone(),
status: connector_status,
connector_wallets_details: helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates(state, &key_store, &self.metadata, &self.connector_wallets_details).await?,
additional_merchant_data: if let Some(mcd) = merchant_recipient_data {
Some(domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::Encrypt(Secret::new(mcd)),
km_types::Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_operation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt additional_merchant_data")?)
} else {
None
},
connector_wallets_details: encrypted_data.connector_wallets_details,
additional_merchant_data: encrypted_data.additional_merchant_data,
version: hyperswitch_domain_models::consts::API_VERSION,
})
}
@ -2582,26 +2567,44 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate {
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to serialize MerchantRecipientData")?;
let encrypted_data = domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::BatchEncrypt(McaFromRequest::to_encryptable(
McaFromRequest {
connector_account_details: self.connector_account_details.ok_or(
errors::ApiErrorResponse::MissingRequiredField {
field_name: "connector_account_details",
},
)?,
connector_wallets_details:
helpers::get_connector_wallets_details_with_apple_pay_certificates(
&self.metadata,
&self.connector_wallets_details,
)
.await?,
additional_merchant_data: merchant_recipient_data.map(Secret::new),
},
)),
identifier.clone(),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_batchoperation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while decrypting connector account details".to_string())?;
let encrypted_data = McaFromRequest::from_encryptable(encrypted_data)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while decrypting connector account details")?;
Ok(domain::MerchantConnectorAccount {
merchant_id: business_profile.merchant_id.clone(),
connector_type: self.connector_type,
connector_name: self.connector_name.to_string(),
merchant_connector_id: common_utils::generate_merchant_connector_account_id_of_default_length(),
connector_account_details: domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::Encrypt(self.connector_account_details.ok_or(
errors::ApiErrorResponse::MissingRequiredField {
field_name: "connector_account_details",
},
)?),
identifier.clone(),
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_operation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt connector account details")?,
connector_account_details: encrypted_data.connector_account_details,
payment_methods_enabled,
disabled,
metadata: self.metadata.clone(),
@ -2624,26 +2627,12 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate {
applepay_verified_domains: None,
pm_auth_config: self.pm_auth_config.clone(),
status: connector_status,
connector_wallets_details: helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates(state, &key_store, &self.metadata, &self.connector_wallets_details).await?,
connector_wallets_details: encrypted_data.connector_wallets_details,
test_mode: self.test_mode,
business_country: self.business_country,
business_label: self.business_label.clone(),
business_sub_label: self.business_sub_label.clone(),
additional_merchant_data: if let Some(mcd) = merchant_recipient_data {
Some(domain_types::crypto_operation(
key_manager_state,
type_name!(domain::MerchantConnectorAccount),
domain_types::CryptoOperation::Encrypt(Secret::new(mcd)),
identifier,
key_store.key.peek(),
)
.await
.and_then(|val| val.try_into_operation())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt additional_merchant_data")?)
} else {
None
},
additional_merchant_data: encrypted_data.additional_merchant_data,
version: hyperswitch_domain_models::consts::API_VERSION,
})
}

View File

@ -76,10 +76,7 @@ use crate::{
services,
types::{
api::{self, admin, enums as api_enums, MandateValidationFieldsExt},
domain::{
self,
types::{self, AsyncLift},
},
domain::{self, types},
storage::{self, enums as storage_enums, ephemeral_key, CardTokenData},
transformers::{ForeignFrom, ForeignTryFrom},
AdditionalMerchantData, AdditionalPaymentMethodConnectorResponse, ErrorResponse,
@ -4555,12 +4552,10 @@ pub fn is_apple_pay_simplified_flow(
// As part of migration fallback this function checks apple pay details are present in connector_wallets_details
// If yes, it will encrypt connector_wallets_details and store it in the database.
// If no, it will check if apple pay details are present in metadata and merge it with connector_wallets_details, encrypt and store it.
pub async fn get_encrypted_connector_wallets_details_with_apple_pay_certificates(
state: &SessionState,
key_store: &domain::MerchantKeyStore,
pub async fn get_connector_wallets_details_with_apple_pay_certificates(
connector_metadata: &Option<masking::Secret<tera::Value>>,
connector_wallets_details_optional: &Option<api_models::admin::ConnectorWalletDetails>,
) -> RouterResult<Option<Encryptable<masking::Secret<serde_json::Value>>>> {
) -> RouterResult<Option<masking::Secret<serde_json::Value>>> {
let connector_wallet_details_with_apple_pay_metadata_optional =
get_apple_pay_metadata_if_needed(connector_metadata, connector_wallets_details_optional)
.await?;
@ -4574,25 +4569,7 @@ pub async fn get_encrypted_connector_wallets_details_with_apple_pay_certificates
.transpose()?
.map(masking::Secret::new);
let key_manager_state: KeyManagerState = state.into();
let encrypted_connector_wallets_details = connector_wallets_details
.clone()
.async_lift(|wallets_details| async {
types::crypto_operation(
&key_manager_state,
type_name!(domain::MerchantConnectorAccount),
types::CryptoOperation::EncryptOptional(wallets_details),
Identifier::Merchant(key_store.merchant_id.clone()),
key_store.key.get_inner().peek(),
)
.await
.and_then(|val| val.try_into_optionaloperation())
})
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed while encrypting connector wallets details")?;
Ok(encrypted_connector_wallets_details)
Ok(connector_wallets_details)
}
async fn get_apple_pay_metadata_if_needed(