Skip to main content

a3s_code_core/permissions/
policy.rs

1use serde::{Deserialize, Serialize};
2
3use super::{MatchingRules, PermissionChecker, PermissionDecision, PermissionRule};
4
5/// Permission policy configuration
6///
7/// Evaluation order:
8/// 1. Deny rules - any match results in denial
9/// 2. Allow rules - any match results in auto-approval
10/// 3. Ask rules - any match requires user confirmation
11/// 4. Default - falls back to default_decision
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct PermissionPolicy {
14    /// Rules that always deny (checked first)
15    #[serde(default)]
16    pub deny: Vec<PermissionRule>,
17
18    /// Rules that auto-approve without confirmation
19    #[serde(default)]
20    pub allow: Vec<PermissionRule>,
21
22    /// Rules that always require confirmation
23    #[serde(default)]
24    pub ask: Vec<PermissionRule>,
25
26    /// Default decision when no rules match
27    #[serde(default = "default_decision")]
28    pub default_decision: PermissionDecision,
29
30    /// Whether the permission system is enabled
31    #[serde(default = "default_enabled")]
32    pub enabled: bool,
33}
34
35fn default_decision() -> PermissionDecision {
36    PermissionDecision::Ask
37}
38
39fn default_enabled() -> bool {
40    true
41}
42
43impl Default for PermissionPolicy {
44    fn default() -> Self {
45        Self {
46            deny: Vec::new(),
47            allow: Vec::new(),
48            ask: Vec::new(),
49            default_decision: PermissionDecision::Ask,
50            enabled: true,
51        }
52    }
53}
54
55impl PermissionPolicy {
56    /// Create a new permission policy
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Create a strict policy that asks for everything
62    pub fn strict() -> Self {
63        Self {
64            deny: Vec::new(),
65            allow: Vec::new(),
66            ask: Vec::new(),
67            default_decision: PermissionDecision::Ask,
68            enabled: true,
69        }
70    }
71
72    /// Add a deny rule
73    pub fn deny(mut self, rule: &str) -> Self {
74        self.deny.push(PermissionRule::new(rule));
75        self
76    }
77
78    /// Add an allow rule
79    pub fn allow(mut self, rule: &str) -> Self {
80        self.allow.push(PermissionRule::new(rule));
81        self
82    }
83
84    /// Add an ask rule
85    pub fn ask(mut self, rule: &str) -> Self {
86        self.ask.push(PermissionRule::new(rule));
87        self
88    }
89
90    /// Add multiple deny rules
91    pub fn deny_all(mut self, rules: &[&str]) -> Self {
92        for rule in rules {
93            self.deny.push(PermissionRule::new(rule));
94        }
95        self
96    }
97
98    /// Add multiple allow rules
99    pub fn allow_all(mut self, rules: &[&str]) -> Self {
100        for rule in rules {
101            self.allow.push(PermissionRule::new(rule));
102        }
103        self
104    }
105
106    /// Add multiple ask rules
107    pub fn ask_all(mut self, rules: &[&str]) -> Self {
108        for rule in rules {
109            self.ask.push(PermissionRule::new(rule));
110        }
111        self
112    }
113
114    /// Check permission for a tool invocation
115    ///
116    /// Returns the permission decision based on rule evaluation order:
117    /// 1. Deny rules (any match = Deny)
118    /// 2. Allow rules (any match = Allow)
119    /// 3. Ask rules (any match = Ask)
120    /// 4. Default decision
121    pub fn check(&self, tool_name: &str, args: &serde_json::Value) -> PermissionDecision {
122        if !self.enabled {
123            return PermissionDecision::Allow;
124        }
125
126        // 1. Check deny rules first
127        for rule in &self.deny {
128            if rule.matches(tool_name, args) {
129                return PermissionDecision::Deny;
130            }
131        }
132
133        // 2. Check allow rules
134        for rule in &self.allow {
135            if rule.matches(tool_name, args) {
136                return PermissionDecision::Allow;
137            }
138        }
139
140        // 3. Check ask rules
141        for rule in &self.ask {
142            if rule.matches(tool_name, args) {
143                return PermissionDecision::Ask;
144            }
145        }
146
147        // 4. Fall back to default
148        self.default_decision
149    }
150
151    /// Check if a tool invocation is allowed (Allow or not Deny)
152    pub fn is_allowed(&self, tool_name: &str, args: &serde_json::Value) -> bool {
153        matches!(self.check(tool_name, args), PermissionDecision::Allow)
154    }
155
156    /// Check if a tool invocation is denied
157    pub fn is_denied(&self, tool_name: &str, args: &serde_json::Value) -> bool {
158        matches!(self.check(tool_name, args), PermissionDecision::Deny)
159    }
160
161    /// Check if a tool invocation requires confirmation
162    pub fn requires_confirmation(&self, tool_name: &str, args: &serde_json::Value) -> bool {
163        matches!(self.check(tool_name, args), PermissionDecision::Ask)
164    }
165
166    /// Get matching rules for debugging/logging
167    pub fn get_matching_rules(&self, tool_name: &str, args: &serde_json::Value) -> MatchingRules {
168        let mut result = MatchingRules::default();
169
170        for rule in &self.deny {
171            if rule.matches(tool_name, args) {
172                result.deny.push(rule.rule.clone());
173            }
174        }
175
176        for rule in &self.allow {
177            if rule.matches(tool_name, args) {
178                result.allow.push(rule.rule.clone());
179            }
180        }
181
182        for rule in &self.ask {
183            if rule.matches(tool_name, args) {
184                result.ask.push(rule.rule.clone());
185            }
186        }
187
188        result
189    }
190}
191
192impl PermissionChecker for PermissionPolicy {
193    fn check(&self, tool_name: &str, args: &serde_json::Value) -> PermissionDecision {
194        self.check(tool_name, args)
195    }
196}