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}