// Copyright 2018-2025 the Deno authors. MIT license. use indexmap::IndexMap; use serde::Deserialize; use url::Url; use super::UndefinedPermissionError; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum PermissionConfigValue { All, Some(Vec), None, } impl<'de> serde::Deserialize<'de> for PermissionConfigValue { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct Visitor; impl<'d> serde::de::Visitor<'d> for Visitor { type Value = PermissionConfigValue; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "either an array or bool") } fn visit_bool(self, v: bool) -> Result where E: serde::de::Error, { if v { Ok(PermissionConfigValue::All) } else { Ok(PermissionConfigValue::None) } } fn visit_seq(self, mut seq: A) -> Result where A: serde::de::SeqAccess<'d>, { let mut out = Vec::with_capacity(seq.size_hint().unwrap_or(8)); while let Some(element) = seq.next_element::()? { out.push(element); } if out.is_empty() { Ok(PermissionConfigValue::None) } else { Ok(PermissionConfigValue::Some(out)) } } fn visit_unit(self) -> Result where E: serde::de::Error, { Ok(PermissionConfigValue::None) } } deserializer.deserialize_any(Visitor) } } #[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq, Hash)] #[serde(default, deny_unknown_fields)] pub struct AllowDenyPermissionConfig { pub allow: Option, pub deny: Option, } #[derive(Deserialize)] #[serde(untagged)] pub enum AllowDenyPermissionConfigValue { Boolean(bool), AllowList(Vec), Object(AllowDenyPermissionConfig), } fn deserialize_allow_deny<'de, D: serde::Deserializer<'de>>( de: D, ) -> Result { AllowDenyPermissionConfigValue::deserialize(de).map(|value| match value { AllowDenyPermissionConfigValue::Boolean(b) => AllowDenyPermissionConfig { allow: Some(if b { PermissionConfigValue::All } else { PermissionConfigValue::None }), deny: None, }, AllowDenyPermissionConfigValue::AllowList(allow) => { AllowDenyPermissionConfig { allow: Some(if allow.is_empty() { PermissionConfigValue::None } else { PermissionConfigValue::Some(allow) }), deny: None, } } AllowDenyPermissionConfigValue::Object(allow_deny) => allow_deny, }) } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Hash)] #[serde(untagged)] pub enum PermissionNameOrObject { Name(String), Object(Box), } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PermissionsObjectWithBase { pub base: Url, pub permissions: PermissionsObject, } #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Default, Hash)] #[serde(default, deny_unknown_fields)] pub struct PermissionsObject { #[serde(default)] pub all: Option, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub read: AllowDenyPermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub write: AllowDenyPermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub import: AllowDenyPermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub env: AllowDenyPermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub net: AllowDenyPermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub run: AllowDenyPermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub ffi: AllowDenyPermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub sys: AllowDenyPermissionConfig, } #[derive(Clone, Debug, Default)] pub struct PermissionsConfig { pub sets: IndexMap, } impl PermissionsConfig { pub fn parse( value: serde_json::Value, base: &Url, ) -> Result { let sets: IndexMap = serde_json::from_value(value)?; Ok(Self { sets: sets .into_iter() .map(|(k, permissions)| { ( k, PermissionsObjectWithBase { base: base.clone(), permissions, }, ) }) .collect(), }) } pub fn get( &self, name: &str, ) -> Result<&PermissionsObjectWithBase, UndefinedPermissionError> { match self.sets.get(name) { Some(value) => Ok(value), None => Err(UndefinedPermissionError(name.to_string())), } } pub fn merge(self, member: Self) -> Self { let mut sets = self.sets; for (key, value) in member.sets { // When the same key exists in the root and the member, we overwrite // with the member instead of merging because we don't want someone looking // at a member config file and not realizing the permissions are extended // in the root. In the future, we may add an explicit "extends" concept in // permissions in order to support this scenario. sets.insert(key, value); } Self { sets } } } #[cfg(test)] mod test { use pretty_assertions::assert_eq; use serde_json::json; use super::*; #[test] fn deserialize() { assert_eq!( serde_json::from_value::(json!({ "all": true, "read": true, "write": true, "import": true, "env": true, "net": true, "run": true, "ffi": true, "sys": false, })) .unwrap(), PermissionsObject { all: Some(true), read: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), deny: None, }, write: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), deny: None, }, import: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), deny: None, }, env: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), deny: None, }, net: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), deny: None, }, run: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), deny: None, }, ffi: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), deny: None, }, sys: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::None), deny: None, } } ); assert_eq!( serde_json::from_value::(json!({ "read": ["test"], "write": ["test"], "import": ["test"], "env": ["test"], "net": ["test"], "run": ["test"], "ffi": ["test"], "sys": ["test"], })) .unwrap(), PermissionsObject { all: None, read: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None }, write: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, }, import: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, }, env: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, }, net: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, }, run: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, }, ffi: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, }, sys: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, } } ); assert_eq!( serde_json::from_value::(json!({ "read": { "allow": ["test"], "deny": ["test-deny"], }, "write": [], "sys": { "allow": [] } })) .unwrap(), PermissionsObject { all: None, read: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: Some(PermissionConfigValue::Some(vec![ "test-deny".to_string() ])), }, write: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::None), deny: None }, sys: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::None), deny: None }, ..Default::default() } ); assert_eq!( serde_json::from_value::(json!({ "read": { "allow": true, "deny": ["test-deny"], }, })) .unwrap(), PermissionsObject { all: None, read: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), deny: Some(PermissionConfigValue::Some(vec![ "test-deny".to_string() ])), }, ..Default::default() } ); } }