Skip to main content

sen_plugin_host/permission/
trust.rs

1//! Trust flag configuration for CLI integration
2//!
3//! Provides template-based trust flag generation without reserving specific flag names.
4//! Framework users can customize the flag format to match their CLI conventions.
5
6/// Trust flag configuration
7///
8/// Allows framework users to define how trust flags are generated
9/// without the framework reserving specific flag names.
10///
11/// # Example
12///
13/// ```rust
14/// use sen_plugin_host::permission::{TrustFlagConfig, TrustEffect};
15///
16/// // Default: --trust-plugin=name, --trust-command=name
17/// let config = TrustFlagConfig::default();
18///
19/// // Custom: --allow-plugin=name
20/// let config = TrustFlagConfig::new()
21///     .with_flag_template("--allow-{target}")
22///     .with_alias("--yolo", TrustEffect::TrustAll);
23/// ```
24#[derive(Debug, Clone)]
25pub struct TrustFlagConfig {
26    /// Enable trust flags feature
27    pub enabled: bool,
28
29    /// Flag template: {target} is replaced with "plugin" or "command"
30    /// Default: "--trust-{target}"
31    pub flag_template: String,
32
33    /// Value template: {name} is replaced with plugin/command name
34    /// Default: "{name}"
35    pub value_template: String,
36
37    /// Custom aliases for common trust patterns
38    pub aliases: Vec<TrustFlagAlias>,
39
40    /// Whether to show trust flags in help
41    pub show_in_help: bool,
42
43    /// Help text template for generated flags
44    pub help_template: String,
45}
46
47impl Default for TrustFlagConfig {
48    fn default() -> Self {
49        Self {
50            enabled: true,
51            flag_template: "--trust-{target}".into(),
52            value_template: "{name}".into(),
53            aliases: vec![],
54            show_in_help: true,
55            help_template: "Trust {target} '{name}' for this execution".into(),
56        }
57    }
58}
59
60impl TrustFlagConfig {
61    /// Create a new trust flag configuration
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    /// Disable trust flags entirely
67    pub fn disabled() -> Self {
68        Self {
69            enabled: false,
70            ..Self::default()
71        }
72    }
73
74    /// Set the flag template
75    ///
76    /// Variables:
77    /// - `{target}`: "plugin" or "command"
78    pub fn with_flag_template(mut self, template: impl Into<String>) -> Self {
79        self.flag_template = template.into();
80        self
81    }
82
83    /// Set the value template
84    ///
85    /// Variables:
86    /// - `{name}`: plugin or command name
87    pub fn with_value_template(mut self, template: impl Into<String>) -> Self {
88        self.value_template = template.into();
89        self
90    }
91
92    /// Add a custom alias
93    pub fn with_alias(mut self, flag: impl Into<String>, effect: TrustEffect) -> Self {
94        self.aliases.push(TrustFlagAlias {
95            flag: flag.into(),
96            description: effect.default_description(),
97            effect,
98        });
99        self
100    }
101
102    /// Add a custom alias with description
103    pub fn with_alias_desc(
104        mut self,
105        flag: impl Into<String>,
106        description: impl Into<String>,
107        effect: TrustEffect,
108    ) -> Self {
109        self.aliases.push(TrustFlagAlias {
110            flag: flag.into(),
111            description: description.into(),
112            effect,
113        });
114        self
115    }
116
117    /// Hide trust flags from help output
118    pub fn hidden(mut self) -> Self {
119        self.show_in_help = false;
120        self
121    }
122
123    /// Generate the flag name for a target type
124    pub fn generate_flag(&self, target: TrustTarget) -> String {
125        self.flag_template.replace("{target}", target.as_str())
126    }
127
128    /// Generate the value format for a name
129    pub fn generate_value(&self, name: &str) -> String {
130        self.value_template.replace("{name}", name)
131    }
132
133    /// Generate help text for a flag
134    pub fn generate_help(&self, target: TrustTarget, name: &str) -> String {
135        self.help_template
136            .replace("{target}", target.as_str())
137            .replace("{name}", name)
138    }
139
140    /// Parse command line arguments and extract trust directives
141    pub fn parse_args(&self, args: &[String]) -> TrustDirectives {
142        if !self.enabled {
143            return TrustDirectives::default();
144        }
145
146        let mut directives = TrustDirectives::default();
147
148        for arg in args {
149            // Check aliases first
150            for alias in &self.aliases {
151                if arg == &alias.flag {
152                    match &alias.effect {
153                        TrustEffect::TrustAll => directives.trust_all = true,
154                        TrustEffect::TrustSession => directives.trust_session = true,
155                        TrustEffect::TrustNamed { target, name } => match target {
156                            TrustTarget::Plugin => {
157                                directives.trusted_plugins.push(name.clone());
158                            }
159                            TrustTarget::Command => {
160                                directives.trusted_commands.push(name.clone());
161                            }
162                        },
163                    }
164                }
165            }
166
167            // Check template-based flags
168            let plugin_flag = self.generate_flag(TrustTarget::Plugin);
169            let command_flag = self.generate_flag(TrustTarget::Command);
170
171            if let Some(value) = arg.strip_prefix(&format!("{}=", plugin_flag)) {
172                directives.trusted_plugins.push(value.to_string());
173            } else if let Some(value) = arg.strip_prefix(&format!("{}=", command_flag)) {
174                directives.trusted_commands.push(value.to_string());
175            }
176        }
177
178        directives
179    }
180}
181
182/// Target type for trust flags
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub enum TrustTarget {
185    Plugin,
186    Command,
187}
188
189impl TrustTarget {
190    fn as_str(&self) -> &'static str {
191        match self {
192            Self::Plugin => "plugin",
193            Self::Command => "command",
194        }
195    }
196}
197
198/// Custom trust flag alias
199#[derive(Debug, Clone)]
200pub struct TrustFlagAlias {
201    /// The flag (e.g., "--yolo")
202    pub flag: String,
203    /// Description for help text
204    pub description: String,
205    /// What this alias does
206    pub effect: TrustEffect,
207}
208
209/// Effect of a trust flag
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub enum TrustEffect {
212    /// Trust a specific plugin or command
213    TrustNamed { target: TrustTarget, name: String },
214    /// Trust all plugins for this execution (dangerous)
215    TrustAll,
216    /// Trust for this session only (not persisted)
217    TrustSession,
218}
219
220impl TrustEffect {
221    /// Get default description for this effect
222    fn default_description(&self) -> String {
223        match self {
224            Self::TrustNamed { target, name } => {
225                format!("Trust {} '{}'", target.as_str(), name)
226            }
227            Self::TrustAll => "Trust all plugins (dangerous)".into(),
228            Self::TrustSession => "Trust permissions for this session only".into(),
229        }
230    }
231}
232
233/// Parsed trust directives from command line
234#[derive(Debug, Clone, Default)]
235pub struct TrustDirectives {
236    /// Explicitly trusted plugin names
237    pub trusted_plugins: Vec<String>,
238    /// Explicitly trusted command names
239    pub trusted_commands: Vec<String>,
240    /// Trust all plugins (--trust-all or equivalent)
241    pub trust_all: bool,
242    /// Trust for session only (not persisted)
243    pub trust_session: bool,
244}
245
246impl TrustDirectives {
247    /// Check if a plugin is trusted
248    pub fn is_plugin_trusted(&self, name: &str) -> bool {
249        self.trust_all || self.trusted_plugins.iter().any(|p| p == name)
250    }
251
252    /// Check if a command is trusted
253    pub fn is_command_trusted(&self, name: &str) -> bool {
254        self.trust_all || self.trusted_commands.iter().any(|c| c == name)
255    }
256
257    /// Check if any trust directive is active
258    pub fn has_any(&self) -> bool {
259        self.trust_all
260            || self.trust_session
261            || !self.trusted_plugins.is_empty()
262            || !self.trusted_commands.is_empty()
263    }
264}
265
266/// Builder for common trust flag configurations
267pub struct TrustFlagPresets;
268
269impl TrustFlagPresets {
270    /// Standard configuration with --trust-plugin and --trust-command
271    pub fn standard() -> TrustFlagConfig {
272        TrustFlagConfig::default()
273    }
274
275    /// Allow-style flags (--allow-plugin, --allow-command)
276    pub fn allow_style() -> TrustFlagConfig {
277        TrustFlagConfig::new().with_flag_template("--allow-{target}")
278    }
279
280    /// Short flags (-tp, -tc)
281    pub fn short_style() -> TrustFlagConfig {
282        TrustFlagConfig::new()
283            .with_flag_template("-t{target}")
284            .with_alias("-ta", TrustEffect::TrustAll)
285    }
286
287    /// Kubernetes-style (--trust plugin=name)
288    pub fn k8s_style() -> TrustFlagConfig {
289        TrustFlagConfig::new()
290            .with_flag_template("--trust")
291            .with_value_template("{target}={name}")
292    }
293
294    /// Disabled (no trust flags)
295    pub fn disabled() -> TrustFlagConfig {
296        TrustFlagConfig::disabled()
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn test_default_config() {
306        let config = TrustFlagConfig::default();
307        assert!(config.enabled);
308        assert_eq!(config.generate_flag(TrustTarget::Plugin), "--trust-plugin");
309        assert_eq!(
310            config.generate_flag(TrustTarget::Command),
311            "--trust-command"
312        );
313    }
314
315    #[test]
316    fn test_custom_template() {
317        let config = TrustFlagConfig::new().with_flag_template("--allow-{target}");
318
319        assert_eq!(config.generate_flag(TrustTarget::Plugin), "--allow-plugin");
320    }
321
322    #[test]
323    fn test_parse_args() {
324        let config = TrustFlagConfig::default();
325        let args = vec![
326            "--trust-plugin=hello".into(),
327            "--trust-command=db:migrate".into(),
328            "other-arg".into(),
329        ];
330
331        let directives = config.parse_args(&args);
332        assert!(directives.is_plugin_trusted("hello"));
333        assert!(directives.is_command_trusted("db:migrate"));
334        assert!(!directives.is_plugin_trusted("other"));
335    }
336
337    #[test]
338    fn test_alias() {
339        let config = TrustFlagConfig::new().with_alias("--yolo", TrustEffect::TrustAll);
340
341        let args = vec!["--yolo".into()];
342        let directives = config.parse_args(&args);
343
344        assert!(directives.trust_all);
345        assert!(directives.is_plugin_trusted("any-plugin"));
346    }
347
348    #[test]
349    fn test_disabled_config() {
350        let config = TrustFlagConfig::disabled();
351        let args = vec!["--trust-plugin=hello".into()];
352        let directives = config.parse_args(&args);
353
354        assert!(!directives.is_plugin_trusted("hello"));
355    }
356
357    #[test]
358    fn test_presets() {
359        let allow = TrustFlagPresets::allow_style();
360        assert_eq!(allow.generate_flag(TrustTarget::Plugin), "--allow-plugin");
361
362        let short = TrustFlagPresets::short_style();
363        assert_eq!(short.generate_flag(TrustTarget::Plugin), "-tplugin");
364    }
365}