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};
20use crate::types::{PermissionRequestData, RequestId, SessionId};
21
22/// Return a [`PermissionHandler`] that approves every request.
23pub fn approve_all() -> Arc<dyn PermissionHandler> {
24    Arc::new(PolicyHandler {
25        policy: Policy::ApproveAll,
26    })
27}
28
29/// Return a [`PermissionHandler`] that denies every request.
30pub fn deny_all() -> Arc<dyn PermissionHandler> {
31    Arc::new(PolicyHandler {
32        policy: Policy::DenyAll,
33    })
34}
35
36/// Return a [`PermissionHandler`] that consults a predicate for each
37/// request. `true` approves, `false` denies.
38///
39/// ```rust,no_run
40/// # use github_copilot_sdk::permission;
41/// let handler = permission::approve_if(|data| {
42///     data.extra.get("tool").and_then(|v| v.as_str()) != Some("shell")
43/// });
44/// # let _ = handler;
45/// ```
46pub fn approve_if<F>(predicate: F) -> Arc<dyn PermissionHandler>
47where
48    F: Fn(&PermissionRequestData) -> bool + Send + Sync + 'static,
49{
50    Arc::new(PolicyHandler {
51        policy: Policy::Predicate(Arc::new(predicate)),
52    })
53}
54
55/// Internal policy enum used by both the standalone helpers and the
56/// `SessionConfig` policy builders.
57///
58/// Stored as `pub(crate)` on `SessionConfig::permission_policy` so that
59/// the order of `with_permission_handler(...)` and the policy builders
60/// does not matter -- the policy is applied at `Client::create_session`
61/// time.
62#[derive(Clone)]
63pub(crate) enum Policy {
64    ApproveAll,
65    DenyAll,
66    Predicate(Arc<dyn Fn(&PermissionRequestData) -> bool + Send + Sync>),
67}
68
69impl std::fmt::Debug for Policy {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        match self {
72            Self::ApproveAll => f.write_str("Policy::ApproveAll"),
73            Self::DenyAll => f.write_str("Policy::DenyAll"),
74            Self::Predicate(_) => f.write_str("Policy::Predicate(<fn>)"),
75        }
76    }
77}
78
79/// Resolve the effective permission handler for a session, given the
80/// caller-supplied handler and policy. Called by `Client::create_session`
81/// and `Client::resume_session`.
82///
83/// Semantics:
84/// - When `policy` is `Some`, the policy entirely replaces the handler
85///   for permission decisions. (Caller-supplied handler, if any, is
86///   discarded -- the policy is what answers permission requests.)
87/// - When `policy` is `None` and `handler` is `Some`, the handler stands.
88/// - When both are `None`, returns `None` (no handler -- the SDK sends
89///   `requestPermission: false`).
90pub(crate) fn resolve_handler(
91    handler: Option<Arc<dyn PermissionHandler>>,
92    policy: Option<Policy>,
93) -> Option<Arc<dyn PermissionHandler>> {
94    match (handler, policy) {
95        (_, Some(policy)) => Some(Arc::new(PolicyHandler { policy })),
96        (Some(h), None) => Some(h),
97        (None, None) => None,
98    }
99}
100
101struct PolicyHandler {
102    policy: Policy,
103}
104
105#[async_trait]
106impl PermissionHandler for PolicyHandler {
107    async fn handle(
108        &self,
109        _session_id: SessionId,
110        _request_id: RequestId,
111        data: PermissionRequestData,
112    ) -> PermissionResult {
113        let approved = match &self.policy {
114            Policy::ApproveAll => true,
115            Policy::DenyAll => false,
116            Policy::Predicate(f) => f(&data),
117        };
118        if approved {
119            PermissionResult::approve_once()
120        } else {
121            PermissionResult::reject(None)
122        }
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    fn data() -> PermissionRequestData {
131        PermissionRequestData {
132            extra: serde_json::json!({ "tool": "shell" }),
133            ..Default::default()
134        }
135    }
136
137    #[tokio::test]
138    async fn approve_all_approves() {
139        let h = approve_all();
140        assert!(matches!(
141            h.handle(SessionId::from("s"), RequestId::new("1"), data())
142                .await,
143            PermissionResult::Decision(crate::types::PermissionDecision::ApproveOnce(_))
144        ));
145    }
146
147    #[tokio::test]
148    async fn deny_all_denies() {
149        let h = deny_all();
150        assert!(matches!(
151            h.handle(SessionId::from("s"), RequestId::new("1"), data())
152                .await,
153            PermissionResult::Decision(crate::types::PermissionDecision::Reject(_))
154        ));
155    }
156
157    #[tokio::test]
158    async fn approve_if_consults_predicate() {
159        let h = approve_if(|d| d.extra.get("tool").and_then(|v| v.as_str()) != Some("shell"));
160        assert!(matches!(
161            h.handle(SessionId::from("s"), RequestId::new("1"), data())
162                .await,
163            PermissionResult::Decision(crate::types::PermissionDecision::Reject(_))
164        ));
165    }
166
167    #[tokio::test]
168    async fn resolve_handler_policy_wins() {
169        struct AlwaysApprove;
170        #[async_trait]
171        impl PermissionHandler for AlwaysApprove {
172            async fn handle(
173                &self,
174                _: SessionId,
175                _: RequestId,
176                _: PermissionRequestData,
177            ) -> PermissionResult {
178                PermissionResult::approve_once()
179            }
180        }
181        let resolved =
182            resolve_handler(Some(Arc::new(AlwaysApprove)), Some(Policy::DenyAll)).unwrap();
183        // Policy wins -- the AlwaysApprove handler is discarded.
184        assert!(matches!(
185            resolved
186                .handle(SessionId::from("s"), RequestId::new("1"), data())
187                .await,
188            PermissionResult::Decision(crate::types::PermissionDecision::Reject(_))
189        ));
190    }
191
192    #[tokio::test]
193    async fn resolve_handler_with_only_handler() {
194        struct H;
195        #[async_trait]
196        impl PermissionHandler for H {
197            async fn handle(
198                &self,
199                _: SessionId,
200                _: RequestId,
201                _: PermissionRequestData,
202            ) -> PermissionResult {
203                PermissionResult::approve_once()
204            }
205        }
206        let resolved = resolve_handler(Some(Arc::new(H)), None).unwrap();
207        assert!(matches!(
208            resolved
209                .handle(SessionId::from("s"), RequestId::new("1"), data())
210                .await,
211            PermissionResult::Decision(crate::types::PermissionDecision::ApproveOnce(_))
212        ));
213    }
214
215    #[test]
216    fn resolve_handler_with_neither_returns_none() {
217        assert!(resolve_handler(None, None).is_none());
218    }
219}