use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRequest {
pub id: String,
pub session_id: String,
pub title: String,
pub description: Option<String>,
pub metadata: Value,
pub risk_level: RiskLevel,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum RiskLevel {
Low, Medium, High, Critical, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PermissionResult {
Granted,
Denied(String),
RequiresConfirmation(PermissionRequest),
}
#[async_trait]
pub trait PermissionProvider: Send + Sync {
async fn check_permission(&self, request: &PermissionRequest) -> PermissionResult;
async fn grant_permission(&self, request_id: &str) -> Result<(), String>;
async fn deny_permission(&self, request_id: &str, reason: String) -> Result<(), String>;
}
pub struct InteractivePermissionProvider {
pending_requests: Arc<RwLock<HashMap<String, PermissionRequest>>>,
auto_approve_low_risk: bool,
}
impl InteractivePermissionProvider {
pub fn new(auto_approve_low_risk: bool) -> Self {
Self {
pending_requests: Arc::new(RwLock::new(HashMap::new())),
auto_approve_low_risk,
}
}
}
#[async_trait]
impl PermissionProvider for InteractivePermissionProvider {
async fn check_permission(&self, request: &PermissionRequest) -> PermissionResult {
if self.auto_approve_low_risk && request.risk_level == RiskLevel::Low {
return PermissionResult::Granted;
}
let mut pending = self.pending_requests.write().await;
pending.insert(request.id.clone(), request.clone());
PermissionResult::RequiresConfirmation(request.clone())
}
async fn grant_permission(&self, request_id: &str) -> Result<(), String> {
let mut pending = self.pending_requests.write().await;
pending.remove(request_id);
Ok(())
}
async fn deny_permission(&self, request_id: &str, reason: String) -> Result<(), String> {
let mut pending = self.pending_requests.write().await;
pending.remove(request_id);
Err(reason)
}
}
pub struct AutoApprovePermissionProvider;
#[async_trait]
impl PermissionProvider for AutoApprovePermissionProvider {
async fn check_permission(&self, _request: &PermissionRequest) -> PermissionResult {
PermissionResult::Granted
}
async fn grant_permission(&self, _request_id: &str) -> Result<(), String> {
Ok(())
}
async fn deny_permission(&self, _request_id: &str, reason: String) -> Result<(), String> {
Err(reason)
}
}
pub struct PermissionManager {
provider: Box<dyn PermissionProvider>,
}
impl PermissionManager {
pub fn new(provider: Box<dyn PermissionProvider>) -> Self {
Self { provider }
}
pub async fn ask(&self, request: PermissionRequest) -> Result<(), crate::error::Error> {
match self.provider.check_permission(&request).await {
PermissionResult::Granted => Ok(()),
PermissionResult::Denied(reason) => {
Err(crate::error::Error::Other(anyhow::anyhow!(
"Permission denied: {}",
reason
)))
}
PermissionResult::RequiresConfirmation(_) => {
Err(crate::error::Error::Other(anyhow::anyhow!(
"Operation requires user confirmation: {}",
request.title
)))
}
}
}
}
pub fn create_permission_request(
id: impl Into<String>,
session_id: impl Into<String>,
title: impl Into<String>,
risk_level: RiskLevel,
metadata: Value,
) -> PermissionRequest {
PermissionRequest {
id: id.into(),
session_id: session_id.into(),
title: title.into(),
description: None,
metadata,
risk_level,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[tokio::test]
async fn test_auto_approve_low_risk() {
let provider = InteractivePermissionProvider::new(true);
let request = create_permission_request(
"test",
"session1",
"Read file",
RiskLevel::Low,
json!({}),
);
let result = provider.check_permission(&request).await;
assert!(matches!(result, PermissionResult::Granted));
}
#[tokio::test]
async fn test_requires_confirmation_high_risk() {
let provider = InteractivePermissionProvider::new(true);
let request = create_permission_request(
"test",
"session1",
"Delete system files",
RiskLevel::Critical,
json!({}),
);
let result = provider.check_permission(&request).await;
assert!(matches!(result, PermissionResult::RequiresConfirmation(_)));
}
#[tokio::test]
async fn test_auto_approve_provider() {
let provider = AutoApprovePermissionProvider;
let request = create_permission_request(
"test",
"session1",
"Any operation",
RiskLevel::Critical,
json!({}),
);
let result = provider.check_permission(&request).await;
assert!(matches!(result, PermissionResult::Granted));
}
}