diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index d42136f84f..10ff6d2e0f 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -591,6 +591,62 @@ fn _090_run_permissions_request_sync() { ]); } +#[test] +fn permissions_prompt_allow_all() { + let args = "run --quiet run/permissions_prompt_allow_all.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + // "run" permissions + Output("┌ ⚠️ Deno requests run access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-run to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all run permissions) >"), + Input("a\n"), + Output("✅ Granted all run access.\r\n"), + // "read" permissions + Output("┌ ⚠️ Deno requests read access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-read to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >"), + Input("a\n"), + Output("✅ Granted all read access.\r\n"), + // "write" permissions + Output("┌ ⚠️ Deno requests write access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-write to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all write permissions) >"), + Input("a\n"), + Output("✅ Granted all write access.\r\n"), + // "net" permissions + Output("┌ ⚠️ Deno requests net access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-net to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >"), + Input("a\n"), + Output("✅ Granted all net access.\r\n"), + // "env" permissions + Output("┌ ⚠️ Deno requests env access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-env to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions) >"), + Input("a\n"), + Output("✅ Granted all env access.\r\n"), + // "sys" permissions + Output("┌ ⚠️ Deno requests sys access to \"loadavg\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-sys to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions) >"), + Input("a\n"), + Output("✅ Granted all sys access.\r\n"), + // "ffi" permissions + Output("┌ ⚠️ Deno requests ffi access to \"FOO\".\r\n├ Requested by `Deno.permissions.query()` API\r\n ├ Run again with --allow-ffi to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all ffi permissions) >"), + Input("a\n"), + Output("✅ Granted all ffi access.\r\n") + ]); +} + +#[test] +fn permissions_prompt_allow_all_2() { + let args = "run --quiet run/permissions_prompt_allow_all_2.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + // "env" permissions + Output("┌ ⚠️ Deno requests env access to \"FOO\".\r\n├ Run again with --allow-env to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all env permissions) >"), + Input("d\n"), + Output("✅ Granted all env access.\r\n"), + // "sys" permissions + Output("┌ ⚠️ Deno requests sys access to \"FOO\".\r\n├ Run again with --allow-sys to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all sys permissions) >"), + Input("d\n"), + Output("✅ Granted all sys access.\r\n"), + // "read" permissions + Output("┌ ⚠️ Deno requests read access to \"FOO\".\r\n├ Run again with --allow-read to bypass this prompt.\r\n└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) >"), + Input("d\n"), + Output("✅ Granted all read access.\r\n"), + ]); +} + itest!(_091_use_define_for_class_fields { args: "run --check run/091_use_define_for_class_fields.ts", output: "run/091_use_define_for_class_fields.ts.out", diff --git a/cli/tests/testdata/run/permissions_prompt_allow_all.ts b/cli/tests/testdata/run/permissions_prompt_allow_all.ts new file mode 100644 index 0000000000..8aa7d040ec --- /dev/null +++ b/cli/tests/testdata/run/permissions_prompt_allow_all.ts @@ -0,0 +1,20 @@ +Deno.permissions.requestSync({ name: "run", command: "FOO" }); +Deno.permissions.requestSync({ name: "run", command: "BAR" }); + +Deno.permissions.requestSync({ name: "read", path: "FOO" }); +Deno.permissions.requestSync({ name: "read", path: "BAR" }); + +Deno.permissions.requestSync({ name: "write", path: "FOO" }); +Deno.permissions.requestSync({ name: "write", path: "BAR" }); + +Deno.permissions.requestSync({ name: "net", host: "FOO" }); +Deno.permissions.requestSync({ name: "net", host: "BAR" }); + +Deno.permissions.requestSync({ name: "env", variable: "FOO" }); +Deno.permissions.requestSync({ name: "env", variable: "BAR" }); + +Deno.permissions.requestSync({ name: "sys", kind: "loadavg" }); +Deno.permissions.requestSync({ name: "sys", kind: "hostname" }); + +Deno.permissions.requestSync({ name: "ffi", path: "FOO" }); +Deno.permissions.requestSync({ name: "ffi", path: "BAR" }); diff --git a/cli/tests/testdata/run/permissions_prompt_allow_all_2.ts b/cli/tests/testdata/run/permissions_prompt_allow_all_2.ts new file mode 100644 index 0000000000..f42b357536 --- /dev/null +++ b/cli/tests/testdata/run/permissions_prompt_allow_all_2.ts @@ -0,0 +1,8 @@ +Deno.env.get("FOO"); +Deno.env.get("BAR"); + +Deno.loadavg(); +Deno.hostname(); + +Deno.cwd(); +Deno.lstatSync(new URL("../", import.meta.url)); diff --git a/runtime/permissions/mod.rs b/runtime/permissions/mod.rs index 2981463123..035c39304f 100644 --- a/runtime/permissions/mod.rs +++ b/runtime/permissions/mod.rs @@ -90,7 +90,7 @@ impl PermissionState { api_name: Option<&str>, info: Option<&str>, prompt: bool, - ) -> (Result<(), AnyError>, bool) { + ) -> (Result<(), AnyError>, bool, bool) { self.check2(name, api_name, || info.map(|s| s.to_string()), prompt) } @@ -101,11 +101,11 @@ impl PermissionState { api_name: Option<&str>, info: impl Fn() -> Option, prompt: bool, - ) -> (Result<(), AnyError>, bool) { + ) -> (Result<(), AnyError>, bool, bool) { match self { PermissionState::Granted => { Self::log_perm_access(name, info); - (Ok(()), false) + (Ok(()), false, false) } PermissionState::Prompt if prompt => { let msg = format!( @@ -113,14 +113,19 @@ impl PermissionState { name, info().map_or(String::new(), |info| { format!(" to {info}") }), ); - if PromptResponse::Allow == permission_prompt(&msg, name, api_name) { - Self::log_perm_access(name, info); - (Ok(()), true) - } else { - (Err(Self::error(name, info)), true) + match permission_prompt(&msg, name, api_name, true) { + PromptResponse::Allow => { + Self::log_perm_access(name, info); + (Ok(()), true, false) + } + PromptResponse::AllowAll => { + Self::log_perm_access(name, info); + (Ok(()), true, true) + } + PromptResponse::Deny => (Err(Self::error(name, info)), true, false), } } - _ => (Err(Self::error(name, info)), false), + _ => (Err(Self::error(name, info)), false, false), } } } @@ -161,6 +166,7 @@ impl UnitPermission { &format!("access to {}", self.description), self.name, Some("Deno.permissions.query()"), + false, ) { self.state = PermissionState::Granted; @@ -179,7 +185,7 @@ impl UnitPermission { } pub fn check(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _is_allow_all) = self.state.check(self.name, None, None, self.prompt); if prompted { if result.is_ok() { @@ -357,19 +363,26 @@ impl UnaryPermission { let (resolved_path, display_path) = resolved_and_display_path(path); let state = self.query(Some(&resolved_path)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("read access to \"{}\"", display_path.display()), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(ReadDescriptor(resolved_path)); - PermissionState::Granted - } else { - self.denied_list.insert(ReadDescriptor(resolved_path)); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("read access to \"{}\"", display_path.display()), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(ReadDescriptor(resolved_path)); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(ReadDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(ReadDescriptor(resolved_path)); @@ -385,6 +398,7 @@ impl UnaryPermission { "read access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -421,7 +435,7 @@ impl UnaryPermission { path: &Path, api_name: Option<&str>, ) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(path)).check2( + let (result, prompted, is_allow_all) = self.query(Some(path)).check2( self.name, api_name, || Some(format!("\"{}\"", path.to_path_buf().display())), @@ -430,7 +444,12 @@ impl UnaryPermission { if prompted { let resolved_path = resolve_from_cwd(path)?; if result.is_ok() { - self.granted_list.insert(ReadDescriptor(resolved_path)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(ReadDescriptor(resolved_path)); + } } else { self.denied_list.insert(ReadDescriptor(resolved_path)); self.global_state = PermissionState::Denied; @@ -448,25 +467,33 @@ impl UnaryPermission { api_name: &str, ) -> Result<(), AnyError> { let resolved_path = resolve_from_cwd(path)?; - let (result, prompted) = self.query(Some(&resolved_path)).check( - self.name, - Some(api_name), - Some(&format!("<{display}>")), - self.prompt, - ); + let (result, prompted, is_allow_all) = + self.query(Some(&resolved_path)).check( + self.name, + Some(api_name), + Some(&format!("<{display}>")), + self.prompt, + ); if prompted { if result.is_ok() { - self.granted_list.insert(ReadDescriptor(resolved_path)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(ReadDescriptor(resolved_path)); + } } else { - self.denied_list.insert(ReadDescriptor(resolved_path)); self.global_state = PermissionState::Denied; + if !is_allow_all { + self.denied_list.insert(ReadDescriptor(resolved_path)); + } } } result } pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, api_name, Some("all"), self.prompt); @@ -530,19 +557,26 @@ impl UnaryPermission { let (resolved_path, display_path) = resolved_and_display_path(path); let state = self.query(Some(&resolved_path)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("write access to \"{}\"", display_path.display()), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(WriteDescriptor(resolved_path)); - PermissionState::Granted - } else { - self.denied_list.insert(WriteDescriptor(resolved_path)); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("write access to \"{}\"", display_path.display()), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(WriteDescriptor(resolved_path)); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(WriteDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(WriteDescriptor(resolved_path)); @@ -558,6 +592,7 @@ impl UnaryPermission { "write access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -594,7 +629,7 @@ impl UnaryPermission { path: &Path, api_name: Option<&str>, ) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(path)).check2( + let (result, prompted, is_allow_all) = self.query(Some(path)).check2( self.name, api_name, || Some(format!("\"{}\"", path.to_path_buf().display())), @@ -603,7 +638,12 @@ impl UnaryPermission { if prompted { let resolved_path = resolve_from_cwd(path)?; if result.is_ok() { - self.granted_list.insert(WriteDescriptor(resolved_path)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(WriteDescriptor(resolved_path)); + } } else { self.denied_list.insert(WriteDescriptor(resolved_path)); self.global_state = PermissionState::Denied; @@ -613,7 +653,7 @@ impl UnaryPermission { } pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, api_name, Some("all"), self.prompt); @@ -685,19 +725,26 @@ impl UnaryPermission { let state = self.query(Some(host)); let host = NetDescriptor::new(&host); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("network access to \"{host}\""), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(host); - PermissionState::Granted - } else { - self.denied_list.insert(host); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("network access to \"{host}\""), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(host); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(host); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(host); @@ -713,6 +760,7 @@ impl UnaryPermission { "network access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -756,7 +804,7 @@ impl UnaryPermission { api_name: Option<&str>, ) -> Result<(), AnyError> { let new_host = NetDescriptor::new(&host); - let (result, prompted) = self.query(Some(host)).check( + let (result, prompted, is_allow_all) = self.query(Some(host)).check( self.name, api_name, Some(&format!("\"{new_host}\"")), @@ -764,7 +812,12 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self.granted_list.insert(new_host); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(new_host); + } } else { self.denied_list.insert(new_host); self.global_state = PermissionState::Denied; @@ -787,7 +840,7 @@ impl UnaryPermission { Some(port) => format!("{hostname}:{port}"), }; let host = &(&hostname, url.port_or_known_default()); - let (result, prompted) = self.query(Some(host)).check( + let (result, prompted, is_allow_all) = self.query(Some(host)).check( self.name, api_name, Some(&format!("\"{display_host}\"")), @@ -795,7 +848,12 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self.granted_list.insert(NetDescriptor::new(&host)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(NetDescriptor::new(&host)); + } } else { self.denied_list.insert(NetDescriptor::new(&host)); self.global_state = PermissionState::Denied; @@ -805,7 +863,7 @@ impl UnaryPermission { } pub fn check_all(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query::<&str>(None) .check(self.name, None, Some("all"), self.prompt); @@ -859,19 +917,26 @@ impl UnaryPermission { if let Some(env) = env { let state = self.query(Some(env)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("env access to \"{env}\""), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(EnvDescriptor::new(env)); - PermissionState::Granted - } else { - self.denied_list.insert(EnvDescriptor::new(env)); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("env access to \"{env}\""), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(EnvDescriptor::new(env)); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(EnvDescriptor::new(env)); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(EnvDescriptor::new(env)); @@ -887,6 +952,7 @@ impl UnaryPermission { "env access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -915,7 +981,7 @@ impl UnaryPermission { } pub fn check(&mut self, env: &str) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(env)).check( + let (result, prompted, is_allow_all) = self.query(Some(env)).check( self.name, None, Some(&format!("\"{env}\"")), @@ -923,7 +989,12 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self.granted_list.insert(EnvDescriptor::new(env)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(EnvDescriptor::new(env)); + } } else { self.denied_list.insert(EnvDescriptor::new(env)); self.global_state = PermissionState::Denied; @@ -933,7 +1004,7 @@ impl UnaryPermission { } pub fn check_all(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, None, Some("all"), self.prompt); @@ -993,19 +1064,26 @@ impl UnaryPermission { } if let Some(kind) = kind { let desc = SysDescriptor(kind.to_string()); - if PromptResponse::Allow - == permission_prompt( - &format!("sys access to \"{kind}\""), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(desc); - PermissionState::Granted - } else { - self.denied_list.insert(desc); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("sys access to \"{kind}\""), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(desc); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(desc); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else { if PromptResponse::Allow @@ -1013,6 +1091,7 @@ impl UnaryPermission { "sys access", self.name, Some("Deno.permissions.query()"), + true, ) { self.global_state = PermissionState::Granted; @@ -1041,7 +1120,7 @@ impl UnaryPermission { kind: &str, api_name: Option<&str>, ) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(kind)).check( + let (result, prompted, is_allow_all) = self.query(Some(kind)).check( self.name, api_name, Some(&format!("\"{kind}\"")), @@ -1049,7 +1128,12 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self.granted_list.insert(SysDescriptor(kind.to_string())); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(SysDescriptor(kind.to_string())); + } } else { self.denied_list.insert(SysDescriptor(kind.to_string())); self.global_state = PermissionState::Denied; @@ -1059,7 +1143,7 @@ impl UnaryPermission { } pub fn check_all(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _is_allow_all) = self .query(None) .check(self.name, None, Some("all"), self.prompt); @@ -1116,23 +1200,30 @@ impl UnaryPermission { if let Some(cmd) = cmd { let state = self.query(Some(cmd)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("run access to \"{cmd}\""), - self.name, - Some("Deno.permissions.query()"), - ) - { - self - .granted_list - .insert(RunDescriptor::from_str(cmd).unwrap()); - PermissionState::Granted - } else { - self - .denied_list - .insert(RunDescriptor::from_str(cmd).unwrap()); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("run access to \"{cmd}\""), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self + .granted_list + .insert(RunDescriptor::from_str(cmd).unwrap()); + PermissionState::Granted + } + PromptResponse::Deny => { + self + .denied_list + .insert(RunDescriptor::from_str(cmd).unwrap()); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self @@ -1150,6 +1241,7 @@ impl UnaryPermission { "run access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -1184,7 +1276,7 @@ impl UnaryPermission { cmd: &str, api_name: Option<&str>, ) -> Result<(), AnyError> { - let (result, prompted) = self.query(Some(cmd)).check( + let (result, prompted, is_allow_all) = self.query(Some(cmd)).check( self.name, api_name, Some(&format!("\"{cmd}\"")), @@ -1192,9 +1284,14 @@ impl UnaryPermission { ); if prompted { if result.is_ok() { - self - .granted_list - .insert(RunDescriptor::from_str(cmd).unwrap()); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self + .granted_list + .insert(RunDescriptor::from_str(cmd).unwrap()); + } } else { self .denied_list @@ -1206,7 +1303,7 @@ impl UnaryPermission { } pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, api_name, Some("all"), self.prompt); @@ -1267,19 +1364,26 @@ impl UnaryPermission { let (resolved_path, display_path) = resolved_and_display_path(path); let state = self.query(Some(&resolved_path)); if state == PermissionState::Prompt { - if PromptResponse::Allow - == permission_prompt( - &format!("ffi access to \"{}\"", display_path.display()), - self.name, - Some("Deno.permissions.query()"), - ) - { - self.granted_list.insert(FfiDescriptor(resolved_path)); - PermissionState::Granted - } else { - self.denied_list.insert(FfiDescriptor(resolved_path)); - self.global_state = PermissionState::Denied; - PermissionState::Denied + match permission_prompt( + &format!("ffi access to \"{}\"", display_path.display()), + self.name, + Some("Deno.permissions.query()"), + true, + ) { + PromptResponse::Allow => { + self.granted_list.insert(FfiDescriptor(resolved_path)); + PermissionState::Granted + } + PromptResponse::Deny => { + self.denied_list.insert(FfiDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + PermissionState::Denied + } + PromptResponse::AllowAll => { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + PermissionState::Granted + } } } else if state == PermissionState::Granted { self.granted_list.insert(FfiDescriptor(resolved_path)); @@ -1295,6 +1399,7 @@ impl UnaryPermission { "ffi access", self.name, Some("Deno.permissions.query()"), + true, ) { self.granted_list.clear(); @@ -1328,16 +1433,22 @@ impl UnaryPermission { pub fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> { if let Some(path) = path { let (resolved_path, display_path) = resolved_and_display_path(path); - let (result, prompted) = self.query(Some(&resolved_path)).check( - self.name, - None, - Some(&format!("\"{}\"", display_path.display())), - self.prompt, - ); + let (result, prompted, is_allow_all) = + self.query(Some(&resolved_path)).check( + self.name, + None, + Some(&format!("\"{}\"", display_path.display())), + self.prompt, + ); if prompted { if result.is_ok() { - self.granted_list.insert(FfiDescriptor(resolved_path)); + if is_allow_all { + self.granted_list.clear(); + self.global_state = PermissionState::Granted; + } else { + self.granted_list.insert(FfiDescriptor(resolved_path)); + } } else { self.denied_list.insert(FfiDescriptor(resolved_path)); self.global_state = PermissionState::Denied; @@ -1346,7 +1457,7 @@ impl UnaryPermission { result } else { - let (result, prompted) = + let (result, prompted, _) = self.query(None).check(self.name, None, None, self.prompt); if prompted { @@ -1362,7 +1473,7 @@ impl UnaryPermission { } pub fn check_all(&mut self) -> Result<(), AnyError> { - let (result, prompted) = + let (result, prompted, _) = self .query(None) .check(self.name, None, Some("all"), self.prompt); diff --git a/runtime/permissions/prompter.rs b/runtime/permissions/prompter.rs index c33483645f..d148b485e7 100644 --- a/runtime/permissions/prompter.rs +++ b/runtime/permissions/prompter.rs @@ -11,6 +11,7 @@ pub const PERMISSION_EMOJI: &str = "⚠️"; pub enum PromptResponse { Allow, Deny, + AllowAll, } static PERMISSION_PROMPTER: Lazy>> = @@ -26,11 +27,14 @@ pub fn permission_prompt( message: &str, flag: &str, api_name: Option<&str>, + is_unary: bool, ) -> PromptResponse { if let Some(before_callback) = MAYBE_BEFORE_PROMPT_CALLBACK.lock().as_mut() { before_callback(); } - let r = PERMISSION_PROMPTER.lock().prompt(message, flag, api_name); + let r = PERMISSION_PROMPTER + .lock() + .prompt(message, flag, api_name, is_unary); if let Some(after_callback) = MAYBE_AFTER_PROMPT_CALLBACK.lock().as_mut() { after_callback(); } @@ -53,6 +57,7 @@ pub trait PermissionPrompter: Send + Sync { message: &str, name: &str, api_name: Option<&str>, + is_unary: bool, ) -> PromptResponse; } @@ -64,6 +69,7 @@ impl PermissionPrompter for TtyPrompter { message: &str, name: &str, api_name: Option<&str>, + is_unary: bool, ) -> PromptResponse { if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) { return PromptResponse::Deny; @@ -198,7 +204,11 @@ impl PermissionPrompter for TtyPrompter { let _stderr_guard = std::io::stderr().lock(); // print to stderr so that if stdout is piped this is still displayed. - const OPTS: &str = "[y/n] (y = yes, allow; n = no, deny)"; + let opts: String = if is_unary { + format!("[y/n/A] (y = yes, allow; n = no, deny; A = allow all {name} permissions)") + } else { + "[y/n] (y = yes, allow; n = no, deny)".to_string() + }; eprint!("┌ {PERMISSION_EMOJI} "); eprint!("{}", colors::bold("Deno requests ")); eprint!("{}", colors::bold(message)); @@ -209,7 +219,7 @@ impl PermissionPrompter for TtyPrompter { let msg = format!("Run again with --allow-{name} to bypass this prompt."); eprintln!("├ {}", colors::italic(&msg)); eprint!("└ {}", colors::bold("Allow?")); - eprint!(" {OPTS} > "); + eprint!(" {opts} > "); let value = loop { let mut input = String::new(); let stdin = std::io::stdin(); @@ -221,24 +231,30 @@ impl PermissionPrompter for TtyPrompter { None => break PromptResponse::Deny, Some(v) => v, }; - match ch.to_ascii_lowercase() { - 'y' => { + match ch { + 'y' | 'Y' => { clear_n_lines(if api_name.is_some() { 4 } else { 3 }); let msg = format!("Granted {message}."); eprintln!("✅ {}", colors::bold(&msg)); break PromptResponse::Allow; } - 'n' => { + 'n' | 'N' => { clear_n_lines(if api_name.is_some() { 4 } else { 3 }); let msg = format!("Denied {message}."); eprintln!("❌ {}", colors::bold(&msg)); break PromptResponse::Deny; } + 'A' if is_unary => { + clear_n_lines(if api_name.is_some() { 4 } else { 3 }); + let msg = format!("Granted all {name} access."); + eprintln!("✅ {}", colors::bold(&msg)); + break PromptResponse::AllowAll; + } _ => { // If we don't get a recognized option try again. clear_n_lines(1); eprint!("└ {}", colors::bold("Unrecognized option. Allow?")); - eprint!(" {OPTS} > "); + eprint!(" {opts} > "); } }; }; @@ -264,6 +280,7 @@ pub mod tests { _message: &str, _name: &str, _api_name: Option<&str>, + _is_unary: bool, ) -> PromptResponse { if STUB_PROMPT_VALUE.load(Ordering::SeqCst) { PromptResponse::Allow