code_mesh_core/
permission.rs

1//! Permission system for Code Mesh Core
2//!
3//! This module provides a comprehensive permission system for controlling
4//! access to tools, resources, and operations within the Code Mesh ecosystem.
5
6use crate::{Error, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::{HashMap, HashSet};
9use std::path::{Path, PathBuf};
10
11/// Permission levels for operations
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum PermissionLevel {
15    /// No access allowed
16    None = 0,
17    
18    /// Read-only access
19    Read = 1,
20    
21    /// Restricted access with limitations
22    Restricted = 2,
23    
24    /// Standard access for normal operations
25    Standard = 3,
26    
27    /// Elevated access for advanced operations
28    Elevated = 4,
29    
30    /// Full administrative access
31    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/// Permission for a specific resource or operation
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Permission {
72    /// The resource being accessed
73    pub resource: String,
74    
75    /// The operation being performed
76    pub operation: String,
77    
78    /// Required permission level
79    pub level: PermissionLevel,
80    
81    /// Additional constraints
82    pub constraints: Vec<PermissionConstraint>,
83}
84
85/// Constraints that can be applied to permissions
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(tag = "type", rename_all = "snake_case")]
88pub enum PermissionConstraint {
89    /// Path-based constraints
90    PathConstraint {
91        /// Allowed paths or patterns
92        allowed: Vec<String>,
93        /// Denied paths or patterns
94        denied: Vec<String>,
95    },
96    
97    /// Size-based constraints
98    SizeConstraint {
99        /// Maximum size in bytes
100        max_size: u64,
101    },
102    
103    /// Time-based constraints
104    TimeConstraint {
105        /// Maximum execution time in seconds
106        max_duration: u64,
107    },
108    
109    /// Network-based constraints
110    NetworkConstraint {
111        /// Whether network access is allowed
112        allowed: bool,
113        /// Allowed hosts/domains
114        allowed_hosts: Vec<String>,
115        /// Denied hosts/domains
116        denied_hosts: Vec<String>,
117    },
118    
119    /// Resource-based constraints
120    ResourceConstraint {
121        /// Maximum memory usage in MB
122        max_memory_mb: Option<u64>,
123        /// Maximum CPU usage percentage
124        max_cpu_percent: Option<u32>,
125    },
126    
127    /// Custom constraints
128    CustomConstraint {
129        /// Constraint name
130        name: String,
131        /// Constraint parameters
132        params: HashMap<String, serde_json::Value>,
133    },
134}
135
136/// Context for permission evaluation
137#[derive(Debug, Clone)]
138pub struct PermissionContext {
139    /// User or session identifier
140    pub user_id: String,
141    
142    /// Session identifier
143    pub session_id: String,
144    
145    /// Current working directory
146    pub working_dir: PathBuf,
147    
148    /// Requested resource
149    pub resource: String,
150    
151    /// Requested operation
152    pub operation: String,
153    
154    /// Additional context parameters
155    pub params: HashMap<String, serde_json::Value>,
156    
157    /// Current permission level
158    pub current_level: PermissionLevel,
159}
160
161impl PermissionContext {
162    /// Create a new permission context
163    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    /// Add a parameter to the context
182    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    /// Set the current permission level
192    pub fn with_level(mut self, level: PermissionLevel) -> Self {
193        self.current_level = level;
194        self
195    }
196}
197
198/// Permission manager for evaluating and enforcing permissions
199pub struct PermissionManager {
200    /// Default permission level
201    default_level: PermissionLevel,
202    
203    /// Resource-specific permissions
204    resource_permissions: HashMap<String, HashMap<String, Permission>>,
205    
206    /// User-specific permission overrides
207    user_permissions: HashMap<String, HashMap<String, PermissionLevel>>,
208    
209    /// Global permission constraints
210    global_constraints: Vec<PermissionConstraint>,
211}
212
213impl PermissionManager {
214    /// Create a new permission manager
215    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    /// Add a permission rule for a resource and operation
225    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    /// Set user-specific permission level for a resource
238    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    /// Add a global constraint
251    pub fn add_global_constraint(&mut self, constraint: PermissionConstraint) {
252        self.global_constraints.push(constraint);
253    }
254
255    /// Check if an operation is permitted
256    pub fn check_permission(&self, context: &PermissionContext) -> Result<bool> {
257        // Get the required permission level
258        let required_level = self.get_required_level(context);
259        
260        // Get the user's permission level
261        let user_level = self.get_user_level(context);
262        
263        // Check if user has sufficient permission level
264        if user_level < required_level {
265            return Ok(false);
266        }
267
268        // Check constraints
269        self.check_constraints(context)?;
270
271        Ok(true)
272    }
273
274    /// Enforce permission (returns error if not permitted)
275    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    /// Get the required permission level for a context
287    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        // Check for wildcard operations
295        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        // Check for wildcard resources
302        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    /// Get the user's permission level for a context
315    fn get_user_level(&self, context: &PermissionContext) -> PermissionLevel {
316        // Check user-specific permissions
317        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    /// Check all applicable constraints
330    fn check_constraints(&self, context: &PermissionContext) -> Result<()> {
331        // Check global constraints
332        for constraint in &self.global_constraints {
333            self.check_constraint(constraint, context)?;
334        }
335
336        // Check resource-specific constraints
337        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    /// Check a specific constraint
349    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    /// Check path-based constraints
385    fn check_path_constraint(
386        &self,
387        allowed: &[String],
388        denied: &[String],
389        context: &PermissionContext,
390    ) -> Result<()> {
391        // Get the target path from context
392        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(()); // No path to check
396        };
397
398        // Make path absolute relative to working directory
399        let abs_path = if target_path.is_absolute() {
400            target_path
401        } else {
402            context.working_dir.join(target_path)
403        };
404
405        // Check denied patterns first
406        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 patterns are specified, check them
417        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    /// Check if a path matches a pattern (supports glob patterns)
437    fn path_matches_pattern(&self, path: &Path, pattern: &str) -> Result<bool> {
438        // Simple glob pattern matching
439        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    /// Check size-based constraints
446    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    /// Check time-based constraints
462    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    /// Check network-based constraints
478    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                // Check denied hosts first
494                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                // Check allowed hosts if specified
504                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    /// Check resource-based constraints
526    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    /// Check custom constraints (extensible)
564    fn check_custom_constraint(
565        &self,
566        _name: &str,
567        _params: &HashMap<String, serde_json::Value>,
568        _context: &PermissionContext,
569    ) -> Result<()> {
570        // Custom constraint checking would be implemented here
571        // This allows for extensible constraint types
572        Ok(())
573    }
574
575    /// Create a permission manager with common defaults
576    pub fn with_defaults() -> Self {
577        let mut manager = Self::new(PermissionLevel::Restricted);
578
579        // Add common permission rules
580        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 }, // 10MB
600                ],
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 }, // 5 minutes
613                    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}