diff --git a/cli/dts/lib.deno.ns.d.ts b/cli/dts/lib.deno.ns.d.ts index 9108f6dd6f..738d0ba54b 100644 --- a/cli/dts/lib.deno.ns.d.ts +++ b/cli/dts/lib.deno.ns.d.ts @@ -2164,6 +2164,7 @@ declare namespace Deno { export interface EnvPermissionDescriptor { name: "env"; + variable?: string; } export interface PluginPermissionDescriptor { diff --git a/cli/flags.rs b/cli/flags.rs index 5feb463711..9002147011 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -128,7 +128,7 @@ pub struct Flags { pub argv: Vec, pub subcommand: DenoSubcommand, - pub allow_env: bool, + pub allow_env: Option>, pub allow_hrtime: bool, pub allow_net: Option>, pub allow_plugin: bool, @@ -207,8 +207,15 @@ impl Flags { _ => {} } - if self.allow_env { - args.push("--allow-env".to_string()); + match &self.allow_env { + Some(env_allowlist) if env_allowlist.is_empty() => { + args.push("--allow-env".to_string()); + } + Some(env_allowlist) => { + let s = format!("--allow-env={}", env_allowlist.join(",")); + args.push(s); + } + _ => {} } match &self.allow_run { @@ -527,7 +534,7 @@ fn repl_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.repl = true; flags.subcommand = DenoSubcommand::Repl; flags.allow_net = Some(vec![]); - flags.allow_env = true; + flags.allow_env = Some(vec![]); flags.allow_run = Some(vec![]); flags.allow_read = Some(vec![]); flags.allow_write = Some(vec![]); @@ -538,7 +545,7 @@ fn repl_parse(flags: &mut Flags, matches: &clap::ArgMatches) { fn eval_parse(flags: &mut Flags, matches: &clap::ArgMatches) { runtime_args_parse(flags, matches, false, true); flags.allow_net = Some(vec![]); - flags.allow_env = true; + flags.allow_env = Some(vec![]); flags.allow_run = Some(vec![]); flags.allow_read = Some(vec![]); flags.allow_write = Some(vec![]); @@ -1402,7 +1409,19 @@ fn permission_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { .arg( Arg::with_name("allow-env") .long("allow-env") - .help("Allow environment access"), + .min_values(0) + .takes_value(true) + .use_delimiter(true) + .require_equals(true) + .help("Allow environment access") + .validator(|keys| { + for key in keys.split(',') { + if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { + return Err(format!("invalid key \"{}\"", key)); + } + } + Ok(()) + }), ) .arg( Arg::with_name("allow-run") @@ -1826,15 +1845,26 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { debug!("net allowlist: {:#?}", &flags.allow_net); } + if let Some(env_wl) = matches.values_of("allow-env") { + let env_allowlist: Vec = env_wl + .map(|env: &str| { + if cfg!(windows) { + env.to_uppercase() + } else { + env.to_string() + } + }) + .collect(); + flags.allow_env = Some(env_allowlist); + debug!("env allowlist: {:#?}", &flags.allow_env); + } + if let Some(run_wl) = matches.values_of("allow-run") { let run_allowlist: Vec = run_wl.map(ToString::to_string).collect(); flags.allow_run = Some(run_allowlist); debug!("run allowlist: {:#?}", &flags.allow_run); } - if matches.is_present("allow-env") { - flags.allow_env = true; - } if matches.is_present("allow-plugin") { flags.allow_plugin = true; } @@ -1843,7 +1873,7 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { } if matches.is_present("allow-all") { flags.allow_read = Some(vec![]); - flags.allow_env = true; + flags.allow_env = Some(vec![]); flags.allow_net = Some(vec![]); flags.allow_run = Some(vec![]); flags.allow_write = Some(vec![]); @@ -2054,7 +2084,7 @@ mod tests { script: "gist.ts".to_string(), }, allow_net: Some(vec![]), - allow_env: true, + allow_env: Some(vec![]), allow_run: Some(vec![]), allow_read: Some(vec![]), allow_write: Some(vec![]), @@ -2426,7 +2456,7 @@ mod tests { ext: "js".to_string(), }, allow_net: Some(vec![]), - allow_env: true, + allow_env: Some(vec![]), allow_run: Some(vec![]), allow_read: Some(vec![]), allow_write: Some(vec![]), @@ -2449,7 +2479,7 @@ mod tests { ext: "js".to_string(), }, allow_net: Some(vec![]), - allow_env: true, + allow_env: Some(vec![]), allow_run: Some(vec![]), allow_read: Some(vec![]), allow_write: Some(vec![]), @@ -2473,7 +2503,7 @@ mod tests { ext: "ts".to_string(), }, allow_net: Some(vec![]), - allow_env: true, + allow_env: Some(vec![]), allow_run: Some(vec![]), allow_read: Some(vec![]), allow_write: Some(vec![]), @@ -2510,7 +2540,7 @@ mod tests { seed: Some(1), inspect: Some("127.0.0.1:9229".parse().unwrap()), allow_net: Some(vec![]), - allow_env: true, + allow_env: Some(vec![]), allow_run: Some(vec![]), allow_read: Some(vec![]), allow_write: Some(vec![]), @@ -2540,7 +2570,7 @@ mod tests { }, argv: svec!["arg1", "arg2"], allow_net: Some(vec![]), - allow_env: true, + allow_env: Some(vec![]), allow_run: Some(vec![]), allow_read: Some(vec![]), allow_write: Some(vec![]), @@ -2560,7 +2590,7 @@ mod tests { repl: true, subcommand: DenoSubcommand::Repl, allow_net: Some(vec![]), - allow_env: true, + allow_env: Some(vec![]), allow_run: Some(vec![]), allow_read: Some(vec![]), allow_write: Some(vec![]), @@ -2594,7 +2624,7 @@ mod tests { seed: Some(1), inspect: Some("127.0.0.1:9229".parse().unwrap()), allow_net: Some(vec![]), - allow_env: true, + allow_env: Some(vec![]), allow_run: Some(vec![]), allow_read: Some(vec![]), allow_write: Some(vec![]), @@ -2671,6 +2701,55 @@ mod tests { ); } + #[test] + fn allow_env_allowlist() { + let r = + flags_from_vec(svec!["deno", "run", "--allow-env=HOME", "script.ts"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run { + script: "script.ts".to_string(), + }, + allow_env: Some(svec!["HOME"]), + ..Flags::default() + } + ); + } + + #[test] + fn allow_env_allowlist_multiple() { + let r = flags_from_vec(svec![ + "deno", + "run", + "--allow-env=HOME,PATH", + "script.ts" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run { + script: "script.ts".to_string(), + }, + allow_env: Some(svec!["HOME", "PATH"]), + ..Flags::default() + } + ); + } + + #[test] + fn allow_env_allowlist_validator() { + let r = + flags_from_vec(svec!["deno", "run", "--allow-env=HOME", "script.ts"]); + assert!(r.is_ok()); + let r = + flags_from_vec(svec!["deno", "run", "--allow-env=H=ME", "script.ts"]); + assert!(r.is_err()); + let r = + flags_from_vec(svec!["deno", "run", "--allow-env=H\0ME", "script.ts"]); + assert!(r.is_err()); + } + #[test] fn bundle() { let r = flags_from_vec(svec!["deno", "bundle", "source.ts"]); diff --git a/runtime/ops/os.rs b/runtime/ops/os.rs index b9511fcdc3..c2c9fb5508 100644 --- a/runtime/ops/os.rs +++ b/runtime/ops/os.rs @@ -54,7 +54,7 @@ fn op_set_env( args: SetEnv, _zero_copy: Option, ) -> Result<(), AnyError> { - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check(&args.key)?; let invalid_key = args.key.is_empty() || args.key.contains(&['=', '\0'] as &[char]); let invalid_value = args.value.contains('\0'); @@ -70,7 +70,7 @@ fn op_env( _args: (), _zero_copy: Option, ) -> Result, AnyError> { - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check_all()?; Ok(env::vars().collect()) } @@ -79,7 +79,7 @@ fn op_get_env( key: String, _zero_copy: Option, ) -> Result, AnyError> { - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check(&key)?; if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(type_error("Key contains invalid characters.")); } @@ -89,12 +89,13 @@ fn op_get_env( }; Ok(r) } + fn op_delete_env( state: &mut OpState, key: String, _zero_copy: Option, ) -> Result<(), AnyError> { - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check(&key)?; if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(type_error("Key contains invalid characters.")); } @@ -116,7 +117,7 @@ fn op_loadavg( _zero_copy: Option, ) -> Result<(f64, f64, f64), AnyError> { super::check_unstable(state, "Deno.loadavg"); - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check_all()?; match sys_info::loadavg() { Ok(loadavg) => Ok((loadavg.one, loadavg.five, loadavg.fifteen)), Err(_) => Ok((0.0, 0.0, 0.0)), @@ -129,7 +130,7 @@ fn op_hostname( _zero_copy: Option, ) -> Result { super::check_unstable(state, "Deno.hostname"); - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check_all()?; let hostname = sys_info::hostname().unwrap_or_else(|_| "".to_string()); Ok(hostname) } @@ -140,7 +141,7 @@ fn op_os_release( _zero_copy: Option, ) -> Result { super::check_unstable(state, "Deno.osRelease"); - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check_all()?; let release = sys_info::os_release().unwrap_or_else(|_| "".to_string()); Ok(release) } @@ -164,7 +165,7 @@ fn op_system_memory_info( _zero_copy: Option, ) -> Result, AnyError> { super::check_unstable(state, "Deno.systemMemoryInfo"); - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check_all()?; match sys_info::mem_info() { Ok(info) => Ok(Some(MemInfo { total: info.total, @@ -191,7 +192,7 @@ fn op_system_cpu_info( _zero_copy: Option, ) -> Result { super::check_unstable(state, "Deno.systemCpuInfo"); - state.borrow_mut::().env.check()?; + state.borrow_mut::().env.check_all()?; let cores = sys_info::cpu_num().ok(); let speed = sys_info::cpu_speed().ok(); diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs index ce89def548..832af485b3 100644 --- a/runtime/ops/permissions.rs +++ b/runtime/ops/permissions.rs @@ -21,6 +21,7 @@ pub struct PermissionArgs { name: String, path: Option, host: Option, + variable: Option, command: Option, } @@ -41,7 +42,7 @@ pub fn op_query_permission( } .as_ref(), ), - "env" => permissions.env.query(), + "env" => permissions.env.query(args.variable.as_deref()), "run" => permissions.run.query(args.command.as_deref()), "plugin" => permissions.plugin.query(), "hrtime" => permissions.hrtime.query(), @@ -72,7 +73,7 @@ pub fn op_revoke_permission( } .as_ref(), ), - "env" => permissions.env.revoke(), + "env" => permissions.env.revoke(args.variable.as_deref()), "run" => permissions.run.revoke(args.command.as_deref()), "plugin" => permissions.plugin.revoke(), "hrtime" => permissions.hrtime.revoke(), @@ -103,7 +104,7 @@ pub fn op_request_permission( } .as_ref(), ), - "env" => permissions.env.request(), + "env" => permissions.env.request(args.variable.as_deref()), "run" => permissions.run.request(args.command.as_deref()), "plugin" => permissions.plugin.request(), "hrtime" => permissions.hrtime.request(), diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index 92de420e31..905ae1334b 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -1,6 +1,8 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + use crate::permissions::resolve_read_allowlist; use crate::permissions::resolve_write_allowlist; +use crate::permissions::EnvDescriptor; use crate::permissions::NetDescriptor; use crate::permissions::PermissionState; use crate::permissions::Permissions; @@ -186,6 +188,26 @@ fn merge_write_permission( Ok(main) } +fn merge_env_permission( + mut main: UnaryPermission, + worker: Option>, +) -> Result, AnyError> { + if let Some(worker) = worker { + if (worker.global_state < main.global_state) + || !worker.granted_list.iter().all(|x| main.check(&x.0).is_ok()) + { + return Err(custom_error( + "PermissionDenied", + "Can't escalate parent thread permissions", + )); + } else { + main.global_state = worker.global_state; + main.granted_list = worker.granted_list; + } + } + Ok(main) +} + fn merge_run_permission( mut main: UnaryPermission, worker: Option>, @@ -211,7 +233,7 @@ fn create_worker_permissions( worker_perms: PermissionsArg, ) -> Result { Ok(Permissions { - env: merge_boolean_permission(main_perms.env, worker_perms.env)?, + env: merge_env_permission(main_perms.env, worker_perms.env)?, hrtime: merge_boolean_permission(main_perms.hrtime, worker_perms.hrtime)?, net: merge_net_permission(main_perms.net, worker_perms.net)?, plugin: merge_boolean_permission(main_perms.plugin, worker_perms.plugin)?, @@ -223,8 +245,8 @@ fn create_worker_permissions( #[derive(Debug, Deserialize)] struct PermissionsArg { - #[serde(default, deserialize_with = "as_permission_state")] - env: Option, + #[serde(default, deserialize_with = "as_unary_env_permission")] + env: Option>, #[serde(default, deserialize_with = "as_permission_state")] hrtime: Option, #[serde(default, deserialize_with = "as_unary_net_permission")] @@ -366,6 +388,26 @@ where })) } +fn as_unary_env_permission<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let value: UnaryPermissionBase = + deserializer.deserialize_any(ParseBooleanOrStringVec)?; + + Ok(Some(UnaryPermission:: { + global_state: value.global_state, + granted_list: value + .paths + .into_iter() + .map(|env| EnvDescriptor(env.to_uppercase())) + .collect(), + ..Default::default() + })) +} + fn as_unary_run_permission<'de, D>( deserializer: D, ) -> Result>, D::Error> diff --git a/runtime/permissions.rs b/runtime/permissions.rs index a266de40b9..e43e0eaa05 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -195,6 +195,9 @@ impl fmt::Display for NetDescriptor { } } +#[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)] +pub struct EnvDescriptor(pub String); + #[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)] pub struct RunDescriptor(pub String); @@ -574,6 +577,114 @@ impl UnaryPermission { } } +impl UnaryPermission { + pub fn query(&self, env: Option<&str>) -> PermissionState { + #[cfg(windows)] + let env = env.map(|env| env.to_uppercase()); + #[cfg(windows)] + let env = env.as_deref(); + if self.global_state == PermissionState::Denied + && match env { + None => true, + Some(env) => self.denied_list.iter().any(|env_| env_.0 == env), + } + { + PermissionState::Denied + } else if self.global_state == PermissionState::Granted + || match env { + None => false, + Some(env) => self.granted_list.iter().any(|env_| env_.0 == env), + } + { + PermissionState::Granted + } else { + PermissionState::Prompt + } + } + + pub fn request(&mut self, env: Option<&str>) -> PermissionState { + if let Some(env) = env { + #[cfg(windows)] + let env = env.to_uppercase(); + let state = self.query(Some(&env)); + if state == PermissionState::Prompt { + if permission_prompt(&format!("env access to \"{}\"", env)) { + self.granted_list.retain(|env_| env_.0 != env); + self.granted_list.insert(EnvDescriptor(env.to_string())); + PermissionState::Granted + } else { + self.denied_list.retain(|env_| env_.0 != env); + self.denied_list.insert(EnvDescriptor(env.to_string())); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + } else { + state + } + } else { + let state = self.query(None); + if state == PermissionState::Prompt { + if permission_prompt("env access") { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } else { + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + } else { + state + } + } + } + + pub fn revoke(&mut self, env: Option<&str>) -> PermissionState { + if let Some(env) = env { + #[cfg(windows)] + let env = env.to_uppercase(); + self.granted_list.retain(|env_| env_.0 != env); + } else { + self.granted_list.clear(); + if self.global_state == PermissionState::Granted { + self.global_state = PermissionState::Prompt; + } + } + self.query(env) + } + + pub fn check(&mut self, env: &str) -> Result<(), AnyError> { + #[cfg(windows)] + let env = &env.to_uppercase(); + let (result, prompted) = self.query(Some(env)).check( + self.name, + Some(&format!("\"{}\"", env)), + self.prompt, + ); + if prompted { + if result.is_ok() { + self.granted_list.insert(EnvDescriptor(env.to_string())); + } else { + self.denied_list.insert(EnvDescriptor(env.to_string())); + self.global_state = PermissionState::Denied; + } + } + 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 UnaryPermission { pub fn query(&self, cmd: Option<&str>) -> PermissionState { if self.global_state == PermissionState::Denied @@ -677,7 +788,7 @@ pub struct Permissions { pub read: UnaryPermission, pub write: UnaryPermission, pub net: UnaryPermission, - pub env: UnitPermission, + pub env: UnaryPermission, pub run: UnaryPermission, pub plugin: UnitPermission, pub hrtime: UnitPermission, @@ -685,7 +796,7 @@ pub struct Permissions { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct PermissionsOptions { - pub allow_env: bool, + pub allow_env: Option>, pub allow_hrtime: bool, pub allow_net: Option>, pub allow_plugin: bool, @@ -745,13 +856,31 @@ impl Permissions { } } - pub fn new_env(state: bool, prompt: bool) -> UnitPermission { - boolean_permission_from_flag_bool( - state, - "env", - "environment variables", + pub fn new_env( + state: &Option>, + prompt: bool, + ) -> UnaryPermission { + UnaryPermission:: { + name: "env", + description: "environment variables", + global_state: global_state_from_option(state), + granted_list: state + .as_ref() + .map(|v| { + v.iter() + .map(|x| { + EnvDescriptor(if cfg!(windows) { + x.to_uppercase() + } else { + x.clone() + }) + }) + .collect() + }) + .unwrap_or_else(HashSet::new), + denied_list: Default::default(), prompt, - ) + } } pub fn new_run( @@ -772,11 +901,11 @@ impl Permissions { } pub fn new_plugin(state: bool, prompt: bool) -> UnitPermission { - boolean_permission_from_flag_bool(state, "plugin", "open a plugin", prompt) + unit_permission_from_flag_bool(state, "plugin", "open a plugin", prompt) } pub fn new_hrtime(state: bool, prompt: bool) -> UnitPermission { - boolean_permission_from_flag_bool( + unit_permission_from_flag_bool( state, "hrtime", "high precision time", @@ -789,7 +918,7 @@ impl Permissions { read: Permissions::new_read(&opts.allow_read, opts.prompt), write: Permissions::new_write(&opts.allow_write, opts.prompt), net: Permissions::new_net(&opts.allow_net, opts.prompt), - env: Permissions::new_env(opts.allow_env, opts.prompt), + env: Permissions::new_env(&opts.allow_env, opts.prompt), run: Permissions::new_run(&opts.allow_run, opts.prompt), plugin: Permissions::new_plugin(opts.allow_plugin, opts.prompt), hrtime: Permissions::new_hrtime(opts.allow_hrtime, opts.prompt), @@ -801,7 +930,7 @@ impl Permissions { read: Permissions::new_read(&Some(vec![]), false), write: Permissions::new_write(&Some(vec![]), false), net: Permissions::new_net(&Some(vec![]), false), - env: Permissions::new_env(true, false), + env: Permissions::new_env(&Some(vec![]), false), run: Permissions::new_run(&Some(vec![]), false), plugin: Permissions::new_plugin(true, false), hrtime: Permissions::new_hrtime(true, false), @@ -845,7 +974,7 @@ impl deno_websocket::WebSocketPermissions for Permissions { } } -fn boolean_permission_from_flag_bool( +fn unit_permission_from_flag_bool( flag: bool, name: &'static str, description: &'static str, @@ -1289,9 +1418,9 @@ mod tests { global_state: PermissionState::Prompt, ..Permissions::new_net(&Some(svec!["127.0.0.1:8000"]), false) }, - env: UnitPermission { - state: PermissionState::Prompt, - ..Default::default() + env: UnaryPermission { + global_state: PermissionState::Prompt, + ..Permissions::new_env(&Some(svec!["HOME"]), false) }, run: UnaryPermission { global_state: PermissionState::Prompt, @@ -1322,8 +1451,10 @@ mod tests { assert_eq!(perms1.net.query(Some(&("127.0.0.1", None))), PermissionState::Granted); assert_eq!(perms2.net.query::<&str>(None), PermissionState::Prompt); assert_eq!(perms2.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); - assert_eq!(perms1.env.query(), PermissionState::Granted); - assert_eq!(perms2.env.query(), PermissionState::Prompt); + assert_eq!(perms1.env.query(None), PermissionState::Granted); + assert_eq!(perms1.env.query(Some(&"HOME".to_string())), PermissionState::Granted); + assert_eq!(perms2.env.query(None), PermissionState::Prompt); + assert_eq!(perms2.env.query(Some(&"HOME".to_string())), PermissionState::Granted); assert_eq!(perms1.run.query(None), PermissionState::Granted); assert_eq!(perms1.run.query(Some(&"deno".to_string())), PermissionState::Granted); assert_eq!(perms2.run.query(None), PermissionState::Prompt); @@ -1356,9 +1487,10 @@ mod tests { set_prompt_result(false); assert_eq!(perms.net.request(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); set_prompt_result(true); - assert_eq!(perms.env.request(), PermissionState::Granted); + assert_eq!(perms.env.request(Some(&"HOME".to_string())), PermissionState::Granted); + assert_eq!(perms.env.query(None), PermissionState::Prompt); set_prompt_result(false); - assert_eq!(perms.env.request(), PermissionState::Granted); + assert_eq!(perms.env.request(Some(&"HOME".to_string())), PermissionState::Granted); set_prompt_result(true); assert_eq!(perms.run.request(Some(&"deno".to_string())), PermissionState::Granted); assert_eq!(perms.run.query(None), PermissionState::Prompt); @@ -1390,9 +1522,9 @@ mod tests { global_state: PermissionState::Prompt, ..Permissions::new_net(&Some(svec!["127.0.0.1"]), false) }, - env: UnitPermission { - state: PermissionState::Granted, - ..Default::default() + env: UnaryPermission { + global_state: PermissionState::Prompt, + ..Permissions::new_env(&Some(svec!["HOME"]), false) }, run: UnaryPermission { global_state: PermissionState::Prompt, @@ -1417,7 +1549,7 @@ mod tests { assert_eq!(perms.write.query(Some(&Path::new("/foo/bar"))), PermissionState::Prompt); assert_eq!(perms.net.revoke(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted); assert_eq!(perms.net.revoke(Some(&("127.0.0.1", None))), PermissionState::Prompt); - assert_eq!(perms.env.revoke(), PermissionState::Prompt); + 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.plugin.revoke(), PermissionState::Prompt); assert_eq!(perms.hrtime.revoke(), PermissionState::Denied); @@ -1430,7 +1562,7 @@ mod tests { read: Permissions::new_read(&None, true), write: Permissions::new_write(&None, true), net: Permissions::new_net(&None, true), - env: Permissions::new_env(false, true), + env: Permissions::new_env(&None, true), run: Permissions::new_run(&None, true), plugin: Permissions::new_plugin(false, true), hrtime: Permissions::new_hrtime(false, true), @@ -1465,6 +1597,12 @@ mod tests { assert!(perms.run.check("cat").is_ok()); assert!(perms.run.check("ls").is_err()); + set_prompt_result(true); + assert!(perms.env.check("HOME").is_ok()); + set_prompt_result(false); + assert!(perms.env.check("HOME").is_ok()); + assert!(perms.env.check("PATH").is_err()); + set_prompt_result(true); assert!(perms.hrtime.check().is_ok()); set_prompt_result(false); @@ -1477,7 +1615,7 @@ mod tests { read: Permissions::new_read(&None, true), write: Permissions::new_write(&None, true), net: Permissions::new_net(&None, true), - env: Permissions::new_env(false, true), + env: Permissions::new_env(&None, true), run: Permissions::new_run(&None, true), plugin: Permissions::new_plugin(false, true), hrtime: Permissions::new_hrtime(false, true), @@ -1519,9 +1657,38 @@ mod tests { set_prompt_result(false); assert!(perms.run.check("ls").is_ok()); + set_prompt_result(false); + assert!(perms.env.check("HOME").is_err()); + set_prompt_result(true); + assert!(perms.env.check("HOME").is_err()); + assert!(perms.env.check("PATH").is_ok()); + set_prompt_result(false); + assert!(perms.env.check("PATH").is_ok()); + set_prompt_result(false); assert!(perms.hrtime.check().is_err()); set_prompt_result(true); assert!(perms.hrtime.check().is_err()); } + + #[test] + #[cfg(windows)] + fn test_env_windows() { + let mut perms = Permissions::allow_all(); + perms.env = UnaryPermission { + global_state: PermissionState::Prompt, + ..Permissions::new_env(&Some(svec!["HOME"]), false) + }; + + set_prompt_result(true); + assert!(perms.env.check("HOME").is_ok()); + set_prompt_result(false); + assert!(perms.env.check("HOME").is_ok()); + assert!(perms.env.check("hOmE").is_ok()); + + assert_eq!( + perms.env.revoke(Some(&"HomE".to_string())), + PermissionState::Prompt + ); + } }