1use std::sync::Arc;
2
3use anyhow::{Result, bail};
4use extension::{ExtensionCapability, ExtensionManifest};
5
6pub struct CapabilityGranter {
7 granted_capabilities: Vec<ExtensionCapability>,
8 manifest: Arc<ExtensionManifest>,
9}
10
11impl CapabilityGranter {
12 pub fn new(
13 granted_capabilities: Vec<ExtensionCapability>,
14 manifest: Arc<ExtensionManifest>,
15 ) -> Self {
16 Self {
17 granted_capabilities,
18 manifest,
19 }
20 }
21
22 pub fn grant_exec(
23 &self,
24 desired_command: &str,
25 desired_args: &[impl AsRef<str> + std::fmt::Debug],
26 ) -> Result<()> {
27 self.manifest.allow_exec(desired_command, desired_args)?;
28
29 let is_allowed = self
30 .granted_capabilities
31 .iter()
32 .any(|capability| match capability {
33 ExtensionCapability::ProcessExec(capability) => {
34 capability.allows(desired_command, desired_args)
35 }
36 });
37
38 if !is_allowed {
39 bail!(
40 "capability for process:exec {desired_command} {desired_args:?} is not granted by the extension host",
41 );
42 }
43
44 Ok(())
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use std::collections::BTreeMap;
51
52 use extension::{ProcessExecCapability, SchemaVersion};
53
54 use super::*;
55
56 fn extension_manifest() -> ExtensionManifest {
57 ExtensionManifest {
58 id: "test".into(),
59 name: "Test".to_string(),
60 version: "1.0.0".into(),
61 schema_version: SchemaVersion::ZERO,
62 description: None,
63 repository: None,
64 authors: vec![],
65 lib: Default::default(),
66 themes: vec![],
67 icon_themes: vec![],
68 languages: vec![],
69 grammars: BTreeMap::default(),
70 language_servers: BTreeMap::default(),
71 context_servers: BTreeMap::default(),
72 slash_commands: BTreeMap::default(),
73 indexed_docs_providers: BTreeMap::default(),
74 snippets: None,
75 capabilities: vec![],
76 debug_adapters: Default::default(),
77 debug_locators: Default::default(),
78 }
79 }
80
81 #[test]
82 fn test_grant_exec() {
83 let manifest = Arc::new(ExtensionManifest {
84 capabilities: vec![ExtensionCapability::ProcessExec(ProcessExecCapability {
85 command: "ls".to_string(),
86 args: vec!["-la".to_string()],
87 })],
88 ..extension_manifest()
89 });
90
91 // It returns an error when the extension host has no granted capabilities.
92 let granter = CapabilityGranter::new(Vec::new(), manifest.clone());
93 assert!(granter.grant_exec("ls", &["-la"]).is_err());
94
95 // It succeeds when the extension host has the exact capability.
96 let granter = CapabilityGranter::new(
97 vec![ExtensionCapability::ProcessExec(ProcessExecCapability {
98 command: "ls".to_string(),
99 args: vec!["-la".to_string()],
100 })],
101 manifest.clone(),
102 );
103 assert!(granter.grant_exec("ls", &["-la"]).is_ok());
104
105 // It succeeds when the extension host has a wildcard capability.
106 let granter = CapabilityGranter::new(
107 vec![ExtensionCapability::ProcessExec(ProcessExecCapability {
108 command: "*".to_string(),
109 args: vec!["**".to_string()],
110 })],
111 manifest.clone(),
112 );
113 assert!(granter.grant_exec("ls", &["-la"]).is_ok());
114 }
115}