Skip to main content

sen_plugin_host/permission/
presets.rs

1//! Pre-configured permission setups for common use cases
2//!
3//! Provides ready-to-use configurations that framework users can use
4//! directly or as starting points for customization.
5
6use 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
20/// Complete permission configuration bundle
21pub struct PermissionConfig {
22    /// Permission checking strategy
23    pub strategy: Arc<dyn PermissionStrategy>,
24    /// Permission storage
25    pub store: Arc<dyn PermissionStore>,
26    /// User prompt handler
27    pub prompt: Arc<dyn PromptHandler>,
28    /// Audit sink
29    pub audit: Arc<dyn AuditSink>,
30    /// Trust flag configuration
31    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    /// Create a new configuration with custom components
44    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
61/// Builder for permission configurations
62pub 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    /// Create a new builder
73    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    /// Set the application name (used for default paths)
85    pub fn app_name(mut self, name: impl Into<String>) -> Self {
86        self.app_name = Some(name.into());
87        self
88    }
89
90    /// Set the permission strategy
91    pub fn strategy(mut self, strategy: impl PermissionStrategy + 'static) -> Self {
92        self.strategy = Some(Arc::new(strategy));
93        self
94    }
95
96    /// Set the permission store
97    pub fn store(mut self, store: impl PermissionStore + 'static) -> Self {
98        self.store = Some(Arc::new(store));
99        self
100    }
101
102    /// Set the prompt handler
103    pub fn prompt(mut self, prompt: impl PromptHandler + 'static) -> Self {
104        self.prompt = Some(Arc::new(prompt));
105        self
106    }
107
108    /// Set the audit sink
109    pub fn audit(mut self, audit: impl AuditSink + 'static) -> Self {
110        self.audit = Some(Arc::new(audit));
111        self
112    }
113
114    /// Set trust flag configuration
115    pub fn trust_flags(mut self, config: TrustFlagConfig) -> Self {
116        self.trust_flags = config;
117        self
118    }
119
120    /// Build the configuration
121    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/// Error type for preset initialization
154#[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
166// ============================================================================
167// Preset Configurations
168// ============================================================================
169
170/// Preset configurations for common use cases
171pub struct PermissionPresets;
172
173impl PermissionPresets {
174    /// Interactive development mode
175    ///
176    /// - Default permission strategy (prompts for new permissions)
177    /// - File-based permission storage
178    /// - Terminal prompts
179    /// - File-based audit log
180    /// - Standard trust flags
181    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    /// Strict mode for security-conscious environments
202    ///
203    /// - Strict permission strategy (denies in non-interactive)
204    /// - File-based storage
205    /// - Verbose terminal prompts
206    /// - File-based audit log
207    /// - Standard trust flags
208    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    /// CI/CD pipeline mode
229    ///
230    /// - CI strategy (no prompts, requires pre-granted permissions)
231    /// - Read-only file storage
232    /// - Auto-deny prompt handler
233    /// - File-based audit log
234    /// - Trust flags disabled
235    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    /// Permissive development mode
263    ///
264    /// - Permissive strategy (allows non-network without prompt)
265    /// - File-based storage
266    /// - Terminal prompts for network
267    /// - Memory-based audit (not persisted)
268    /// - Allow-style trust flags
269    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    /// Testing mode (in-memory, no persistence)
287    ///
288    /// - Default strategy
289    /// - In-memory storage
290    /// - Auto-approve prompt handler
291    /// - Memory-based audit
292    /// - Trust flags enabled
293    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    /// Dangerous: Trust all plugins (DEVELOPMENT ONLY)
304    ///
305    /// - Trust-all strategy (bypasses all checks)
306    /// - In-memory storage
307    /// - Auto-approve
308    /// - Null audit
309    /// - Trust flags disabled (not needed)
310    ///
311    /// # Safety
312    ///
313    /// This configuration bypasses all security checks.
314    /// Only use for development/testing in controlled environments.
315    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}