Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PLATFORM-833]: Add a way of using the Display implementation instead of the Debug implementation when redacting #58

Merged
merged 5 commits into from
Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
//! | `#[redact(partial)]` | | If the string is long enough, a small part of the<br>beginning and end will be exposed. If the string is too short to securely expose a portion of it, it will be redacted entirely. | | Disabled. The entire string will be redacted. |
//! | `#[redact(with = 'X')]` | | Specifies the `char` the string will be redacted with. | | `'*'` |
//! | `#[redact(fixed = <integer>)]` | | If this modifier is present, the length and contents of<br>the string are completely ignored and the string will always<br>be redacted as a fixed number of redaction characters. | | Disabled. |
//! | `#[redact(display)]` | | Overrides the redaction behavior to use the type's [`std::fmt::Display`] implementation instead of [`std::fmt::Debug`]. | | Disabled. |
//!
//! # Redacting All Fields in a Struct or Enum Variant
//!
Expand Down
50 changes: 29 additions & 21 deletions src/private.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fmt::Debug;
use std::fmt::{Debug, Display};

#[repr(transparent)]
pub struct DisplayDebug(String);
Expand All @@ -15,9 +15,6 @@ impl AsRef<str> for DisplayDebug {
}

pub struct RedactFlags {
/// Sourced from [`std::fmt::Formatter::alternate`]
pub debug_alternate: bool,

/// Whether the type we're redacting is an Option<T> or not. Poor man's specialization! This is detected
/// by the proc macro reading the path to the type, so it's not perfect.
///
Expand Down Expand Up @@ -107,16 +104,31 @@ impl RedactFlags {
}
}

pub fn redact(this: &dyn Debug, flags: RedactFlags) -> DisplayDebug {
pub enum RedactionTarget<'a> {
/// Redact the output of the type's [`std::fmt::Debug`] implementation.
Debug {
this: &'a dyn Debug,

/// Sourced from [`std::fmt::Formatter::alternate`]
alternate: bool,
},

/// Redact the output of the type's [`std::fmt::Display`] implementation.
Display(&'a dyn Display),
}

pub fn redact(this: RedactionTarget, flags: RedactFlags) -> DisplayDebug {
let mut redacted = String::new();

let to_redactable_string = || match this {
RedactionTarget::Debug { this, alternate: false } => format!("{:?}", this),
RedactionTarget::Debug { this, alternate: true } => format!("{:#?}", this),
RedactionTarget::Display(this) => this.to_string(),
};

#[cfg(feature = "toggle")]
if crate::toggle::get_redaction_behavior().is_plaintext() {
return DisplayDebug(if flags.debug_alternate {
format!("{:#?}", this)
} else {
format!("{:?}", this)
});
return DisplayDebug(to_redactable_string());
}

(|| {
Expand All @@ -125,20 +137,16 @@ pub fn redact(this: &dyn Debug, flags: RedactFlags) -> DisplayDebug {
return;
}

let debug_formatted = if flags.debug_alternate {
format!("{:#?}", this)
} else {
format!("{:?}", this)
};
let redactable_string = to_redactable_string();

redacted.reserve(debug_formatted.len());
redacted.reserve(redactable_string.len());

// Specialize for Option<T>
if flags.is_option {
if debug_formatted == "None" {
if redactable_string == "None" {
// We don't need to do any redacting
// https://prima.slack.com/archives/C03URH9N43U/p1661423554871499
} else if let Some(inner) = debug_formatted
} else if let Some(inner) = redactable_string
.strip_prefix("Some(")
.and_then(|inner| inner.strip_suffix(')'))
{
Expand All @@ -147,15 +155,15 @@ pub fn redact(this: &dyn Debug, flags: RedactFlags) -> DisplayDebug {
redacted.push(')');
} else {
// This should never happen, but just in case...
flags.redact_full(&debug_formatted, &mut redacted);
flags.redact_full(&redactable_string, &mut redacted);
}
return;
}

if flags.partial {
flags.redact_partial(&debug_formatted, &mut redacted);
flags.redact_partial(&redactable_string, &mut redacted);
} else {
flags.redact_full(&debug_formatted, &mut redacted);
flags.redact_full(&redactable_string, &mut redacted);
}
})();

Expand Down
5 changes: 4 additions & 1 deletion veil-macros/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,13 @@ pub(super) fn derive_redact(
Ok(quote! {
impl #impl_generics ::std::fmt::Debug for #name_ident #ty_generics #where_clause {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
let debug_alternate = f.alternate();
#[allow(unused)] // Suppresses unused warning with `#[redact(display)]`
let alternate = f.alternate();

match self {
#(Self::#variant_idents #variant_destructures => { #variant_bodies; },)*
}

Ok(())
}
}
Expand Down
9 changes: 9 additions & 0 deletions veil-macros/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub struct FieldFlags {
///
/// Fields are not redacted by default unless their parent is marked as `#[redact(all)]`, and this flag turns off that redaction for this specific field.
pub skip: bool,

/// Whether to use the type's [`std::fmt::Display`] implementation instead of [`std::fmt::Debug`].
pub display: bool,
}
impl FieldFlags {
/// Returns a list of `FieldFlags` parsed from an attribute.
Expand Down Expand Up @@ -121,6 +124,11 @@ impl FieldFlags {
flags.variant = true;
}

// #[redact(display)]
syn::Meta::Path(path) if path.is_ident("display") => {
flags.display = true;
}

// #[redact(with = 'X')]
syn::Meta::NameValue(kv) if kv.path.is_ident("with") => match kv.lit {
syn::Lit::Char(with) => flags.redact_char = with.value(),
Expand Down Expand Up @@ -164,6 +172,7 @@ impl Default for FieldFlags {
variant: false,
all: false,
skip: false,
display: false,
}
}
}
Expand Down
28 changes: 22 additions & 6 deletions veil-macros/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,28 @@ pub(crate) fn generate_redact_call(
// This is the one place where we actually track whether the derive macro had any effect! Nice.
unused.redacted_something();

quote! {
::veil::private::redact(#field_accessor, ::veil::private::RedactFlags {
debug_alternate,
is_option: #is_option,
#field_flags
})
if field_flags.display {
// std::fmt::Display
quote! {
::veil::private::redact(
::veil::private::RedactionTarget::Display(#field_accessor),
::veil::private::RedactFlags {
is_option: #is_option,
#field_flags
}
)
}
} else {
// std::fmt::Debug
quote! {
::veil::private::redact(
::veil::private::RedactionTarget::Debug { this: #field_accessor, alternate },
::veil::private::RedactFlags {
is_option: #is_option,
#field_flags
}
)
}
}
} else {
field_accessor
Expand Down
5 changes: 4 additions & 1 deletion veil-macros/src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,11 @@ pub(super) fn derive_redact(
Ok(quote! {
impl #impl_generics ::std::fmt::Debug for #name_ident #ty_generics #where_clause {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
let debug_alternate = f.alternate();
#[allow(unused)] // Suppresses unused warning with `#[redact(display)]`
let alternate = f.alternate();

#impl_debug;

Ok(())
}
}
Expand Down
12 changes: 12 additions & 0 deletions veil-tests/src/redaction_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,15 @@ fn test_sensitive_tuple_structs() {
SENSITIVE_DATA[3],
));
}

#[test]
fn test_display_redaction() {
#[derive(Redact)]
struct RedactDisplay(#[redact(display)] String);

#[derive(Redact)]
struct RedactDebug(#[redact] String);

assert_eq!(format!("{:?}", RedactDebug("\"".to_string())), r#"RedactDebug("\"")"#);
assert_eq!(format!("{:?}", RedactDisplay("\"".to_string())), r#"RedactDisplay(")"#);
}