1use crate::{Error, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::path::{Path, PathBuf};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum PermissionLevel {
15 None = 0,
17
18 Read = 1,
20
21 Restricted = 2,
23
24 Standard = 3,
26
27 Elevated = 4,
29
30 Admin = 5,
32}
33
34impl Default for PermissionLevel {
35 fn default() -> Self {
36 PermissionLevel::Restricted
37 }
38}
39
40impl std::str::FromStr for PermissionLevel {
41 type Err = Error;
42
43 fn from_str(s: &str) -> Result<Self> {
44 match s.to_lowercase().as_str() {
45 "none" => Ok(PermissionLevel::None),
46 "read" => Ok(PermissionLevel::Read),
47 "restricted" => Ok(PermissionLevel::Restricted),
48 "standard" => Ok(PermissionLevel::Standard),
49 "elevated" => Ok(PermissionLevel::Elevated),
50 "admin" => Ok(PermissionLevel::Admin),
51 _ => Err(Error::Other(anyhow::anyhow!("Invalid permission level: {}", s))),
52 }
53 }
54}
55
56impl std::fmt::Display for PermissionLevel {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 PermissionLevel::None => write!(f, "none"),
60 PermissionLevel::Read => write!(f, "read"),
61 PermissionLevel::Restricted => write!(f, "restricted"),
62 PermissionLevel::Standard => write!(f, "standard"),
63 PermissionLevel::Elevated => write!(f, "elevated"),
64 PermissionLevel::Admin => write!(f, "admin"),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Permission {
72 pub resource: String,
74
75 pub operation: String,
77
78 pub level: PermissionLevel,
80
81 pub constraints: Vec<PermissionConstraint>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(tag = "type", rename_all = "snake_case")]
88pub enum PermissionConstraint {
89 PathConstraint {
91 allowed: Vec<String>,
93 denied: Vec<String>,
95 },
96
97 SizeConstraint {
99 max_size: u64,
101 },
102
103 TimeConstraint {
105 max_duration: u64,
107 },
108
109 NetworkConstraint {
111 allowed: bool,
113 allowed_hosts: Vec<String>,
115 denied_hosts: Vec<String>,
117 },
118
119 ResourceConstraint {
121 max_memory_mb: Option<u64>,
123 max_cpu_percent: Option<u32>,
125 },
126
127 CustomConstraint {
129 name: String,
131 params: HashMap<String, serde_json::Value>,
133 },
134}
135
136#[derive(Debug, Clone)]
138pub struct PermissionContext {
139 pub user_id: String,
141
142 pub session_id: String,
144
145 pub working_dir: PathBuf,
147
148 pub resource: String,
150
151 pub operation: String,
153
154 pub params: HashMap<String, serde_json::Value>,
156
157 pub current_level: PermissionLevel,
159}
160
161impl PermissionContext {
162 pub fn new(
164 user_id: String,
165 session_id: String,
166 working_dir: PathBuf,
167 resource: String,
168 operation: String,
169 ) -> Self {
170 Self {
171 user_id,
172 session_id,
173 working_dir,
174 resource,
175 operation,
176 params: HashMap::new(),
177 current_level: PermissionLevel::default(),
178 }
179 }
180
181 pub fn with_param<K, V>(mut self, key: K, value: V) -> Self
183 where
184 K: Into<String>,
185 V: Into<serde_json::Value>,
186 {
187 self.params.insert(key.into(), value.into());
188 self
189 }
190
191 pub fn with_level(mut self, level: PermissionLevel) -> Self {
193 self.current_level = level;
194 self
195 }
196}
197
198pub struct PermissionManager {
200 default_level: PermissionLevel,
202
203 resource_permissions: HashMap<String, HashMap<String, Permission>>,
205
206 user_permissions: HashMap<String, HashMap<String, PermissionLevel>>,
208
209 global_constraints: Vec<PermissionConstraint>,
211}
212
213impl PermissionManager {
214 pub fn new(default_level: PermissionLevel) -> Self {
216 Self {
217 default_level,
218 resource_permissions: HashMap::new(),
219 user_permissions: HashMap::new(),
220 global_constraints: Vec::new(),
221 }
222 }
223
224 pub fn add_permission(
226 &mut self,
227 resource: String,
228 operation: String,
229 permission: Permission,
230 ) {
231 self.resource_permissions
232 .entry(resource)
233 .or_insert_with(HashMap::new)
234 .insert(operation, permission);
235 }
236
237 pub fn set_user_permission(
239 &mut self,
240 user_id: String,
241 resource: String,
242 level: PermissionLevel,
243 ) {
244 self.user_permissions
245 .entry(user_id)
246 .or_insert_with(HashMap::new)
247 .insert(resource, level);
248 }
249
250 pub fn add_global_constraint(&mut self, constraint: PermissionConstraint) {
252 self.global_constraints.push(constraint);
253 }
254
255 pub fn check_permission(&self, context: &PermissionContext) -> Result<bool> {
257 let required_level = self.get_required_level(context);
259
260 let user_level = self.get_user_level(context);
262
263 if user_level < required_level {
265 return Ok(false);
266 }
267
268 self.check_constraints(context)?;
270
271 Ok(true)
272 }
273
274 pub fn enforce_permission(&self, context: &PermissionContext) -> Result<()> {
276 if !self.check_permission(context)? {
277 return Err(Error::Other(anyhow::anyhow!(
278 "Permission denied for operation '{}' on resource '{}'",
279 context.operation,
280 context.resource
281 )));
282 }
283 Ok(())
284 }
285
286 fn get_required_level(&self, context: &PermissionContext) -> PermissionLevel {
288 if let Some(resource_perms) = self.resource_permissions.get(&context.resource) {
289 if let Some(permission) = resource_perms.get(&context.operation) {
290 return permission.level;
291 }
292 }
293
294 if let Some(resource_perms) = self.resource_permissions.get(&context.resource) {
296 if let Some(permission) = resource_perms.get("*") {
297 return permission.level;
298 }
299 }
300
301 if let Some(resource_perms) = self.resource_permissions.get("*") {
303 if let Some(permission) = resource_perms.get(&context.operation) {
304 return permission.level;
305 }
306 if let Some(permission) = resource_perms.get("*") {
307 return permission.level;
308 }
309 }
310
311 self.default_level
312 }
313
314 fn get_user_level(&self, context: &PermissionContext) -> PermissionLevel {
316 if let Some(user_perms) = self.user_permissions.get(&context.user_id) {
318 if let Some(&level) = user_perms.get(&context.resource) {
319 return level;
320 }
321 if let Some(&level) = user_perms.get("*") {
322 return level;
323 }
324 }
325
326 context.current_level
327 }
328
329 fn check_constraints(&self, context: &PermissionContext) -> Result<()> {
331 for constraint in &self.global_constraints {
333 self.check_constraint(constraint, context)?;
334 }
335
336 if let Some(resource_perms) = self.resource_permissions.get(&context.resource) {
338 if let Some(permission) = resource_perms.get(&context.operation) {
339 for constraint in &permission.constraints {
340 self.check_constraint(constraint, context)?;
341 }
342 }
343 }
344
345 Ok(())
346 }
347
348 fn check_constraint(
350 &self,
351 constraint: &PermissionConstraint,
352 context: &PermissionContext,
353 ) -> Result<()> {
354 match constraint {
355 PermissionConstraint::PathConstraint { allowed, denied } => {
356 self.check_path_constraint(allowed, denied, context)?;
357 }
358 PermissionConstraint::SizeConstraint { max_size } => {
359 self.check_size_constraint(*max_size, context)?;
360 }
361 PermissionConstraint::TimeConstraint { max_duration } => {
362 self.check_time_constraint(*max_duration, context)?;
363 }
364 PermissionConstraint::NetworkConstraint {
365 allowed,
366 allowed_hosts,
367 denied_hosts,
368 } => {
369 self.check_network_constraint(*allowed, allowed_hosts, denied_hosts, context)?;
370 }
371 PermissionConstraint::ResourceConstraint {
372 max_memory_mb,
373 max_cpu_percent,
374 } => {
375 self.check_resource_constraint(*max_memory_mb, *max_cpu_percent, context)?;
376 }
377 PermissionConstraint::CustomConstraint { name, params } => {
378 self.check_custom_constraint(name, params, context)?;
379 }
380 }
381 Ok(())
382 }
383
384 fn check_path_constraint(
386 &self,
387 allowed: &[String],
388 denied: &[String],
389 context: &PermissionContext,
390 ) -> Result<()> {
391 let target_path = if let Some(path_value) = context.params.get("path") {
393 PathBuf::from(path_value.as_str().unwrap_or(""))
394 } else {
395 return Ok(()); };
397
398 let abs_path = if target_path.is_absolute() {
400 target_path
401 } else {
402 context.working_dir.join(target_path)
403 };
404
405 for pattern in denied {
407 if self.path_matches_pattern(&abs_path, pattern)? {
408 return Err(Error::Other(anyhow::anyhow!(
409 "Path {} is denied by pattern {}",
410 abs_path.display(),
411 pattern
412 )));
413 }
414 }
415
416 if !allowed.is_empty() {
418 let mut path_allowed = false;
419 for pattern in allowed {
420 if self.path_matches_pattern(&abs_path, pattern)? {
421 path_allowed = true;
422 break;
423 }
424 }
425 if !path_allowed {
426 return Err(Error::Other(anyhow::anyhow!(
427 "Path {} is not allowed by any pattern",
428 abs_path.display()
429 )));
430 }
431 }
432
433 Ok(())
434 }
435
436 fn path_matches_pattern(&self, path: &Path, pattern: &str) -> Result<bool> {
438 let pattern = glob::Pattern::new(pattern)
440 .map_err(|e| Error::Other(anyhow::anyhow!("Invalid glob pattern {}: {}", pattern, e)))?;
441
442 Ok(pattern.matches_path(path))
443 }
444
445 fn check_size_constraint(&self, max_size: u64, context: &PermissionContext) -> Result<()> {
447 if let Some(size_value) = context.params.get("size") {
448 if let Some(size) = size_value.as_u64() {
449 if size > max_size {
450 return Err(Error::Other(anyhow::anyhow!(
451 "Size {} exceeds maximum allowed size {}",
452 size,
453 max_size
454 )));
455 }
456 }
457 }
458 Ok(())
459 }
460
461 fn check_time_constraint(&self, max_duration: u64, context: &PermissionContext) -> Result<()> {
463 if let Some(duration_value) = context.params.get("duration") {
464 if let Some(duration) = duration_value.as_u64() {
465 if duration > max_duration {
466 return Err(Error::Other(anyhow::anyhow!(
467 "Duration {} exceeds maximum allowed duration {}",
468 duration,
469 max_duration
470 )));
471 }
472 }
473 }
474 Ok(())
475 }
476
477 fn check_network_constraint(
479 &self,
480 allowed: bool,
481 allowed_hosts: &[String],
482 denied_hosts: &[String],
483 context: &PermissionContext,
484 ) -> Result<()> {
485 if !allowed {
486 if context.params.contains_key("network") {
487 return Err(Error::Other(anyhow::anyhow!("Network access is not allowed")));
488 }
489 }
490
491 if let Some(host_value) = context.params.get("host") {
492 if let Some(host) = host_value.as_str() {
493 for denied_host in denied_hosts {
495 if host.contains(denied_host) {
496 return Err(Error::Other(anyhow::anyhow!(
497 "Host {} is denied",
498 host
499 )));
500 }
501 }
502
503 if !allowed_hosts.is_empty() {
505 let mut host_allowed = false;
506 for allowed_host in allowed_hosts {
507 if host.contains(allowed_host) {
508 host_allowed = true;
509 break;
510 }
511 }
512 if !host_allowed {
513 return Err(Error::Other(anyhow::anyhow!(
514 "Host {} is not in allowed hosts list",
515 host
516 )));
517 }
518 }
519 }
520 }
521
522 Ok(())
523 }
524
525 fn check_resource_constraint(
527 &self,
528 max_memory_mb: Option<u64>,
529 max_cpu_percent: Option<u32>,
530 context: &PermissionContext,
531 ) -> Result<()> {
532 if let Some(max_mem) = max_memory_mb {
533 if let Some(memory_value) = context.params.get("memory_mb") {
534 if let Some(memory) = memory_value.as_u64() {
535 if memory > max_mem {
536 return Err(Error::Other(anyhow::anyhow!(
537 "Memory usage {} MB exceeds maximum {}MB",
538 memory,
539 max_mem
540 )));
541 }
542 }
543 }
544 }
545
546 if let Some(max_cpu) = max_cpu_percent {
547 if let Some(cpu_value) = context.params.get("cpu_percent") {
548 if let Some(cpu) = cpu_value.as_u64() {
549 if cpu > max_cpu as u64 {
550 return Err(Error::Other(anyhow::anyhow!(
551 "CPU usage {}% exceeds maximum {}%",
552 cpu,
553 max_cpu
554 )));
555 }
556 }
557 }
558 }
559
560 Ok(())
561 }
562
563 fn check_custom_constraint(
565 &self,
566 _name: &str,
567 _params: &HashMap<String, serde_json::Value>,
568 _context: &PermissionContext,
569 ) -> Result<()> {
570 Ok(())
573 }
574
575 pub fn with_defaults() -> Self {
577 let mut manager = Self::new(PermissionLevel::Restricted);
578
579 manager.add_permission(
581 "file".to_string(),
582 "read".to_string(),
583 Permission {
584 resource: "file".to_string(),
585 operation: "read".to_string(),
586 level: PermissionLevel::Read,
587 constraints: vec![],
588 },
589 );
590
591 manager.add_permission(
592 "file".to_string(),
593 "write".to_string(),
594 Permission {
595 resource: "file".to_string(),
596 operation: "write".to_string(),
597 level: PermissionLevel::Standard,
598 constraints: vec![
599 PermissionConstraint::SizeConstraint { max_size: 10 * 1024 * 1024 }, ],
601 },
602 );
603
604 manager.add_permission(
605 "bash".to_string(),
606 "execute".to_string(),
607 Permission {
608 resource: "bash".to_string(),
609 operation: "execute".to_string(),
610 level: PermissionLevel::Elevated,
611 constraints: vec![
612 PermissionConstraint::TimeConstraint { max_duration: 300 }, PermissionConstraint::NetworkConstraint {
614 allowed: false,
615 allowed_hosts: vec![],
616 denied_hosts: vec![],
617 },
618 ],
619 },
620 );
621
622 manager.add_permission(
623 "web".to_string(),
624 "fetch".to_string(),
625 Permission {
626 resource: "web".to_string(),
627 operation: "fetch".to_string(),
628 level: PermissionLevel::Standard,
629 constraints: vec![
630 PermissionConstraint::NetworkConstraint {
631 allowed: true,
632 allowed_hosts: vec![],
633 denied_hosts: vec!["localhost".to_string(), "127.0.0.1".to_string()],
634 },
635 ],
636 },
637 );
638
639 manager
640 }
641}
642
643impl Default for PermissionManager {
644 fn default() -> Self {
645 Self::with_defaults()
646 }
647}