cersei_tools/
permissions.rs1use super::PermissionLevel;
4use async_trait::async_trait;
5use serde::{Deserialize, Serialize};
6
7#[async_trait]
10pub trait PermissionPolicy: Send + Sync {
11 async fn check(&self, request: &PermissionRequest) -> PermissionDecision;
12}
13
14#[derive(Debug, Clone)]
15pub struct PermissionRequest {
16 pub tool_name: String,
17 pub tool_input: serde_json::Value,
18 pub permission_level: PermissionLevel,
19 pub description: String,
20 pub id: String,
21}
22
23#[derive(Debug, Clone)]
24pub enum PermissionDecision {
25 Allow,
26 Deny(String),
27 AllowOnce,
28 AllowForSession,
29}
30
31pub struct AllowAll;
35
36#[async_trait]
37impl PermissionPolicy for AllowAll {
38 async fn check(&self, _request: &PermissionRequest) -> PermissionDecision {
39 PermissionDecision::Allow
40 }
41}
42
43pub struct AllowReadOnly;
45
46#[async_trait]
47impl PermissionPolicy for AllowReadOnly {
48 async fn check(&self, request: &PermissionRequest) -> PermissionDecision {
49 match request.permission_level {
50 PermissionLevel::None | PermissionLevel::ReadOnly => PermissionDecision::Allow,
51 _ => PermissionDecision::Deny(format!(
52 "Tool '{}' requires {:?} permission (read-only mode)",
53 request.tool_name, request.permission_level
54 )),
55 }
56 }
57}
58
59pub struct DenyAll;
61
62#[async_trait]
63impl PermissionPolicy for DenyAll {
64 async fn check(&self, request: &PermissionRequest) -> PermissionDecision {
65 PermissionDecision::Deny(format!(
66 "Tool '{}' blocked by DenyAll policy",
67 request.tool_name
68 ))
69 }
70}
71
72pub struct RuleBased {
74 pub rules: Vec<PermissionRule>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct PermissionRule {
79 pub tool_name: Option<String>,
80 pub path_pattern: Option<String>,
81 pub action: PermissionAction,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub enum PermissionAction {
86 Allow,
87 Deny,
88}
89
90#[async_trait]
91impl PermissionPolicy for RuleBased {
92 async fn check(&self, request: &PermissionRequest) -> PermissionDecision {
93 for rule in &self.rules {
94 let name_matches = rule
95 .tool_name
96 .as_ref()
97 .map(|n| n == &request.tool_name || n == "all")
98 .unwrap_or(true);
99
100 if name_matches {
101 return match rule.action {
102 PermissionAction::Allow => PermissionDecision::Allow,
103 PermissionAction::Deny => PermissionDecision::Deny(format!(
104 "Tool '{}' blocked by rule",
105 request.tool_name
106 )),
107 };
108 }
109 }
110 PermissionDecision::Allow
112 }
113}
114
115pub struct InteractivePolicy {
117 pub handler: Box<dyn Fn(&PermissionRequest) -> PermissionDecision + Send + Sync>,
118}
119
120impl InteractivePolicy {
121 pub fn new(
122 handler: impl Fn(&PermissionRequest) -> PermissionDecision + Send + Sync + 'static,
123 ) -> Self {
124 Self {
125 handler: Box::new(handler),
126 }
127 }
128
129 pub fn via_stream() -> StreamDeferredPolicy {
131 StreamDeferredPolicy
132 }
133}
134
135#[async_trait]
136impl PermissionPolicy for InteractivePolicy {
137 async fn check(&self, request: &PermissionRequest) -> PermissionDecision {
138 (self.handler)(request)
139 }
140}
141
142pub struct StreamDeferredPolicy;
144
145#[async_trait]
146impl PermissionPolicy for StreamDeferredPolicy {
147 async fn check(&self, _request: &PermissionRequest) -> PermissionDecision {
148 PermissionDecision::Allow
151 }
152}