1use std::collections::HashMap;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum PermissionMode {
5 Default,
6 Plan,
7 Auto,
8}
9
10#[derive(Debug, Clone)]
11pub struct Permission {
12 pub tool: String,
13 pub action: String,
14 pub allowed: bool,
15 pub requires_approval: bool,
16 pub note: String,
17}
18
19#[derive(Debug, Clone)]
20pub struct PermissionDecision {
21 pub allowed: bool,
22 pub reason: String,
23 pub requires_approval: bool,
24 pub matched_permission: Option<Permission>,
25}
26
27pub struct PermissionManager {
28 mode: PermissionMode,
29 permissions: HashMap<String, Permission>,
30}
31
32impl PermissionManager {
33 pub fn new(mode: PermissionMode) -> Self {
34 Self {
35 mode,
36 permissions: Default::default(),
37 }
38 }
39
40 pub fn grant(&mut self, resource: impl Into<String>, action: impl Into<String>) {
41 let tool = resource.into();
42 let action = action.into();
43 let key = format!("{tool}:{action}");
44 self.permissions.insert(
45 key,
46 Permission {
47 tool,
48 action,
49 allowed: true,
50 requires_approval: false,
51 note: String::new(),
52 },
53 );
54 }
55
56 pub fn grant_with_approval(
57 &mut self,
58 resource: impl Into<String>,
59 action: impl Into<String>,
60 note: impl Into<String>,
61 ) {
62 let tool = resource.into();
63 let action = action.into();
64 let key = format!("{tool}:{action}");
65 self.permissions.insert(
66 key,
67 Permission {
68 tool,
69 action,
70 allowed: true,
71 requires_approval: true,
72 note: note.into(),
73 },
74 );
75 }
76
77 pub fn revoke(&mut self, resource: &str, action: &str) {
78 let key = format!("{resource}:{action}");
79 self.permissions.insert(
80 key,
81 Permission {
82 tool: resource.to_string(),
83 action: action.to_string(),
84 allowed: false,
85 requires_approval: false,
86 note: String::new(),
87 },
88 );
89 }
90
91 fn match_permission(&self, tool: &str, action: &str) -> Option<Permission> {
92 for key in [
93 format!("{tool}:{action}"),
94 format!("{tool}:*"),
95 format!("*:{action}"),
96 format!("*:*"),
97 ] {
98 if let Some(p) = self.permissions.get(&key) {
99 return Some(p.clone());
100 }
101 }
102 None
103 }
104
105 pub fn evaluate(&self, resource: &str, action: &str) -> PermissionDecision {
106 match self.mode {
107 PermissionMode::Auto => PermissionDecision {
108 allowed: true,
109 reason: "AUTO mode".into(),
110 requires_approval: false,
111 matched_permission: None,
112 },
113 PermissionMode::Plan => PermissionDecision {
114 allowed: false,
115 reason: "PLAN mode blocks all".into(),
116 requires_approval: false,
117 matched_permission: None,
118 },
119 PermissionMode::Default => match self.match_permission(resource, action) {
120 None => PermissionDecision {
121 allowed: false,
122 reason: "not granted".into(),
123 requires_approval: false,
124 matched_permission: None,
125 },
126 Some(p) if !p.allowed => PermissionDecision {
127 allowed: false,
128 reason: if p.note.is_empty() {
129 "permission denied".into()
130 } else {
131 p.note.clone()
132 },
133 requires_approval: false,
134 matched_permission: Some(p),
135 },
136 Some(p) if p.requires_approval => PermissionDecision {
137 allowed: false,
138 reason: if p.note.is_empty() {
139 "requires approval".into()
140 } else {
141 p.note.clone()
142 },
143 requires_approval: true,
144 matched_permission: Some(p),
145 },
146 Some(p) => PermissionDecision {
147 allowed: true,
148 reason: "granted".into(),
149 requires_approval: false,
150 matched_permission: Some(p),
151 },
152 },
153 }
154 }
155}