sen_plugin_host/permission/
presets.rs1use std::path::PathBuf;
7use std::sync::Arc;
8
9use super::prompt::{AutoPromptHandler, PromptHandler, TerminalPromptHandler};
10use super::store::{
11 FilePermissionStore, MemoryPermissionStore, PermissionStore, ReadOnlyPermissionStore,
12};
13use super::strategy::{
14 CiPermissionStrategy, DefaultPermissionStrategy, PermissionStrategy,
15 PermissivePermissionStrategy, StrictPermissionStrategy, TrustAllStrategy,
16};
17use super::trust::{TrustFlagConfig, TrustFlagPresets};
18use crate::audit::{AuditSink, FileAuditSink, MemoryAuditSink, NullAuditSink};
19
20pub struct PermissionConfig {
22 pub strategy: Arc<dyn PermissionStrategy>,
24 pub store: Arc<dyn PermissionStore>,
26 pub prompt: Arc<dyn PromptHandler>,
28 pub audit: Arc<dyn AuditSink>,
30 pub trust_flags: TrustFlagConfig,
32}
33
34impl std::fmt::Debug for PermissionConfig {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("PermissionConfig")
37 .field("trust_flags", &self.trust_flags)
38 .finish_non_exhaustive()
39 }
40}
41
42impl PermissionConfig {
43 pub fn new(
45 strategy: impl PermissionStrategy + 'static,
46 store: impl PermissionStore + 'static,
47 prompt: impl PromptHandler + 'static,
48 audit: impl AuditSink + 'static,
49 trust_flags: TrustFlagConfig,
50 ) -> Self {
51 Self {
52 strategy: Arc::new(strategy),
53 store: Arc::new(store),
54 prompt: Arc::new(prompt),
55 audit: Arc::new(audit),
56 trust_flags,
57 }
58 }
59}
60
61pub struct PermissionConfigBuilder {
63 strategy: Option<Arc<dyn PermissionStrategy>>,
64 store: Option<Arc<dyn PermissionStore>>,
65 prompt: Option<Arc<dyn PromptHandler>>,
66 audit: Option<Arc<dyn AuditSink>>,
67 trust_flags: TrustFlagConfig,
68 app_name: Option<String>,
69}
70
71impl PermissionConfigBuilder {
72 pub fn new() -> Self {
74 Self {
75 strategy: None,
76 store: None,
77 prompt: None,
78 audit: None,
79 trust_flags: TrustFlagConfig::default(),
80 app_name: None,
81 }
82 }
83
84 pub fn app_name(mut self, name: impl Into<String>) -> Self {
86 self.app_name = Some(name.into());
87 self
88 }
89
90 pub fn strategy(mut self, strategy: impl PermissionStrategy + 'static) -> Self {
92 self.strategy = Some(Arc::new(strategy));
93 self
94 }
95
96 pub fn store(mut self, store: impl PermissionStore + 'static) -> Self {
98 self.store = Some(Arc::new(store));
99 self
100 }
101
102 pub fn prompt(mut self, prompt: impl PromptHandler + 'static) -> Self {
104 self.prompt = Some(Arc::new(prompt));
105 self
106 }
107
108 pub fn audit(mut self, audit: impl AuditSink + 'static) -> Self {
110 self.audit = Some(Arc::new(audit));
111 self
112 }
113
114 pub fn trust_flags(mut self, config: TrustFlagConfig) -> Self {
116 self.trust_flags = config;
117 self
118 }
119
120 pub fn build(self) -> Result<PermissionConfig, PresetError> {
122 let app_name = self.app_name.as_deref().unwrap_or("plugin-host");
123
124 let store: Arc<dyn PermissionStore> = match self.store {
125 Some(s) => s,
126 None => {
127 let store = FilePermissionStore::default_for_app(app_name)
128 .map_err(|e| PresetError::StoreInit(e.to_string()))?;
129 Arc::new(store)
130 }
131 };
132
133 Ok(PermissionConfig {
134 strategy: self
135 .strategy
136 .unwrap_or_else(|| Arc::new(DefaultPermissionStrategy)),
137 store,
138 prompt: self
139 .prompt
140 .unwrap_or_else(|| Arc::new(TerminalPromptHandler::new())),
141 audit: self.audit.unwrap_or_else(|| Arc::new(NullAuditSink)),
142 trust_flags: self.trust_flags,
143 })
144 }
145}
146
147impl Default for PermissionConfigBuilder {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153#[derive(Debug, thiserror::Error)]
155pub enum PresetError {
156 #[error("Failed to initialize store: {0}")]
157 StoreInit(String),
158
159 #[error("Failed to initialize audit: {0}")]
160 AuditInit(String),
161
162 #[error("Invalid configuration: {0}")]
163 InvalidConfig(String),
164}
165
166pub struct PermissionPresets;
172
173impl PermissionPresets {
174 pub fn interactive(app_name: &str) -> Result<PermissionConfig, PresetError> {
182 let config_dir = dirs::config_dir()
183 .unwrap_or_else(|| PathBuf::from(".config"))
184 .join(app_name);
185
186 let store = FilePermissionStore::new(config_dir.join("permissions.json"))
187 .map_err(|e| PresetError::StoreInit(e.to_string()))?;
188
189 let audit = FileAuditSink::new(config_dir.join("audit.jsonl"))
190 .map_err(|e| PresetError::AuditInit(e.to_string()))?;
191
192 Ok(PermissionConfig {
193 strategy: Arc::new(DefaultPermissionStrategy),
194 store: Arc::new(store),
195 prompt: Arc::new(TerminalPromptHandler::new()),
196 audit: Arc::new(audit),
197 trust_flags: TrustFlagPresets::standard(),
198 })
199 }
200
201 pub fn strict(app_name: &str) -> Result<PermissionConfig, PresetError> {
209 let config_dir = dirs::config_dir()
210 .unwrap_or_else(|| PathBuf::from(".config"))
211 .join(app_name);
212
213 let store = FilePermissionStore::new(config_dir.join("permissions.json"))
214 .map_err(|e| PresetError::StoreInit(e.to_string()))?;
215
216 let audit = FileAuditSink::new(config_dir.join("audit.jsonl"))
217 .map_err(|e| PresetError::AuditInit(e.to_string()))?;
218
219 Ok(PermissionConfig {
220 strategy: Arc::new(StrictPermissionStrategy),
221 store: Arc::new(store),
222 prompt: Arc::new(TerminalPromptHandler::new()),
223 audit: Arc::new(audit),
224 trust_flags: TrustFlagPresets::standard(),
225 })
226 }
227
228 pub fn ci(
236 app_name: &str,
237 permissions_file: Option<PathBuf>,
238 ) -> Result<PermissionConfig, PresetError> {
239 let config_dir = dirs::config_dir()
240 .unwrap_or_else(|| PathBuf::from(".config"))
241 .join(app_name);
242
243 let store_path = permissions_file.unwrap_or_else(|| config_dir.join("permissions.json"));
244
245 let inner_store = FilePermissionStore::new(&store_path)
246 .map_err(|e| PresetError::StoreInit(e.to_string()))?;
247
248 let store = ReadOnlyPermissionStore::new(inner_store);
249
250 let audit = FileAuditSink::new(config_dir.join("audit.jsonl"))
251 .map_err(|e| PresetError::AuditInit(e.to_string()))?;
252
253 Ok(PermissionConfig {
254 strategy: Arc::new(CiPermissionStrategy),
255 store: Arc::new(store),
256 prompt: Arc::new(AutoPromptHandler::always_deny()),
257 audit: Arc::new(audit),
258 trust_flags: TrustFlagPresets::disabled(),
259 })
260 }
261
262 pub fn permissive(app_name: &str) -> Result<PermissionConfig, PresetError> {
270 let config_dir = dirs::config_dir()
271 .unwrap_or_else(|| PathBuf::from(".config"))
272 .join(app_name);
273
274 let store = FilePermissionStore::new(config_dir.join("permissions.json"))
275 .map_err(|e| PresetError::StoreInit(e.to_string()))?;
276
277 Ok(PermissionConfig {
278 strategy: Arc::new(PermissivePermissionStrategy),
279 store: Arc::new(store),
280 prompt: Arc::new(TerminalPromptHandler::minimal()),
281 audit: Arc::new(MemoryAuditSink::new()),
282 trust_flags: TrustFlagPresets::allow_style(),
283 })
284 }
285
286 pub fn testing() -> PermissionConfig {
294 PermissionConfig {
295 strategy: Arc::new(DefaultPermissionStrategy),
296 store: Arc::new(MemoryPermissionStore::new()),
297 prompt: Arc::new(AutoPromptHandler::always_allow()),
298 audit: Arc::new(MemoryAuditSink::new()),
299 trust_flags: TrustFlagPresets::standard(),
300 }
301 }
302
303 pub fn trust_all_dangerous() -> PermissionConfig {
316 PermissionConfig {
317 strategy: Arc::new(TrustAllStrategy::new_dangerous()),
318 store: Arc::new(MemoryPermissionStore::new()),
319 prompt: Arc::new(AutoPromptHandler::always_allow()),
320 audit: Arc::new(NullAuditSink),
321 trust_flags: TrustFlagPresets::disabled(),
322 }
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_builder() {
332 let config = PermissionConfigBuilder::new()
333 .app_name("test-app")
334 .strategy(DefaultPermissionStrategy)
335 .store(MemoryPermissionStore::new())
336 .prompt(AutoPromptHandler::always_deny())
337 .audit(NullAuditSink)
338 .build()
339 .unwrap();
340
341 assert!(!config.prompt.is_interactive());
342 }
343
344 #[test]
345 fn test_testing_preset() {
346 let config = PermissionPresets::testing();
347 assert!(!config.prompt.is_interactive());
348 }
349
350 #[test]
351 fn test_trust_all_dangerous() {
352 let config = PermissionPresets::trust_all_dangerous();
353 assert!(!config.trust_flags.enabled);
354 }
355}