mssql_auth/provider.rs
1//! Authentication provider traits.
2//!
3//! This module defines the `AuthProvider` trait for implementing
4//! authentication strategies, as specified in ARCHITECTURE.md.
5
6use bytes::Bytes;
7
8use crate::error::AuthError;
9
10/// Authentication method enumeration.
11///
12/// This indicates which authentication flow to use during connection.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum AuthMethod {
15 /// SQL Server authentication (username/password in Login7).
16 SqlServer,
17 /// Azure AD / Entra ID federated authentication.
18 AzureAd,
19 /// Integrated Windows authentication (SSPI/Kerberos).
20 Integrated,
21 /// Certificate-based authentication.
22 Certificate,
23}
24
25impl AuthMethod {
26 /// Check if this method uses federated authentication.
27 #[must_use]
28 pub fn is_federated(&self) -> bool {
29 matches!(self, Self::AzureAd)
30 }
31
32 /// Check if this method uses SSPI.
33 #[must_use]
34 pub fn is_sspi(&self) -> bool {
35 matches!(self, Self::Integrated)
36 }
37
38 /// Check if this method uses Login7 credentials.
39 #[must_use]
40 pub fn uses_login7_credentials(&self) -> bool {
41 matches!(self, Self::SqlServer)
42 }
43}
44
45/// Authentication data produced by an auth provider.
46///
47/// This contains the data needed to authenticate with SQL Server,
48/// depending on the authentication method being used.
49#[derive(Debug, Clone)]
50pub enum AuthData {
51 /// SQL Server credentials for Login7 packet.
52 SqlServer {
53 /// Username.
54 username: String,
55 /// Obfuscated password bytes (XOR + bit rotation).
56 password_bytes: Vec<u8>,
57 },
58 /// Federated authentication token for FEDAUTH feature.
59 FedAuth {
60 /// The access token.
61 token: String,
62 /// Token nonce (optional, for certain flows).
63 nonce: Option<Bytes>,
64 },
65 /// SSPI blob for integrated authentication.
66 Sspi {
67 /// The SSPI authentication blob.
68 blob: Vec<u8>,
69 },
70 /// No additional authentication data needed.
71 None,
72}
73
74/// Trait for authentication providers.
75///
76/// Authentication providers are responsible for producing the authentication
77/// data needed for the TDS connection. Different providers support different
78/// authentication methods (SQL auth, Azure AD, integrated, etc.).
79///
80/// # Example
81///
82/// ```rust,ignore
83/// use mssql_auth::{AuthProvider, SqlServerAuth};
84///
85/// let provider = SqlServerAuth::new("username", "password");
86/// let auth_data = provider.authenticate().await?;
87/// ```
88pub trait AuthProvider: Send + Sync {
89 /// Get the authentication method this provider uses.
90 fn method(&self) -> AuthMethod;
91
92 /// Authenticate and produce authentication data.
93 ///
94 /// This may involve network calls (e.g., for Azure AD token acquisition)
95 /// so it returns a future in async implementations.
96 fn authenticate(&self) -> Result<AuthData, AuthError>;
97
98 /// Get additional feature extension data for Login7.
99 ///
100 /// Some authentication methods (like Azure AD) require feature extensions
101 /// in the Login7 packet. This returns the raw feature data if needed.
102 fn feature_extension_data(&self) -> Option<Bytes> {
103 None
104 }
105
106 /// Check if this provider needs to refresh its authentication.
107 ///
108 /// For token-based authentication, this can check if the token is expired
109 /// or about to expire.
110 fn needs_refresh(&self) -> bool {
111 false
112 }
113}
114
115/// Async authentication provider trait.
116///
117/// This is for authentication methods that require async operations,
118/// such as acquiring tokens from Azure AD endpoints.
119#[allow(async_fn_in_trait)]
120pub trait AsyncAuthProvider: Send + Sync {
121 /// Get the authentication method this provider uses.
122 fn method(&self) -> AuthMethod;
123
124 /// Authenticate asynchronously and produce authentication data.
125 async fn authenticate_async(&self) -> Result<AuthData, AuthError>;
126
127 /// Get additional feature extension data for Login7.
128 fn feature_extension_data(&self) -> Option<Bytes> {
129 None
130 }
131
132 /// Check if this provider needs to refresh its authentication.
133 fn needs_refresh(&self) -> bool {
134 false
135 }
136}
137
138// Implement AuthProvider for any AsyncAuthProvider by blocking
139// (for use in synchronous contexts when needed)
140impl<T: AsyncAuthProvider> AuthProvider for T {
141 fn method(&self) -> AuthMethod {
142 <T as AsyncAuthProvider>::method(self)
143 }
144
145 fn authenticate(&self) -> Result<AuthData, AuthError> {
146 // This is a fallback - in practice, async providers should be used
147 // with authenticate_async(). This implementation is for compatibility.
148 Err(AuthError::Configuration(
149 "Async auth provider must use authenticate_async()".into(),
150 ))
151 }
152
153 fn feature_extension_data(&self) -> Option<Bytes> {
154 <T as AsyncAuthProvider>::feature_extension_data(self)
155 }
156
157 fn needs_refresh(&self) -> bool {
158 <T as AsyncAuthProvider>::needs_refresh(self)
159 }
160}
161
162#[cfg(test)]
163#[allow(clippy::unwrap_used)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_auth_method_properties() {
169 assert!(AuthMethod::AzureAd.is_federated());
170 assert!(!AuthMethod::SqlServer.is_federated());
171
172 assert!(AuthMethod::Integrated.is_sspi());
173 assert!(!AuthMethod::SqlServer.is_sspi());
174
175 assert!(AuthMethod::SqlServer.uses_login7_credentials());
176 assert!(!AuthMethod::AzureAd.uses_login7_credentials());
177 }
178}