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        (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        // Policy wins -- the AlwaysApprove handler is discarded.
232        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}