Skip to main content

github_copilot_sdk/
permission.rs

1//! Permission policy primitives that produce a [`PermissionHandler`](crate::handler::PermissionHandler).
2//!
3//! Compose these into a session via the builder methods
4//! [`SessionConfig::approve_all_permissions`](crate::types::SessionConfig::approve_all_permissions),
5//! [`deny_all_permissions`](crate::types::SessionConfig::deny_all_permissions),
6//! and [`approve_permissions_if`](crate::types::SessionConfig::approve_permissions_if).
7//! The same primitives are also available as standalone functions that
8//! return an `Arc<dyn PermissionHandler>` you can install via
9//! [`SessionConfig::with_permission_handler`](crate::types::SessionConfig::with_permission_handler).
10//!
11//! For a one-shot approve / deny without composition, see
12//! [`ApproveAllHandler`](crate::handler::ApproveAllHandler) and
13//! [`DenyAllHandler`](crate::handler::DenyAllHandler).
14
15use 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
22/// Return a [`PermissionHandler`] that approves requests when managed settings
23/// are disabled.
24///
25/// When managed settings are enabled, the handler logs an error and returns a
26/// user-not-available decision.
27pub fn approve_all() -> Arc<dyn PermissionHandler> {
28    Arc::new(PolicyHandler {
29        policy: Policy::ApproveAll,
30    })
31}
32
33/// Return a [`PermissionHandler`] that denies every request.
34pub fn deny_all() -> Arc<dyn PermissionHandler> {
35    Arc::new(PolicyHandler {
36        policy: Policy::DenyAll,
37    })
38}
39
40/// Return a [`PermissionHandler`] that consults a predicate for each
41/// request. `true` approves, `false` denies.
42///
43/// ```rust,no_run
44/// # use github_copilot_sdk::permission;
45/// let handler = permission::approve_if(|data| {
46///     data.extra.get("tool").and_then(|v| v.as_str()) != Some("shell")
47/// });
48/// # let _ = handler;
49/// ```
50pub 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/// Internal policy enum used by both the standalone helpers and the
60/// `SessionConfig` policy builders.
61///
62/// Stored as `pub(crate)` on `SessionConfig::permission_policy` so that
63/// the order of `with_permission_handler(...)` and the policy builders
64/// does not matter -- the policy is applied at `Client::create_session`
65/// time.
66#[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
83/// Resolve the effective permission handler for a session, given the
84/// caller-supplied handler and policy. Called by `Client::create_session`
85/// and `Client::resume_session`.
86///
87/// Semantics:
88/// - When `policy` is `Some`, the policy entirely replaces the handler
89///   for permission decisions. (Caller-supplied handler, if any, is
90///   discarded -- the policy is what answers permission requests.)
91/// - When `policy` is `None` and `handler` is `Some`, the handler stands.
92/// - When both are `None`, returns `None` (no handler -- the SDK sends
93///   `requestPermission: false`).
94pub(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        // Policy wins -- the AlwaysApprove handler is discarded.
246        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}