metabase_api_rs/api/
auth_adapter.rs

1//! Adapter to integrate existing AuthManager with AuthProvider trait
2//!
3//! This module provides an adapter that allows the existing AuthManager
4//! to work with the new AuthProvider trait abstraction.
5
6use super::auth::{AuthManager, Credentials as ApiCredentials};
7use crate::core::error::{Error, Result};
8use crate::core::models::User;
9use crate::transport::auth_traits::{
10    AuthProvider, AuthResponse, Credentials as TransportCredentials,
11};
12use async_trait::async_trait;
13use secrecy::ExposeSecret;
14use std::sync::Arc;
15use tokio::sync::RwLock;
16
17/// Adapter that implements AuthProvider for the existing AuthManager
18pub struct AuthManagerAdapter {
19    inner: Arc<RwLock<AuthManager>>,
20    http_provider: Arc<dyn AuthProvider>,
21}
22
23impl AuthManagerAdapter {
24    /// Create a new adapter with an AuthManager and HTTP provider
25    pub fn new(auth_manager: AuthManager, http_provider: Arc<dyn AuthProvider>) -> Self {
26        Self {
27            inner: Arc::new(RwLock::new(auth_manager)),
28            http_provider,
29        }
30    }
31
32    /// Get a reference to the inner AuthManager (for read operations)
33    pub async fn inner(&self) -> tokio::sync::RwLockReadGuard<'_, AuthManager> {
34        self.inner.read().await
35    }
36
37    /// Get a mutable reference to the inner AuthManager (for write operations)
38    pub async fn inner_mut(&self) -> tokio::sync::RwLockWriteGuard<'_, AuthManager> {
39        self.inner.write().await
40    }
41
42    /// Convert API credentials to transport credentials
43    #[allow(dead_code)]
44    fn convert_credentials(creds: &ApiCredentials) -> TransportCredentials {
45        match creds {
46            ApiCredentials::EmailPassword { email, password } => {
47                TransportCredentials::EmailPassword {
48                    email: email.clone(),
49                    password: password.expose_secret().to_string(),
50                }
51            }
52            ApiCredentials::ApiKey { key } => {
53                TransportCredentials::ApiKey(key.expose_secret().to_string())
54            }
55        }
56    }
57}
58
59#[async_trait]
60impl AuthProvider for AuthManagerAdapter {
61    async fn authenticate(&self, credentials: &TransportCredentials) -> Result<AuthResponse> {
62        // Use the HTTP provider to perform actual authentication
63        let response = self.http_provider.authenticate(credentials).await?;
64
65        // Update the AuthManager with the session info
66        let mut manager = self.inner.write().await;
67        manager.set_session_with_ttl(
68            response.session_token.clone(),
69            response.user.clone(),
70            response.expires_in,
71        );
72
73        Ok(response)
74    }
75
76    async fn refresh_session(&self, session_token: &str) -> Result<AuthResponse> {
77        // Use the HTTP provider to refresh the session
78        let response = self.http_provider.refresh_session(session_token).await?;
79
80        // Update the AuthManager with the new session info
81        let mut manager = self.inner.write().await;
82        manager.set_session_with_ttl(
83            response.session_token.clone(),
84            response.user.clone(),
85            response.expires_in,
86        );
87
88        Ok(response)
89    }
90
91    async fn validate_token(&self, session_token: &str) -> Result<bool> {
92        // First check local state
93        let manager = self.inner.read().await;
94        if let Some(current_token) = manager.session_token() {
95            if current_token != session_token {
96                return Ok(false);
97            }
98            if !manager.is_authenticated() {
99                return Ok(false);
100            }
101        } else {
102            return Ok(false);
103        }
104        drop(manager); // Release the read lock
105
106        // Then validate with the server
107        self.http_provider.validate_token(session_token).await
108    }
109
110    async fn logout(&self, session_token: &str) -> Result<()> {
111        // Logout via HTTP provider
112        self.http_provider.logout(session_token).await?;
113
114        // Clear local session
115        let mut manager = self.inner.write().await;
116        manager.clear_session();
117
118        Ok(())
119    }
120
121    async fn get_user(&self, session_token: &str) -> Result<User> {
122        // First try to get from local cache
123        let manager = self.inner.read().await;
124        if let Some(user) = manager.current_user() {
125            if let Some(current_token) = manager.session_token() {
126                if current_token == session_token {
127                    return Ok(user.clone());
128                }
129            }
130        }
131        drop(manager); // Release the read lock
132
133        // Otherwise fetch from server
134        let user = self.http_provider.get_user(session_token).await?;
135
136        // Update local cache
137        let mut manager = self.inner.write().await;
138        if let Some(current_token) = manager.session_token() {
139            if current_token == session_token {
140                manager.set_session(session_token.to_string(), user.clone());
141            }
142        }
143
144        Ok(user)
145    }
146}
147
148/// Builder for AuthManagerAdapter
149pub struct AuthManagerAdapterBuilder {
150    auth_manager: Option<AuthManager>,
151    http_provider: Option<Arc<dyn AuthProvider>>,
152}
153
154impl Default for AuthManagerAdapterBuilder {
155    fn default() -> Self {
156        Self::new()
157    }
158}
159
160impl AuthManagerAdapterBuilder {
161    /// Create a new builder
162    pub fn new() -> Self {
163        Self {
164            auth_manager: None,
165            http_provider: None,
166        }
167    }
168
169    /// Set the AuthManager
170    pub fn auth_manager(mut self, manager: AuthManager) -> Self {
171        self.auth_manager = Some(manager);
172        self
173    }
174
175    /// Set the HTTP provider
176    pub fn http_provider(mut self, provider: Arc<dyn AuthProvider>) -> Self {
177        self.http_provider = Some(provider);
178        self
179    }
180
181    /// Build the adapter
182    pub fn build(self) -> Result<AuthManagerAdapter> {
183        let auth_manager = self.auth_manager.unwrap_or_default();
184        let http_provider = self
185            .http_provider
186            .ok_or_else(|| Error::Config("HTTP provider is required".to_string()))?;
187
188        Ok(AuthManagerAdapter::new(auth_manager, http_provider))
189    }
190}
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::transport::auth_traits::MockAuthProvider;
196
197    #[tokio::test]
198    async fn test_adapter_authentication() {
199        // Create a mock provider
200        let mock_provider = Arc::new(MockAuthProvider::default());
201
202        // Create an AuthManager
203        let auth_manager = AuthManager::new();
204
205        // Create the adapter
206        let adapter = AuthManagerAdapter::new(auth_manager, mock_provider);
207
208        // Test authentication
209        let credentials = TransportCredentials::EmailPassword {
210            email: "test@example.com".to_string(),
211            password: "password123".to_string(),
212        };
213
214        let response = adapter.authenticate(&credentials).await.unwrap();
215        assert_eq!(response.session_token, "mock_session_token_123");
216
217        // Verify the AuthManager was updated
218        let manager = adapter.inner().await;
219        assert!(manager.is_authenticated());
220        assert_eq!(manager.session_token(), Some("mock_session_token_123"));
221    }
222
223    #[tokio::test]
224    async fn test_adapter_logout() {
225        // Create a mock provider
226        let mock_provider = Arc::new(MockAuthProvider::default());
227
228        // Create an AuthManager with a session
229        let mut auth_manager = AuthManager::new();
230        auth_manager.set_session(
231            "test_token".to_string(),
232            User {
233                id: crate::core::models::common::UserId(1),
234                email: "test@example.com".to_string(),
235                first_name: "Test".to_string(),
236                last_name: "User".to_string(),
237                is_active: true,
238                is_superuser: false,
239                is_qbnewb: false,
240                date_joined: chrono::Utc::now(),
241                last_login: None,
242                locale: None,
243                google_auth: false,
244                ldap_auth: false,
245                common_name: Some("Test User".to_string()),
246                group_ids: Vec::new(),
247                login_attributes: None,
248                user_group_memberships: Vec::new(),
249            },
250        );
251
252        // Create the adapter
253        let adapter = AuthManagerAdapter::new(auth_manager, mock_provider);
254
255        // Verify initial state
256        {
257            let manager = adapter.inner().await;
258            assert!(manager.is_authenticated());
259        }
260
261        // Logout
262        adapter.logout("test_token").await.unwrap();
263
264        // Verify session was cleared
265        {
266            let manager = adapter.inner().await;
267            assert!(!manager.is_authenticated());
268            assert!(manager.session_token().is_none());
269        }
270    }
271}