coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
//! Permission Service Implementation

use super::*;
use std::sync::Arc;
use tokio::sync::{RwLock, mpsc};
use dashmap::DashMap;
use tracing::{debug, info};

/// Main permission service implementation
pub struct PermissionServiceImpl {
    storage: Arc<dyn PermissionStorage>,
    config: PermissionConfig,
    session_permissions: Arc<DashMap<String, Vec<GrantedPermission>>>,
    auto_approve_sessions: Arc<RwLock<HashSet<String>>>,
    pending_requests: Arc<DashMap<Uuid, mpsc::Sender<PermissionResponse>>>,
    validator: SecurityValidator,
}

impl PermissionServiceImpl {
    pub fn new(
        storage: Arc<dyn PermissionStorage>,
        config: PermissionConfig,
    ) -> Self {
        Self {
            storage,
            config,
            session_permissions: Arc::new(DashMap::new()),
            auto_approve_sessions: Arc::new(RwLock::new(HashSet::new())),
            pending_requests: Arc::new(DashMap::new()),
            validator: SecurityValidator::new(),
        }
    }
    
    /// Create a simple permission service for testing
    pub fn new_simple() -> Self {
        let config = PermissionConfig {
            enabled: true,
            auto_approve_all: true, // Auto-approve for testing
            request_timeout_secs: 30,
            max_persistent_permissions: 50,
        };
        
        Self::new(
            Arc::new(InMemoryPermissionStorage::new()),
            config,
        )
    }
    
    /// Check if session is auto-approved
    async fn is_auto_approved(&self, session_id: &str) -> bool {
        if self.config.auto_approve_all {
            return true;
        }
        
        let auto_approve = self.auto_approve_sessions.read().await;
        auto_approve.contains(session_id)
    }
    
    /// Find existing permission that matches the request
    async fn find_existing_permission(
        &self,
        request: &PermissionRequest,
    ) -> Result<Option<GrantedPermission>, PermissionError> {
        // Check session permissions first
        if let Some(permissions) = self.session_permissions.get(&request.session_id) {
            for granted in permissions.iter() {
                if self.permission_matches(&granted.request, request) && !self.is_expired(granted) {
                    return Ok(Some(granted.clone()));
                }
            }
        }
        
        // Check persistent permissions
        let persistent = self.storage.get_persistent_permissions(&request.session_id).await?;
        for granted in persistent {
            if self.permission_matches(&granted.request, request) && !self.is_expired(&granted) {
                return Ok(Some(granted));
            }
        }
        
        Ok(None)
    }
    
    /// Check if two permission requests match
    fn permission_matches(&self, granted: &PermissionRequest, requested: &PermissionRequest) -> bool {
        granted.tool_name == requested.tool_name
            && granted.permission == requested.permission
            && granted.action == requested.action
            && self.path_matches(&granted.path, &requested.path)
    }
    
    /// Check if requested path is within granted path scope
    fn path_matches(&self, granted_path: &Option<PathBuf>, requested_path: &Option<PathBuf>) -> bool {
        match (granted_path, requested_path) {
            (None, None) => true,
            (Some(granted), Some(requested)) => {
                // Check if requested path is within granted path
                requested.starts_with(granted) || granted.starts_with(requested)
            },
            (None, Some(_)) => true, // No path restriction granted
            (Some(_), None) => false, // Path restriction exists but none requested
        }
    }
    
    /// Check if permission has expired
    fn is_expired(&self, permission: &GrantedPermission) -> bool {
        if let Some(expires_at) = permission.expires_at {
            chrono::Utc::now() > expires_at
        } else {
            false
        }
    }
    
    /// Request user approval (simplified for now)
    async fn request_user_approval(
        &self,
        request: PermissionRequest,
    ) -> Result<PermissionResponse, PermissionError> {
        info!("🔐 Permission requested: {} for {} ({})", 
              request.permission, request.tool_name, request.description);
        
        // For now, auto-approve all requests
        // In a real implementation, this would show a UI dialog
        info!("✅ Permission auto-approved for development");
        
        // Store the permission in session
        let granted = GrantedPermission {
            request: request.clone(),
            granted_at: chrono::Utc::now(),
            expires_at: None, // No expiration for now
            persistent: false,
        };
        
        self.session_permissions
            .entry(request.session_id.clone())
            .or_insert_with(Vec::new)
            .push(granted);
        
        Ok(PermissionResponse::Granted)
    }
}

#[async_trait]
impl PermissionService for PermissionServiceImpl {
    async fn request_permission(
        &self,
        request: PermissionRequest,
    ) -> Result<PermissionResponse, PermissionError> {
        debug!("Permission request: {:?}", request);
        
        // Check if permissions are disabled
        if !self.config.enabled {
            return Ok(PermissionResponse::Granted);
        }
        
        // Check if session is auto-approved
        if self.is_auto_approved(&request.session_id).await {
            debug!("Session {} is auto-approved", request.session_id);
            return Ok(PermissionResponse::Granted);
        }
        
        // Check for existing permission
        if let Some(_existing) = self.find_existing_permission(&request).await? {
            debug!("Found existing permission for request");
            return Ok(PermissionResponse::Granted);
        }
        
        // Validate security constraints
        self.validator.validate_request(&request)?;
        
        // Request user approval
        self.request_user_approval(request).await
    }
    
    async fn check_permission(
        &self,
        session_id: &str,
        tool_name: &str,
        permission: Permission,
        path: Option<&PathBuf>,
    ) -> Result<bool, PermissionError> {
        // Check if permissions are disabled
        if !self.config.enabled {
            return Ok(true);
        }
        
        // Check auto-approve
        if self.is_auto_approved(session_id).await {
            return Ok(true);
        }
        
        // Check session permissions
        if let Some(permissions) = self.session_permissions.get(session_id) {
            for granted in permissions.iter() {
                if granted.request.tool_name == tool_name
                    && granted.request.permission == permission
                    && self.path_matches(&granted.request.path, &path.map(|p| p.to_path_buf()))
                    && !self.is_expired(granted)
                {
                    return Ok(true);
                }
            }
        }
        
        // Check persistent permissions
        let persistent = self.storage.get_persistent_permissions(session_id).await?;
        for granted in persistent {
            if granted.request.tool_name == tool_name
                && granted.request.permission == permission
                && self.path_matches(&granted.request.path, &path.map(|p| p.to_path_buf()))
                && !self.is_expired(&granted)
            {
                return Ok(true);
            }
        }
        
        Ok(false)
    }
    
    async fn grant_persistent(
        &self,
        request: PermissionRequest,
    ) -> Result<(), PermissionError> {
        let granted = GrantedPermission {
            request,
            granted_at: chrono::Utc::now(),
            expires_at: None,
            persistent: true,
        };
        
        self.storage.store_persistent_permission(granted).await?;
        Ok(())
    }
    
    async fn revoke_permission(
        &self,
        session_id: &str,
        permission_id: Uuid,
    ) -> Result<(), PermissionError> {
        // Remove from session permissions
        if let Some(mut permissions) = self.session_permissions.get_mut(session_id) {
            permissions.retain(|p| p.request.id != permission_id);
        }
        
        // Remove from persistent storage
        self.storage.revoke_persistent_permission(permission_id).await?;
        Ok(())
    }
    
    async fn auto_approve_session(&self, session_id: &str) -> Result<(), PermissionError> {
        let mut auto_approve = self.auto_approve_sessions.write().await;
        auto_approve.insert(session_id.to_string());
        info!("Session {} set to auto-approve permissions", session_id);
        Ok(())
    }
    
    async fn list_permissions(
        &self,
        session_id: &str,
    ) -> Result<Vec<GrantedPermission>, PermissionError> {
        let mut permissions = Vec::new();
        
        // Add session permissions
        if let Some(session_perms) = self.session_permissions.get(session_id) {
            permissions.extend(session_perms.clone());
        }
        
        // Add persistent permissions
        let persistent = self.storage.get_persistent_permissions(session_id).await?;
        permissions.extend(persistent);
        
        // Filter out expired permissions
        permissions.retain(|p| !self.is_expired(p));
        
        Ok(permissions)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    
    #[tokio::test]
    async fn test_permission_service_auto_approve() {
        let service = PermissionServiceImpl::new_simple();
        
        let request = create_permission_request(
            "test-session",
            "file_tool",
            Permission::FileRead,
            "Read test file",
            "read",
            Some(PathBuf::from("/tmp/test.txt")),
            serde_json::json!({}),
        );
        
        let response = service.request_permission(request).await.unwrap();
        assert_eq!(response, PermissionResponse::Granted);
    }
    
    #[tokio::test]
    async fn test_permission_check() {
        let service = PermissionServiceImpl::new_simple();
        
        // Should be auto-approved
        let has_permission = service.check_permission(
            "test-session",
            "file_tool",
            Permission::FileRead,
            Some(&PathBuf::from("/tmp/test.txt")),
        ).await.unwrap();
        
        assert!(has_permission);
    }
}