use std::borrow::Cow;
use crate::credentials::Credentials;
use crate::error::AuthError;
use crate::provider::{AuthData, AuthMethod, AuthProvider};
#[derive(Clone)]
pub struct SqlServerAuth {
username: Cow<'static, str>,
password: Cow<'static, str>,
}
impl SqlServerAuth {
pub fn new(
username: impl Into<Cow<'static, str>>,
password: impl Into<Cow<'static, str>>,
) -> Self {
Self {
username: username.into(),
password: password.into(),
}
}
pub fn from_credentials(credentials: &Credentials) -> Result<Self, AuthError> {
match credentials {
Credentials::SqlServer { username, password } => Ok(Self {
username: Cow::Owned(username.to_string()),
password: Cow::Owned(password.to_string()),
}),
_ => Err(AuthError::UnsupportedMethod(
"SqlServerAuth requires SQL Server credentials".into(),
)),
}
}
#[must_use]
pub fn username(&self) -> &str {
&self.username
}
#[must_use]
pub fn encode_password(password: &str) -> Vec<u8> {
password
.encode_utf16()
.flat_map(|c| {
let byte1 = (c & 0xFF) as u8;
let byte2 = (c >> 8) as u8;
let encoded1 = (byte1 ^ 0xA5).rotate_right(4);
let encoded2 = (byte2 ^ 0xA5).rotate_right(4);
[encoded1, encoded2]
})
.collect()
}
}
impl AuthProvider for SqlServerAuth {
fn method(&self) -> AuthMethod {
AuthMethod::SqlServer
}
fn authenticate(&self) -> Result<AuthData, AuthError> {
tracing::debug!(
username = %self.username,
"authenticating with SQL Server credentials"
);
let password_bytes = Self::encode_password(&self.password);
Ok(AuthData::SqlServer {
username: self.username.to_string(),
password_bytes,
})
}
}
impl std::fmt::Debug for SqlServerAuth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SqlServerAuth")
.field("username", &self.username)
.field("password", &"[REDACTED]")
.finish()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn test_password_encoding() {
let encoded = SqlServerAuth::encode_password("test");
assert!(!encoded.is_empty());
assert_eq!(encoded.len(), 8); }
#[test]
fn test_password_encoding_known_value() {
let encoded = SqlServerAuth::encode_password("a");
assert_eq!(encoded, vec![0x4C, 0x5A]);
}
#[test]
fn test_sql_server_auth_provider() {
let auth = SqlServerAuth::new("sa", "Password123!");
assert_eq!(auth.method(), AuthMethod::SqlServer);
assert_eq!(auth.username(), "sa");
let data = auth.authenticate().unwrap();
match &data {
AuthData::SqlServer {
username,
password_bytes,
} => {
assert_eq!(username, "sa");
assert!(!password_bytes.is_empty());
}
_ => panic!("Expected SqlServer auth data"),
}
}
#[test]
fn test_from_credentials() {
let creds = Credentials::sql_server("user", "pass");
let auth = SqlServerAuth::from_credentials(&creds).unwrap();
assert_eq!(auth.username(), "user");
}
#[test]
fn test_from_credentials_wrong_type() {
let creds = Credentials::azure_token("token");
let result = SqlServerAuth::from_credentials(&creds);
assert!(result.is_err());
}
#[test]
fn test_debug_redacts_password() {
let auth = SqlServerAuth::new("sa", "secret");
let debug = format!("{auth:?}");
assert!(debug.contains("sa"));
assert!(!debug.contains("secret"));
assert!(debug.contains("[REDACTED]"));
}
}