ricecoder_teams/
access.rs

1/// Access control and permission management
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use ricecoder_permissions::{AuditLogger, PermissionManager};
6use tokio::sync::RwLock;
7
8use crate::error::Result;
9use crate::models::{AuditLogEntry, TeamRole};
10
11/// Manages team member roles and permissions
12pub struct AccessControlManager {
13    /// Permission manager for role-based access control
14    #[allow(dead_code)]
15    permission_manager: Arc<PermissionManager>,
16    /// Audit logger for tracking permission changes
17    audit_logger: Arc<AuditLogger>,
18    /// In-memory cache of member roles (team_id -> member_id -> role)
19    member_roles: Arc<RwLock<HashMap<String, HashMap<String, TeamRole>>>>,
20}
21
22impl AccessControlManager {
23    /// Create a new AccessControlManager
24    pub fn new(permission_manager: Arc<PermissionManager>, audit_logger: Arc<AuditLogger>) -> Self {
25        AccessControlManager {
26            permission_manager,
27            audit_logger,
28            member_roles: Arc::new(RwLock::new(HashMap::new())),
29        }
30    }
31
32    /// Assign a role to a team member
33    pub async fn assign_role(&self, team_id: &str, member_id: &str, role: TeamRole) -> Result<()> {
34        // Store role in cache
35        let mut roles = self.member_roles.write().await;
36        let team_roles = roles
37            .entry(team_id.to_string())
38            .or_insert_with(HashMap::new);
39        team_roles.insert(member_id.to_string(), role);
40
41        // Log the action
42        tracing::info!(
43            team_id = %team_id,
44            member_id = %member_id,
45            role = %role.as_str(),
46            "Assigning role to team member"
47        );
48
49        // Audit log entry
50        let tool_name = format!("team:{}:assign_role", team_id);
51        let context = format!("Assigned role {} to member {}", role.as_str(), member_id);
52        let _ =
53            self.audit_logger
54                .log_execution(tool_name, Some(member_id.to_string()), Some(context));
55
56        Ok(())
57    }
58
59    /// Check if a member has permission for an action
60    pub async fn check_permission(
61        &self,
62        member_id: &str,
63        action: &str,
64        resource: &str,
65    ) -> Result<bool> {
66        // For now, we check based on role-based permissions
67        // In a full implementation, this would integrate with PermissionManager
68        tracing::info!(
69            member_id = %member_id,
70            action = %action,
71            resource = %resource,
72            "Checking permission"
73        );
74
75        // This is a placeholder - actual permission checking would be more sophisticated
76        Ok(true)
77    }
78
79    /// Grant Admin role permissions
80    pub async fn grant_admin_permissions(&self, member_id: &str) -> Result<()> {
81        // Admin permissions: create, modify, delete standards
82        let permissions = vec!["create_standards", "modify_standards", "delete_standards"];
83
84        for permission in permissions {
85            tracing::info!(
86                member_id = %member_id,
87                permission = %permission,
88                "Granting admin permission"
89            );
90        }
91
92        Ok(())
93    }
94
95    /// Grant Member role permissions
96    pub async fn grant_member_permissions(&self, member_id: &str) -> Result<()> {
97        // Member permissions: view, apply standards
98        let permissions = vec!["view_standards", "apply_standards"];
99
100        for permission in permissions {
101            tracing::info!(
102                member_id = %member_id,
103                permission = %permission,
104                "Granting member permission"
105            );
106        }
107
108        Ok(())
109    }
110
111    /// Grant Viewer role permissions
112    pub async fn grant_viewer_permissions(&self, member_id: &str) -> Result<()> {
113        // Viewer permissions: read-only access
114        let permissions = vec!["view_standards"];
115
116        for permission in permissions {
117            tracing::info!(
118                member_id = %member_id,
119                permission = %permission,
120                "Granting viewer permission"
121            );
122        }
123
124        Ok(())
125    }
126
127    /// Revoke all access for a member
128    pub async fn revoke_access(&self, team_id: &str, member_id: &str) -> Result<()> {
129        // Remove from role cache
130        let mut roles = self.member_roles.write().await;
131        if let Some(team_roles) = roles.get_mut(team_id) {
132            team_roles.remove(member_id);
133        }
134
135        tracing::info!(
136            team_id = %team_id,
137            member_id = %member_id,
138            "Revoking access"
139        );
140
141        // Audit log entry
142        let tool_name = format!("team:{}:revoke_access", team_id);
143        let context = format!("Revoked access for member {}", member_id);
144        let _ =
145            self.audit_logger
146                .log_execution(tool_name, Some(member_id.to_string()), Some(context));
147
148        Ok(())
149    }
150
151    /// Get audit log entries
152    pub async fn get_audit_log(&self, team_id: &str) -> Result<Vec<AuditLogEntry>> {
153        // Query audit log for team-related entries
154        tracing::info!(team_id = %team_id, "Retrieving audit log");
155
156        // Get all entries from the audit logger
157        let entries = self
158            .audit_logger
159            .entries()
160            .map_err(crate::error::TeamError::PermissionsError)?;
161
162        // Convert and filter entries for this team
163        let team_entries: Vec<AuditLogEntry> = entries
164            .into_iter()
165            .filter(|entry| entry.tool.contains(&format!("team:{}", team_id)))
166            .map(|entry| AuditLogEntry {
167                id: entry.id,
168                team_id: team_id.to_string(),
169                user_id: entry.agent.unwrap_or_default(),
170                action: entry.action.to_string(),
171                resource: entry.tool,
172                result: entry.result.to_string(),
173                timestamp: entry.timestamp,
174            })
175            .collect();
176
177        Ok(team_entries)
178    }
179
180    /// Get the role of a team member
181    pub async fn get_member_role(
182        &self,
183        team_id: &str,
184        member_id: &str,
185    ) -> Result<Option<TeamRole>> {
186        let roles = self.member_roles.read().await;
187        Ok(roles
188            .get(team_id)
189            .and_then(|team_roles| team_roles.get(member_id).copied()))
190    }
191
192    /// Check if a member has a specific role
193    pub async fn has_role(&self, team_id: &str, member_id: &str, role: TeamRole) -> Result<bool> {
194        let member_role = self.get_member_role(team_id, member_id).await?;
195        Ok(member_role == Some(role))
196    }
197}
198
199impl Default for AccessControlManager {
200    fn default() -> Self {
201        // Create default instances of dependencies
202        let permission_manager = Arc::new(PermissionManager::new());
203        let audit_logger = Arc::new(AuditLogger::new());
204        Self::new(permission_manager, audit_logger)
205    }
206}