1use std::borrow::Cow;
8
9#[cfg(feature = "zeroize")]
10use zeroize::{Zeroize, ZeroizeOnDrop};
11
12#[derive(Clone)]
17pub enum Credentials {
18 SqlServer {
20 username: Cow<'static, str>,
22 password: Cow<'static, str>,
24 },
25
26 AzureAccessToken {
28 token: Cow<'static, str>,
30 },
31
32 #[cfg(feature = "azure-identity")]
34 AzureManagedIdentity {
35 client_id: Option<Cow<'static, str>>,
37 },
38
39 #[cfg(feature = "azure-identity")]
41 AzureServicePrincipal {
42 tenant_id: Cow<'static, str>,
44 client_id: Cow<'static, str>,
46 client_secret: Cow<'static, str>,
48 },
49
50 #[cfg(feature = "integrated-auth")]
52 Integrated,
53
54 #[cfg(feature = "cert-auth")]
56 Certificate {
57 cert_path: Cow<'static, str>,
59 password: Option<Cow<'static, str>>,
61 },
62}
63
64impl Credentials {
65 pub fn sql_server(
67 username: impl Into<Cow<'static, str>>,
68 password: impl Into<Cow<'static, str>>,
69 ) -> Self {
70 Self::SqlServer {
71 username: username.into(),
72 password: password.into(),
73 }
74 }
75
76 pub fn azure_token(token: impl Into<Cow<'static, str>>) -> Self {
78 Self::AzureAccessToken {
79 token: token.into(),
80 }
81 }
82
83 #[must_use]
85 pub fn is_sql_auth(&self) -> bool {
86 matches!(self, Self::SqlServer { .. })
87 }
88
89 #[must_use]
91 pub fn is_azure_ad(&self) -> bool {
92 #[allow(clippy::match_like_matches_macro)]
93 match self {
94 Self::AzureAccessToken { .. } => true,
95 #[cfg(feature = "azure-identity")]
96 Self::AzureManagedIdentity { .. } | Self::AzureServicePrincipal { .. } => true,
97 _ => false,
98 }
99 }
100
101 #[must_use]
103 pub fn method_name(&self) -> &'static str {
104 match self {
105 Self::SqlServer { .. } => "SQL Server Authentication",
106 Self::AzureAccessToken { .. } => "Azure AD Access Token",
107 #[cfg(feature = "azure-identity")]
108 Self::AzureManagedIdentity { .. } => "Azure Managed Identity",
109 #[cfg(feature = "azure-identity")]
110 Self::AzureServicePrincipal { .. } => "Azure Service Principal",
111 #[cfg(feature = "integrated-auth")]
112 Self::Integrated => "Integrated Authentication",
113 #[cfg(feature = "cert-auth")]
114 Self::Certificate { .. } => "Certificate Authentication",
115 }
116 }
117}
118
119impl std::fmt::Debug for Credentials {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 match self {
123 Self::SqlServer { username, .. } => f
124 .debug_struct("SqlServer")
125 .field("username", username)
126 .field("password", &"[REDACTED]")
127 .finish(),
128 Self::AzureAccessToken { .. } => f
129 .debug_struct("AzureAccessToken")
130 .field("token", &"[REDACTED]")
131 .finish(),
132 #[cfg(feature = "azure-identity")]
133 Self::AzureManagedIdentity { client_id } => f
134 .debug_struct("AzureManagedIdentity")
135 .field("client_id", client_id)
136 .finish(),
137 #[cfg(feature = "azure-identity")]
138 Self::AzureServicePrincipal {
139 tenant_id,
140 client_id,
141 ..
142 } => f
143 .debug_struct("AzureServicePrincipal")
144 .field("tenant_id", tenant_id)
145 .field("client_id", client_id)
146 .field("client_secret", &"[REDACTED]")
147 .finish(),
148 #[cfg(feature = "integrated-auth")]
149 Self::Integrated => f.debug_struct("Integrated").finish(),
150 #[cfg(feature = "cert-auth")]
151 Self::Certificate { cert_path, .. } => f
152 .debug_struct("Certificate")
153 .field("cert_path", cert_path)
154 .field("password", &"[REDACTED]")
155 .finish(),
156 }
157 }
158}
159
160#[cfg(feature = "zeroize")]
170#[derive(Clone, Zeroize, ZeroizeOnDrop)]
171pub struct SecretString(String);
172
173#[cfg(feature = "zeroize")]
174impl SecretString {
175 pub fn new(value: impl Into<String>) -> Self {
177 Self(value.into())
178 }
179
180 #[must_use]
187 pub fn expose_secret(&self) -> &str {
188 &self.0
189 }
190}
191
192#[cfg(feature = "zeroize")]
193impl std::fmt::Debug for SecretString {
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 write!(f, "[REDACTED]")
196 }
197}
198
199#[cfg(feature = "zeroize")]
200impl From<String> for SecretString {
201 fn from(s: String) -> Self {
202 Self::new(s)
203 }
204}
205
206#[cfg(feature = "zeroize")]
207impl From<&str> for SecretString {
208 fn from(s: &str) -> Self {
209 Self::new(s)
210 }
211}
212
213#[cfg(feature = "zeroize")]
228#[derive(Clone, Zeroize, ZeroizeOnDrop)]
229pub struct SecureCredentials {
230 kind: SecureCredentialKind,
231}
232
233#[cfg(feature = "zeroize")]
234#[derive(Clone, Zeroize, ZeroizeOnDrop)]
235enum SecureCredentialKind {
236 SqlServer {
237 username: String,
238 password: SecretString,
239 },
240 AzureAccessToken {
241 token: SecretString,
242 },
243 #[cfg(feature = "azure-identity")]
244 AzureManagedIdentity {
245 client_id: Option<String>,
246 },
247 #[cfg(feature = "azure-identity")]
248 AzureServicePrincipal {
249 tenant_id: String,
250 client_id: String,
251 client_secret: SecretString,
252 },
253 #[cfg(feature = "integrated-auth")]
254 Integrated,
255 #[cfg(feature = "cert-auth")]
256 Certificate {
257 cert_path: String,
258 password: Option<SecretString>,
259 },
260}
261
262#[cfg(feature = "zeroize")]
263impl SecureCredentials {
264 pub fn sql_server(username: impl Into<String>, password: impl Into<String>) -> Self {
266 Self {
267 kind: SecureCredentialKind::SqlServer {
268 username: username.into(),
269 password: SecretString::new(password),
270 },
271 }
272 }
273
274 pub fn azure_token(token: impl Into<String>) -> Self {
276 Self {
277 kind: SecureCredentialKind::AzureAccessToken {
278 token: SecretString::new(token),
279 },
280 }
281 }
282
283 #[must_use]
285 pub fn is_sql_auth(&self) -> bool {
286 matches!(self.kind, SecureCredentialKind::SqlServer { .. })
287 }
288
289 #[must_use]
291 pub fn is_azure_ad(&self) -> bool {
292 #[allow(clippy::match_like_matches_macro)]
293 match &self.kind {
294 SecureCredentialKind::AzureAccessToken { .. } => true,
295 #[cfg(feature = "azure-identity")]
296 SecureCredentialKind::AzureManagedIdentity { .. }
297 | SecureCredentialKind::AzureServicePrincipal { .. } => true,
298 _ => false,
299 }
300 }
301
302 #[must_use]
304 pub fn method_name(&self) -> &'static str {
305 match &self.kind {
306 SecureCredentialKind::SqlServer { .. } => "SQL Server Authentication",
307 SecureCredentialKind::AzureAccessToken { .. } => "Azure AD Access Token",
308 #[cfg(feature = "azure-identity")]
309 SecureCredentialKind::AzureManagedIdentity { .. } => "Azure Managed Identity",
310 #[cfg(feature = "azure-identity")]
311 SecureCredentialKind::AzureServicePrincipal { .. } => "Azure Service Principal",
312 #[cfg(feature = "integrated-auth")]
313 SecureCredentialKind::Integrated => "Integrated Authentication",
314 #[cfg(feature = "cert-auth")]
315 SecureCredentialKind::Certificate { .. } => "Certificate Authentication",
316 }
317 }
318
319 #[must_use]
323 pub fn username(&self) -> Option<&str> {
324 match &self.kind {
325 SecureCredentialKind::SqlServer { username, .. } => Some(username),
326 _ => None,
327 }
328 }
329
330 #[must_use]
339 pub fn password(&self) -> Option<&str> {
340 match &self.kind {
341 SecureCredentialKind::SqlServer { password, .. } => Some(password.expose_secret()),
342 _ => None,
343 }
344 }
345
346 #[must_use]
355 pub fn token(&self) -> Option<&str> {
356 match &self.kind {
357 SecureCredentialKind::AzureAccessToken { token } => Some(token.expose_secret()),
358 _ => None,
359 }
360 }
361}
362
363#[cfg(feature = "zeroize")]
364impl std::fmt::Debug for SecureCredentials {
365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366 match &self.kind {
367 SecureCredentialKind::SqlServer { username, .. } => f
368 .debug_struct("SecureCredentials::SqlServer")
369 .field("username", username)
370 .field("password", &"[REDACTED]")
371 .finish(),
372 SecureCredentialKind::AzureAccessToken { .. } => f
373 .debug_struct("SecureCredentials::AzureAccessToken")
374 .field("token", &"[REDACTED]")
375 .finish(),
376 #[cfg(feature = "azure-identity")]
377 SecureCredentialKind::AzureManagedIdentity { client_id } => f
378 .debug_struct("SecureCredentials::AzureManagedIdentity")
379 .field("client_id", client_id)
380 .finish(),
381 #[cfg(feature = "azure-identity")]
382 SecureCredentialKind::AzureServicePrincipal {
383 tenant_id,
384 client_id,
385 ..
386 } => f
387 .debug_struct("SecureCredentials::AzureServicePrincipal")
388 .field("tenant_id", tenant_id)
389 .field("client_id", client_id)
390 .field("client_secret", &"[REDACTED]")
391 .finish(),
392 #[cfg(feature = "integrated-auth")]
393 SecureCredentialKind::Integrated => {
394 f.debug_struct("SecureCredentials::Integrated").finish()
395 }
396 #[cfg(feature = "cert-auth")]
397 SecureCredentialKind::Certificate { cert_path, .. } => f
398 .debug_struct("SecureCredentials::Certificate")
399 .field("cert_path", cert_path)
400 .field("password", &"[REDACTED]")
401 .finish(),
402 }
403 }
404}
405
406#[cfg(feature = "zeroize")]
408impl From<Credentials> for SecureCredentials {
409 fn from(creds: Credentials) -> Self {
410 match creds {
411 Credentials::SqlServer { username, password } => {
412 SecureCredentials::sql_server(username.into_owned(), password.into_owned())
413 }
414 Credentials::AzureAccessToken { token } => {
415 SecureCredentials::azure_token(token.into_owned())
416 }
417 #[cfg(feature = "azure-identity")]
418 Credentials::AzureManagedIdentity { client_id } => SecureCredentials {
419 kind: SecureCredentialKind::AzureManagedIdentity {
420 client_id: client_id.map(|c| c.into_owned()),
421 },
422 },
423 #[cfg(feature = "azure-identity")]
424 Credentials::AzureServicePrincipal {
425 tenant_id,
426 client_id,
427 client_secret,
428 } => SecureCredentials {
429 kind: SecureCredentialKind::AzureServicePrincipal {
430 tenant_id: tenant_id.into_owned(),
431 client_id: client_id.into_owned(),
432 client_secret: SecretString::new(client_secret.into_owned()),
433 },
434 },
435 #[cfg(feature = "integrated-auth")]
436 Credentials::Integrated => SecureCredentials {
437 kind: SecureCredentialKind::Integrated,
438 },
439 #[cfg(feature = "cert-auth")]
440 Credentials::Certificate {
441 cert_path,
442 password,
443 } => SecureCredentials {
444 kind: SecureCredentialKind::Certificate {
445 cert_path: cert_path.into_owned(),
446 password: password.map(|p| SecretString::new(p.into_owned())),
447 },
448 },
449 }
450 }
451}