github_copilot_sdk/
permission.rs1use std::sync::Arc;
16
17use async_trait::async_trait;
18
19use crate::handler::{PermissionHandler, PermissionResult, permission_handler_failure};
20use crate::types::{PermissionRequestData, RequestId, SessionId};
21
22pub fn approve_all() -> Arc<dyn PermissionHandler> {
28 Arc::new(PolicyHandler {
29 policy: Policy::ApproveAll,
30 })
31}
32
33pub fn deny_all() -> Arc<dyn PermissionHandler> {
35 Arc::new(PolicyHandler {
36 policy: Policy::DenyAll,
37 })
38}
39
40pub fn approve_if<F>(predicate: F) -> Arc<dyn PermissionHandler>
51where
52 F: Fn(&PermissionRequestData) -> bool + Send + Sync + 'static,
53{
54 Arc::new(PolicyHandler {
55 policy: Policy::Predicate(Arc::new(predicate)),
56 })
57}
58
59#[derive(Clone)]
67pub(crate) enum Policy {
68 ApproveAll,
69 DenyAll,
70 Predicate(Arc<dyn Fn(&PermissionRequestData) -> bool + Send + Sync>),
71}
72
73impl std::fmt::Debug for Policy {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 Self::ApproveAll => f.write_str("Policy::ApproveAll"),
77 Self::DenyAll => f.write_str("Policy::DenyAll"),
78 Self::Predicate(_) => f.write_str("Policy::Predicate(<fn>)"),
79 }
80 }
81}
82
83pub(crate) fn resolve_handler(
95 handler: Option<Arc<dyn PermissionHandler>>,
96 policy: Option<Policy>,
97) -> Option<Arc<dyn PermissionHandler>> {
98 match (handler, policy) {
99 (_, Some(policy)) => Some(Arc::new(PolicyHandler { policy })),
100 (handler, None) => handler,
101 }
102}
103
104struct PolicyHandler {
105 policy: Policy,
106}
107
108#[async_trait]
109impl PermissionHandler for PolicyHandler {
110 async fn handle(
111 &self,
112 _session_id: SessionId,
113 _request_id: RequestId,
114 data: PermissionRequestData,
115 ) -> PermissionResult {
116 let approved = match &self.policy {
117 Policy::ApproveAll => true,
118 Policy::DenyAll => false,
119 Policy::Predicate(f) => f(&data),
120 };
121 if approved {
122 if matches!(self.policy, Policy::ApproveAll) && data.managed_settings_enabled {
123 permission_handler_failure(
124 "approve-all policy cannot be used when managed settings are enabled",
125 )
126 } else if data.managed_approval_required == Some(true) {
127 PermissionResult::no_result()
128 } else {
129 PermissionResult::approve_once()
130 }
131 } else {
132 PermissionResult::reject(None)
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 fn data() -> PermissionRequestData {
142 PermissionRequestData {
143 extra: serde_json::json!({ "tool": "shell" }),
144 ..Default::default()
145 }
146 }
147
148 #[tokio::test]
149 async fn approve_all_approves() {
150 let h = approve_all();
151 assert!(matches!(
152 h.handle(SessionId::from("s"), RequestId::new("1"), data())
153 .await,
154 PermissionResult::Decision {
155 decision: crate::types::PermissionDecision::ApproveOnce(_),
156 ..
157 }
158 ));
159 }
160
161 #[tokio::test]
162 async fn approve_all_fails_when_managed_settings_enabled() {
163 let h = approve_all();
164 let mut request = data();
165 request.managed_settings_enabled = true;
166 assert!(matches!(
167 h.handle(SessionId::from("s"), RequestId::new("1"), request)
168 .await,
169 PermissionResult::Decision {
170 decision: crate::types::PermissionDecision::UserNotAvailable(_),
171 ..
172 }
173 ));
174 }
175
176 #[tokio::test]
177 async fn deny_all_denies() {
178 let h = deny_all();
179 assert!(matches!(
180 h.handle(SessionId::from("s"), RequestId::new("1"), data())
181 .await,
182 PermissionResult::Decision {
183 decision: crate::types::PermissionDecision::Reject(_),
184 ..
185 }
186 ));
187 }
188
189 #[tokio::test]
190 async fn approve_if_consults_predicate() {
191 let h = approve_if(|d| d.extra.get("tool").and_then(|v| v.as_str()) != Some("shell"));
192 assert!(matches!(
193 h.handle(SessionId::from("s"), RequestId::new("1"), data())
194 .await,
195 PermissionResult::Decision {
196 decision: crate::types::PermissionDecision::Reject(_),
197 ..
198 }
199 ));
200 }
201
202 #[tokio::test]
203 async fn approve_if_leaves_managed_approval_pending_when_predicate_approves() {
204 let h = approve_if(|_| true);
205 let mut request = data();
206 request.managed_approval_required = Some(true);
207 assert!(matches!(
208 h.handle(SessionId::from("s"), RequestId::new("1"), request)
209 .await,
210 PermissionResult::NoResult
211 ));
212 }
213
214 #[tokio::test]
215 async fn approve_if_still_rejects_managed_request_when_predicate_denies() {
216 let h = approve_if(|_| false);
217 let mut request = data();
218 request.managed_approval_required = Some(true);
219 assert!(matches!(
220 h.handle(SessionId::from("s"), RequestId::new("1"), request)
221 .await,
222 PermissionResult::Decision {
223 decision: crate::types::PermissionDecision::Reject(_),
224 ..
225 }
226 ));
227 }
228
229 #[tokio::test]
230 async fn resolve_handler_policy_wins() {
231 struct AlwaysApprove;
232 #[async_trait]
233 impl PermissionHandler for AlwaysApprove {
234 async fn handle(
235 &self,
236 _: SessionId,
237 _: RequestId,
238 _: PermissionRequestData,
239 ) -> PermissionResult {
240 PermissionResult::approve_once()
241 }
242 }
243 let resolved =
244 resolve_handler(Some(Arc::new(AlwaysApprove)), Some(Policy::DenyAll)).unwrap();
245 assert!(matches!(
247 resolved
248 .handle(SessionId::from("s"), RequestId::new("1"), data())
249 .await,
250 PermissionResult::Decision {
251 decision: crate::types::PermissionDecision::Reject(_),
252 ..
253 }
254 ));
255 }
256
257 #[tokio::test]
258 async fn resolve_handler_with_only_handler() {
259 struct H;
260 #[async_trait]
261 impl PermissionHandler for H {
262 async fn handle(
263 &self,
264 _: SessionId,
265 _: RequestId,
266 _: PermissionRequestData,
267 ) -> PermissionResult {
268 PermissionResult::approve_once()
269 }
270 }
271 let resolved = resolve_handler(Some(Arc::new(H)), None).unwrap();
272 assert!(matches!(
273 resolved
274 .handle(SessionId::from("s"), RequestId::new("1"), data())
275 .await,
276 PermissionResult::Decision {
277 decision: crate::types::PermissionDecision::ApproveOnce(_),
278 ..
279 }
280 ));
281 }
282
283 #[test]
284 fn resolve_handler_with_neither_returns_none() {
285 assert!(resolve_handler(None, None).is_none());
286 }
287}