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::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Permission {
FileRead,
FileWrite,
FileDelete,
DirectoryList,
DirectoryCreate,
ShellCommand,
NetworkAccess,
ProcessSpawn,
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"),
}
}
}
#[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>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PermissionResponse {
Granted,
GrantedPersistent,
Denied,
}
#[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,
}
#[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),
}
#[async_trait]
pub trait PermissionService: Send + Sync {
async fn request_permission(
&self,
request: PermissionRequest,
) -> Result<PermissionResponse, PermissionError>;
async fn check_permission(
&self,
session_id: &str,
tool_name: &str,
permission: Permission,
path: Option<&PathBuf>,
) -> Result<bool, PermissionError>;
async fn grant_persistent(
&self,
request: PermissionRequest,
) -> Result<(), PermissionError>;
async fn revoke_permission(
&self,
session_id: &str,
permission_id: Uuid,
) -> Result<(), PermissionError>;
async fn auto_approve_session(&self, session_id: &str) -> Result<(), PermissionError>;
async fn list_permissions(
&self,
session_id: &str,
) -> Result<Vec<GrantedPermission>, PermissionError>;
}
#[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()
));
}
}
}
};
}
#[derive(Debug, Clone)]
pub struct PermissionConfig {
pub enabled: bool,
pub auto_approve_all: bool,
pub request_timeout_secs: u64,
pub max_persistent_permissions: usize,
}
impl Default for PermissionConfig {
fn default() -> Self {
Self {
enabled: true,
auto_approve_all: false,
request_timeout_secs: 300, max_persistent_permissions: 100,
}
}
}
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);
}
}