use std::fmt::Debug;
use zeroize::Zeroizing;
#[derive(Clone, PartialEq, Eq)]
pub struct SqlServerAuth {
user: String,
password: Zeroizing<String>,
}
impl SqlServerAuth {
pub(crate) fn into_credentials(self) -> (String, Zeroizing<String>) {
(self.user, self.password)
}
}
impl Debug for SqlServerAuth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SqlServerAuth")
.field("user", &self.user)
.field("password", &"<HIDDEN>")
.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
#[cfg_attr(
docsrs,
doc(cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"))))
)]
pub struct WindowsAuth {
pub(crate) user: String,
pub(crate) password: Zeroizing<String>,
pub(crate) domain: Option<String>,
}
#[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
#[cfg_attr(
docsrs,
doc(cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"))))
)]
impl Debug for WindowsAuth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WindowsAuth")
.field("user", &self.user)
.field("password", &"<HIDDEN>")
.field("domain", &self.domain)
.finish()
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum AuthMethod {
SqlServer(SqlServerAuth),
#[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
#[cfg_attr(
docsrs,
doc(cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"))))
)]
Windows(WindowsAuth),
#[cfg(any(
all(windows, feature = "winauth"),
all(unix, feature = "integrated-auth-gssapi"),
doc
))]
#[cfg_attr(
docsrs,
doc(cfg(any(windows, all(unix, feature = "integrated-auth-gssapi"))))
)]
Integrated,
AADToken(String),
#[doc(hidden)]
None,
}
impl Debug for AuthMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SqlServer(a) => f.debug_tuple("SqlServer").field(a).finish(),
#[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
Self::Windows(a) => f.debug_tuple("Windows").field(a).finish(),
#[cfg(any(
all(windows, feature = "winauth"),
all(unix, feature = "integrated-auth-gssapi"),
doc
))]
Self::Integrated => f.write_str("Integrated"),
Self::AADToken(_) => f.debug_tuple("AADToken").field(&"<HIDDEN>").finish(),
Self::None => f.write_str("None"),
}
}
}
impl AuthMethod {
pub fn sql_server(user: impl ToString, password: impl ToString) -> Self {
Self::SqlServer(SqlServerAuth {
user: user.to_string(),
password: Zeroizing::new(password.to_string()),
})
}
#[cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"), doc))]
#[cfg_attr(
docsrs,
doc(cfg(any(all(windows, feature = "winauth"), all(unix, feature = "sspi-rs"))))
)]
pub fn windows(user: impl AsRef<str>, password: impl ToString) -> Self {
let (domain, user) = match user.as_ref().find('\\') {
Some(idx) => (Some(&user.as_ref()[..idx]), &user.as_ref()[idx + 1..]),
_ => (None, user.as_ref()),
};
Self::Windows(WindowsAuth {
user: user.to_string(),
password: Zeroizing::new(password.to_string()),
domain: domain.map(|s| s.to_string()),
})
}
pub fn aad_token(token: impl ToString) -> Self {
Self::AADToken(token.to_string())
}
}
#[cfg(test)]
mod tests {
use super::AuthMethod;
use zeroize::Zeroize;
#[test]
fn sql_server_password_can_be_consumed_and_zeroized() {
let AuthMethod::SqlServer(auth) = AuthMethod::sql_server("sa", "secret") else {
unreachable!();
};
let (user, mut password) = auth.into_credentials();
assert_eq!("sa", user);
assert_eq!("secret", password.as_str());
password.zeroize();
assert!(password.is_empty());
}
#[test]
fn debug_redacts_credentials() {
let sql = format!("{:?}", AuthMethod::sql_server("sa", "sql-secret"));
assert!(!sql.contains("sql-secret"), "SQL password leaked: {sql}");
let aad = format!("{:?}", AuthMethod::aad_token("aad-secret-token"));
assert!(!aad.contains("aad-secret-token"), "AAD token leaked: {aad}");
assert!(aad.contains("HIDDEN"));
}
}