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 all commits
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
18 changes: 15 additions & 3 deletions veil-macros/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub(super) fn derive_redact(
attrs[0].span(),
"at least `#[redact(all, variant)]` is required here to redact all variant names",
));
} else if flags.display {
return Err(syn::Error::new(attrs[0].span(), "`#[redact(display)]` is invalid here"));
} else {
Some(flags)
}
Expand Down Expand Up @@ -56,6 +58,13 @@ pub(super) fn derive_redact(
all_fields_flags: Some(flags),
}
} else if flags.variant {
if flags.display {
return Err(syn::Error::new(
variant.attrs[0].span(),
"`#[redact(display)]` is invalid here, enum variants are always displayed using std::fmt::Display",
));
}

// #[redact(variant, ...)]
EnumVariantFieldFlags {
variant_flags: Some(flags),
Expand All @@ -64,7 +73,7 @@ pub(super) fn derive_redact(
} else {
return Err(syn::Error::new(
variant.span(),
"expected `#[redact(all, ...)]` or `#[redact(variant, ...)]`, or both as separate attributes",
"please specify at least `#[redact(all, ...)]` or `#[redact(variant, ...)]` first, or both as separate attributes",
));
}
}
Expand Down Expand Up @@ -100,7 +109,7 @@ pub(super) fn derive_redact(
} else {
return Err(syn::Error::new(
variant.span(),
"expected `#[redact(all, ...)]` or `#[redact(variant, ...)]`, or both as separate attributes",
"please specify at least `#[redact(all, ...)]` or `#[redact(variant, ...)]` first, or both as separate attributes",
));
}
}
Expand Down Expand Up @@ -183,10 +192,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
3 changes: 2 additions & 1 deletion veil-tests/src/compile_tests/fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ fail_tests! {
redact_union,
redact_units,
redact_skip,
redact_missing_all
redact_missing_all,
redact_display_enum_variant
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
fn main() {}

#[derive(veil::Redact)]
pub enum Foo {
#[redact(variant, display)]
Bar,
}

#[derive(veil::Redact)]
pub enum Bar {
#[redact(display)]
Baz,
}

#[derive(veil::Redact)]
#[redact(all, variant, display)]
pub enum Baz {
Qux,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error: `#[redact(display)]` is invalid here, enum variants are always displayed using std::fmt::Display
--> src/compile_tests/fail/redact_display_enum_variant.rs:5:5
|
5 | #[redact(variant, display)]
| ^

error: please specify at least `#[redact(all, ...)]` or `#[redact(variant, ...)]` first, or both as separate attributes
--> src/compile_tests/fail/redact_display_enum_variant.rs:11:5
|
11 | #[redact(display)]
| ^

error: `#[redact(display)]` is invalid here
--> src/compile_tests/fail/redact_display_enum_variant.rs:16:1
|
16 | #[redact(all, variant, display)]
| ^
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error: at least `#[redact(all, variant)]` is required here to redact all variant
4 | #[redact(all)]
| ^

error: expected `#[redact(all, ...)]` or `#[redact(variant, ...)]`, or both as separate attributes
error: please specify at least `#[redact(all, ...)]` or `#[redact(variant, ...)]` first, or both as separate attributes
--> src/compile_tests/fail/redact_enum_without_variant.rs:11:5
|
11 | #[redact]
Expand Down
21 changes: 20 additions & 1 deletion veil-tests/src/compile_tests/succeed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ struct RedactAllWithFlags {
field3: String,
}

#[derive(Redact)]
#[redact(all, partial, with = 'X', display)]
struct RedactAllWithFlagsDisplay {
field: String,

#[redact(skip)]
field2: String,

field3: String,
}

#[derive(Redact)]
struct RedactNamedDisplay {
#[redact(display)]
field: String,
#[redact]
field2: String,
}

#[derive(Redact)]
enum CreditCardIssuer {
#[redact(variant)]
Expand All @@ -76,7 +95,7 @@ enum CreditCardIssuer {
MasterCard,

#[redact(variant)]
#[redact(all, fixed = 6, with = '$')]
#[redact(all, fixed = 6, with = '$', display)]
SecretAgentCard {
secret_data_1: String,
secret_data_2: String,
Expand Down
48 changes: 48 additions & 0 deletions veil-tests/src/redaction_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub const SENSITIVE_DATA: &[&str] = &[
"SensitiveVariant",
];

const DEBUGGY_PHRASE: &str = "Hello \"William\"!\nAnd here's the newline...";

pub fn assert_has_sensitive_data<T: std::fmt::Debug>(data: T) {
for redacted in [format!("{data:?}"), format!("{data:#?}")] {
assert!(
Expand Down Expand Up @@ -147,3 +149,49 @@ 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(")"#);
}

#[test]
fn test_named_display_redaction() {
#[derive(Redact)]
struct RedactMultipleNamedDisplay {
#[redact(display)]
foo: String,
#[redact]
bar: String,
}

assert_eq!(
format!("{:?}", RedactMultipleNamedDisplay { foo: DEBUGGY_PHRASE.to_string(), bar: DEBUGGY_PHRASE.to_string() }),
"RedactMultipleNamedDisplay { foo: ***** \"*******\"!\n*** ****'* *** *******..., bar: \"***** \\\"*******\\\"!\\**** ****'* *** *******...\" }"
);
}

#[test]
fn test_enum_display_redaction() {
#[derive(Redact)]
enum RedactEnum {
Foo {
#[redact(display)]
foo: String,
#[redact]
bar: String,
},
}

assert_eq!(
format!("{:?}", RedactEnum::Foo { foo: DEBUGGY_PHRASE.to_string(), bar: DEBUGGY_PHRASE.to_string() }),
"Foo { foo: ***** \"*******\"!\n*** ****'* *** *******..., bar: \"***** \\\"*******\\\"!\\**** ****'* *** *******...\" }"
);
}