deno/libs/config/deno_json/permissions.rs
David Sherret 17d02c228f
feat: permissions in the config file (#30330)
Co-authored-by: nathanwhit <nathanwhit@users.noreply.github.com>
2025-09-02 13:37:33 +00:00

358 lines
9.7 KiB
Rust

// 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<String>),
None,
}
impl<'de> serde::Deserialize<'de> for PermissionConfigValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v {
Ok(PermissionConfigValue::All)
} else {
Ok(PermissionConfigValue::None)
}
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
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::<String>()? {
out.push(element);
}
if out.is_empty() {
Ok(PermissionConfigValue::None)
} else {
Ok(PermissionConfigValue::Some(out))
}
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
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<PermissionConfigValue>,
pub deny: Option<PermissionConfigValue>,
}
#[derive(Deserialize)]
#[serde(untagged)]
pub enum AllowDenyPermissionConfigValue {
Boolean(bool),
AllowList(Vec<String>),
Object(AllowDenyPermissionConfig),
}
fn deserialize_allow_deny<'de, D: serde::Deserializer<'de>>(
de: D,
) -> Result<AllowDenyPermissionConfig, D::Error> {
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<PermissionsObject>),
}
#[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<bool>,
#[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<String, PermissionsObjectWithBase>,
}
impl PermissionsConfig {
pub fn parse(
value: serde_json::Value,
base: &Url,
) -> Result<Self, serde_json::Error> {
let sets: IndexMap<String, PermissionsObject> =
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::<PermissionsObject>(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::<PermissionsObject>(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::<PermissionsObject>(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::<PermissionsObject>(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()
}
);
}
}