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 (Some(h), None) => Some(h),
101 (None, None) => None,
102 }
103}
104
105struct PolicyHandler {
106 policy: Policy,
107}
108
109#[async_trait]
110impl PermissionHandler for PolicyHandler {
111 async fn handle(
112 &self,
113 _session_id: SessionId,
114 _request_id: RequestId,
115 data: PermissionRequestData,
116 ) -> PermissionResult {
117 let approved = match &self.policy {
118 Policy::ApproveAll => true,
119 Policy::DenyAll => false,
120 Policy::Predicate(f) => f(&data),
121 };
122 if approved {
123 if matches!(self.policy, Policy::ApproveAll) && data.managed_settings_enabled {
124 permission_handler_failure(
125 "approve-all policy cannot be used when managed settings are enabled",
126 )
127 } else if data.managed_approval_required == Some(true) {
128 PermissionResult::no_result()
129 } else {
130 PermissionResult::approve_once()
131 }
132 } else {
133 PermissionResult::reject(None)
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 fn data() -> PermissionRequestData {
143 PermissionRequestData {
144 extra: serde_json::json!({ "tool": "shell" }),
145 ..Default::default()
146 }
147 }
148
149 #[tokio::test]
150 async fn approve_all_approves() {
151 let h = approve_all();
152 assert!(matches!(
153 h.handle(SessionId::from("s"), RequestId::new("1"), data())
154 .await,
155 PermissionResult::Decision(crate::types::PermissionDecision::ApproveOnce(_))
156 ));
157 }
158
159 #[tokio::test]
160 async fn approve_all_fails_when_managed_settings_enabled() {
161 let h = approve_all();
162 let mut request = data();
163 request.managed_settings_enabled = true;
164 assert!(matches!(
165 h.handle(SessionId::from("s"), RequestId::new("1"), request)
166 .await,
167 PermissionResult::Decision(crate::types::PermissionDecision::UserNotAvailable(_))
168 ));
169 }
170
171 #[tokio::test]
172 async fn deny_all_denies() {
173 let h = deny_all();
174 assert!(matches!(
175 h.handle(SessionId::from("s"), RequestId::new("1"), data())
176 .await,
177 PermissionResult::Decision(crate::types::PermissionDecision::Reject(_))
178 ));
179 }
180
181 #[tokio::test]
182 async fn approve_if_consults_predicate() {
183 let h = approve_if(|d| d.extra.get("tool").and_then(|v| v.as_str()) != Some("shell"));
184 assert!(matches!(
185 h.handle(SessionId::from("s"), RequestId::new("1"), data())
186 .await,
187 PermissionResult::Decision(crate::types::PermissionDecision::Reject(_))
188 ));
189 }
190
191 #[tokio::test]
192 async fn approve_if_leaves_managed_approval_pending_when_predicate_approves() {
193 let h = approve_if(|_| true);
194 let mut request = data();
195 request.managed_approval_required = Some(true);
196 assert!(matches!(
197 h.handle(SessionId::from("s"), RequestId::new("1"), request)
198 .await,
199 PermissionResult::NoResult
200 ));
201 }
202
203 #[tokio::test]
204 async fn approve_if_still_rejects_managed_request_when_predicate_denies() {
205 let h = approve_if(|_| false);
206 let mut request = data();
207 request.managed_approval_required = Some(true);
208 assert!(matches!(
209 h.handle(SessionId::from("s"), RequestId::new("1"), request)
210 .await,
211 PermissionResult::Decision(crate::types::PermissionDecision::Reject(_))
212 ));
213 }
214
215 #[tokio::test]
216 async fn resolve_handler_policy_wins() {
217 struct AlwaysApprove;
218 #[async_trait]
219 impl PermissionHandler for AlwaysApprove {
220 async fn handle(
221 &self,
222 _: SessionId,
223 _: RequestId,
224 _: PermissionRequestData,
225 ) -> PermissionResult {
226 PermissionResult::approve_once()
227 }
228 }
229 let resolved =
230 resolve_handler(Some(Arc::new(AlwaysApprove)), Some(Policy::DenyAll)).unwrap();
231 assert!(matches!(
233 resolved
234 .handle(SessionId::from("s"), RequestId::new("1"), data())
235 .await,
236 PermissionResult::Decision(crate::types::PermissionDecision::Reject(_))
237 ));
238 }
239
240 #[tokio::test]
241 async fn resolve_handler_with_only_handler() {
242 struct H;
243 #[async_trait]
244 impl PermissionHandler for H {
245 async fn handle(
246 &self,
247 _: SessionId,
248 _: RequestId,
249 _: PermissionRequestData,
250 ) -> PermissionResult {
251 PermissionResult::approve_once()
252 }
253 }
254 let resolved = resolve_handler(Some(Arc::new(H)), None).unwrap();
255 assert!(matches!(
256 resolved
257 .handle(SessionId::from("s"), RequestId::new("1"), data())
258 .await,
259 PermissionResult::Decision(crate::types::PermissionDecision::ApproveOnce(_))
260 ));
261 }
262
263 #[test]
264 fn resolve_handler_with_neither_returns_none() {
265 assert!(resolve_handler(None, None).is_none());
266 }
267}