diff options
author | Nayeem Rahman <nayeemrmn99@gmail.com> | 2021-10-13 18:04:44 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-13 13:04:44 -0400 |
commit | 7a22df9b7641274b2a83ce53845215d17cfda2c8 (patch) | |
tree | 6cf99f74682635a0128c5cf79f5f7d3ecfbc85f1 /runtime/permissions.rs | |
parent | 43a63530acb16e57cbb190eacedbd097c536a775 (diff) |
fix(runtime/ops/worker_host): move permission arg parsing to Rust (#12297)
Diffstat (limited to 'runtime/permissions.rs')
-rw-r--r-- | runtime/permissions.rs | 1029 |
1 files changed, 951 insertions, 78 deletions
diff --git a/runtime/permissions.rs b/runtime/permissions.rs index 081268aa9..e62ce4eb1 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -7,8 +7,11 @@ use deno_core::error::uri_error; use deno_core::error::AnyError; #[cfg(test)] use deno_core::parking_lot::Mutex; +use deno_core::serde::de; use deno_core::serde::Deserialize; +use deno_core::serde::Deserializer; use deno_core::serde::Serialize; +use deno_core::serde_json; use deno_core::url; use deno_core::ModuleSpecifier; use deno_core::OpState; @@ -17,6 +20,8 @@ use std::collections::HashSet; use std::fmt; use std::hash::Hash; use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::string::ToString; #[cfg(test)] use std::sync::atomic::AtomicBool; #[cfg(test)] @@ -115,7 +120,7 @@ impl Default for PermissionState { } } -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct UnitPermission { pub name: &'static str, pub description: &'static str, @@ -161,7 +166,7 @@ impl UnitPermission { /// A normalized environment variable name. On Windows this will /// be uppercase and on other platforms it will stay as-is. -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] struct EnvVarName { inner: String, } @@ -184,7 +189,7 @@ impl AsRef<str> for EnvVarName { } } -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct UnaryPermission<T: Eq + Hash> { pub name: &'static str, pub description: &'static str, @@ -194,13 +199,13 @@ pub struct UnaryPermission<T: Eq + Hash> { pub prompt: bool, } -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct ReadDescriptor(pub PathBuf); -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct WriteDescriptor(pub PathBuf); -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct NetDescriptor(pub String, pub Option<u16>); impl NetDescriptor { @@ -225,7 +230,7 @@ impl fmt::Display for NetDescriptor { } } -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] pub struct EnvDescriptor(EnvVarName); impl EnvDescriptor { @@ -240,11 +245,38 @@ impl AsRef<str> for EnvDescriptor { } } -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] -pub struct RunDescriptor(pub String); +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub enum RunDescriptor { + Name(String), + Path(PathBuf), +} + +impl FromStr for RunDescriptor { + type Err = (); + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let is_path = s.contains('/'); + #[cfg(windows)] + let is_path = is_path || s.contains('\\') || Path::new(s).is_absolute(); + if is_path { + Ok(Self::Path(resolve_from_cwd(Path::new(s)).unwrap())) + } else { + Ok(Self::Name(s.to_string())) + } + } +} + +impl ToString for RunDescriptor { + fn to_string(&self) -> String { + match self { + RunDescriptor::Name(s) => s.clone(), + RunDescriptor::Path(p) => p.to_string_lossy().to_string(), + } + } +} -#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)] -pub struct FfiDescriptor(pub String); +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct FfiDescriptor(pub PathBuf); impl UnaryPermission<ReadDescriptor> { pub fn query(&self, path: Option<&Path>) -> PermissionState { @@ -369,6 +401,32 @@ impl UnaryPermission<ReadDescriptor> { } result } + + pub fn check_all(&mut self) -> Result<(), AnyError> { + let (result, prompted) = + self.query(None).check(self.name, Some("all"), self.prompt); + if prompted { + if result.is_ok() { + self.global_state = PermissionState::Granted; + } else { + self.global_state = PermissionState::Denied; + } + } + result + } +} + +impl Default for UnaryPermission<ReadDescriptor> { + fn default() -> Self { + UnaryPermission::<ReadDescriptor> { + name: "read", + description: "read the file system", + global_state: Default::default(), + granted_list: Default::default(), + denied_list: Default::default(), + prompt: false, + } + } } impl UnaryPermission<WriteDescriptor> { @@ -470,6 +528,32 @@ impl UnaryPermission<WriteDescriptor> { } result } + + pub fn check_all(&mut self) -> Result<(), AnyError> { + let (result, prompted) = + self.query(None).check(self.name, Some("all"), self.prompt); + if prompted { + if result.is_ok() { + self.global_state = PermissionState::Granted; + } else { + self.global_state = PermissionState::Denied; + } + } + result + } +} + +impl Default for UnaryPermission<WriteDescriptor> { + fn default() -> Self { + UnaryPermission::<WriteDescriptor> { + name: "write", + description: "write to the file system", + global_state: Default::default(), + granted_list: Default::default(), + denied_list: Default::default(), + prompt: false, + } + } } impl UnaryPermission<NetDescriptor> { @@ -615,6 +699,34 @@ impl UnaryPermission<NetDescriptor> { } result } + + pub fn check_all(&mut self) -> Result<(), AnyError> { + let (result, prompted) = + self + .query::<&str>(None) + .check(self.name, Some("all"), self.prompt); + if prompted { + if result.is_ok() { + self.global_state = PermissionState::Granted; + } else { + self.global_state = PermissionState::Denied; + } + } + result + } +} + +impl Default for UnaryPermission<NetDescriptor> { + fn default() -> Self { + UnaryPermission::<NetDescriptor> { + name: "net", + description: "network", + global_state: Default::default(), + granted_list: Default::default(), + denied_list: Default::default(), + prompt: false, + } + } } impl UnaryPermission<EnvDescriptor> { @@ -623,14 +735,14 @@ impl UnaryPermission<EnvDescriptor> { if self.global_state == PermissionState::Denied && match env.as_ref() { None => true, - Some(env) => self.denied_list.iter().any(|env_| &env_.0 == env), + Some(env) => self.denied_list.contains(&EnvDescriptor::new(env)), } { PermissionState::Denied } else if self.global_state == PermissionState::Granted || match env.as_ref() { None => false, - Some(env) => self.granted_list.iter().any(|env_| &env_.0 == env), + Some(env) => self.granted_list.contains(&EnvDescriptor::new(env)), } { PermissionState::Granted @@ -717,19 +829,36 @@ impl UnaryPermission<EnvDescriptor> { } } +impl Default for UnaryPermission<EnvDescriptor> { + fn default() -> Self { + UnaryPermission::<EnvDescriptor> { + name: "env", + description: "environment variables", + global_state: Default::default(), + granted_list: Default::default(), + denied_list: Default::default(), + prompt: false, + } + } +} + impl UnaryPermission<RunDescriptor> { pub fn query(&self, cmd: Option<&str>) -> PermissionState { if self.global_state == PermissionState::Denied && match cmd { None => true, - Some(cmd) => self.denied_list.iter().any(|cmd_| cmd_.0 == cmd), + Some(cmd) => self + .denied_list + .contains(&RunDescriptor::from_str(cmd).unwrap()), } { PermissionState::Denied } else if self.global_state == PermissionState::Granted || match cmd { None => false, - Some(cmd) => self.granted_list.iter().any(|cmd_| cmd_.0 == cmd), + Some(cmd) => self + .granted_list + .contains(&RunDescriptor::from_str(cmd).unwrap()), } { PermissionState::Granted @@ -743,15 +872,21 @@ impl UnaryPermission<RunDescriptor> { let state = self.query(Some(cmd)); if state == PermissionState::Prompt { if permission_prompt(&format!("run access to \"{}\"", cmd)) { - self.granted_list.insert(RunDescriptor(cmd.to_string())); + self + .granted_list + .insert(RunDescriptor::from_str(cmd).unwrap()); PermissionState::Granted } else { - self.denied_list.insert(RunDescriptor(cmd.to_string())); + self + .denied_list + .insert(RunDescriptor::from_str(cmd).unwrap()); self.global_state = PermissionState::Denied; PermissionState::Denied } } else if state == PermissionState::Granted { - self.granted_list.insert(RunDescriptor(cmd.to_string())); + self + .granted_list + .insert(RunDescriptor::from_str(cmd).unwrap()); PermissionState::Granted } else { state @@ -775,7 +910,9 @@ impl UnaryPermission<RunDescriptor> { pub fn revoke(&mut self, cmd: Option<&str>) -> PermissionState { if let Some(cmd) = cmd { - self.granted_list.remove(&RunDescriptor(cmd.to_string())); + self + .granted_list + .remove(&RunDescriptor::from_str(cmd).unwrap()); } else { self.granted_list.clear(); } @@ -793,9 +930,13 @@ impl UnaryPermission<RunDescriptor> { ); if prompted { if result.is_ok() { - self.granted_list.insert(RunDescriptor(cmd.to_string())); + self + .granted_list + .insert(RunDescriptor::from_str(cmd).unwrap()); } else { - self.denied_list.insert(RunDescriptor(cmd.to_string())); + self + .denied_list + .insert(RunDescriptor::from_str(cmd).unwrap()); self.global_state = PermissionState::Denied; } } @@ -816,19 +957,33 @@ impl UnaryPermission<RunDescriptor> { } } +impl Default for UnaryPermission<RunDescriptor> { + fn default() -> Self { + UnaryPermission::<RunDescriptor> { + name: "run", + description: "run a subprocess", + global_state: Default::default(), + granted_list: Default::default(), + denied_list: Default::default(), + prompt: false, + } + } +} + impl UnaryPermission<FfiDescriptor> { - pub fn query(&self, lib: Option<&str>) -> PermissionState { + pub fn query(&self, path: Option<&Path>) -> PermissionState { + let path = path.map(|p| resolve_from_cwd(p).unwrap()); if self.global_state == PermissionState::Denied - && match lib { + && match path.as_ref() { None => true, - Some(lib) => self.denied_list.iter().any(|lib_| lib_.0 == lib), + Some(path) => self.denied_list.contains(&FfiDescriptor(path.clone())), } { PermissionState::Denied } else if self.global_state == PermissionState::Granted - || match lib { + || match path.as_ref() { None => false, - Some(lib) => self.granted_list.iter().any(|lib_| lib_.0 == lib), + Some(path) => self.granted_list.contains(&FfiDescriptor(path.clone())), } { PermissionState::Granted @@ -837,20 +992,24 @@ impl UnaryPermission<FfiDescriptor> { } } - pub fn request(&mut self, lib: Option<&str>) -> PermissionState { - if let Some(lib) = lib { - let state = self.query(Some(lib)); + pub fn request(&mut self, path: Option<&Path>) -> PermissionState { + if let Some(path) = path { + let (resolved_path, display_path) = resolved_and_display_path(path); + let state = self.query(Some(&resolved_path)); if state == PermissionState::Prompt { - if permission_prompt(&format!("ffi access to \"{}\"", lib)) { - self.granted_list.insert(FfiDescriptor(lib.to_string())); + if permission_prompt(&format!( + "ffi access to \"{}\"", + display_path.display() + )) { + self.granted_list.insert(FfiDescriptor(resolved_path)); PermissionState::Granted } else { - self.denied_list.insert(FfiDescriptor(lib.to_string())); + self.denied_list.insert(FfiDescriptor(resolved_path)); self.global_state = PermissionState::Denied; PermissionState::Denied } } else if state == PermissionState::Granted { - self.granted_list.insert(FfiDescriptor(lib.to_string())); + self.granted_list.insert(FfiDescriptor(resolved_path)); PermissionState::Granted } else { state @@ -872,29 +1031,31 @@ impl UnaryPermission<FfiDescriptor> { } } - pub fn revoke(&mut self, lib: Option<&str>) -> PermissionState { - if let Some(lib) = lib { - self.granted_list.remove(&FfiDescriptor(lib.to_string())); + pub fn revoke(&mut self, path: Option<&Path>) -> PermissionState { + if let Some(path) = path { + let path = resolve_from_cwd(path).unwrap(); + self.granted_list.remove(&FfiDescriptor(path)); } else { self.granted_list.clear(); } if self.global_state == PermissionState::Granted { self.global_state = PermissionState::Prompt; } - self.query(lib) + self.query(path) } - pub fn check(&mut self, lib: &str) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(lib)).check( + pub fn check(&mut self, path: &Path) -> Result<(), AnyError> { + let (resolved_path, display_path) = resolved_and_display_path(path); + let (result, prompted) = self.query(Some(&resolved_path)).check( self.name, - Some(&format!("\"{}\"", lib)), + Some(&format!("\"{}\"", display_path.display())), self.prompt, ); if prompted { if result.is_ok() { - self.granted_list.insert(FfiDescriptor(lib.to_string())); + self.granted_list.insert(FfiDescriptor(resolved_path)); } else { - self.denied_list.insert(FfiDescriptor(lib.to_string())); + self.denied_list.insert(FfiDescriptor(resolved_path)); self.global_state = PermissionState::Denied; } } @@ -915,7 +1076,20 @@ impl UnaryPermission<FfiDescriptor> { } } -#[derive(Clone, Debug, Default, PartialEq)] +impl Default for UnaryPermission<FfiDescriptor> { + fn default() -> Self { + UnaryPermission::<FfiDescriptor> { + name: "ffi", + description: "load a dynamic library", + global_state: Default::default(), + granted_list: Default::default(), + denied_list: Default::default(), + prompt: false, + } + } +} + +#[derive(Clone, Debug, PartialEq)] pub struct Permissions { pub read: UnaryPermission<ReadDescriptor>, pub write: UnaryPermission<WriteDescriptor>, @@ -926,12 +1100,26 @@ pub struct Permissions { pub hrtime: UnitPermission, } +impl Default for Permissions { + fn default() -> Self { + Self { + read: Permissions::new_read(&None, false), + write: Permissions::new_write(&None, false), + net: Permissions::new_net(&None, false), + env: Permissions::new_env(&None, false), + run: Permissions::new_run(&None, false), + ffi: Permissions::new_ffi(&None, false), + hrtime: Permissions::new_hrtime(false, false), + } + } +} + #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct PermissionsOptions { pub allow_env: Option<Vec<String>>, pub allow_hrtime: bool, pub allow_net: Option<Vec<String>>, - pub allow_ffi: Option<Vec<String>>, + pub allow_ffi: Option<Vec<PathBuf>>, pub allow_read: Option<Vec<PathBuf>>, pub allow_run: Option<Vec<String>>, pub allow_write: Option<Vec<PathBuf>>, @@ -944,12 +1132,10 @@ impl Permissions { prompt: bool, ) -> UnaryPermission<ReadDescriptor> { UnaryPermission::<ReadDescriptor> { - name: "read", - description: "read the file system", global_state: global_state_from_option(state), granted_list: resolve_read_allowlist(state), - denied_list: Default::default(), prompt, + ..Default::default() } } @@ -958,12 +1144,10 @@ impl Permissions { prompt: bool, ) -> UnaryPermission<WriteDescriptor> { UnaryPermission::<WriteDescriptor> { - name: "write", - description: "write to the file system", global_state: global_state_from_option(state), granted_list: resolve_write_allowlist(state), - denied_list: Default::default(), prompt, + ..Default::default() } } @@ -972,8 +1156,6 @@ impl Permissions { prompt: bool, ) -> UnaryPermission<NetDescriptor> { UnaryPermission::<NetDescriptor> { - name: "net", - description: "network", global_state: global_state_from_option(state), granted_list: state .as_ref() @@ -983,8 +1165,8 @@ impl Permissions { .collect() }) .unwrap_or_else(HashSet::new), - denied_list: Default::default(), prompt, + ..Default::default() } } @@ -993,15 +1175,13 @@ impl Permissions { prompt: bool, ) -> UnaryPermission<EnvDescriptor> { UnaryPermission::<EnvDescriptor> { - name: "env", - description: "environment variables", global_state: global_state_from_option(state), granted_list: state .as_ref() .map(|v| v.iter().map(EnvDescriptor::new).collect()) .unwrap_or_else(HashSet::new), - denied_list: Default::default(), prompt, + ..Default::default() } } @@ -1010,32 +1190,29 @@ impl Permissions { prompt: bool, ) -> UnaryPermission<RunDescriptor> { UnaryPermission::<RunDescriptor> { - name: "run", - description: "run a subprocess", global_state: global_state_from_option(state), granted_list: state .as_ref() - .map(|v| v.iter().map(|x| RunDescriptor(x.clone())).collect()) + .map(|v| { + v.iter() + .map(|x| RunDescriptor::from_str(x).unwrap()) + .collect() + }) .unwrap_or_else(HashSet::new), - denied_list: Default::default(), prompt, + ..Default::default() } } pub fn new_ffi( - state: &Option<Vec<String>>, + state: &Option<Vec<PathBuf>>, prompt: bool, ) -> UnaryPermission<FfiDescriptor> { UnaryPermission::<FfiDescriptor> { - name: "ffi", - description: "load a dynamic library", global_state: global_state_from_option(state), - granted_list: state - .as_ref() - .map(|v| v.iter().map(|x| FfiDescriptor(x.clone())).collect()) - .unwrap_or_else(HashSet::new), - denied_list: Default::default(), + granted_list: resolve_ffi_allowlist(state), prompt, + ..Default::default() } } @@ -1137,7 +1314,7 @@ impl deno_websocket::WebSocketPermissions for Permissions { } impl deno_ffi::FfiPermissions for Permissions { - fn check(&mut self, path: &str) -> Result<(), AnyError> { + fn check(&mut self, path: &Path) -> Result<(), AnyError> { self.ffi.check(path) } } @@ -1196,6 +1373,20 @@ pub fn resolve_write_allowlist( } } +pub fn resolve_ffi_allowlist( + allow: &Option<Vec<PathBuf>>, +) -> HashSet<FfiDescriptor> { + if let Some(v) = allow { + v.iter() + .map(|raw_path| { + FfiDescriptor(resolve_from_cwd(Path::new(&raw_path)).unwrap()) + }) + .collect() + } else { + HashSet::new() + } +} + /// Arbitrary helper. Resolves the path from CWD, and also gets a path that /// can be displayed without leaking the CWD when not allowed. fn resolved_and_display_path(path: &Path) -> (PathBuf, PathBuf) { @@ -1204,6 +1395,458 @@ fn resolved_and_display_path(path: &Path) -> (PathBuf, PathBuf) { (resolved_path, display_path) } +fn escalation_error() -> AnyError { + custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + ) +} + +#[derive(Debug, PartialEq)] +pub enum ChildUnitPermissionArg { + Inherit, + Granted, + NotGranted, +} + +impl Default for ChildUnitPermissionArg { + fn default() -> Self { + ChildUnitPermissionArg::Inherit + } +} + +impl<'de> Deserialize<'de> for ChildUnitPermissionArg { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct ChildUnitPermissionArgVisitor; + impl<'de> de::Visitor<'de> for ChildUnitPermissionArgVisitor { + type Value = ChildUnitPermissionArg; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("\"inherit\" or boolean") + } + + fn visit_unit<E>(self) -> Result<ChildUnitPermissionArg, E> + where + E: de::Error, + { + Ok(ChildUnitPermissionArg::Inherit) + } + + fn visit_str<E>(self, v: &str) -> Result<ChildUnitPermissionArg, E> + where + E: de::Error, + { + if v == "inherit" { + Ok(ChildUnitPermissionArg::Inherit) + } else { + Err(de::Error::invalid_value(de::Unexpected::Str(v), &self)) + } + } + + fn visit_bool<E>(self, v: bool) -> Result<ChildUnitPermissionArg, E> + where + E: de::Error, + { + match v { + true => Ok(ChildUnitPermissionArg::Granted), + false => Ok(ChildUnitPermissionArg::NotGranted), + } + } + } + deserializer.deserialize_any(ChildUnitPermissionArgVisitor) + } +} + +#[derive(Debug, PartialEq)] +pub enum ChildUnaryPermissionArg { + Inherit, + Granted, + NotGranted, + GrantedList(Vec<String>), +} + +impl Default for ChildUnaryPermissionArg { + fn default() -> Self { + ChildUnaryPermissionArg::Inherit + } +} + +impl<'de> Deserialize<'de> for ChildUnaryPermissionArg { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct ChildUnaryPermissionArgVisitor; + impl<'de> de::Visitor<'de> for ChildUnaryPermissionArgVisitor { + type Value = ChildUnaryPermissionArg; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("\"inherit\" or boolean or string[]") + } + + fn visit_unit<E>(self) -> Result<ChildUnaryPermissionArg, E> + where + E: de::Error, + { + Ok(ChildUnaryPermissionArg::Inherit) + } + + fn visit_str<E>(self, v: &str) -> Result<ChildUnaryPermissionArg, E> + where + E: de::Error, + { + if v == "inherit" { + Ok(ChildUnaryPermissionArg::Inherit) + } else { + Err(de::Error::invalid_value(de::Unexpected::Str(v), &self)) + } + } + + fn visit_bool<E>(self, v: bool) -> Result<ChildUnaryPermissionArg, E> + where + E: de::Error, + { + match v { + true => Ok(ChildUnaryPermissionArg::Granted), + false => Ok(ChildUnaryPermissionArg::NotGranted), + } + } + + fn visit_seq<V>( + self, + mut v: V, + ) -> Result<ChildUnaryPermissionArg, V::Error> + where + V: de::SeqAccess<'de>, + { + let mut granted_list = vec![]; + while let Some(value) = v.next_element::<String>()? { + granted_list.push(value); + } + Ok(ChildUnaryPermissionArg::GrantedList(granted_list)) + } + } + deserializer.deserialize_any(ChildUnaryPermissionArgVisitor) + } +} + +/// Directly deserializable from JS worker and test permission options. +#[derive(Debug, Default, PartialEq)] +pub struct ChildPermissionsArg { + env: ChildUnaryPermissionArg, + hrtime: ChildUnitPermissionArg, + net: ChildUnaryPermissionArg, + ffi: ChildUnaryPermissionArg, + read: ChildUnaryPermissionArg, + run: ChildUnaryPermissionArg, + write: ChildUnaryPermissionArg, +} + +impl<'de> Deserialize<'de> for ChildPermissionsArg { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + struct ChildPermissionsArgVisitor; + impl<'de> de::Visitor<'de> for ChildPermissionsArgVisitor { + type Value = ChildPermissionsArg; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("\"inherit\" or \"none\" or object") + } + + fn visit_unit<E>(self) -> Result<ChildPermissionsArg, E> + where + E: de::Error, + { + Ok(ChildPermissionsArg::default()) + } + + fn visit_str<E>(self, v: &str) -> Result<ChildPermissionsArg, E> + where + E: de::Error, + { + if v == "inherit" { + Ok(ChildPermissionsArg::default()) + } else if v == "none" { + Ok(ChildPermissionsArg { + env: ChildUnaryPermissionArg::NotGranted, + hrtime: ChildUnitPermissionArg::NotGranted, + net: ChildUnaryPermissionArg::NotGranted, + ffi: ChildUnaryPermissionArg::NotGranted, + read: ChildUnaryPermissionArg::NotGranted, + run: ChildUnaryPermissionArg::NotGranted, + write: ChildUnaryPermissionArg::NotGranted, + }) + } else { + Err(de::Error::invalid_value(de::Unexpected::Str(v), &self)) + } + } + + fn visit_map<V>(self, mut v: V) -> Result<ChildPermissionsArg, V::Error> + where + V: de::MapAccess<'de>, + { + let mut child_permissions_arg = ChildPermissionsArg::default(); + while let Some((key, value)) = + v.next_entry::<String, serde_json::Value>()? + { + if key == "env" { + let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value); + child_permissions_arg.env = arg.map_err(|e| { + de::Error::custom(format!("(deno.permissions.env) {}", e)) + })?; + } else if key == "hrtime" { + let arg = serde_json::from_value::<ChildUnitPermissionArg>(value); + child_permissions_arg.hrtime = arg.map_err(|e| { + de::Error::custom(format!("(deno.permissions.hrtime) {}", e)) + })?; + } else if key == "net" { + let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value); + child_permissions_arg.net = arg.map_err(|e| { + de::Error::custom(format!("(deno.permissions.net) {}", e)) + })?; + } else if key == "ffi" { + let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value); + child_permissions_arg.ffi = arg.map_err(|e| { + de::Error::custom(format!("(deno.permissions.ffi) {}", e)) + })?; + } else if key == "read" { + let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value); + child_permissions_arg.read = arg.map_err(|e| { + de::Error::custom(format!("(deno.permissions.read) {}", e)) + })?; + } else if key == "run" { + let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value); + child_permissions_arg.run = arg.map_err(|e| { + de::Error::custom(format!("(deno.permissions.run) {}", e)) + })?; + } else if key == "write" { + let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value); + child_permissions_arg.write = arg.map_err(|e| { + de::Error::custom(format!("(deno.permissions.write) {}", e)) + })?; + } else { + return Err(de::Error::custom("unknown permission name")); + } + } + Ok(child_permissions_arg) + } + } + deserializer.deserialize_any(ChildPermissionsArgVisitor) + } +} + +pub fn create_child_permissions( + main_perms: &mut Permissions, + child_permissions_arg: ChildPermissionsArg, +) -> Result<Permissions, AnyError> { + let mut worker_perms = Permissions::default(); + match child_permissions_arg.env { + ChildUnaryPermissionArg::Inherit => { + worker_perms.env = main_perms.env.clone(); + } + ChildUnaryPermissionArg::Granted => { + if main_perms.env.check_all().is_err() { + return Err(escalation_error()); + } + worker_perms.env.global_state = PermissionState::Granted; + } + ChildUnaryPermissionArg::NotGranted => {} + ChildUnaryPermissionArg::GrantedList(granted_list) => { + worker_perms.env.granted_list = + Permissions::new_env(&Some(granted_list), false).granted_list; + if !worker_perms + .env + .granted_list + .iter() + .all(|desc| main_perms.env.check(desc.as_ref()).is_ok()) + { + return Err(escalation_error()); + } + } + } + worker_perms.env.denied_list = main_perms.env.denied_list.clone(); + if main_perms.env.global_state == PermissionState::Denied { + worker_perms.env.global_state = PermissionState::Denied; + } + worker_perms.env.prompt = main_perms.env.prompt; + match child_permissions_arg.hrtime { + ChildUnitPermissionArg::Inherit => { + worker_perms.hrtime = main_perms.hrtime.clone(); + } + ChildUnitPermissionArg::Granted => { + if main_perms.hrtime.check().is_err() { + return Err(escalation_error()); + } + worker_perms.hrtime.state = PermissionState::Granted; + } + ChildUnitPermissionArg::NotGranted => {} + } + if main_perms.hrtime.state == PermissionState::Denied { + worker_perms.hrtime.state = PermissionState::Denied; + } + worker_perms.hrtime.prompt = main_perms.hrtime.prompt; + match child_permissions_arg.net { + ChildUnaryPermissionArg::Inherit => { + worker_perms.net = main_perms.net.clone(); + } + ChildUnaryPermissionArg::Granted => { + if main_perms.net.check_all().is_err() { + return Err(escalation_error()); + } + worker_perms.net.global_state = PermissionState::Granted; + } + ChildUnaryPermissionArg::NotGranted => {} + ChildUnaryPermissionArg::GrantedList(granted_list) => { + worker_perms.net.granted_list = + Permissions::new_net(&Some(granted_list), false).granted_list; + if !worker_perms + .net + .granted_list + .iter() + .all(|desc| main_perms.net.check(&(&desc.0, desc.1)).is_ok()) + { + return Err(escalation_error()); + } + } + } + worker_perms.net.denied_list = main_perms.net.denied_list.clone(); + if main_perms.net.global_state == PermissionState::Denied { + worker_perms.net.global_state = PermissionState::Denied; + } + worker_perms.net.prompt = main_perms.net.prompt; + match child_permissions_arg.ffi { + ChildUnaryPermissionArg::Inherit => { + worker_perms.ffi = main_perms.ffi.clone(); + } + ChildUnaryPermissionArg::Granted => { + if main_perms.ffi.check_all().is_err() { + return Err(escalation_error()); + } + worker_perms.ffi.global_state = PermissionState::Granted; + } + ChildUnaryPermissionArg::NotGranted => {} + ChildUnaryPermissionArg::GrantedList(granted_list) => { + worker_perms.ffi.granted_list = Permissions::new_ffi( + &Some(granted_list.iter().map(PathBuf::from).collect()), + false, + ) + .granted_list; + if !worker_perms + .ffi + .granted_list + .iter() + .all(|desc| main_perms.ffi.check(&desc.0).is_ok()) + { + return Err(escalation_error()); + } + } + } + worker_perms.ffi.denied_list = main_perms.ffi.denied_list.clone(); + if main_perms.ffi.global_state == PermissionState::Denied { + worker_perms.ffi.global_state = PermissionState::Denied; + } + worker_perms.ffi.prompt = main_perms.ffi.prompt; + match child_permissions_arg.read { + ChildUnaryPermissionArg::Inherit => { + worker_perms.read = main_perms.read.clone(); + } + ChildUnaryPermissionArg::Granted => { + if main_perms.read.check_all().is_err() { + return Err(escalation_error()); + } + worker_perms.read.global_state = PermissionState::Granted; + } + ChildUnaryPermissionArg::NotGranted => {} + ChildUnaryPermissionArg::GrantedList(granted_list) => { + worker_perms.read.granted_list = Permissions::new_read( + &Some(granted_list.iter().map(PathBuf::from).collect()), + false, + ) + .granted_list; + if !worker_perms + .read + .granted_list + .iter() + .all(|desc| main_perms.read.check(&desc.0).is_ok()) + { + return Err(escalation_error()); + } + } + } + worker_perms.read.denied_list = main_perms.read.denied_list.clone(); + if main_perms.read.global_state == PermissionState::Denied { + worker_perms.read.global_state = PermissionState::Denied; + } + worker_perms.read.prompt = main_perms.read.prompt; + match child_permissions_arg.run { + ChildUnaryPermissionArg::Inherit => { + worker_perms.run = main_perms.run.clone(); + } + ChildUnaryPermissionArg::Granted => { + if main_perms.run.check_all().is_err() { + return Err(escalation_error()); + } + worker_perms.run.global_state = PermissionState::Granted; + } + ChildUnaryPermissionArg::NotGranted => {} + ChildUnaryPermissionArg::GrantedList(granted_list) => { + worker_perms.run.granted_list = + Permissions::new_run(&Some(granted_list), false).granted_list; + if !worker_perms + .run + .granted_list + .iter() + .all(|desc| main_perms.run.check(&desc.to_string()).is_ok()) + { + return Err(escalation_error()); + } + } + } + worker_perms.run.denied_list = main_perms.run.denied_list.clone(); + if main_perms.run.global_state == PermissionState::Denied { + worker_perms.run.global_state = PermissionState::Denied; + } + worker_perms.run.prompt = main_perms.run.prompt; + match child_permissions_arg.write { + ChildUnaryPermissionArg::Inherit => { + worker_perms.write = main_perms.write.clone(); + } + ChildUnaryPermissionArg::Granted => { + if main_perms.write.check_all().is_err() { + return Err(escalation_error()); + } + worker_perms.write.global_state = PermissionState::Granted; + } + ChildUnaryPermissionArg::NotGranted => {} + ChildUnaryPermissionArg::GrantedList(granted_list) => { + worker_perms.write.granted_list = Permissions::new_write( + &Some(granted_list.iter().map(PathBuf::from).collect()), + false, + ) + .granted_list; + if !worker_perms + .write + .granted_list + .iter() + .all(|desc| main_perms.write.check(&desc.0).is_ok()) + { + return Err(escalation_error()); + } + } + } + worker_perms.write.denied_list = main_perms.write.denied_list.clone(); + if main_perms.write.global_state == PermissionState::Denied { + worker_perms.write.global_state = PermissionState::Denied; + } + worker_perms.write.prompt = main_perms.write.prompt; + Ok(worker_perms) +} + /// Shows the permission prompt and returns the answer according to the user input. /// This loops until the user gives the proper input. #[cfg(not(test))] @@ -1371,6 +2014,7 @@ fn set_prompt_result(value: bool) { mod tests { use super::*; use deno_core::resolve_url_or_path; + use deno_core::serde_json::json; // Creates vector of strings, Vec<String> macro_rules! svec { @@ -1701,11 +2345,11 @@ mod tests { }, ffi: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_ffi(&Some(svec!["deno"]), false) + ..Permissions::new_ffi(&Some(vec![PathBuf::from("deno")]), false) }, hrtime: UnitPermission { state: PermissionState::Prompt, - ..Default::default() + ..Permissions::new_hrtime(false, false) }, }; #[rustfmt::skip] @@ -1733,9 +2377,9 @@ mod tests { assert_eq!(perms2.run.query(None), PermissionState::Prompt); assert_eq!(perms2.run.query(Some(&"deno".to_string())), PermissionState::Granted); assert_eq!(perms1.ffi.query(None), PermissionState::Granted); - assert_eq!(perms1.ffi.query(Some(&"deno".to_string())), PermissionState::Granted); + assert_eq!(perms1.ffi.query(Some(Path::new("deno"))), PermissionState::Granted); assert_eq!(perms2.ffi.query(None), PermissionState::Prompt); - assert_eq!(perms2.ffi.query(Some(&"deno".to_string())), PermissionState::Granted); + assert_eq!(perms2.ffi.query(Some(Path::new("deno"))), PermissionState::Granted); assert_eq!(perms1.hrtime.query(), PermissionState::Granted); assert_eq!(perms2.hrtime.query(), PermissionState::Prompt); }; @@ -1772,10 +2416,10 @@ mod tests { set_prompt_result(false); assert_eq!(perms.run.request(Some(&"deno".to_string())), PermissionState::Granted); set_prompt_result(true); - assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted); + assert_eq!(perms.ffi.request(Some(Path::new("deno"))), PermissionState::Granted); assert_eq!(perms.ffi.query(None), PermissionState::Prompt); set_prompt_result(false); - assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted); + assert_eq!(perms.ffi.request(Some(Path::new("deno"))), PermissionState::Granted); set_prompt_result(false); assert_eq!(perms.hrtime.request(), PermissionState::Denied); set_prompt_result(true); @@ -1817,11 +2461,11 @@ mod tests { }, ffi: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_ffi(&Some(svec!["deno"]), false) + ..Permissions::new_ffi(&Some(vec![PathBuf::from("deno")]), false) }, hrtime: UnitPermission { state: PermissionState::Denied, - ..Default::default() + ..Permissions::new_hrtime(false, false) }, }; #[rustfmt::skip] @@ -1837,7 +2481,7 @@ mod tests { assert_eq!(perms.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); assert_eq!(perms.env.revoke(Some(&"HOME".to_string())), PermissionState::Prompt); assert_eq!(perms.run.revoke(Some(&"deno".to_string())), PermissionState::Prompt); - assert_eq!(perms.ffi.revoke(Some(&"deno".to_string())), PermissionState::Prompt); + assert_eq!(perms.ffi.revoke(Some(Path::new("deno"))), PermissionState::Prompt); assert_eq!(perms.hrtime.revoke(), PermissionState::Denied); }; } @@ -1977,4 +2621,233 @@ mod tests { PermissionState::Prompt ); } + + #[test] + fn test_deserialize_child_permissions_arg() { + assert_eq!( + ChildPermissionsArg::default(), + ChildPermissionsArg { + env: ChildUnaryPermissionArg::Inherit, + hrtime: ChildUnitPermissionArg::Inherit, + net: ChildUnaryPermissionArg::Inherit, + ffi: ChildUnaryPermissionArg::Inherit, + read: ChildUnaryPermissionArg::Inherit, + run: ChildUnaryPermissionArg::Inherit, + write: ChildUnaryPermissionArg::Inherit, + } + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!("inherit")).unwrap(), + ChildPermissionsArg::default() + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!("none")).unwrap(), + ChildPermissionsArg { + env: ChildUnaryPermissionArg::NotGranted, + hrtime: ChildUnitPermissionArg::NotGranted, + net: ChildUnaryPermissionArg::NotGranted, + ffi: ChildUnaryPermissionArg::NotGranted, + read: ChildUnaryPermissionArg::NotGranted, + run: ChildUnaryPermissionArg::NotGranted, + write: ChildUnaryPermissionArg::NotGranted, + } + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!({})).unwrap(), + ChildPermissionsArg::default() + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!({ + "env": ["foo", "bar"], + })) + .unwrap(), + ChildPermissionsArg { + env: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar"]), + ..Default::default() + } + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!({ + "hrtime": true, + })) + .unwrap(), + ChildPermissionsArg { + hrtime: ChildUnitPermissionArg::Granted, + ..Default::default() + } + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!({ + "hrtime": false, + })) + .unwrap(), + ChildPermissionsArg { + hrtime: ChildUnitPermissionArg::NotGranted, + ..Default::default() + } + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!({ + "env": true, + "net": true, + "ffi": true, + "read": true, + "run": true, + "write": true, + })) + .unwrap(), + ChildPermissionsArg { + env: ChildUnaryPermissionArg::Granted, + net: ChildUnaryPermissionArg::Granted, + ffi: ChildUnaryPermissionArg::Granted, + read: ChildUnaryPermissionArg::Granted, + run: ChildUnaryPermissionArg::Granted, + write: ChildUnaryPermissionArg::Granted, + ..Default::default() + } + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!({ + "env": false, + "net": false, + "ffi": false, + "read": false, + "run": false, + "write": false, + })) + .unwrap(), + ChildPermissionsArg { + env: ChildUnaryPermissionArg::NotGranted, + net: ChildUnaryPermissionArg::NotGranted, + ffi: ChildUnaryPermissionArg::NotGranted, + read: ChildUnaryPermissionArg::NotGranted, + run: ChildUnaryPermissionArg::NotGranted, + write: ChildUnaryPermissionArg::NotGranted, + ..Default::default() + } + ); + assert_eq!( + serde_json::from_value::<ChildPermissionsArg>(json!({ + "env": ["foo", "bar"], + "net": ["foo", "bar:8000"], + "ffi": ["foo", "file:///bar/baz"], + "read": ["foo", "file:///bar/baz"], + "run": ["foo", "file:///bar/baz", "./qux"], + "write": ["foo", "file:///bar/baz"], + })) + .unwrap(), + ChildPermissionsArg { + env: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar"]), + net: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar:8000"]), + ffi: ChildUnaryPermissionArg::GrantedList(svec![ + "foo", + "file:///bar/baz" + ]), + read: ChildUnaryPermissionArg::GrantedList(svec![ + "foo", + "file:///bar/baz" + ]), + run: ChildUnaryPermissionArg::GrantedList(svec![ + "foo", + "file:///bar/baz", + "./qux" + ]), + write: ChildUnaryPermissionArg::GrantedList(svec![ + "foo", + "file:///bar/baz" + ]), + ..Default::default() + } + ); + } + + #[test] + fn test_create_child_permissions() { + let mut main_perms = Permissions { + env: Permissions::new_env(&Some(vec![]), false), + hrtime: Permissions::new_hrtime(true, false), + net: Permissions::new_net(&Some(svec!["foo", "bar"]), false), + ..Default::default() + }; + assert_eq!( + create_child_permissions( + &mut main_perms.clone(), + ChildPermissionsArg { + env: ChildUnaryPermissionArg::Inherit, + hrtime: ChildUnitPermissionArg::NotGranted, + net: ChildUnaryPermissionArg::GrantedList(svec!["foo"]), + ffi: ChildUnaryPermissionArg::NotGranted, + ..Default::default() + } + ) + .unwrap(), + Permissions { + env: Permissions::new_env(&Some(vec![]), false), + net: Permissions::new_net(&Some(svec!["foo"]), false), + ..Default::default() + } + ); + assert!(create_child_permissions( + &mut main_perms.clone(), + ChildPermissionsArg { + net: ChildUnaryPermissionArg::Granted, + ..Default::default() + } + ) + .is_err()); + assert!(create_child_permissions( + &mut main_perms.clone(), + ChildPermissionsArg { + net: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar", "baz"]), + ..Default::default() + } + ) + .is_err()); + assert!(create_child_permissions( + &mut main_perms, + ChildPermissionsArg { + ffi: ChildUnaryPermissionArg::GrantedList(svec!["foo"]), + ..Default::default() + } + ) + .is_err()); + } + + #[test] + fn test_create_child_permissions_with_prompt() { + let _guard = PERMISSION_PROMPT_GUARD.lock(); + let mut main_perms = Permissions::from_options(&PermissionsOptions { + prompt: true, + ..Default::default() + }); + set_prompt_result(true); + let worker_perms = create_child_permissions( + &mut main_perms, + ChildPermissionsArg { + hrtime: ChildUnitPermissionArg::Granted, + read: ChildUnaryPermissionArg::Granted, + run: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar"]), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(main_perms, worker_perms); + } + + #[test] + fn test_create_child_permissions_with_inherited_denied_list() { + let _guard = PERMISSION_PROMPT_GUARD.lock(); + let mut main_perms = Permissions::from_options(&PermissionsOptions { + prompt: true, + ..Default::default() + }); + set_prompt_result(false); + assert!(main_perms.write.check(&PathBuf::from("foo")).is_err()); + let worker_perms = create_child_permissions( + &mut main_perms.clone(), + ChildPermissionsArg::default(), + ) + .unwrap(); + assert_eq!(worker_perms.write.denied_list, main_perms.write.denied_list); + } } |