capability_granter.rs

  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}