use super::*;
use std::sync::Arc;
use tokio::sync::{RwLock, mpsc};
use dashmap::DashMap;
use tracing::{debug, info};
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(),
}
}
pub fn new_simple() -> Self {
let config = PermissionConfig {
enabled: true,
auto_approve_all: true, request_timeout_secs: 30,
max_persistent_permissions: 50,
};
Self::new(
Arc::new(InMemoryPermissionStorage::new()),
config,
)
}
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)
}
async fn find_existing_permission(
&self,
request: &PermissionRequest,
) -> Result<Option<GrantedPermission>, PermissionError> {
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()));
}
}
}
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)
}
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)
}
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)) => {
requested.starts_with(granted) || granted.starts_with(requested)
},
(None, Some(_)) => true, (Some(_), None) => false, }
}
fn is_expired(&self, permission: &GrantedPermission) -> bool {
if let Some(expires_at) = permission.expires_at {
chrono::Utc::now() > expires_at
} else {
false
}
}
async fn request_user_approval(
&self,
request: PermissionRequest,
) -> Result<PermissionResponse, PermissionError> {
info!("🔐 Permission requested: {} for {} ({})",
request.permission, request.tool_name, request.description);
info!("✅ Permission auto-approved for development");
let granted = GrantedPermission {
request: request.clone(),
granted_at: chrono::Utc::now(),
expires_at: None, 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);
if !self.config.enabled {
return Ok(PermissionResponse::Granted);
}
if self.is_auto_approved(&request.session_id).await {
debug!("Session {} is auto-approved", request.session_id);
return Ok(PermissionResponse::Granted);
}
if let Some(_existing) = self.find_existing_permission(&request).await? {
debug!("Found existing permission for request");
return Ok(PermissionResponse::Granted);
}
self.validator.validate_request(&request)?;
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> {
if !self.config.enabled {
return Ok(true);
}
if self.is_auto_approved(session_id).await {
return Ok(true);
}
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);
}
}
}
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> {
if let Some(mut permissions) = self.session_permissions.get_mut(session_id) {
permissions.retain(|p| p.request.id != permission_id);
}
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();
if let Some(session_perms) = self.session_permissions.get(session_id) {
permissions.extend(session_perms.clone());
}
let persistent = self.storage.get_persistent_permissions(session_id).await?;
permissions.extend(persistent);
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();
let has_permission = service.check_permission(
"test-session",
"file_tool",
Permission::FileRead,
Some(&PathBuf::from("/tmp/test.txt")),
).await.unwrap();
assert!(has_permission);
}
}