feat(pii): implement a masking strategy for UPI VPAs (#1641)

Co-authored-by: Prasunna Soppa <prasunna.soppa@juspay.in>
Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com>
This commit is contained in:
SargamPuram
2023-08-01 17:58:46 +05:30
committed by GitHub
parent 5773faf739
commit e3a33bb5c2
6 changed files with 63 additions and 9 deletions

View File

@ -922,7 +922,7 @@ pub struct CryptoData {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct UpiData { pub struct UpiData {
#[schema(value_type = Option<String>, example = "successtest@iata")] #[schema(value_type = Option<String>, example = "successtest@iata")]
pub vpa_id: Option<Secret<String>>, pub vpa_id: Option<Secret<String, pii::UpiVpaMaskingStrategy>>,
} }
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)]

View File

@ -329,13 +329,33 @@ where
} }
} }
/// Strategy for masking UPI VPA's
#[derive(Debug)]
pub struct UpiVpaMaskingStrategy;
impl<T> Strategy<T> for UpiVpaMaskingStrategy
where
T: AsRef<str> + std::fmt::Debug,
{
fn fmt(val: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let vpa_str: &str = val.as_ref();
if let Some((user_identifier, bank_or_psp)) = vpa_str.split_once('@') {
let masked_user_identifier = "*".repeat(user_identifier.len());
write!(f, "{masked_user_identifier}@{bank_or_psp}")
} else {
WithType::fmt(val, f)
}
}
}
#[cfg(test)] #[cfg(test)]
mod pii_masking_strategy_tests { mod pii_masking_strategy_tests {
use std::str::FromStr; use std::str::FromStr;
use masking::{ExposeInterface, Secret}; use masking::{ExposeInterface, Secret};
use super::{ClientSecret, Email, IpAddress}; use super::{ClientSecret, Email, IpAddress, UpiVpaMaskingStrategy};
use crate::pii::{EmailStrategy, REDACTED}; use crate::pii::{EmailStrategy, REDACTED};
/* /*
@ -435,4 +455,16 @@ mod pii_masking_strategy_tests {
let secret: Secret<String> = Secret::new("+40712345678".to_string()); let secret: Secret<String> = Secret::new("+40712345678".to_string());
assert_eq!("*** alloc::string::String ***", format!("{secret:?}")); assert_eq!("*** alloc::string::String ***", format!("{secret:?}"));
} }
#[test]
fn test_valid_upi_vpa_masking() {
let secret: Secret<String, UpiVpaMaskingStrategy> = Secret::new("my_name@upi".to_string());
assert_eq!("*******@upi", format!("{secret:?}"));
}
#[test]
fn test_invalid_upi_vpa_masking() {
let secret: Secret<String, UpiVpaMaskingStrategy> = Secret::new("my_name_upi".to_string());
assert_eq!("*** alloc::string::String ***", format!("{secret:?}"));
}
} }

View File

@ -40,3 +40,25 @@ where
self.inner_secret self.inner_secret
} }
} }
/// Interface that consumes a secret and converts it to a secret with a different masking strategy.
pub trait SwitchStrategy<FromStrategy, ToStrategy> {
/// The type returned by `switch_strategy()`.
type Output;
/// Consumes the secret and converts it to a secret with a different masking strategy.
fn switch_strategy(self) -> Self::Output;
}
impl<S, FromStrategy, ToStrategy> SwitchStrategy<FromStrategy, ToStrategy>
for Secret<S, FromStrategy>
where
FromStrategy: crate::Strategy<S>,
ToStrategy: crate::Strategy<S>,
{
type Output = Secret<S, ToStrategy>;
fn switch_strategy(self) -> Self::Output {
Secret::new(self.inner_secret)
}
}

View File

@ -16,7 +16,7 @@ mod strategy;
pub use strategy::{Strategy, WithType, WithoutType}; pub use strategy::{Strategy, WithType, WithoutType};
mod abs; mod abs;
pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface}; pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface, SwitchStrategy};
mod secret; mod secret;
mod strong_secret; mod strong_secret;

View File

@ -5,7 +5,7 @@ use common_utils::{
crypto::Encryptable, crypto::Encryptable,
date_time, date_time,
ext_traits::StringExt, ext_traits::StringExt,
pii::{IpAddress, SecretSerdeValue}, pii::{IpAddress, SecretSerdeValue, UpiVpaMaskingStrategy},
}; };
use error_stack::{IntoReport, ResultExt}; use error_stack::{IntoReport, ResultExt};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -63,7 +63,7 @@ pub enum StripeWallet {
#[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)] #[derive(Default, Serialize, PartialEq, Eq, Deserialize, Clone, Debug)]
pub struct StripeUpi { pub struct StripeUpi {
pub vpa_id: masking::Secret<String>, pub vpa_id: masking::Secret<String, UpiVpaMaskingStrategy>,
} }
#[derive(Debug, Default, Serialize, PartialEq, Eq, Deserialize, Clone)] #[derive(Debug, Default, Serialize, PartialEq, Eq, Deserialize, Clone)]

View File

@ -1,7 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use api_models::enums::PaymentMethod; use api_models::enums::PaymentMethod;
use masking::Secret; use masking::{Secret, SwitchStrategy};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
@ -90,9 +90,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for IatapayPaymentsRequest {
}; };
let return_url = item.get_return_url()?; let return_url = item.get_return_url()?;
let payer_info = match item.request.payment_method_data.clone() { let payer_info = match item.request.payment_method_data.clone() {
api::PaymentMethodData::Upi(upi_data) => { api::PaymentMethodData::Upi(upi_data) => upi_data.vpa_id.map(|id| PayerInfo {
upi_data.vpa_id.map(|id| PayerInfo { token_id: id }) token_id: id.switch_strategy(),
} }),
_ => None, _ => None,
}; };
let amount = let amount =