coderlib 0.1.0

A Rust library for AI-powered code assistance and agentic system
Documentation
//! Permission System for CoderLib
//!
//! This module provides a comprehensive permission system for controlling AI access
//! to system resources, with user approval workflows and persistent permissions.

use std::path::PathBuf;
use std::collections::HashSet;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use async_trait::async_trait;

pub mod service;
pub mod storage;
pub mod validator;

pub use service::*;
pub use storage::*;
pub use validator::*;

/// Core permission types that tools can request
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Permission {
    /// Read files from filesystem
    FileRead,
    /// Write files to filesystem
    FileWrite,
    /// Delete files from filesystem
    FileDelete,
    /// List directory contents
    DirectoryList,
    /// Create directories
    DirectoryCreate,
    /// Execute shell commands
    ShellCommand,
    /// Access network resources
    NetworkAccess,
    /// Spawn processes
    ProcessSpawn,
    /// Access system information
    SystemInfo,
}

impl std::fmt::Display for Permission {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Permission::FileRead => write!(f, "file_read"),
            Permission::FileWrite => write!(f, "file_write"),
            Permission::FileDelete => write!(f, "file_delete"),
            Permission::DirectoryList => write!(f, "directory_list"),
            Permission::DirectoryCreate => write!(f, "directory_create"),
            Permission::ShellCommand => write!(f, "shell_command"),
            Permission::NetworkAccess => write!(f, "network_access"),
            Permission::ProcessSpawn => write!(f, "process_spawn"),
            Permission::SystemInfo => write!(f, "system_info"),
        }
    }
}

/// A permission request from a tool
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRequest {
    pub id: Uuid,
    pub session_id: String,
    pub tool_name: String,
    pub permission: Permission,
    pub description: String,
    pub action: String,
    pub path: Option<PathBuf>,
    pub params: serde_json::Value,
    pub created_at: chrono::DateTime<chrono::Utc>,
}

/// Response to a permission request
#[derive(Debug, Clone, PartialEq)]
pub enum PermissionResponse {
    /// Permission granted for this request only
    Granted,
    /// Permission granted and stored persistently
    GrantedPersistent,
    /// Permission denied
    Denied,
}

/// A granted permission with metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GrantedPermission {
    pub request: PermissionRequest,
    pub granted_at: chrono::DateTime<chrono::Utc>,
    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
    pub persistent: bool,
}

/// Permission system errors
#[derive(Debug, thiserror::Error)]
pub enum PermissionError {
    #[error("Permission denied: {0}")]
    Denied(String),
    
    #[error("Security validation failed: {0}")]
    SecurityViolation(String),
    
    #[error("Permission request timed out")]
    Timeout,
    
    #[error("Permission channel closed")]
    ChannelClosed,
    
    #[error("Storage error: {0}")]
    Storage(#[from] sqlx::Error),
    
    #[error("Serialization error: {0}")]
    Serialization(#[from] serde_json::Error),
    
    #[error("Integration error: {0}")]
    Integration(String),
}

/// Main permission service trait
#[async_trait]
pub trait PermissionService: Send + Sync {
    /// Request permission for a tool operation
    async fn request_permission(
        &self,
        request: PermissionRequest,
    ) -> Result<PermissionResponse, PermissionError>;
    
    /// Check if permission is already granted
    async fn check_permission(
        &self,
        session_id: &str,
        tool_name: &str,
        permission: Permission,
        path: Option<&PathBuf>,
    ) -> Result<bool, PermissionError>;
    
    /// Grant persistent permission
    async fn grant_persistent(
        &self,
        request: PermissionRequest,
    ) -> Result<(), PermissionError>;
    
    /// Revoke a specific permission
    async fn revoke_permission(
        &self,
        session_id: &str,
        permission_id: Uuid,
    ) -> Result<(), PermissionError>;
    
    /// Enable auto-approval for a session (non-interactive mode)
    async fn auto_approve_session(&self, session_id: &str) -> Result<(), PermissionError>;
    
    /// List all permissions for a session
    async fn list_permissions(
        &self,
        session_id: &str,
    ) -> Result<Vec<GrantedPermission>, PermissionError>;
}

/// Macro for requesting permissions in tools
#[macro_export]
macro_rules! require_permission {
    ($permission_service:expr, $session_id:expr, $tool_name:expr, $permission:expr, $description:expr, $action:expr) => {
        {
            use $crate::permission::{PermissionRequest, PermissionResponse};
            use uuid::Uuid;
            
            let request = PermissionRequest {
                id: Uuid::new_v4(),
                session_id: $session_id.to_string(),
                tool_name: $tool_name.to_string(),
                permission: $permission,
                description: $description.to_string(),
                action: $action.to_string(),
                path: None,
                params: serde_json::json!({}),
                created_at: chrono::Utc::now(),
            };
            
            match $permission_service.request_permission(request).await? {
                PermissionResponse::Granted | PermissionResponse::GrantedPersistent => {},
                PermissionResponse::Denied => {
                    return Err(crate::core::error::CoderLibError::PermissionDenied(
                        "Permission denied by user".to_string()
                    ));
                }
            }
        }
    };
    
    ($permission_service:expr, $session_id:expr, $tool_name:expr, $permission:expr, $description:expr, $action:expr, $path:expr) => {
        {
            use $crate::permission::{PermissionRequest, PermissionResponse};
            use uuid::Uuid;
            
            let request = PermissionRequest {
                id: Uuid::new_v4(),
                session_id: $session_id.to_string(),
                tool_name: $tool_name.to_string(),
                permission: $permission,
                description: $description.to_string(),
                action: $action.to_string(),
                path: Some($path.to_path_buf()),
                params: serde_json::json!({}),
                created_at: chrono::Utc::now(),
            };
            
            match $permission_service.request_permission(request).await? {
                PermissionResponse::Granted | PermissionResponse::GrantedPersistent => {},
                PermissionResponse::Denied => {
                    return Err(crate::core::error::CoderLibError::PermissionDenied(
                        "Permission denied by user".to_string()
                    ));
                }
            }
        }
    };
}

/// Configuration for permission system
#[derive(Debug, Clone)]
pub struct PermissionConfig {
    /// Enable permission checking
    pub enabled: bool,
    /// Auto-approve all permissions (for testing/non-interactive mode)
    pub auto_approve_all: bool,
    /// Default permission timeout in seconds
    pub request_timeout_secs: u64,
    /// Maximum number of persistent permissions per session
    pub max_persistent_permissions: usize,
}

impl Default for PermissionConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            auto_approve_all: false,
            request_timeout_secs: 300, // 5 minutes
            max_persistent_permissions: 100,
        }
    }
}

/// Helper function to create a permission request
pub fn create_permission_request(
    session_id: &str,
    tool_name: &str,
    permission: Permission,
    description: &str,
    action: &str,
    path: Option<PathBuf>,
    params: serde_json::Value,
) -> PermissionRequest {
    PermissionRequest {
        id: Uuid::new_v4(),
        session_id: session_id.to_string(),
        tool_name: tool_name.to_string(),
        permission,
        description: description.to_string(),
        action: action.to_string(),
        path,
        params,
        created_at: chrono::Utc::now(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;
    
    #[test]
    fn test_permission_request_creation() {
        let request = create_permission_request(
            "test-session",
            "file_tool",
            Permission::FileRead,
            "Read configuration file",
            "read",
            Some(PathBuf::from("/tmp/config.json")),
            serde_json::json!({"encoding": "utf-8"}),
        );
        
        assert_eq!(request.session_id, "test-session");
        assert_eq!(request.tool_name, "file_tool");
        assert_eq!(request.permission, Permission::FileRead);
        assert_eq!(request.description, "Read configuration file");
        assert_eq!(request.action, "read");
        assert_eq!(request.path, Some(PathBuf::from("/tmp/config.json")));
    }
    
    #[test]
    fn test_permission_config_default() {
        let config = PermissionConfig::default();
        assert!(config.enabled);
        assert!(!config.auto_approve_all);
        assert_eq!(config.request_timeout_secs, 300);
        assert_eq!(config.max_persistent_permissions, 100);
    }
}