1use crate::permission::policy::{PermissionPolicy, SecurityLevel};
6use crate::permission::types::{
7 AuditEntry, CachedPermission, PermissionAction, PermissionDecision, PermissionRequest,
8 PermissionResponse,
9};
10use parking_lot::RwLock;
11use sh_layer1::generate_short_id;
12use std::collections::HashMap;
13use std::sync::Arc;
14
15pub type PermissionPromptCallback =
17 Box<dyn Fn(&PermissionRequest) -> PermissionResponse + Send + Sync>;
18
19pub type PermissionResult<T> = Result<T, PermissionError>;
21
22#[derive(Debug, thiserror::Error)]
24pub enum PermissionError {
25 #[error("Permission denied: {0}")]
27 Denied(String),
28
29 #[error("Action blocked by security policy: {0}")]
31 BlockedByPolicy(String),
32
33 #[error("No permission prompt callback configured")]
35 NoCallback,
36
37 #[error("Permission cache error: {0}")]
39 CacheError(String),
40
41 #[error("Invalid permission request: {0}")]
43 InvalidRequest(String),
44}
45
46pub struct PermissionManager {
48 policy: RwLock<PermissionPolicy>,
50 cache: RwLock<HashMap<String, CachedPermission>>,
52 audit_log: RwLock<Vec<AuditEntry>>,
54 prompt_callback: RwLock<Option<Arc<PermissionPromptCallback>>>,
56}
57
58impl Default for PermissionManager {
59 fn default() -> Self {
60 Self::new(PermissionPolicy::default())
61 }
62}
63
64impl PermissionManager {
65 pub fn new(policy: PermissionPolicy) -> Self {
67 Self {
68 policy: RwLock::new(policy),
69 cache: RwLock::new(HashMap::new()),
70 audit_log: RwLock::new(Vec::new()),
71 prompt_callback: RwLock::new(None),
72 }
73 }
74
75 pub fn set_prompt_callback(&self, callback: PermissionPromptCallback) {
77 *self.prompt_callback.write() = Some(Arc::new(callback));
78 }
79
80 pub fn clear_prompt_callback(&self) {
82 *self.prompt_callback.write() = None;
83 }
84
85 pub fn set_policy(&self, policy: PermissionPolicy) {
87 *self.policy.write() = policy;
88 }
89
90 pub fn security_level(&self) -> SecurityLevel {
92 self.policy.read().level
93 }
94
95 pub fn check_permission(
97 &self,
98 request: PermissionRequest,
99 ) -> PermissionResult<PermissionResponse> {
100 let category = request.action.category();
101
102 if self.policy.read().is_category_blocked(category) {
104 return Err(PermissionError::BlockedByPolicy(format!(
105 "Category '{}' is blocked by security policy",
106 category
107 )));
108 }
109
110 if let Some(block_reason) = self.check_blocked(&request.action) {
112 return Err(PermissionError::BlockedByPolicy(block_reason));
113 }
114
115 if self.policy.read().should_auto_approve(category) {
117 let response = PermissionResponse::allow(request.id.clone());
118 self.log_audit(&request, &response, false);
119 return Ok(response);
120 }
121
122 if self.policy.read().enable_cache {
124 if let Some(cached) = self.check_cache(&request) {
125 let response = PermissionResponse {
126 request_id: request.id.clone(),
127 decision: cached.decision,
128 reason: Some("From cache".to_string()),
129 timestamp: chrono::Utc::now(),
130 };
131 self.log_audit(&request, &response, true);
132 return Ok(response);
133 }
134 }
135
136 if let Some(callback) = self.prompt_callback.read().as_ref() {
138 let response = callback(&request);
139 let description = request.action.description();
140
141 if response.decision.should_remember() && self.policy.read().enable_cache {
143 self.cache_permission(&request.action, response.decision);
144 }
145
146 self.log_audit(&request, &response, false);
148
149 if !response.decision.is_allowed() {
151 return Err(PermissionError::Denied(format!(
152 "User denied: {}",
153 description
154 )));
155 }
156
157 Ok(response)
158 } else {
159 if self.policy.read().level == SecurityLevel::Trusted {
161 let response = PermissionResponse::allow(request.id.clone());
162 self.log_audit(&request, &response, false);
163 Ok(response)
164 } else {
165 Err(PermissionError::NoCallback)
166 }
167 }
168 }
169
170 fn check_blocked(&self, action: &PermissionAction) -> Option<String> {
172 let policy = self.policy.read();
173
174 match action {
175 PermissionAction::CommandExecute { command, .. } => {
176 for blocked in &policy.blocked_commands {
177 if command.starts_with(blocked) || command.contains(blocked) {
178 return Some(format!(
179 "Command '{}' is blocked by security policy",
180 command
181 ));
182 }
183 }
184 None
185 }
186 PermissionAction::FileRead { path }
187 | PermissionAction::FileWrite { path, .. }
188 | PermissionAction::FileDelete { path } => {
189 if policy.is_path_blocked(path) {
190 return Some(format!("Path '{}' is blocked by security policy", path));
191 }
192 None
193 }
194 PermissionAction::NetworkRequest { url, .. } => {
195 if policy.blocked_urls.iter().any(|u| url.contains(u)) {
196 return Some(format!("URL '{}' is blocked by security policy", url));
197 }
198 None
199 }
200 _ => None,
201 }
202 }
203
204 fn check_cache(&self, request: &PermissionRequest) -> Option<CachedPermission> {
206 let cache = self.cache.read();
207 let key = self.cache_key(&request.action);
208
209 if let Some(cached) = cache.get(&key) {
210 if cached.is_valid() {
211 let mut cached = cached.clone();
212 cached.use_once();
213 self.cache.write().insert(key, cached.clone());
214 return Some(cached);
215 }
216 }
217
218 None
219 }
220
221 fn cache_permission(&self, action: &PermissionAction, decision: PermissionDecision) {
223 let key = self.cache_key(action);
224 let expire_seconds = self.policy.read().cache_expire_seconds;
225
226 let cached = CachedPermission {
227 action_pattern: key.clone(),
228 decision,
229 cached_at: chrono::Utc::now(),
230 expires_at: if expire_seconds > 0 {
231 Some(chrono::Utc::now() + chrono::Duration::seconds(expire_seconds as i64))
232 } else {
233 None
234 },
235 use_count: 0,
236 };
237
238 self.cache.write().insert(key, cached);
239 }
240
241 fn cache_key(&self, action: &PermissionAction) -> String {
243 match action {
244 PermissionAction::CommandExecute { command, args } => {
245 format!("cmd:{}:{}", command, args.join(" "))
246 }
247 PermissionAction::FileRead { path } => format!("read:{}", path),
248 PermissionAction::FileWrite { path, .. } => format!("write:{}", path),
249 PermissionAction::FileDelete { path } => format!("delete:{}", path),
250 PermissionAction::NetworkRequest { url, method } => format!("net:{}:{}", method, url),
251 PermissionAction::EnvAccess { names } => format!("env:{}", names.join(",")),
252 PermissionAction::PackageInstall { packages } => format!("pkg:{}", packages.join(",")),
253 PermissionAction::SystemAccess { resource } => format!("sys:{}", resource),
254 PermissionAction::Custom { description } => format!("custom:{}", description),
255 }
256 }
257
258 fn log_audit(
260 &self,
261 request: &PermissionRequest,
262 response: &PermissionResponse,
263 from_cache: bool,
264 ) {
265 if !self.policy.read().audit_enabled {
266 return;
267 }
268
269 let entry = AuditEntry {
270 id: generate_short_id(),
271 request: request.clone(),
272 response: response.clone(),
273 from_cache,
274 timestamp: chrono::Utc::now(),
275 };
276
277 let max = self.policy.read().max_audit_entries;
278 let mut log = self.audit_log.write();
279 log.push(entry);
280
281 if log.len() > max {
283 let excess = log.len() - max;
284 log.drain(0..excess);
285 }
286 }
287
288 pub fn get_audit_log(&self) -> Vec<AuditEntry> {
290 self.audit_log.read().clone()
291 }
292
293 pub fn clear_audit_log(&self) {
295 self.audit_log.write().clear();
296 }
297
298 pub fn cache_stats(&self) -> (usize, usize) {
300 let cache = self.cache.read();
301 let total = cache.len();
302 let valid = cache.values().filter(|c| c.is_valid()).count();
303 (total, valid)
304 }
305
306 pub fn clear_cache(&self) {
308 self.cache.write().clear();
309 }
310
311 pub fn request_command(&self, command: &str, args: Vec<String>) -> PermissionRequest {
313 PermissionRequest::new(PermissionAction::CommandExecute {
314 command: command.to_string(),
315 args,
316 })
317 }
318
319 pub fn request_file_read(&self, path: &str) -> PermissionRequest {
321 PermissionRequest::new(PermissionAction::FileRead {
322 path: path.to_string(),
323 })
324 }
325
326 pub fn request_file_write(
328 &self,
329 path: &str,
330 content_preview: Option<&str>,
331 ) -> PermissionRequest {
332 PermissionRequest::new(PermissionAction::FileWrite {
333 path: path.to_string(),
334 content_preview: content_preview.map(|s| s.to_string()),
335 })
336 }
337
338 pub fn request_file_delete(&self, path: &str) -> PermissionRequest {
340 PermissionRequest::new(PermissionAction::FileDelete {
341 path: path.to_string(),
342 })
343 }
344
345 pub fn request_network(&self, url: &str, method: &str) -> PermissionRequest {
347 PermissionRequest::new(PermissionAction::NetworkRequest {
348 url: url.to_string(),
349 method: method.to_string(),
350 })
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 #[test]
359 fn test_permission_manager_creation() {
360 let manager = PermissionManager::default();
361 assert_eq!(manager.security_level(), SecurityLevel::Standard);
362 }
363
364 #[test]
365 fn test_trusted_policy_auto_approves() {
366 let manager = PermissionManager::new(PermissionPolicy::trusted());
367 let request = PermissionRequest::new(PermissionAction::FileRead {
368 path: "/test/file.txt".to_string(),
369 });
370
371 let result = manager.check_permission(request);
372 assert!(result.is_ok());
373 }
374
375 #[test]
376 fn test_blocked_path_denied() {
377 let manager = PermissionManager::default();
378 let request = PermissionRequest::new(PermissionAction::FileRead {
379 path: ".env".to_string(),
380 });
381
382 let result = manager.check_permission(request);
383 assert!(result.is_err());
384 assert!(matches!(
385 result.unwrap_err(),
386 PermissionError::BlockedByPolicy(_)
387 ));
388 }
389
390 #[test]
391 fn test_cache_stats() {
392 let manager = PermissionManager::default();
393 let (total, valid) = manager.cache_stats();
394 assert_eq!(total, 0);
395 assert_eq!(valid, 0);
396 }
397}