diff --git a/cli/args/flags.rs b/cli/args/flags.rs index c992145c727e99..61e5139f53452c 100644 --- a/cli/args/flags.rs +++ b/cli/args/flags.rs @@ -814,6 +814,7 @@ pub struct PermissionFlags { pub allow_all: bool, pub allow_env: Option>, pub deny_env: Option>, + pub ignore_env: Option>, pub allow_ffi: Option>, pub deny_ffi: Option>, pub allow_net: Option>, @@ -836,6 +837,7 @@ impl PermissionFlags { self.allow_all || self.allow_env.is_some() || self.deny_env.is_some() + || self.ignore_env.is_some() || self.allow_ffi.is_some() || self.deny_ffi.is_some() || self.allow_net.is_some() @@ -974,6 +976,17 @@ impl Flags { _ => {} } + match &self.permissions.ignore_env { + Some(env_ignorelist) if env_ignorelist.is_empty() => { + args.push("--ignore-env".to_string()); + } + Some(env_ignorelist) => { + let s = format!("--ignore-env={}", env_ignorelist.join(",")); + args.push(s); + } + _ => {} + } + match &self.permissions.allow_run { Some(run_allowlist) if run_allowlist.is_empty() => { args.push("--allow-run".to_string()); @@ -4163,6 +4176,30 @@ fn compile_args_without_check_args(app: Command) -> Command { } fn permission_args(app: Command, requires: Option<&'static str>) -> Command { + let make_deny_ignore_env_arg = |arg: Arg| { + let mut arg = arg + .num_args(0..) + .use_value_delimiter(true) + .require_equals(true) + .value_name("VARIABLE_NAME") + .long_help("false") + .value_parser(|key: &str| { + if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { + return Err(format!("invalid key \"{key}\"")); + } + + Ok(if cfg!(windows) { + key.to_uppercase() + } else { + key.to_string() + }) + }) + .hide(true); + if let Some(requires) = requires { + arg = arg.requires(requires) + } + arg + }; app .after_help(cstr!(r#"Permission options: Docs: https://docs.deno.com/go/permissions @@ -4367,33 +4404,8 @@ fn permission_args(app: Command, requires: Option<&'static str>) -> Command { arg } ) - .arg( - { - let mut arg = Arg::new("deny-env") - .long("deny-env") - .num_args(0..) - .use_value_delimiter(true) - .require_equals(true) - .value_name("VARIABLE_NAME") - .long_help("false") - .value_parser(|key: &str| { - if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { - return Err(format!("invalid key \"{key}\"")); - } - - Ok(if cfg!(windows) { - key.to_uppercase() - } else { - key.to_string() - }) - }) - .hide(true); - if let Some(requires) = requires { - arg = arg.requires(requires) - } - arg - } - ) + .arg(make_deny_ignore_env_arg(Arg::new("deny-env").long("deny-env"))) + .arg(make_deny_ignore_env_arg(Arg::new("ignore-env").long("ignore-env"))) .arg( { let mut arg = Arg::new("allow-sys") @@ -6624,6 +6636,11 @@ fn permission_args_parse( debug!("env denylist: {:#?}", &flags.permissions.deny_env); } + if let Some(env_wl) = matches.remove_many::("ignore-env") { + flags.permissions.ignore_env = Some(env_wl.collect()); + debug!("env ignorelist: {:#?}", &flags.permissions.ignore_env); + } + if let Some(run_wl) = matches.remove_many::("allow-run") { flags.permissions.allow_run = Some(run_wl.collect()); debug!("run allowlist: {:#?}", &flags.permissions.allow_run); @@ -9208,6 +9225,26 @@ mod tests { ); } + #[test] + fn ignore_env_ignorelist() { + let r = + flags_from_vec(svec!["deno", "run", "--ignore-env=HOME", "script.ts"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run(RunFlags::new_default( + "script.ts".to_string(), + )), + permissions: PermissionFlags { + ignore_env: Some(svec!["HOME"]), + ..Default::default() + }, + code_cache_enabled: true, + ..Flags::default() + } + ); + } + #[test] fn allow_env_allowlist_multiple() { let r = flags_from_vec(svec![ @@ -9252,6 +9289,30 @@ mod tests { ); } + #[test] + fn deny_env_ignorelist_multiple() { + let r = flags_from_vec(svec![ + "deno", + "run", + "--ignore-env=HOME,PATH", + "script.ts" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run(RunFlags::new_default( + "script.ts".to_string(), + )), + permissions: PermissionFlags { + ignore_env: Some(svec!["HOME", "PATH"]), + ..Default::default() + }, + code_cache_enabled: true, + ..Flags::default() + } + ); + } + #[test] fn allow_env_allowlist_validator() { let r = diff --git a/cli/args/mod.rs b/cli/args/mod.rs index 38d97892512051..eda928a46727f2 100644 --- a/cli/args/mod.rs +++ b/cli/args/mod.rs @@ -1621,6 +1621,11 @@ fn flags_to_permissions_options( config.and_then(|c| c.permissions.env.deny.as_ref()), &identity, ), + ignore_env: handle_deny( + flags.ignore_env.as_ref(), + config.and_then(|c| c.permissions.env.ignore.as_ref()), + &identity, + ), allow_net: handle_allow( flags.allow_all, config.and_then(|c| c.permissions.all), @@ -1711,6 +1716,7 @@ fn flags_to_permissions_options( #[cfg(test)] mod test { + use deno_config::deno_json::AllowDenyIgnorePermissionConfig; use deno_config::deno_json::AllowDenyPermissionConfig; use deno_config::deno_json::PermissionsObject; use pretty_assertions::assert_eq; @@ -1823,13 +1829,16 @@ mod test { "example.com".to_string(), ])), }, - env: AllowDenyPermissionConfig { + env: AllowDenyIgnorePermissionConfig { allow: Some(PermissionConfigValue::Some(vec![ "env-allow".to_string(), ])), deny: Some(PermissionConfigValue::Some(vec![ "env-deny".to_string(), ])), + ignore: Some(PermissionConfigValue::Some(vec![ + "env-ignore".to_string(), + ])), }, net: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec![ @@ -1874,6 +1883,7 @@ mod test { PermissionsOptions { allow_env: Some(vec!["env-allow".to_string()]), deny_env: Some(vec!["env-deny".to_string()]), + ignore_env: Some(vec!["env-ignore".to_string()]), allow_net: Some(vec!["net-allow".to_string()]), deny_net: Some(vec!["net-deny".to_string()]), allow_ffi: Some(vec![ @@ -1973,6 +1983,7 @@ mod test { PermissionsOptions { allow_env: Some(vec![]), deny_env: None, + ignore_env: None, allow_net: Some(vec![]), deny_net: None, allow_ffi: Some(vec![]), diff --git a/cli/schemas/config-file.v1.json b/cli/schemas/config-file.v1.json index 4c31bd01004a6b..8df236dfab7cd7 100644 --- a/cli/schemas/config-file.v1.json +++ b/cli/schemas/config-file.v1.json @@ -16,6 +16,23 @@ "items": { "type": "string" } }] }, + "allowDenyIgnorePermissionConfig": { + "type": "object", + "description": "Object form to allow, deny, and/or ignore permissions.", + "additionalProperties": false, + "properties": { + "allow": { "$ref": "#/$defs/permissionConfigValue" }, + "deny": { "$ref": "#/$defs/permissionConfigValue" }, + "ignore": { "$ref": "#/$defs/permissionConfigValue" } + } + }, + "allowDenyIgnorePermissionConfigValue": { + "oneOf": [{ + "$ref": "#/$defs/permissionConfigValue" + }, { + "$ref": "#/$defs/allowDenyIgnorePermissionConfig" + }] + }, "allowDenyPermissionConfig": { "type": "object", "description": "Object form to allow and/or deny permissions.", @@ -52,7 +69,7 @@ "read": { "$ref": "#/$defs/allowDenyPermissionConfigValue" }, "write": { "$ref": "#/$defs/allowDenyPermissionConfigValue" }, "import": { "$ref": "#/$defs/allowDenyPermissionConfigValue" }, - "env": { "$ref": "#/$defs/allowDenyPermissionConfigValue" }, + "env": { "$ref": "#/$defs/allowDenyIgnorePermissionConfigValue" }, "net": { "$ref": "#/$defs/allowDenyPermissionConfigValue" }, "run": { "$ref": "#/$defs/allowDenyPermissionConfigValue" }, "ffi": { "$ref": "#/$defs/allowDenyPermissionConfigValue" }, diff --git a/ext/os/lib.rs b/ext/os/lib.rs index c7734f85f7e122..58d5ad0606a281 100644 --- a/ext/os/lib.rs +++ b/ext/os/lib.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::env; use std::ffi::OsString; +use std::ops::ControlFlow; use std::sync::Arc; use std::sync::atomic::AtomicI32; use std::sync::atomic::Ordering; @@ -154,7 +155,9 @@ fn op_set_env( #[string] key: &str, #[string] value: &str, ) -> Result<(), OsError> { - state.borrow_mut::().check_env(key)?; + if check_env_with_maybe_exit(state, key)?.is_break() { + return Ok(()); + } if key.is_empty() { return Err(OsError::EnvEmptyKey); } @@ -173,6 +176,21 @@ fn op_set_env( Ok(()) } +fn check_env_with_maybe_exit( + state: &mut OpState, + key: &str, +) -> Result, PermissionCheckError> { + match state.borrow_mut::().check_env(key) { + Ok(()) => Ok(ControlFlow::Continue(())), + Err(PermissionCheckError::PermissionDenied(err)) + if err.state == PermissionState::Ignored => + { + Ok(ControlFlow::Break(())) + } + Err(err) => Err(err), + } +} + #[op2(stack_trace)] #[serde] fn op_env( @@ -192,7 +210,9 @@ fn op_env( PermissionState::Granted | PermissionState::Prompt | PermissionState::Denied => return Err(err.into()), - PermissionState::GrantedPartial | PermissionState::DeniedPartial => false, + PermissionState::GrantedPartial + | PermissionState::DeniedPartial + | PermissionState::Ignored => false, }, Err(err) => return Err(err), }; @@ -209,7 +229,8 @@ fn op_env( PermissionState::Granted | PermissionState::GrantedPartial => { Some((k, v)) } - PermissionState::Prompt + PermissionState::Ignored + | PermissionState::Prompt | PermissionState::Denied | PermissionState::DeniedPartial => None, } @@ -251,8 +272,9 @@ fn op_get_env( let skip_permission_check = SORTED_NODE_ENV_VAR_ALLOWLIST.binary_search(&key).is_ok(); - if !skip_permission_check { - state.borrow_mut::().check_env(key)?; + if !skip_permission_check && check_env_with_maybe_exit(state, key)?.is_break() + { + return Ok(None); } get_env_var(key) @@ -263,7 +285,9 @@ fn op_delete_env( state: &mut OpState, #[string] key: &str, ) -> Result<(), OsError> { - state.borrow_mut::().check_env(key)?; + if check_env_with_maybe_exit(state, key)?.is_break() { + return Ok(()); + } if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(OsError::EnvInvalidKey(key.to_string())); } diff --git a/libs/config/deno_json/mod.rs b/libs/config/deno_json/mod.rs index 667d2f5d1c6c01..57ef92d56c1a41 100644 --- a/libs/config/deno_json/mod.rs +++ b/libs/config/deno_json/mod.rs @@ -40,6 +40,8 @@ use crate::util::is_skippable_io_error; mod permissions; mod ts; +pub use permissions::AllowDenyIgnorePermissionConfig; +pub use permissions::AllowDenyIgnorePermissionConfigValue; pub use permissions::AllowDenyPermissionConfig; pub use permissions::AllowDenyPermissionConfigValue; pub use permissions::PermissionConfigValue; diff --git a/libs/config/deno_json/permissions.rs b/libs/config/deno_json/permissions.rs index a28b5538d0d90d..565db1c6e5aec7 100644 --- a/libs/config/deno_json/permissions.rs +++ b/libs/config/deno_json/permissions.rs @@ -77,6 +77,20 @@ impl AllowDenyPermissionConfig { } } +#[derive(Deserialize, Default, Clone, Debug, PartialEq, Eq, Hash)] +#[serde(default, deny_unknown_fields)] +pub struct AllowDenyIgnorePermissionConfig { + pub allow: Option, + pub deny: Option, + pub ignore: Option, +} + +impl AllowDenyIgnorePermissionConfig { + pub fn is_none(&self) -> bool { + self.allow.is_none() && self.deny.is_none() && self.ignore.is_none() + } +} + #[derive(Deserialize)] #[serde(untagged)] pub enum AllowDenyPermissionConfigValue { @@ -111,6 +125,45 @@ fn deserialize_allow_deny<'de, D: serde::Deserializer<'de>>( }) } +#[derive(Deserialize)] +#[serde(untagged)] +pub enum AllowDenyIgnorePermissionConfigValue { + Boolean(bool), + AllowList(Vec), + Object(AllowDenyIgnorePermissionConfig), +} + +fn deserialize_allow_deny_ignore<'de, D: serde::Deserializer<'de>>( + de: D, +) -> Result { + AllowDenyIgnorePermissionConfigValue::deserialize(de).map(|value| match value + { + AllowDenyIgnorePermissionConfigValue::Boolean(b) => { + AllowDenyIgnorePermissionConfig { + allow: Some(if b { + PermissionConfigValue::All + } else { + PermissionConfigValue::None + }), + deny: None, + ignore: None, + } + } + AllowDenyIgnorePermissionConfigValue::AllowList(allow) => { + AllowDenyIgnorePermissionConfig { + allow: Some(if allow.is_empty() { + PermissionConfigValue::None + } else { + PermissionConfigValue::Some(allow) + }), + deny: None, + ignore: None, + } + } + AllowDenyIgnorePermissionConfigValue::Object(obj) => obj, + }) +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Hash)] #[serde(untagged)] pub enum PermissionNameOrObject { @@ -135,8 +188,8 @@ pub struct PermissionsObject { 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_ignore")] + pub env: AllowDenyIgnorePermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] pub net: AllowDenyPermissionConfig, #[serde(default, deserialize_with = "deserialize_allow_deny")] @@ -252,9 +305,10 @@ mod test { allow: Some(PermissionConfigValue::All), deny: None, }, - env: AllowDenyPermissionConfig { + env: AllowDenyIgnorePermissionConfig { allow: Some(PermissionConfigValue::All), deny: None, + ignore: None, }, net: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::All), @@ -301,9 +355,10 @@ mod test { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, }, - env: AllowDenyPermissionConfig { + env: AllowDenyIgnorePermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), deny: None, + ignore: None, }, net: AllowDenyPermissionConfig { allow: Some(PermissionConfigValue::Some(vec!["test".to_string()])), diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs index 9592a92f3756cd..f71d95df56bfc7 100644 --- a/runtime/ops/permissions.rs +++ b/runtime/ops/permissions.rs @@ -37,7 +37,9 @@ impl From for PermissionStatus { PermissionStatus { state: match state { PermissionState::Granted | PermissionState::GrantedPartial => "granted", - PermissionState::DeniedPartial | PermissionState::Denied => "denied", + PermissionState::Ignored + | PermissionState::DeniedPartial + | PermissionState::Denied => "denied", PermissionState::Prompt => "prompt", }, partial: state == PermissionState::GrantedPartial, diff --git a/runtime/permissions/lib.rs b/runtime/permissions/lib.rs index 80100338cceb52..61f2bb30605672 100644 --- a/runtime/permissions/lib.rs +++ b/runtime/permissions/lib.rs @@ -135,6 +135,7 @@ pub enum PermissionState { Prompt = 2, Denied = 3, DeniedPartial = 4, + Ignored = 5, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -720,13 +721,14 @@ fn format_display_name(display_name: Cow<'_, str>) -> Cow<'_, str> { #[derive(Debug, Clone, Eq, PartialEq)] enum AllowOrDenyDescRef<'a, TAllowDesc: AllowDescriptor> { Allow(&'a TAllowDesc), - Deny(&'a TAllowDesc::DenyDesc, /* is prompt */ bool), + Deny(&'a TAllowDesc::DenyDesc, /* precedence */ u8), } #[derive(Debug, Clone, Eq, PartialEq)] enum UnaryPermissionDesc { Granted(TAllowDesc), FlagDenied(TAllowDesc::DenyDesc), + FlagIgnored(TAllowDesc::DenyDesc), PromptDenied(TAllowDesc::DenyDesc), } @@ -758,7 +760,7 @@ impl std::cmp::Ord } } } - AllowOrDenyDescRef::Deny(self_desc, self_is_prompt) => { + AllowOrDenyDescRef::Deny(self_desc, self_precedence) => { match other.allow_or_deny_desc() { AllowOrDenyDescRef::Allow(other_desc) => { match other_desc.cmp_deny(self_desc) { @@ -770,9 +772,9 @@ impl std::cmp::Ord Ordering::Greater => Ordering::Less, } } - AllowOrDenyDescRef::Deny(other_desc, other_is_prompt) => { + AllowOrDenyDescRef::Deny(other_desc, other_precedence) => { match self_desc.cmp_deny(other_desc) { - Ordering::Equal => self_is_prompt.cmp(&other_is_prompt), + Ordering::Equal => self_precedence.cmp(&other_precedence), ordering => ordering, } } @@ -787,10 +789,13 @@ impl UnaryPermissionDesc { match self { UnaryPermissionDesc::Granted(desc) => AllowOrDenyDescRef::Allow(desc), UnaryPermissionDesc::FlagDenied(desc) => { - AllowOrDenyDescRef::Deny(desc, false) + AllowOrDenyDescRef::Deny(desc, 0) } UnaryPermissionDesc::PromptDenied(desc) => { - AllowOrDenyDescRef::Deny(desc, true) + AllowOrDenyDescRef::Deny(desc, 1) + } + UnaryPermissionDesc::FlagIgnored(desc) => { + AllowOrDenyDescRef::Deny(desc, 2) } } } @@ -799,7 +804,8 @@ impl UnaryPermissionDesc { match self { UnaryPermissionDesc::FlagDenied(_) => 0, UnaryPermissionDesc::PromptDenied(_) => 1, - UnaryPermissionDesc::Granted(_) => 2, + UnaryPermissionDesc::FlagIgnored(_) => 2, + UnaryPermissionDesc::Granted(_) => 3, } } } @@ -809,6 +815,7 @@ struct UnaryPermissionDescriptors { inner: Vec>, has_flag_denied: bool, has_prompt_denied: bool, + has_flag_ignored: bool, } impl Default @@ -819,6 +826,7 @@ impl Default inner: Default::default(), has_flag_denied: false, has_prompt_denied: false, + has_flag_ignored: false, } } } @@ -835,8 +843,8 @@ impl UnaryPermissionDescriptors { self.inner.iter() } - pub fn has_any_denied(&self) -> bool { - self.has_flag_denied || self.has_prompt_denied + pub fn has_any_denied_or_ignored(&self) -> bool { + self.has_flag_denied || self.has_prompt_denied || self.has_flag_ignored } pub fn has_prompt_denied(&self) -> bool { @@ -849,6 +857,9 @@ impl UnaryPermissionDescriptors { UnaryPermissionDesc::FlagDenied(_) => { self.has_flag_denied = true; } + UnaryPermissionDesc::FlagIgnored(_) => { + self.has_flag_ignored = true; + } UnaryPermissionDesc::PromptDenied(_) => { self.has_prompt_denied = true; } @@ -862,6 +873,7 @@ impl UnaryPermissionDescriptors { self.inner.retain(|v| match v { UnaryPermissionDesc::Granted(v) => !desc.revokes(v), UnaryPermissionDesc::FlagDenied(_) + | UnaryPermissionDesc::FlagIgnored(_) | UnaryPermissionDesc::PromptDenied(_) => true, }) } @@ -870,6 +882,7 @@ impl UnaryPermissionDescriptors { self.inner.retain(|v| match v { UnaryPermissionDesc::Granted(_) => false, UnaryPermissionDesc::FlagDenied(_) + | UnaryPermissionDesc::FlagIgnored(_) | UnaryPermissionDesc::PromptDenied(_) => true, }) } @@ -879,6 +892,7 @@ impl UnaryPermissionDescriptors { pub struct UnaryPermission { granted_global: bool, flag_denied_global: bool, + flag_ignored_global: bool, prompt_denied_global: bool, descriptors: UnaryPermissionDescriptors, prompt: bool, @@ -889,6 +903,7 @@ impl Default for UnaryPermission { UnaryPermission { granted_global: Default::default(), flag_denied_global: Default::default(), + flag_ignored_global: Default::default(), prompt_denied_global: Default::default(), descriptors: Default::default(), prompt: Default::default(), @@ -901,6 +916,7 @@ impl Clone for UnaryPermission { Self { granted_global: self.granted_global, flag_denied_global: self.flag_denied_global, + flag_ignored_global: self.flag_ignored_global, prompt_denied_global: self.prompt_denied_global, descriptors: self.descriptors.clone(), prompt: self.prompt, @@ -924,7 +940,8 @@ impl< self.granted_global && !self.flag_denied_global && !self.prompt_denied_global - && !self.descriptors.has_any_denied() + && !self.flag_ignored_global + && !self.descriptors.has_any_denied_or_ignored() && !has_broker() } @@ -978,6 +995,8 @@ impl< self.query_allowed_desc_for_exact_match(desc, allow_partial) { state + } else if self.flag_ignored_global { + PermissionState::Ignored } else if matches!(allow_partial, AllowPartial::TreatAsDenied) && self.is_partial_flag_denied(desc) { @@ -1012,6 +1031,11 @@ impl< return Some(PermissionState::Denied); } } + UnaryPermissionDesc::FlagIgnored(v) => { + if desc.matches_deny(v) { + return Some(PermissionState::Ignored); + } + } UnaryPermissionDesc::PromptDenied(v) => { if desc.stronger_than_deny(v) { return Some(PermissionState::Denied); @@ -1117,9 +1141,12 @@ impl< query: Option<&TAllowDesc::QueryDesc<'_>>, ) -> bool { match query { - None => self.descriptors.has_flag_denied, + None => { + self.descriptors.has_flag_denied || self.descriptors.has_flag_ignored + } Some(query) => self.descriptors.iter().any(|desc| match desc { - UnaryPermissionDesc::FlagDenied(v) => query.overlaps_deny(v), + UnaryPermissionDesc::FlagIgnored(v) + | UnaryPermissionDesc::FlagDenied(v) => query.overlaps_deny(v), UnaryPermissionDesc::Granted(_) | UnaryPermissionDesc::PromptDenied(_) => false, }), @@ -1206,12 +1233,14 @@ impl< perms.flag_denied_global = self.flag_denied_global; perms.prompt_denied_global = self.prompt_denied_global; perms.prompt = self.prompt; + perms.flag_ignored_global = self.flag_ignored_global; for item in self.descriptors.iter() { match item { UnaryPermissionDesc::Granted(_) => { // ignore } UnaryPermissionDesc::FlagDenied(_) + | UnaryPermissionDesc::FlagIgnored(_) | UnaryPermissionDesc::PromptDenied(_) => { perms.descriptors.insert(item.clone()); } @@ -1748,7 +1777,7 @@ impl Host { } } -#[derive(Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)] pub struct NetDescriptor(pub Host, pub Option); impl QueryDescriptor for NetDescriptor { @@ -3344,6 +3373,7 @@ impl Permissions { pub struct PermissionsOptions { pub allow_env: Option>, pub deny_env: Option>, + pub ignore_env: Option>, pub allow_net: Option>, pub deny_net: Option>, pub allow_ffi: Option>, @@ -3378,32 +3408,47 @@ pub enum PermissionsFromOptionsError { } impl Permissions { - pub fn new_unary( + pub fn new_unary_with_ignore( allow_list: Option>, deny_list: Option>, + ignore_list: Option>, prompt: bool, ) -> UnaryPermission { let mut descriptors = UnaryPermissionDescriptors::with_capacity( allow_list.as_ref().map(|v| v.len()).unwrap_or(0) + + ignore_list.as_ref().map(|v| v.len()).unwrap_or(0) + deny_list.as_ref().map(|v| v.len()).unwrap_or(0), ); let granted_global = global_from_option(allow_list.as_ref()); let flag_denied_global = global_from_option(deny_list.as_ref()); + let flag_ignored_global = global_from_option(ignore_list.as_ref()); for item in allow_list.unwrap_or_default() { descriptors.insert(UnaryPermissionDesc::Granted(item)); } for item in deny_list.unwrap_or_default() { descriptors.insert(UnaryPermissionDesc::FlagDenied(item)); } + for item in ignore_list.unwrap_or_default() { + descriptors.insert(UnaryPermissionDesc::FlagIgnored(item)); + } UnaryPermission:: { granted_global, flag_denied_global, + flag_ignored_global, descriptors, prompt, ..Default::default() } } + pub fn new_unary( + allow_list: Option>, + deny_list: Option>, + prompt: bool, + ) -> UnaryPermission { + Self::new_unary_with_ignore(allow_list, deny_list, None, prompt) + } + pub const fn new_all(allow_state: bool) -> UnitPermission { unit_permission_from_flag_bools( allow_state, @@ -3519,13 +3564,16 @@ impl Permissions { })?, opts.prompt, ), - env: Permissions::new_unary( + env: Permissions::new_unary_with_ignore( parse_maybe_vec(opts.allow_env.as_deref(), |item| { parser.parse_env_descriptor(item) })?, parse_maybe_vec(opts.deny_env.as_deref(), |text| { parser.parse_env_descriptor(text) })?, + parse_maybe_vec(opts.ignore_env.as_deref(), |text| { + parser.parse_env_descriptor(text) + })?, opts.prompt, ), sys: Permissions::new_unary( @@ -6456,6 +6504,64 @@ mod tests { } } + #[test] + fn test_env_ignore() { + set_prompter(Box::new(TestPrompter)); + let _prompt_value = PERMISSION_PROMPT_STUB_VALUE_SETTER.lock(); + { + let mut perms = Permissions::none_without_prompt(); + perms.env = UnaryPermission { + granted_global: false, + ..Permissions::new_unary_with_ignore( + Some(Vec::from([EnvDescriptor::new(Cow::Borrowed("ALLOWED_*"))])), + Some(Vec::from([EnvDescriptor::new(Cow::Borrowed("DENIED_*"))])), + Some(Vec::from([EnvDescriptor::new(Cow::Borrowed("IGNORED_*"))])), + false, + ) + }; + assert_eq!( + perms.env.query(Some("ALLOWED_TEST")), + PermissionState::Granted + ); + assert_eq!( + perms.env.query(Some("IGNORED_TEST")), + PermissionState::Ignored + ); + assert_eq!( + perms.env.query(Some("DENIED_TEST")), + PermissionState::Denied + ); + } + { + let mut perms = Permissions::none_without_prompt(); + perms.env = UnaryPermission { + granted_global: false, + ..Permissions::new_unary_with_ignore( + Some(Vec::from([EnvDescriptor::new(Cow::Borrowed( + "PREFIX_ALLOWED*", + ))])), + Some(Vec::from([EnvDescriptor::new(Cow::Borrowed("PREFIX*"))])), + Some(Vec::from([EnvDescriptor::new(Cow::Borrowed( + "PREFIX_IGNORED*", + ))])), + false, + ) + }; + assert_eq!( + perms.env.query(Some("PREFIX_TEST")), + PermissionState::Denied + ); + assert_eq!( + perms.env.query(Some("PREFIX_IGNORED_TEST")), + PermissionState::Ignored + ); + assert_eq!( + perms.env.query(Some("PREFIX_ALLOWED_TEST")), + PermissionState::Granted + ); + } + } + #[test] fn test_check_partial_denied() { let parser = TestPermissionDescriptorParser; diff --git a/tests/specs/permission/ignore_env/config/__test__.jsonc b/tests/specs/permission/ignore_env/config/__test__.jsonc new file mode 100644 index 00000000000000..1aa27c2572012e --- /dev/null +++ b/tests/specs/permission/ignore_env/config/__test__.jsonc @@ -0,0 +1,8 @@ +{ + "envs": { + "VAR1": "1", + "VAR2": "2" + }, + "args": "run --quiet -P main.ts", + "output": "undefined\nundefined\n[Object: null prototype] {}\n" +} diff --git a/tests/specs/permission/ignore_env/config/deno.json b/tests/specs/permission/ignore_env/config/deno.json new file mode 100644 index 00000000000000..0719091b80896f --- /dev/null +++ b/tests/specs/permission/ignore_env/config/deno.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "default": { + "env": { + "ignore": true + } + } + } +} diff --git a/tests/specs/permission/ignore_env/config/main.ts b/tests/specs/permission/ignore_env/config/main.ts new file mode 100644 index 00000000000000..cd94f4973e6294 --- /dev/null +++ b/tests/specs/permission/ignore_env/config/main.ts @@ -0,0 +1,7 @@ +console.log(Deno.env.get("VAR1")); +console.log(Deno.env.get("VAR2")); +const object = Deno.env.toObject(); +console.log(object); +if ("VAR1" in object) { + throw "FAILED"; +} diff --git a/tests/specs/permission/ignore_env/flags/__test__.jsonc b/tests/specs/permission/ignore_env/flags/__test__.jsonc new file mode 100644 index 00000000000000..d1900e6c1bc84c --- /dev/null +++ b/tests/specs/permission/ignore_env/flags/__test__.jsonc @@ -0,0 +1,26 @@ +{ + "envs": { + "VAR1": "1", + "VAR2": "2" + }, + "tests": { + "all": { + "args": "run --ignore-env main.ts", + "output": "undefined\nundefined\n[Object: null prototype] {}\n" + }, + "some": { + "args": "run --ignore-env=VAR1 main.ts", + "output": "undefined\nerror: [WILDCARD]\n", + "exitCode": 1 + }, + "some_allow_env": { + "args": "run --ignore-env=VAR1 --allow-env main.ts", + "output": "undefined\n2\n[WILDCARD]" + }, + "deny_env": { + "args": "run --deny-env --ignore-env=VAR1 main.ts", + "output": "undefined\nerror:[WILDCARD]", + "exitCode": 1 + } + } +} diff --git a/tests/specs/permission/ignore_env/flags/main.ts b/tests/specs/permission/ignore_env/flags/main.ts new file mode 100644 index 00000000000000..cd94f4973e6294 --- /dev/null +++ b/tests/specs/permission/ignore_env/flags/main.ts @@ -0,0 +1,7 @@ +console.log(Deno.env.get("VAR1")); +console.log(Deno.env.get("VAR2")); +const object = Deno.env.toObject(); +console.log(object); +if ("VAR1" in object) { + throw "FAILED"; +}