capability_granter.rs

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