1use serde::{Deserialize, Serialize};
7use std::fmt;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11use crate::permissions::strategies::{
12 AcceptEditsModeStrategy, BypassPermissionsModeStrategy, DefaultModeStrategy,
13 DontAskModeStrategy, PermissionModeStrategy, PlanModeStrategy,
14};
15use crate::settings::{PermissionChecker, PermissionDecision};
16use claude_code_agent_sdk::PermissionMode as SdkPermissionMode;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
22#[serde(rename_all = "camelCase")]
23pub enum PermissionMode {
24 Default,
26 AcceptEdits,
28 Plan,
30 DontAsk,
32 #[default]
34 BypassPermissions,
35}
36
37impl PermissionMode {
38 pub fn parse(s: &str) -> Option<Self> {
40 match s {
41 "default" => Some(Self::Default),
42 "acceptEdits" => Some(Self::AcceptEdits),
43 "plan" => Some(Self::Plan),
44 "dontAsk" => Some(Self::DontAsk),
45 "bypassPermissions" => Some(Self::BypassPermissions),
46 _ => None,
47 }
48 }
49
50 pub fn as_str(&self) -> &'static str {
52 match self {
53 Self::Default => "default",
54 Self::AcceptEdits => "acceptEdits",
55 Self::Plan => "plan",
56 Self::DontAsk => "dontAsk",
57 Self::BypassPermissions => "bypassPermissions",
58 }
59 }
60
61 pub fn to_sdk_mode(&self) -> SdkPermissionMode {
65 match self {
66 PermissionMode::Default => SdkPermissionMode::Default,
67 PermissionMode::AcceptEdits => SdkPermissionMode::AcceptEdits,
68 PermissionMode::Plan => SdkPermissionMode::Plan,
69 PermissionMode::DontAsk => {
70 SdkPermissionMode::Default
72 }
73 PermissionMode::BypassPermissions => SdkPermissionMode::BypassPermissions,
74 }
75 }
76
77 pub fn allows_writes(&self) -> bool {
79 matches!(
80 self,
81 Self::Default | Self::AcceptEdits | Self::BypassPermissions
82 )
83 }
84
85 pub fn auto_approve_edits(&self) -> bool {
87 matches!(self, Self::AcceptEdits | Self::BypassPermissions)
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93pub enum ToolPermissionResult {
94 Allowed,
96 Blocked { reason: String },
98 NeedsPermission,
100}
101
102pub struct PermissionHandler {
106 mode: PermissionMode,
107 strategy: Arc<dyn PermissionModeStrategy>,
109 checker: Option<Arc<RwLock<PermissionChecker>>>,
111}
112
113impl fmt::Debug for PermissionHandler {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 f.debug_struct("PermissionHandler")
116 .field("mode", &self.mode)
117 .field("strategy", &"<strategy>")
118 .field("checker", &self.checker)
119 .finish()
120 }
121}
122
123impl Default for PermissionHandler {
124 fn default() -> Self {
125 Self {
126 mode: PermissionMode::Default,
127 strategy: Arc::new(DefaultModeStrategy),
128 checker: None,
129 }
130 }
131}
132
133impl PermissionHandler {
134 pub fn new() -> Self {
138 Self::default()
139 }
140
141 pub fn with_mode(mode: PermissionMode) -> Self {
143 Self {
144 mode,
145 strategy: Self::create_strategy(mode),
146 checker: None,
147 }
148 }
149
150 pub fn with_checker(checker: Arc<RwLock<PermissionChecker>>) -> Self {
154 Self {
155 mode: PermissionMode::Default,
156 strategy: Arc::new(DefaultModeStrategy),
157 checker: Some(checker),
158 }
159 }
160
161 pub fn with_checker_owned(checker: PermissionChecker) -> Self {
165 Self {
166 mode: PermissionMode::Default,
167 strategy: Arc::new(DefaultModeStrategy),
168 checker: Some(Arc::new(RwLock::new(checker))),
169 }
170 }
171
172 fn create_strategy(mode: PermissionMode) -> Arc<dyn PermissionModeStrategy> {
174 match mode {
175 PermissionMode::Default => Arc::new(DefaultModeStrategy),
176 PermissionMode::AcceptEdits => Arc::new(AcceptEditsModeStrategy),
177 PermissionMode::Plan => Arc::new(PlanModeStrategy),
178 PermissionMode::DontAsk => Arc::new(DontAskModeStrategy),
179 PermissionMode::BypassPermissions => Arc::new(BypassPermissionsModeStrategy),
180 }
181 }
182
183 pub fn mode(&self) -> PermissionMode {
185 self.mode
186 }
187
188 pub fn set_mode(&mut self, mode: PermissionMode) {
190 self.mode = mode;
191 self.strategy = Self::create_strategy(mode);
192 }
193
194 pub fn set_checker(&mut self, checker: Arc<RwLock<PermissionChecker>>) {
196 self.checker = Some(checker);
197 }
198
199 pub async fn checker_mut(
201 &mut self,
202 ) -> Option<tokio::sync::RwLockWriteGuard<'_, PermissionChecker>> {
203 if let Some(ref checker) = self.checker {
204 Some(checker.write().await)
205 } else {
206 None
207 }
208 }
209
210 pub fn should_auto_approve(&self, tool_name: &str, input: &serde_json::Value) -> bool {
216 self.strategy.should_auto_approve(tool_name, input)
217 }
218
219 pub fn is_tool_blocked(&self, tool_name: &str) -> bool {
227 self.strategy
228 .is_tool_blocked(tool_name, &serde_json::Value::Null)
229 .is_some()
230 }
231
232 pub async fn check_permission(
237 &self,
238 tool_name: &str,
239 tool_input: &serde_json::Value,
240 ) -> ToolPermissionResult {
241 if let Some(ref checker) = self.checker {
243 let checker_read = checker.read().await;
244 let result = checker_read.check_permission(tool_name, tool_input);
245 match result.decision {
246 PermissionDecision::Deny => {
247 return ToolPermissionResult::Blocked {
248 reason: result
249 .rule
250 .map(|r| format!("Denied by rule: {}", r))
251 .unwrap_or_else(|| "Denied by settings".to_string()),
252 };
253 }
254 PermissionDecision::Allow => {
255 return ToolPermissionResult::Allowed;
256 }
257 PermissionDecision::Ask => {
258 }
260 }
261 }
262
263 let strategy_result = self.strategy.check_permission(tool_name, tool_input);
265
266 if self.mode == PermissionMode::DontAsk {
268 if strategy_result == ToolPermissionResult::NeedsPermission {
269 return ToolPermissionResult::Blocked {
270 reason: "Tool not pre-approved by settings rules in DontAsk mode".to_string(),
271 };
272 }
273 }
274
275 if matches!(
277 tool_name,
278 "AskUserQuestion" | "Task" | "TodoWrite" | "SlashCommand"
279 ) {
280 return ToolPermissionResult::Allowed;
281 }
282
283 strategy_result
284 }
285
286 pub async fn add_allow_rule(&self, tool_name: &str) {
288 if let Some(ref checker) = self.checker {
289 let mut checker_write = checker.write().await;
290 checker_write.add_allow_rule(tool_name);
291 }
292 }
293
294 pub fn add_allow_rule_for_tool_call(&self, tool_name: &str, tool_input: &serde_json::Value) {
297 if let Some(ref checker) = self.checker {
298 if let Ok(mut checker_write) = checker.try_write() {
300 checker_write.add_allow_rule_for_tool_call(tool_name, tool_input);
301 }
302 }
303 }
304}
305
306#[cfg(test)]
307mod tests {
308 use super::*;
309 use serde_json::json;
310
311 #[test]
312 fn test_permission_mode_parse() {
313 assert_eq!(
314 PermissionMode::parse("default"),
315 Some(PermissionMode::Default)
316 );
317 assert_eq!(
318 PermissionMode::parse("acceptEdits"),
319 Some(PermissionMode::AcceptEdits)
320 );
321 assert_eq!(PermissionMode::parse("plan"), Some(PermissionMode::Plan));
322 assert_eq!(
323 PermissionMode::parse("bypassPermissions"),
324 Some(PermissionMode::BypassPermissions)
325 );
326 assert_eq!(PermissionMode::parse("invalid"), None);
327 }
328
329 #[test]
330 fn test_permission_mode_str() {
331 assert_eq!(PermissionMode::Default.as_str(), "default");
332 assert_eq!(PermissionMode::AcceptEdits.as_str(), "acceptEdits");
333 assert_eq!(PermissionMode::Plan.as_str(), "plan");
334 assert_eq!(
335 PermissionMode::BypassPermissions.as_str(),
336 "bypassPermissions"
337 );
338 }
339
340 #[test]
341 fn test_permission_handler_default() {
342 let handler = PermissionHandler::new();
343 let input = json!({});
344
345 assert!(handler.should_auto_approve("Read", &input));
347 assert!(handler.should_auto_approve("Glob", &input));
348 assert!(handler.should_auto_approve("Grep", &input));
349 assert!(handler.should_auto_approve("LS", &input));
350 assert!(handler.should_auto_approve("NotebookRead", &input));
351 assert!(!handler.should_auto_approve("Edit", &input));
353 assert!(!handler.should_auto_approve("Bash", &input));
354 }
355
356 #[test]
357 fn test_permission_handler_accept_edits() {
358 let handler = PermissionHandler::with_mode(PermissionMode::AcceptEdits);
359 let input = json!({});
360
361 assert!(handler.should_auto_approve("Read", &input));
364 assert!(handler.should_auto_approve("Edit", &input));
365 assert!(handler.should_auto_approve("Write", &input));
366 assert!(handler.should_auto_approve("Bash", &input));
367 }
368
369 #[test]
370 fn test_permission_handler_bypass() {
371 let handler = PermissionHandler::with_mode(PermissionMode::BypassPermissions);
372 let input = json!({});
373
374 assert!(handler.should_auto_approve("Read", &input));
376 assert!(handler.should_auto_approve("Edit", &input));
377 assert!(handler.should_auto_approve("Bash", &input));
378 }
379
380 #[test]
381 fn test_permission_handler_plan_mode() {
382 let handler = PermissionHandler::with_mode(PermissionMode::Plan);
383 let input = json!({});
384
385 assert!(handler.should_auto_approve("Read", &input));
387 assert!(handler.should_auto_approve("Glob", &input));
388 assert!(handler.should_auto_approve("Grep", &input));
389 assert!(handler.should_auto_approve("LS", &input));
390 assert!(handler.should_auto_approve("NotebookRead", &input));
391 assert!(!handler.should_auto_approve("Edit", &input));
392
393 assert!(handler.is_tool_blocked("Edit"));
395 assert!(handler.is_tool_blocked("Bash"));
396 assert!(!handler.is_tool_blocked("Read"));
397 assert!(!handler.is_tool_blocked("LS"));
398 }
399
400 #[tokio::test]
401 async fn test_plan_mode_strategy_allows_plan_file_writes() {
402 let handler = PermissionHandler::with_mode(PermissionMode::Plan);
403 let home = dirs::home_dir().unwrap();
404 let plan_file = home.join(".claude").join("plans").join("test.md");
405
406 match handler
407 .check_permission(
408 "Write",
409 &json!({"file_path": plan_file.to_str().unwrap(), "content": "test"}),
410 )
411 .await
412 {
413 ToolPermissionResult::Allowed => {}
414 _ => panic!("Expected Allowed for plan file writes"),
415 }
416 }
417
418 #[tokio::test]
419 async fn test_plan_mode_strategy_blocks_non_plan_writes() {
420 let handler = PermissionHandler::with_mode(PermissionMode::Plan);
421
422 match handler
423 .check_permission("Write", &json!({"file_path": "/tmp/test.txt", "content": "test"}))
424 .await
425 {
426 ToolPermissionResult::Blocked { .. } => {}
427 _ => panic!("Expected Blocked for non-plan file writes"),
428 }
429 }
430
431 #[tokio::test]
432 async fn test_default_mode_strategy() {
433 let handler = PermissionHandler::new();
434
435 match handler.check_permission("Read", &json!({})).await {
437 ToolPermissionResult::Allowed => {}
438 _ => panic!("Expected Allowed for Read"),
439 }
440
441 match handler.check_permission("Write", &json!({})).await {
443 ToolPermissionResult::NeedsPermission => {}
444 _ => panic!("Expected NeedsPermission for Write"),
445 }
446 }
447
448 #[tokio::test]
449 async fn test_bypass_permissions_strategy() {
450 let handler = PermissionHandler::with_mode(PermissionMode::BypassPermissions);
451
452 match handler
454 .check_permission("Bash", &json!({"command": "rm -rf /"}))
455 .await
456 {
457 ToolPermissionResult::Allowed => {}
458 _ => panic!("Expected Allowed for Bash in BypassPermissions mode"),
459 }
460 }
461
462 #[tokio::test]
463 async fn test_accept_edits_strategy() {
464 let handler = PermissionHandler::with_mode(PermissionMode::AcceptEdits);
465
466 match handler.check_permission("Write", &json!({})).await {
468 ToolPermissionResult::Allowed => {}
469 _ => panic!("Expected Allowed for Write in AcceptEdits mode"),
470 }
471 }
472}