refactor(masking): PII improvements (#77)

This commit is contained in:
kos-for-juspay
2022-12-13 08:50:21 +01:00
committed by GitHub
parent b3fefeb2aa
commit 124048ce75
16 changed files with 188 additions and 162 deletions

7
Cargo.lock generated
View File

@ -1829,6 +1829,7 @@ dependencies = [
"diesel", "diesel",
"serde", "serde",
"serde_json", "serde_json",
"subtle",
"zeroize", "zeroize",
] ]
@ -3066,6 +3067,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "supports-color" name = "supports-color"
version = "1.3.1" version = "1.3.1"

View File

@ -21,11 +21,7 @@ where
return WithType::fmt(val, f); return WithType::fmt(val, f);
} }
f.write_str(&format!( write!(f, "{}{}", &val_str[..6], "*".repeat(val_str.len() - 6))
"{}{}",
&val_str[..6],
"*".repeat(val_str.len() - 6)
))
} }
} }
@ -45,12 +41,13 @@ where
return WithType::fmt(val, f); return WithType::fmt(val, f);
} }
f.write_str(&format!( write!(
f,
"{}{}{}", "{}{}{}",
&val_str[..2], &val_str[..2],
"*".repeat(val_str.len() - 5), "*".repeat(val_str.len() - 5),
&val_str[(val_str.len() - 3)..] &val_str[(val_str.len() - 3)..]
)) )
} }
} }
*/ */
@ -71,12 +68,11 @@ where
return WithType::fmt(val, f); return WithType::fmt(val, f);
} }
let parts: Vec<&str> = val_str.split('@').collect(); if let Some((a, b)) = val_str.split_once('@') {
if parts.len() != 2 { write!(f, "{}@{}", "*".repeat(a.len()), b)
return WithType::fmt(val, f); } else {
WithType::fmt(val, f)
} }
f.write_str(&format!("{}@{}", "*".repeat(parts[0].len()), parts[1]))
} }
} }
@ -102,7 +98,7 @@ where
} }
} }
f.write_str(&format!("{}.**.**.**", segments[0])) write!(f, "{}.**.**.**", segments[0])
} }
} }
@ -115,62 +111,62 @@ mod pii_masking_strategy_tests {
#[test] #[test]
fn test_valid_card_number_masking() { fn test_valid_card_number_masking() {
let secret: Secret<String, CardNumber> = Secret::new("1234567890987654".to_string()); let secret: Secret<String, CardNumber> = Secret::new("1234567890987654".to_string());
assert_eq!("123456**********", &format!("{:?}", secret)); assert_eq!("123456**********", format!("{:?}", secret));
} }
#[test] #[test]
fn test_invalid_card_number_masking() { fn test_invalid_card_number_masking() {
let secret: Secret<String, CardNumber> = Secret::new("1234567890".to_string()); let secret: Secret<String, CardNumber> = Secret::new("1234567890".to_string());
assert_eq!("123456****", &format!("{:?}", secret)); assert_eq!("123456****", format!("{:?}", secret));
} }
/* /*
#[test] #[test]
fn test_valid_phone_number_masking() { fn test_valid_phone_number_masking() {
let secret: Secret<String, PhoneNumber> = Secret::new("9922992299".to_string()); let secret: Secret<String, PhoneNumber> = Secret::new("9922992299".to_string());
assert_eq!("99*****299", &format!("{}", secret)); assert_eq!("99*****299", format!("{}", secret));
} }
#[test] #[test]
fn test_invalid_phone_number_masking() { fn test_invalid_phone_number_masking() {
let secret: Secret<String, PhoneNumber> = Secret::new("99229922".to_string()); let secret: Secret<String, PhoneNumber> = Secret::new("99229922".to_string());
assert_eq!("*** alloc::string::String ***", &format!("{}", secret)); assert_eq!("*** alloc::string::String ***", format!("{}", secret));
let secret: Secret<String, PhoneNumber> = Secret::new("9922992299229922".to_string()); let secret: Secret<String, PhoneNumber> = Secret::new("9922992299229922".to_string());
assert_eq!("*** alloc::string::String ***", &format!("{}", secret)); assert_eq!("*** alloc::string::String ***", format!("{}", secret));
} }
*/ */
#[test] #[test]
fn test_valid_email_masking() { fn test_valid_email_masking() {
let secret: Secret<String, Email> = Secret::new("myemail@gmail.com".to_string()); let secret: Secret<String, Email> = Secret::new("myemail@gmail.com".to_string());
assert_eq!("*******@gmail.com", &format!("{:?}", secret)); assert_eq!("*******@gmail.com", format!("{:?}", secret));
} }
#[test] #[test]
fn test_invalid_email_masking() { fn test_invalid_email_masking() {
let secret: Secret<String, Email> = Secret::new("myemailgmail.com".to_string()); let secret: Secret<String, Email> = Secret::new("myemailgmail.com".to_string());
assert_eq!("*** alloc::string::String ***", &format!("{:?}", secret)); assert_eq!("*** alloc::string::String ***", format!("{:?}", secret));
let secret: Secret<String, Email> = Secret::new("myemail@gmail@com".to_string()); let secret: Secret<String, Email> = Secret::new("myemail@gmail@com".to_string());
assert_eq!("*** alloc::string::String ***", &format!("{:?}", secret)); assert_eq!("*** alloc::string::String ***", format!("{:?}", secret));
} }
#[test] #[test]
fn test_valid_ip_addr_masking() { fn test_valid_ip_addr_masking() {
let secret: Secret<String, IpAddress> = Secret::new("123.23.1.78".to_string()); let secret: Secret<String, IpAddress> = Secret::new("123.23.1.78".to_string());
assert_eq!("123.**.**.**", &format!("{:?}", secret)); assert_eq!("123.**.**.**", format!("{:?}", secret));
} }
#[test] #[test]
fn test_invalid_ip_addr_masking() { fn test_invalid_ip_addr_masking() {
let secret: Secret<String, IpAddress> = Secret::new("123.4.56".to_string()); let secret: Secret<String, IpAddress> = Secret::new("123.4.56".to_string());
assert_eq!("*** alloc::string::String ***", &format!("{:?}", secret)); assert_eq!("*** alloc::string::String ***", format!("{:?}", secret));
let secret: Secret<String, IpAddress> = Secret::new("123.4567.12.4".to_string()); let secret: Secret<String, IpAddress> = Secret::new("123.4567.12.4".to_string());
assert_eq!("*** alloc::string::String ***", &format!("{:?}", secret)); assert_eq!("*** alloc::string::String ***", format!("{:?}", secret));
let secret: Secret<String, IpAddress> = Secret::new("123..4.56".to_string()); let secret: Secret<String, IpAddress> = Secret::new("123..4.56".to_string());
assert_eq!("*** alloc::string::String ***", &format!("{:?}", secret)); assert_eq!("*** alloc::string::String ***", format!("{:?}", secret));
} }
} }

View File

@ -16,6 +16,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[dependencies] [dependencies]
subtle = "2.4.1"
bytes = { version = "1", optional = true } bytes = { version = "1", optional = true }
diesel = { git = "https://github.com/juspay/diesel", features = ["postgres", "serde_json", "time"], optional = true, rev = "22f3f59f1db8a3f61623e4d6b375d64cd7bd3d02" } diesel = { git = "https://github.com/juspay/diesel", features = ["postgres", "serde_json", "time"], optional = true, rev = "22f3f59f1db8a3f61623e4d6b375d64cd7bd3d02" }
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }

View File

@ -4,6 +4,12 @@ Personal Identifiable Information protection.
Wrapper types and traits for secret management which help ensure they aren't accidentally copied, logged, or otherwise exposed (as much as possible), and also ensure secrets are securely wiped from memory when dropped. Wrapper types and traits for secret management which help ensure they aren't accidentally copied, logged, or otherwise exposed (as much as possible), and also ensure secrets are securely wiped from memory when dropped.
Secret-keeping library inspired by `secrecy`. Secret-keeping library inspired by `secrecy`.
This solution has such advantages over alternatives:
- alternatives have not implemented several traits from the box which are needed
- alternatives do not have WeakSecret and Secret differentiation
- alternatives do not support masking strategies
- alternatives had several minor problems
## How to use ## How to use
To convert non-secret variable into secret use `new()`. Sample: To convert non-secret variable into secret use `new()`. Sample:
@ -19,13 +25,13 @@ To get value from secret use `expose()`. Sample:
last4_digits: Some(card_number.expose()) last4_digits: Some(card_number.expose())
``` ```
Most fields are under `Option`. To simplify dealing with `Option`, use `expose_cloning()`. Sample: Most fields are under `Option`. To simplify dealing with `Option`, use `expose_option()`. Sample:
```rust,ignore ```rust,ignore
card_info.push_str( card_info.push_str(
&card_detail &card_detail
.card_holder_name .card_holder_name
.expose_cloning() .expose_option()
.unwrap_or_default(), .unwrap_or_default(),
); );
``` ```
@ -38,7 +44,6 @@ Most fields are under `Option`. To simplify dealing with `Option`, use `expose_c
<!-- ```text <!-- ```text
├── src : source code ├── src : source code
│   ├── bachstd : utilities
└── tests : unit and integration tests └── tests : unit and integration tests
``` --> ``` -->

View File

@ -10,10 +10,10 @@ pub trait PeekInterface<S> {
fn peek(&self) -> &S; fn peek(&self) -> &S;
} }
/// Interface to expose a clone of secret /// Interface that consumes a option secret and returns the value.
pub trait PeekOptionInterface<S> { pub trait ExposeOptionInterface<S> {
/// Expose option. /// Expose option.
fn peek_cloning(&self) -> S; fn expose_option(self) -> S;
} }
/// Interface that consumes a secret and returns the inner value. /// Interface that consumes a secret and returns the inner value.
@ -22,23 +22,13 @@ pub trait ExposeInterface<S> {
fn expose(self) -> S; fn expose(self) -> S;
} }
impl<S, I> PeekOptionInterface<Option<S>> for Option<Secret<S, I>> impl<S, I> ExposeOptionInterface<Option<S>> for Option<Secret<S, I>>
where where
S: Clone, S: Clone,
I: crate::Strategy<S>, I: crate::Strategy<S>,
{ {
fn peek_cloning(&self) -> Option<S> { fn expose_option(self) -> Option<S> {
self.as_ref().map(|val| val.peek().clone()) self.map(ExposeInterface::expose)
}
}
impl<S, I> PeekOptionInterface<S> for Secret<S, I>
where
S: Clone,
I: crate::Strategy<S>,
{
fn peek_cloning(&self) -> S {
self.peek().clone()
} }
} }

View File

@ -14,13 +14,13 @@ use super::{PeekInterface, ZeroizableSecret};
/// Because of the nature of how the `BytesMut` type works, it needs some special /// Because of the nature of how the `BytesMut` type works, it needs some special
/// care in order to have a proper zeroizing drop handler. /// care in order to have a proper zeroizing drop handler.
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(docsrs, doc(cfg(feature = "bytes")))] #[cfg_attr(docsrs, cfg(feature = "bytes"))]
pub struct SecretBytesMut(BytesMut); pub struct SecretBytesMut(BytesMut);
impl SecretBytesMut { impl SecretBytesMut {
/// Wrap bytes in `SecretBytesMut` /// Wrap bytes in `SecretBytesMut`
pub fn new(bytes: impl Into<BytesMut>) -> SecretBytesMut { pub fn new(bytes: impl Into<BytesMut>) -> Self {
SecretBytesMut(bytes.into()) Self(bytes.into())
} }
} }
@ -37,8 +37,8 @@ impl fmt::Debug for SecretBytesMut {
} }
impl From<BytesMut> for SecretBytesMut { impl From<BytesMut> for SecretBytesMut {
fn from(bytes: BytesMut) -> SecretBytesMut { fn from(bytes: BytesMut) -> Self {
SecretBytesMut::new(bytes) Self::new(bytes)
} }
} }

View File

@ -1,4 +1,5 @@
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide))]
#![cfg_attr(docsrs, doc(cfg_hide(doc)))]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![warn( #![warn(
missing_docs, missing_docs,
@ -11,7 +12,8 @@
clippy::panicking_unwrap, clippy::panicking_unwrap,
clippy::unreachable, clippy::unreachable,
clippy::unwrap_in_result, clippy::unwrap_in_result,
clippy::unwrap_used clippy::unwrap_used,
clippy::use_self
)] )]
//! //!
@ -27,7 +29,7 @@ mod strategy;
pub use strategy::{Strategy, WithType, WithoutType}; pub use strategy::{Strategy, WithType, WithoutType};
mod abs; mod abs;
pub use abs::{ExposeInterface, PeekInterface, PeekOptionInterface}; pub use abs::{ExposeInterface, ExposeOptionInterface, PeekInterface};
mod secret; mod secret;
mod strong_secret; mod strong_secret;
@ -60,7 +62,7 @@ pub use crate::serde::{Deserialize, SerializableSecret, Serialize};
/// `use masking::prelude::*;` /// `use masking::prelude::*;`
/// ///
pub mod prelude { pub mod prelude {
pub use super::{ExposeInterface, PeekInterface, PeekOptionInterface}; pub use super::{ExposeInterface, ExposeOptionInterface, PeekInterface};
} }
#[cfg(feature = "diesel")] #[cfg(feature = "diesel")]

View File

@ -31,9 +31,7 @@ use crate::{strategy::Strategy, PeekInterface};
/// T: fmt::Display /// T: fmt::Display
/// { /// {
/// fn fmt(val: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result { /// fn fmt(val: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// f.write_str( /// write!(f, "{}", val.to_string().to_ascii_lowercase())
/// &format!("{}", val).to_ascii_lowercase()
/// )
/// } /// }
/// } /// }
/// ///
@ -42,93 +40,93 @@ use crate::{strategy::Strategy, PeekInterface};
/// assert_eq!("hello", &format!("{:?}", my_secret)); /// assert_eq!("hello", &format!("{:?}", my_secret));
/// ``` /// ```
/// ///
pub struct Secret<S, I = crate::WithType> pub struct Secret<Secret, MaskingStrategy = crate::WithType>
where where
I: Strategy<S>, MaskingStrategy: Strategy<Secret>,
{ {
/// Inner secret value pub(crate) inner_secret: Secret,
pub(crate) inner_secret: S, pub(crate) masking_strategy: PhantomData<MaskingStrategy>,
pub(crate) marker: PhantomData<I>,
} }
impl<S, I> Secret<S, I> impl<SecretValue, MaskingStrategy> Secret<SecretValue, MaskingStrategy>
where where
I: Strategy<S>, MaskingStrategy: Strategy<SecretValue>,
{ {
/// Take ownership of a secret value /// Take ownership of a secret value
pub fn new(secret: S) -> Self { pub fn new(secret: SecretValue) -> Self {
Secret { Self {
inner_secret: secret, inner_secret: secret,
marker: PhantomData, masking_strategy: PhantomData,
} }
} }
} }
impl<S, I> PeekInterface<S> for Secret<S, I> impl<SecretValue, MaskingStrategy> PeekInterface<SecretValue>
for Secret<SecretValue, MaskingStrategy>
where where
I: Strategy<S>, MaskingStrategy: Strategy<SecretValue>,
{ {
fn peek(&self) -> &S { fn peek(&self) -> &SecretValue {
&self.inner_secret &self.inner_secret
} }
} }
impl<S, I> From<S> for Secret<S, I> impl<SecretValue, MaskingStrategy> From<SecretValue> for Secret<SecretValue, MaskingStrategy>
where where
I: Strategy<S>, MaskingStrategy: Strategy<SecretValue>,
{ {
fn from(secret: S) -> Secret<S, I> { fn from(secret: SecretValue) -> Self {
Self::new(secret) Self::new(secret)
} }
} }
impl<S, I> Clone for Secret<S, I> impl<SecretValue, MaskingStrategy> Clone for Secret<SecretValue, MaskingStrategy>
where where
S: Clone, SecretValue: Clone,
I: Strategy<S>, MaskingStrategy: Strategy<SecretValue>,
{ {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Secret { Self {
inner_secret: self.inner_secret.clone(), inner_secret: self.inner_secret.clone(),
marker: PhantomData, masking_strategy: PhantomData,
} }
} }
} }
impl<S, I> PartialEq for Secret<S, I> impl<SecretValue, MaskingStrategy> PartialEq for Secret<SecretValue, MaskingStrategy>
where where
Self: PeekInterface<S>, Self: PeekInterface<SecretValue>,
S: PartialEq, SecretValue: PartialEq,
I: Strategy<S>, MaskingStrategy: Strategy<SecretValue>,
{ {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.peek().eq(other.peek()) self.peek().eq(other.peek())
} }
} }
impl<S, I> Eq for Secret<S, I> impl<SecretValue, MaskingStrategy> Eq for Secret<SecretValue, MaskingStrategy>
where where
Self: PeekInterface<S>, Self: PeekInterface<SecretValue>,
S: Eq, SecretValue: Eq,
I: Strategy<S>, MaskingStrategy: Strategy<SecretValue>,
{ {
} }
impl<S, I> fmt::Debug for Secret<S, I> impl<SecretValue, MaskingStrategy> fmt::Debug for Secret<SecretValue, MaskingStrategy>
where where
I: Strategy<S>, MaskingStrategy: Strategy<SecretValue>,
{ {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
I::fmt(&self.inner_secret, f) MaskingStrategy::fmt(&self.inner_secret, f)
} }
} }
impl<S, I> Default for Secret<S, I> impl<SecretValue, MaskingStrategy> Default for Secret<SecretValue, MaskingStrategy>
where where
S: Default, SecretValue: Default,
I: Strategy<S>, MaskingStrategy: Strategy<SecretValue>,
{ {
fn default() -> Self { fn default() -> Self {
S::default().into() SecretValue::default().into()
} }
} }

View File

@ -17,7 +17,7 @@ use crate::{PeekInterface, Secret, Strategy, StrongSecret, ZeroizableSecret};
/// via `serde` serialization. /// via `serde` serialization.
/// ///
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))] #[cfg_attr(docsrs, cfg(feature = "serde"))]
pub trait SerializableSecret: Serialize {} pub trait SerializableSecret: Serialize {}
// #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] // #[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
// pub trait NonSerializableSecret: Serialize {} // pub trait NonSerializableSecret: Serialize {}
@ -33,7 +33,7 @@ where
where where
D: de::Deserializer<'de>, D: de::Deserializer<'de>,
{ {
T::deserialize(deserializer).map(Secret::new) T::deserialize(deserializer).map(Self::new)
} }
} }
@ -59,7 +59,7 @@ where
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
T::deserialize(deserializer).map(StrongSecret::new) T::deserialize(deserializer).map(Self::new)
} }
} }

View File

@ -3,14 +3,14 @@ use core::fmt;
/// Debugging trait which is specialized for handling secret values /// Debugging trait which is specialized for handling secret values
pub trait Strategy<T> { pub trait Strategy<T> {
/// Format information about the secret's type. /// Format information about the secret's type.
fn fmt(value: &T, fmt: &mut fmt::Formatter<'_>) -> std::fmt::Result; fn fmt(value: &T, fmt: &mut fmt::Formatter<'_>) -> fmt::Result;
} }
/// Debug with type /// Debug with type
pub struct WithType; pub struct WithType;
impl<T> Strategy<T> for WithType { impl<T> Strategy<T> for WithType {
fn fmt(_: &T, fmt: &mut fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(_: &T, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str("*** ")?; fmt.write_str("*** ")?;
fmt.write_str(std::any::type_name::<T>())?; fmt.write_str(std::any::type_name::<T>())?;
fmt.write_str(" ***") fmt.write_str(" ***")
@ -21,18 +21,7 @@ impl<T> Strategy<T> for WithType {
pub struct WithoutType; pub struct WithoutType;
impl<T> Strategy<T> for WithoutType { impl<T> Strategy<T> for WithoutType {
fn fmt(_: &T, fmt: &mut fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(_: &T, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str("*** ***") fmt.write_str("*** ***")
} }
} }
pub struct NoMasking;
impl<T> Strategy<T> for NoMasking
where
T: fmt::Display,
{
fn fmt(val: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(val, f)
}
}

View File

@ -23,7 +23,7 @@ where
type Err = core::convert::Infallible; type Err = core::convert::Infallible;
fn from_str(src: &str) -> Result<Self, Self::Err> { fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(Secret::<String, I>::new(src.to_string())) Ok(Self::new(src.to_string()))
} }
} }
@ -34,6 +34,6 @@ where
type Err = core::convert::Infallible; type Err = core::convert::Infallible;
fn from_str(src: &str) -> Result<Self, Self::Err> { fn from_str(src: &str) -> Result<Self, Self::Err> {
Ok(StrongSecret::<String, I>::new(src.to_string())) Ok(Self::new(src.to_string()))
} }
} }

View File

@ -4,6 +4,7 @@
use std::{fmt, marker::PhantomData}; use std::{fmt, marker::PhantomData};
use subtle::ConstantTimeEq;
use zeroize::{self, Zeroize as ZeroizableSecret}; use zeroize::{self, Zeroize as ZeroizableSecret};
use crate::{strategy::Strategy, PeekInterface}; use crate::{strategy::Strategy, PeekInterface};
@ -13,84 +14,106 @@ use crate::{strategy::Strategy, PeekInterface};
/// ///
/// To get access to value use method `expose()` of trait [`crate::ExposeInterface`]. /// To get access to value use method `expose()` of trait [`crate::ExposeInterface`].
/// ///
pub struct StrongSecret<Secret: ZeroizableSecret, MaskingStrategy = crate::WithType> {
pub struct StrongSecret<S: ZeroizableSecret, I = crate::WithType> {
/// Inner secret value /// Inner secret value
pub(crate) inner_secret: S, pub(crate) inner_secret: Secret,
pub(crate) marker: PhantomData<I>, pub(crate) masking_strategy: PhantomData<MaskingStrategy>,
} }
impl<S: ZeroizableSecret, I> StrongSecret<S, I> { impl<Secret: ZeroizableSecret, MaskingStrategy> StrongSecret<Secret, MaskingStrategy> {
/// Take ownership of a secret value /// Take ownership of a secret value
pub fn new(secret: S) -> Self { pub fn new(secret: Secret) -> Self {
StrongSecret { Self {
inner_secret: secret, inner_secret: secret,
marker: PhantomData, masking_strategy: PhantomData,
} }
} }
} }
impl<S: ZeroizableSecret, I> PeekInterface<S> for StrongSecret<S, I> { impl<Secret: ZeroizableSecret, MaskingStrategy> PeekInterface<Secret>
fn peek(&self) -> &S { for StrongSecret<Secret, MaskingStrategy>
{
fn peek(&self) -> &Secret {
&self.inner_secret &self.inner_secret
} }
} }
impl<S: ZeroizableSecret, I> From<S> for StrongSecret<S, I> { impl<Secret: ZeroizableSecret, MaskingStrategy> From<Secret>
fn from(secret: S) -> StrongSecret<S, I> { for StrongSecret<Secret, MaskingStrategy>
{
fn from(secret: Secret) -> Self {
Self::new(secret) Self::new(secret)
} }
} }
impl<S: Clone + ZeroizableSecret, I> Clone for StrongSecret<S, I> { impl<Secret: Clone + ZeroizableSecret, MaskingStrategy> Clone
for StrongSecret<Secret, MaskingStrategy>
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
StrongSecret { Self {
inner_secret: self.inner_secret.clone(), inner_secret: self.inner_secret.clone(),
marker: PhantomData, masking_strategy: PhantomData,
} }
} }
} }
impl<S: ZeroizableSecret, I> PartialEq for StrongSecret<S, I> impl<Secret, MaskingStrategy> PartialEq for StrongSecret<Secret, MaskingStrategy>
where where
Self: PeekInterface<S>, Self: PeekInterface<Secret>,
S: PartialEq, Secret: ZeroizableSecret + StrongEq,
{ {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.peek().eq(other.peek()) StrongEq::strong_eq(self.peek(), other.peek())
} }
} }
impl<S: ZeroizableSecret, I> Eq for StrongSecret<S, I> impl<Secret, MaskingStrategy> Eq for StrongSecret<Secret, MaskingStrategy>
where where
Self: PeekInterface<S>, Self: PeekInterface<Secret>,
S: Eq, Secret: ZeroizableSecret + StrongEq,
{ {
} }
impl<S: ZeroizableSecret, I: Strategy<S>> fmt::Debug for StrongSecret<S, I> { impl<Secret: ZeroizableSecret, MaskingStrategy: Strategy<Secret>> fmt::Debug
for StrongSecret<Secret, MaskingStrategy>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
I::fmt(&self.inner_secret, f) MaskingStrategy::fmt(&self.inner_secret, f)
} }
} }
impl<S: ZeroizableSecret, I: Strategy<S>> fmt::Display for StrongSecret<S, I> { impl<Secret: ZeroizableSecret, MaskingStrategy: Strategy<Secret>> fmt::Display
for StrongSecret<Secret, MaskingStrategy>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
I::fmt(&self.inner_secret, f) MaskingStrategy::fmt(&self.inner_secret, f)
} }
} }
impl<S: ZeroizableSecret, I> Default for StrongSecret<S, I> impl<Secret: ZeroizableSecret, MaskingStrategy> Default for StrongSecret<Secret, MaskingStrategy>
where where
S: ZeroizableSecret + Default, Secret: ZeroizableSecret + Default,
{ {
fn default() -> Self { fn default() -> Self {
S::default().into() Secret::default().into()
} }
} }
impl<T: ZeroizableSecret, S> Drop for StrongSecret<T, S> { impl<Secret: ZeroizableSecret, MaskingStrategy> Drop for StrongSecret<Secret, MaskingStrategy> {
fn drop(&mut self) { fn drop(&mut self) {
self.inner_secret.zeroize(); self.inner_secret.zeroize();
} }
} }
trait StrongEq {
fn strong_eq(&self, other: &Self) -> bool;
}
impl StrongEq for String {
fn strong_eq(&self, other: &Self) -> bool {
let lhs = self.as_bytes();
let rhs = other.as_bytes();
bool::from(lhs.ct_eq(rhs))
}
}

View File

@ -8,7 +8,7 @@ use uuid::Uuid;
use crate::{ use crate::{
core::errors, core::errors,
pii::{self, PeekOptionInterface, Secret}, pii::{self, ExposeOptionInterface, Secret},
services, services,
types::{self, api, storage::enums}, types::{self, api, storage::enums},
}; };
@ -200,8 +200,8 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
name: shipping.address.as_mut().map(|a| { name: shipping.address.as_mut().map(|a| {
format!( format!(
"{} {}", "{} {}",
a.first_name.peek_cloning().unwrap_or_default(), a.first_name.clone().expose_option().unwrap_or_default(),
a.last_name.peek_cloning().unwrap_or_default() a.last_name.clone().expose_option().unwrap_or_default()
) )
.into() .into()
}), }),
@ -209,7 +209,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PaymentIntentRequest {
format!( format!(
"{}{}", "{}{}",
p.country_code.unwrap_or_default(), p.country_code.unwrap_or_default(),
p.number.peek_cloning().unwrap_or_default() p.number.expose_option().unwrap_or_default()
) )
.into() .into()
}), }),

View File

@ -493,17 +493,24 @@ impl BasiliskCardSupport {
) -> RouterResult<api::CardDetailFromLocker> { ) -> RouterResult<api::CardDetailFromLocker> {
let card_number = card let card_number = card
.card_number .card_number
.peek_cloning() .clone()
.expose_option()
.get_required_value("card_number")?; .get_required_value("card_number")?;
let card_exp_month = card let card_exp_month = card
.expiry_month .expiry_month
.peek_cloning() .clone()
.expose_option()
.get_required_value("expiry_month")?; .get_required_value("expiry_month")?;
let card_exp_year = card let card_exp_year = card
.expiry_year .expiry_year
.peek_cloning() .clone()
.expose_option()
.get_required_value("expiry_year")?; .get_required_value("expiry_year")?;
let card_holder_name = card.card_holder_name.peek_cloning().unwrap_or_default(); let card_holder_name = card
.card_holder_name
.clone()
.expose_option()
.unwrap_or_default();
let card_detail = api::CardDetail { let card_detail = api::CardDetail {
card_number: card_number.into(), card_number: card_number.into(),
card_exp_month: card_exp_month.into(), card_exp_month: card_exp_month.into(),
@ -529,20 +536,28 @@ impl BasiliskCardSupport {
) -> RouterResult<api::CardDetailFromLocker> { ) -> RouterResult<api::CardDetailFromLocker> {
let card_number = card let card_number = card
.card_number .card_number
.peek_cloning() .clone()
.expose_option()
.get_required_value("card_number")?; .get_required_value("card_number")?;
let card_exp_month = card let card_exp_month = card
.expiry_month .expiry_month
.peek_cloning() .clone()
.expose_option()
.get_required_value("expiry_month")?; .get_required_value("expiry_month")?;
let card_exp_year = card let card_exp_year = card
.expiry_year .expiry_year
.peek_cloning() .clone()
.expose_option()
.get_required_value("expiry_year")?; .get_required_value("expiry_year")?;
let card_holder_name = card.card_holder_name.peek_cloning().unwrap_or_default(); let card_holder_name = card
.card_holder_name
.clone()
.expose_option()
.unwrap_or_default();
let card_fingerprint = card let card_fingerprint = card
.card_fingerprint .card_fingerprint
.peek_cloning() .clone()
.expose_option()
.get_required_value("card_fingerprint")?; .get_required_value("card_fingerprint")?;
let value1 = payment_methods::mk_card_value1( let value1 = payment_methods::mk_card_value1(
card_number, card_number,

View File

@ -2,7 +2,7 @@ use std::borrow::Cow;
// TODO : Evaluate all the helper functions () // TODO : Evaluate all the helper functions ()
use error_stack::{report, IntoReport, ResultExt}; use error_stack::{report, IntoReport, ResultExt};
use masking::{PeekInterface, PeekOptionInterface}; use masking::{ExposeOptionInterface, PeekInterface};
use router_env::{instrument, tracing}; use router_env::{instrument, tracing};
use uuid::Uuid; use uuid::Uuid;
@ -633,7 +633,7 @@ pub async fn create_customer_if_not_exist<'a, F: Clone, R>(
let new_customer = storage::CustomerNew { let new_customer = storage::CustomerNew {
customer_id: customer_id.to_string(), customer_id: customer_id.to_string(),
merchant_id: merchant_id.to_string(), merchant_id: merchant_id.to_string(),
name: req.name.peek_cloning(), name: req.name.expose_option(),
email: req.email.clone(), email: req.email.clone(),
phone: req.phone.clone(), phone: req.phone.clone(),
phone_country_code: req.phone_country_code.clone(), phone_country_code: req.phone_country_code.clone(),
@ -725,17 +725,17 @@ impl Vault {
let card = resp.card; let card = resp.card;
let card_number = card let card_number = card
.card_number .card_number
.peek_cloning() .expose_option()
.get_required_value("card_number")?; .get_required_value("card_number")?;
let card_exp_month = card let card_exp_month = card
.card_exp_month .card_exp_month
.peek_cloning() .expose_option()
.get_required_value("expiry_month")?; .get_required_value("expiry_month")?;
let card_exp_year = card let card_exp_year = card
.card_exp_year .card_exp_year
.peek_cloning() .expose_option()
.get_required_value("expiry_year")?; .get_required_value("expiry_year")?;
let card_holder_name = card.name_on_card.peek_cloning().unwrap_or_default(); let card_holder_name = card.name_on_card.expose_option().unwrap_or_default();
let card = api::PaymentMethod::Card(api::CCard { let card = api::PaymentMethod::Card(api::CCard {
card_number: card_number.into(), card_number: card_number.into(),
card_exp_month: card_exp_month.into(), card_exp_month: card_exp_month.into(),

View File

@ -6,7 +6,7 @@ use std::{borrow::Cow, collections::HashMap, fmt::Debug, future::Future, str, ti
use actix_web::{body, HttpRequest, HttpResponse, Responder}; use actix_web::{body, HttpRequest, HttpResponse, Responder};
use bytes::Bytes; use bytes::Bytes;
use error_stack::{report, IntoReport, Report, ResultExt}; use error_stack::{report, IntoReport, Report, ResultExt};
use masking::PeekOptionInterface; use masking::ExposeOptionInterface;
use router_env::{ use router_env::{
tracing::{self, instrument}, tracing::{self, instrument},
Tag, Tag,
@ -223,7 +223,7 @@ async fn send_request(
client.body(url_encoded_payload).send() client.body(url_encoded_payload).send()
} }
None => client None => client
.body(request.payload.peek_cloning().unwrap_or_default()) .body(request.payload.expose_option().unwrap_or_default())
.send(), .send(),
} }
.await .await