| + |
//! A data type for holding sensitive data to avoid accidentally
|
| + |
//! leaking it.
|
| + |
//!
|
| + |
//! Once a `Sensitive` value has been created, it contains a string
|
| + |
//! value. It can be created by de-serializing using `serde`.
|
| + |
//!
|
| + |
//! The sensitive data can be accessed with [`Sensitive::as_str`]. The
|
| + |
//! caller needs to be careful to not leak that.
|
| + |
//!
|
| + |
//! `Sensitive` value itself can't be printed (via the `Display`
|
| + |
//! trait), even in debug mode (`Debug` trait), or serialized with
|
| + |
//! `serde`. Instead, the value is replaced with the string
|
| + |
//! `<REDACTED>`.
|
| + |
//!
|
| + |
//! Note that this does not prevent the value from ending up in a core
|
| + |
//! dump.
|
| + |
|
| + |
use std::fmt;
|
| + |
|
| + |
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
| + |
|
| + |
const PLACEHOLDER: &str = "<REDACTED>";
|
| + |
|
| + |
/// Hold a sensitive string, such as a password or an API token. The
|
| + |
/// value can be accessed ([`Sensitive::as_str`]), but won't be
|
| + |
/// printed, even in debug output, and won't be serialized by `serde`.
|
| + |
#[derive()]
|
| + |
pub struct Sensitive {
|
| + |
#[allow(dead_code)]
|
| + |
//#[serde(serialize_with = "serialize")]
|
| + |
data: String,
|
| + |
}
|
| + |
|
| + |
impl fmt::Display for Sensitive {
|
| + |
/// Serialize for normal output.
|
| + |
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
| + |
write!(f, "{}", PLACEHOLDER)
|
| + |
}
|
| + |
}
|
| + |
|
| + |
impl fmt::Debug for Sensitive {
|
| + |
/// Serialize for debug output.
|
| + |
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
| + |
write!(f, "{}", PLACEHOLDER)
|
| + |
}
|
| + |
}
|
| + |
|
| + |
impl Serialize for Sensitive {
|
| + |
/// Serialize for `serde`.
|
| + |
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
| + |
where
|
| + |
S: Serializer,
|
| + |
{
|
| + |
serializer.serialize_str(PLACEHOLDER)
|
| + |
}
|
| + |
}
|
| + |
|
| + |
impl<'de> Deserialize<'de> for Sensitive {
|
| + |
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
| + |
where
|
| + |
D: Deserializer<'de>,
|
| + |
{
|
| + |
deserializer.deserialize_str(SensitiveVisitor)
|
| + |
}
|
| + |
}
|
| + |
|
| + |
struct SensitiveVisitor;
|
| + |
|
| + |
impl<'de> de::Visitor<'de> for SensitiveVisitor {
|
| + |
type Value = Sensitive;
|
| + |
|
| + |
fn expecting(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
|
| + |
Ok(())
|
| + |
}
|
| + |
|
| + |
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
|
| + |
where
|
| + |
E: de::Error,
|
| + |
{
|
| + |
Ok(Sensitive { data: s.into() })
|
| + |
}
|
| + |
}
|
| + |
|
| + |
impl Sensitive {
|
| + |
#[cfg(test)]
|
| + |
fn new(data: &str) -> Self {
|
| + |
Self { data: data.into() }
|
| + |
}
|
| + |
|
| + |
/// Return the contained string in cleartext. Do not leak this string.
|
| + |
pub fn as_str(&self) -> &str {
|
| + |
&self.data
|
| + |
}
|
| + |
}
|
| + |
|
| + |
#[cfg(test)]
|
| + |
mod test_sensitive {
|
| + |
use super::*;
|
| + |
|
| + |
#[test]
|
| + |
fn displayed() {
|
| + |
let s = Sensitive::new("foo");
|
| + |
let output = format!("{s}");
|
| + |
assert!(!output.contains("foo"));
|
| + |
}
|
| + |
|
| + |
#[test]
|
| + |
fn debugged() {
|
| + |
let s = Sensitive::new("foo");
|
| + |
let output = format!("{s:?}");
|
| + |
assert!(!output.contains("foo"));
|
| + |
}
|
| + |
|
| + |
#[test]
|
| + |
fn ser() -> Result<(), Box<dyn std::error::Error>> {
|
| + |
let s = Sensitive::new("foo");
|
| + |
let output = serde_yml::to_string(&s)?;
|
| + |
println!("{output:#?}");
|
| + |
assert!(!output.contains("foo"));
|
| + |
Ok(())
|
| + |
}
|
| + |
|
| + |
#[test]
|
| + |
fn deser() -> Result<(), Box<dyn std::error::Error>> {
|
| + |
#[derive(Deserialize)]
|
| + |
struct Foo {
|
| + |
secret: Sensitive,
|
| + |
}
|
| + |
let s: Foo = serde_yml::from_str("secret: foo")?;
|
| + |
assert_eq!(s.secret.data, "foo");
|
| + |
Ok(())
|
| + |
}
|
| + |
}
|