mockforge_collab/
access_review_provider.rs

1//! Access review data provider for collaboration system
2//!
3//! This module provides a UserDataProvider implementation that integrates
4//! the access review system with the collaboration database.
5
6use chrono::{DateTime, Utc};
7use mockforge_core::security::access_review::{ApiTokenInfo, PrivilegedAccessInfo, UserAccessInfo};
8use mockforge_core::security::{
9    ApiTokenStorage, JustificationStorage, MfaStorage, UserDataProvider,
10};
11use mockforge_core::Error;
12use sqlx::{Pool, Sqlite};
13use std::sync::Arc;
14use uuid::Uuid;
15
16/// User data provider for collaboration system
17pub struct CollabUserDataProvider {
18    db: Pool<Sqlite>,
19    user_service: Arc<crate::user::UserService>,
20    workspace_service: Arc<crate::workspace::WorkspaceService>,
21    token_storage: Option<Arc<dyn ApiTokenStorage>>,
22    mfa_storage: Option<Arc<dyn MfaStorage>>,
23    justification_storage: Option<Arc<dyn JustificationStorage>>,
24}
25
26impl CollabUserDataProvider {
27    /// Create a new user data provider
28    pub fn new(
29        db: Pool<Sqlite>,
30        user_service: Arc<crate::user::UserService>,
31        workspace_service: Arc<crate::workspace::WorkspaceService>,
32    ) -> Self {
33        Self {
34            db,
35            user_service,
36            workspace_service,
37            token_storage: None,
38            mfa_storage: None,
39            justification_storage: None,
40        }
41    }
42
43    /// Create with optional storage backends
44    pub fn with_storage(
45        db: Pool<Sqlite>,
46        user_service: Arc<crate::user::UserService>,
47        workspace_service: Arc<crate::workspace::WorkspaceService>,
48        token_storage: Option<Arc<dyn ApiTokenStorage>>,
49        mfa_storage: Option<Arc<dyn MfaStorage>>,
50        justification_storage: Option<Arc<dyn JustificationStorage>>,
51    ) -> Self {
52        Self {
53            db,
54            user_service,
55            workspace_service,
56            token_storage,
57            mfa_storage,
58            justification_storage,
59        }
60    }
61}
62
63#[async_trait::async_trait]
64impl UserDataProvider for CollabUserDataProvider {
65    async fn get_all_users(&self) -> Result<Vec<UserAccessInfo>, Error> {
66        // Fetch all active users
67        let users = sqlx::query_as!(
68            crate::models::User,
69            r#"
70            SELECT id as "id: Uuid", username, email, password_hash, display_name, avatar_url,
71                   created_at as "created_at: chrono::DateTime<chrono::Utc>",
72                   updated_at as "updated_at: chrono::DateTime<chrono::Utc>",
73                   is_active as "is_active: bool"
74            FROM users
75            WHERE is_active = TRUE
76            ORDER BY created_at
77            "#,
78        )
79        .fetch_all(&self.db)
80        .await
81        .map_err(|e| Error::Generic(format!("Failed to fetch users: {}", e)))?;
82
83        // For each user, get their workspace memberships and roles
84        let mut user_access_list = Vec::new();
85
86        for user in users {
87            // Get all workspace memberships for this user
88            let memberships = sqlx::query_as!(
89                crate::models::WorkspaceMember,
90                r#"
91                SELECT
92                    id as "id: Uuid",
93                    workspace_id as "workspace_id: Uuid",
94                    user_id as "user_id: Uuid",
95                    role as "role: crate::models::UserRole",
96                    joined_at as "joined_at: chrono::DateTime<chrono::Utc>",
97                    last_activity as "last_activity: chrono::DateTime<chrono::Utc>"
98                FROM workspace_members
99                WHERE user_id = ?
100                ORDER BY last_activity DESC
101                "#,
102                user.id
103            )
104            .fetch_all(&self.db)
105            .await
106            .map_err(|e| Error::Generic(format!("Failed to fetch memberships: {}", e)))?;
107
108            // Collect roles and permissions
109            let roles: Vec<String> = memberships.iter().map(|m| format!("{:?}", m.role)).collect();
110
111            // Map roles to permissions (simplified - in reality would use PermissionChecker)
112            let permissions: Vec<String> = memberships
113                .iter()
114                .flat_map(|m| match m.role {
115                    crate::models::UserRole::Admin => vec![
116                        "WorkspaceCreate".to_string(),
117                        "WorkspaceRead".to_string(),
118                        "WorkspaceUpdate".to_string(),
119                        "WorkspaceDelete".to_string(),
120                        "WorkspaceManageMembers".to_string(),
121                        "MockCreate".to_string(),
122                        "MockRead".to_string(),
123                        "MockUpdate".to_string(),
124                        "MockDelete".to_string(),
125                    ],
126                    crate::models::UserRole::Editor => vec![
127                        "MockCreate".to_string(),
128                        "MockRead".to_string(),
129                        "MockUpdate".to_string(),
130                        "MockDelete".to_string(),
131                    ],
132                    crate::models::UserRole::Viewer => vec!["MockRead".to_string()],
133                })
134                .collect();
135
136            // Get most recent activity
137            let last_activity = memberships.iter().map(|m| m.last_activity).max();
138
139            // Calculate days inactive
140            let days_inactive = last_activity.map(|activity| {
141                let duration = Utc::now() - activity;
142                duration.num_days() as u64
143            });
144
145            // Access granted date is the earliest membership join date
146            let access_granted =
147                memberships.iter().map(|m| m.joined_at).min().unwrap_or(user.created_at);
148
149            user_access_list.push(UserAccessInfo {
150                user_id: user.id,
151                username: user.username,
152                email: user.email,
153                roles,
154                permissions,
155                last_login: last_activity, // Using last_activity as proxy for last login
156                access_granted,
157                days_inactive,
158                is_active: user.is_active,
159            });
160        }
161
162        Ok(user_access_list)
163    }
164
165    async fn get_privileged_users(&self) -> Result<Vec<PrivilegedAccessInfo>, Error> {
166        // Get all users with admin role in any workspace
167        let admin_members = sqlx::query_as!(
168            crate::models::WorkspaceMember,
169            r#"
170            SELECT
171                id as "id: Uuid",
172                workspace_id as "workspace_id: Uuid",
173                user_id as "user_id: Uuid",
174                role as "role: crate::models::UserRole",
175                joined_at as "joined_at: chrono::DateTime<chrono::Utc>",
176                last_activity as "last_activity: chrono::DateTime<chrono::Utc>"
177            FROM workspace_members
178            WHERE role = 'admin'
179            ORDER BY last_activity DESC
180            "#,
181        )
182        .fetch_all(&self.db)
183        .await
184        .map_err(|e| Error::Generic(format!("Failed to fetch privileged users: {}", e)))?;
185
186        // Group by user_id and collect roles
187        use std::collections::HashMap;
188        let mut user_roles: HashMap<Uuid, Vec<String>> = HashMap::new();
189        let mut user_activities: HashMap<Uuid, Vec<DateTime<Utc>>> = HashMap::new();
190
191        for member in &admin_members {
192            user_roles
193                .entry(member.user_id)
194                .or_insert_with(Vec::new)
195                .push(format!("{:?}", member.role));
196            user_activities
197                .entry(member.user_id)
198                .or_insert_with(Vec::new)
199                .push(member.last_activity);
200        }
201
202        // Get user details
203        let mut privileged_list = Vec::new();
204
205        for (user_id, roles) in user_roles {
206            // Get user details
207            let user =
208                self.user_service.get_user(user_id).await.map_err(|e| {
209                    Error::Generic(format!("Failed to get user {}: {}", user_id, e))
210                })?;
211
212            let activities = user_activities.get(&user_id).cloned().unwrap_or_default();
213            let last_activity = activities.iter().max().copied();
214
215            // Check MFA status
216            let mfa_enabled = if let Some(ref mfa_storage) = self.mfa_storage {
217                mfa_storage
218                    .get_mfa_status(user_id)
219                    .await
220                    .ok()
221                    .flatten()
222                    .map(|s| s.enabled)
223                    .unwrap_or(false)
224            } else {
225                false
226            };
227
228            // Get justification
229            let (justification, justification_expires) =
230                if let Some(ref just_storage) = self.justification_storage {
231                    just_storage
232                        .get_justification(user_id)
233                        .await
234                        .ok()
235                        .flatten()
236                        .map(|j| (Some(j.justification), j.expires_at))
237                        .unwrap_or((None, None))
238                } else {
239                    (None, None)
240                };
241
242            privileged_list.push(PrivilegedAccessInfo {
243                user_id,
244                username: user.username,
245                roles,
246                mfa_enabled,
247                justification,
248                justification_expires,
249                recent_actions_count: activities.len() as u64,
250                last_privileged_action: last_activity,
251            });
252        }
253
254        Ok(privileged_list)
255    }
256
257    async fn get_api_tokens(&self) -> Result<Vec<ApiTokenInfo>, Error> {
258        if let Some(ref storage) = self.token_storage {
259            storage.get_all_tokens().await
260        } else {
261            // No token storage configured, return empty list
262            Ok(Vec::new())
263        }
264    }
265
266    async fn get_user(&self, user_id: Uuid) -> Result<Option<UserAccessInfo>, Error> {
267        let user = match self.user_service.get_user(user_id).await {
268            Ok(u) => u,
269            Err(_) => return Ok(None),
270        };
271
272        // Get memberships
273        let memberships = sqlx::query_as!(
274            crate::models::WorkspaceMember,
275            r#"
276            SELECT
277                id as "id: Uuid",
278                workspace_id as "workspace_id: Uuid",
279                user_id as "user_id: Uuid",
280                role as "role: crate::models::UserRole",
281                joined_at as "joined_at: chrono::DateTime<chrono::Utc>",
282                last_activity as "last_activity: chrono::DateTime<chrono::Utc>"
283            FROM workspace_members
284            WHERE user_id = ?
285            "#,
286            user_id
287        )
288        .fetch_all(&self.db)
289        .await
290        .map_err(|e| Error::Generic(format!("Failed to fetch memberships: {}", e)))?;
291
292        let roles: Vec<String> = memberships.iter().map(|m| format!("{:?}", m.role)).collect();
293
294        let permissions: Vec<String> = memberships
295            .iter()
296            .flat_map(|m| match m.role {
297                crate::models::UserRole::Admin => vec![
298                    "WorkspaceCreate".to_string(),
299                    "WorkspaceRead".to_string(),
300                    "WorkspaceUpdate".to_string(),
301                    "WorkspaceDelete".to_string(),
302                    "WorkspaceManageMembers".to_string(),
303                    "MockCreate".to_string(),
304                    "MockRead".to_string(),
305                    "MockUpdate".to_string(),
306                    "MockDelete".to_string(),
307                ],
308                crate::models::UserRole::Editor => vec![
309                    "MockCreate".to_string(),
310                    "MockRead".to_string(),
311                    "MockUpdate".to_string(),
312                    "MockDelete".to_string(),
313                ],
314                crate::models::UserRole::Viewer => vec!["MockRead".to_string()],
315            })
316            .collect();
317
318        let last_activity = memberships.iter().map(|m| m.last_activity).max();
319
320        let days_inactive = last_activity.map(|activity| {
321            let duration = Utc::now() - activity;
322            duration.num_days() as u64
323        });
324
325        let access_granted =
326            memberships.iter().map(|m| m.joined_at).min().unwrap_or(user.created_at);
327
328        Ok(Some(UserAccessInfo {
329            user_id: user.id,
330            username: user.username,
331            email: user.email,
332            roles,
333            permissions,
334            last_login: last_activity,
335            access_granted,
336            days_inactive,
337            is_active: user.is_active,
338        }))
339    }
340
341    async fn get_last_login(&self, user_id: Uuid) -> Result<Option<DateTime<Utc>>, Error> {
342        // Use last_activity from workspace_members as proxy for last login
343        let result = sqlx::query!(
344            r#"
345            SELECT MAX(last_activity) as "last_activity: chrono::DateTime<chrono::Utc>"
346            FROM workspace_members
347            WHERE user_id = ?
348            "#,
349            user_id
350        )
351        .fetch_optional(&self.db)
352        .await
353        .map_err(|e| Error::Generic(format!("Failed to get last login: {}", e)))?;
354
355        Ok(result.and_then(|r| r.last_activity))
356    }
357
358    async fn revoke_user_access(&self, user_id: Uuid, reason: String) -> Result<(), Error> {
359        // Deactivate the user
360        self.user_service
361            .deactivate_user(user_id)
362            .await
363            .map_err(|e| Error::Generic(format!("Failed to revoke access: {}", e)))?;
364
365        tracing::info!("Revoked access for user {}: {}", user_id, reason);
366
367        Ok(())
368    }
369
370    async fn update_user_permissions(
371        &self,
372        user_id: Uuid,
373        roles: Vec<String>,
374        permissions: Vec<String>,
375    ) -> Result<(), Error> {
376        // TODO: Implement permission updates
377        // This would involve updating workspace memberships
378        tracing::warn!("update_user_permissions not yet fully implemented for user {}", user_id);
379        Ok(())
380    }
381}