1use std::borrow::Cow;
7
8use crate::credentials::Credentials;
9use crate::error::AuthError;
10use crate::provider::{AuthData, AuthMethod, AuthProvider};
11
12#[derive(Clone)]
30pub struct SqlServerAuth {
31 username: Cow<'static, str>,
32 password: Cow<'static, str>,
33}
34
35impl SqlServerAuth {
36 pub fn new(
38 username: impl Into<Cow<'static, str>>,
39 password: impl Into<Cow<'static, str>>,
40 ) -> Self {
41 Self {
42 username: username.into(),
43 password: password.into(),
44 }
45 }
46
47 pub fn from_credentials(credentials: &Credentials) -> Result<Self, AuthError> {
51 match credentials {
52 Credentials::SqlServer { username, password } => Ok(Self {
53 username: Cow::Owned(username.to_string()),
54 password: Cow::Owned(password.to_string()),
55 }),
56 _ => Err(AuthError::UnsupportedMethod(
57 "SqlServerAuth requires SQL Server credentials".into(),
58 )),
59 }
60 }
61
62 #[must_use]
64 pub fn username(&self) -> &str {
65 &self.username
66 }
67
68 #[must_use]
80 pub fn encode_password(password: &str) -> Vec<u8> {
81 password
82 .encode_utf16()
83 .flat_map(|c| {
84 let byte1 = (c & 0xFF) as u8;
85 let byte2 = (c >> 8) as u8;
86
87 let encoded1 = (byte1 ^ 0xA5).rotate_right(4);
89 let encoded2 = (byte2 ^ 0xA5).rotate_right(4);
90
91 [encoded1, encoded2]
92 })
93 .collect()
94 }
95}
96
97impl AuthProvider for SqlServerAuth {
98 fn method(&self) -> AuthMethod {
99 AuthMethod::SqlServer
100 }
101
102 fn authenticate(&self) -> Result<AuthData, AuthError> {
103 tracing::debug!(
104 username = %self.username,
105 "authenticating with SQL Server credentials"
106 );
107
108 let password_bytes = Self::encode_password(&self.password);
109
110 Ok(AuthData::SqlServer {
111 username: self.username.to_string(),
112 password_bytes,
113 })
114 }
115}
116
117impl std::fmt::Debug for SqlServerAuth {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 f.debug_struct("SqlServerAuth")
120 .field("username", &self.username)
121 .field("password", &"[REDACTED]")
122 .finish()
123 }
124}
125
126#[cfg(test)]
127#[allow(clippy::unwrap_used, clippy::panic)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn test_password_encoding() {
133 let encoded = SqlServerAuth::encode_password("test");
135 assert!(!encoded.is_empty());
136 assert_eq!(encoded.len(), 8); }
138
139 #[test]
140 fn test_password_encoding_known_value() {
141 let encoded = SqlServerAuth::encode_password("a");
146 assert_eq!(encoded, vec![0x4C, 0x5A]);
147 }
148
149 #[test]
150 fn test_sql_server_auth_provider() {
151 let auth = SqlServerAuth::new("sa", "Password123!");
152
153 assert_eq!(auth.method(), AuthMethod::SqlServer);
154 assert_eq!(auth.username(), "sa");
155
156 let data = auth.authenticate().unwrap();
157 match &data {
158 AuthData::SqlServer {
159 username,
160 password_bytes,
161 } => {
162 assert_eq!(username, "sa");
163 assert!(!password_bytes.is_empty());
164 }
165 _ => panic!("Expected SqlServer auth data"),
166 }
167 }
168
169 #[test]
170 fn test_from_credentials() {
171 let creds = Credentials::sql_server("user", "pass");
172 let auth = SqlServerAuth::from_credentials(&creds).unwrap();
173 assert_eq!(auth.username(), "user");
174 }
175
176 #[test]
177 fn test_from_credentials_wrong_type() {
178 let creds = Credentials::azure_token("token");
179 let result = SqlServerAuth::from_credentials(&creds);
180 assert!(result.is_err());
181 }
182
183 #[test]
184 fn test_debug_redacts_password() {
185 let auth = SqlServerAuth::new("sa", "secret");
186 let debug = format!("{auth:?}");
187 assert!(debug.contains("sa"));
188 assert!(!debug.contains("secret"));
189 assert!(debug.contains("[REDACTED]"));
190 }
191}