1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-18 03:44:05 -05:00

feat(permissions): grant all permission for a group in permission prompt (#17140)

This commit adds new "A" option to the interactive permission prompt, that will
allow all subsequent permissions for given group (domain). Ie. when querying for
permissions to access eg. env variables responding with "A" will allow access
to all environmental variables.

This works for all permission domains and should make permission prompts
more ergonomic for users.
This commit is contained in:
Asher Gomez 2023-02-23 09:02:10 +11:00 committed by GitHub
parent 5fcbdd6228
commit c18e0d1d37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 363 additions and 151 deletions

View file

@ -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 { itest!(_091_use_define_for_class_fields {
args: "run --check run/091_use_define_for_class_fields.ts", args: "run --check run/091_use_define_for_class_fields.ts",
output: "run/091_use_define_for_class_fields.ts.out", output: "run/091_use_define_for_class_fields.ts.out",

View file

@ -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" });

View file

@ -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));

View file

@ -90,7 +90,7 @@ impl PermissionState {
api_name: Option<&str>, api_name: Option<&str>,
info: Option<&str>, info: Option<&str>,
prompt: bool, prompt: bool,
) -> (Result<(), AnyError>, bool) { ) -> (Result<(), AnyError>, bool, bool) {
self.check2(name, api_name, || info.map(|s| s.to_string()), prompt) self.check2(name, api_name, || info.map(|s| s.to_string()), prompt)
} }
@ -101,11 +101,11 @@ impl PermissionState {
api_name: Option<&str>, api_name: Option<&str>,
info: impl Fn() -> Option<String>, info: impl Fn() -> Option<String>,
prompt: bool, prompt: bool,
) -> (Result<(), AnyError>, bool) { ) -> (Result<(), AnyError>, bool, bool) {
match self { match self {
PermissionState::Granted => { PermissionState::Granted => {
Self::log_perm_access(name, info); Self::log_perm_access(name, info);
(Ok(()), false) (Ok(()), false, false)
} }
PermissionState::Prompt if prompt => { PermissionState::Prompt if prompt => {
let msg = format!( let msg = format!(
@ -113,14 +113,19 @@ impl PermissionState {
name, name,
info().map_or(String::new(), |info| { format!(" to {info}") }), info().map_or(String::new(), |info| { format!(" to {info}") }),
); );
if PromptResponse::Allow == permission_prompt(&msg, name, api_name) { match permission_prompt(&msg, name, api_name, true) {
Self::log_perm_access(name, info); PromptResponse::Allow => {
(Ok(()), true) Self::log_perm_access(name, info);
} else { (Ok(()), true, false)
(Err(Self::error(name, info)), true) }
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), &format!("access to {}", self.description),
self.name, self.name,
Some("Deno.permissions.query()"), Some("Deno.permissions.query()"),
false,
) )
{ {
self.state = PermissionState::Granted; self.state = PermissionState::Granted;
@ -179,7 +185,7 @@ impl UnitPermission {
} }
pub fn check(&mut self) -> Result<(), AnyError> { 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); self.state.check(self.name, None, None, self.prompt);
if prompted { if prompted {
if result.is_ok() { if result.is_ok() {
@ -357,19 +363,26 @@ impl UnaryPermission<ReadDescriptor> {
let (resolved_path, display_path) = resolved_and_display_path(path); let (resolved_path, display_path) = resolved_and_display_path(path);
let state = self.query(Some(&resolved_path)); let state = self.query(Some(&resolved_path));
if state == PermissionState::Prompt { if state == PermissionState::Prompt {
if PromptResponse::Allow match permission_prompt(
== permission_prompt( &format!("read access to \"{}\"", display_path.display()),
&format!("read access to \"{}\"", display_path.display()), self.name,
self.name, Some("Deno.permissions.query()"),
Some("Deno.permissions.query()"), true,
) ) {
{ PromptResponse::Allow => {
self.granted_list.insert(ReadDescriptor(resolved_path)); self.granted_list.insert(ReadDescriptor(resolved_path));
PermissionState::Granted PermissionState::Granted
} else { }
self.denied_list.insert(ReadDescriptor(resolved_path)); PromptResponse::Deny => {
self.global_state = PermissionState::Denied; self.denied_list.insert(ReadDescriptor(resolved_path));
PermissionState::Denied 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 { } else if state == PermissionState::Granted {
self.granted_list.insert(ReadDescriptor(resolved_path)); self.granted_list.insert(ReadDescriptor(resolved_path));
@ -385,6 +398,7 @@ impl UnaryPermission<ReadDescriptor> {
"read access", "read access",
self.name, self.name,
Some("Deno.permissions.query()"), Some("Deno.permissions.query()"),
true,
) )
{ {
self.granted_list.clear(); self.granted_list.clear();
@ -421,7 +435,7 @@ impl UnaryPermission<ReadDescriptor> {
path: &Path, path: &Path,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let (result, prompted) = self.query(Some(path)).check2( let (result, prompted, is_allow_all) = self.query(Some(path)).check2(
self.name, self.name,
api_name, api_name,
|| Some(format!("\"{}\"", path.to_path_buf().display())), || Some(format!("\"{}\"", path.to_path_buf().display())),
@ -430,7 +444,12 @@ impl UnaryPermission<ReadDescriptor> {
if prompted { if prompted {
let resolved_path = resolve_from_cwd(path)?; let resolved_path = resolve_from_cwd(path)?;
if result.is_ok() { 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 { } else {
self.denied_list.insert(ReadDescriptor(resolved_path)); self.denied_list.insert(ReadDescriptor(resolved_path));
self.global_state = PermissionState::Denied; self.global_state = PermissionState::Denied;
@ -448,25 +467,33 @@ impl UnaryPermission<ReadDescriptor> {
api_name: &str, api_name: &str,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let resolved_path = resolve_from_cwd(path)?; let resolved_path = resolve_from_cwd(path)?;
let (result, prompted) = self.query(Some(&resolved_path)).check( let (result, prompted, is_allow_all) =
self.name, self.query(Some(&resolved_path)).check(
Some(api_name), self.name,
Some(&format!("<{display}>")), Some(api_name),
self.prompt, Some(&format!("<{display}>")),
); self.prompt,
);
if prompted { if prompted {
if result.is_ok() { 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 { } else {
self.denied_list.insert(ReadDescriptor(resolved_path));
self.global_state = PermissionState::Denied; self.global_state = PermissionState::Denied;
if !is_allow_all {
self.denied_list.insert(ReadDescriptor(resolved_path));
}
} }
} }
result result
} }
pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> {
let (result, prompted) = let (result, prompted, _) =
self self
.query(None) .query(None)
.check(self.name, api_name, Some("all"), self.prompt); .check(self.name, api_name, Some("all"), self.prompt);
@ -530,19 +557,26 @@ impl UnaryPermission<WriteDescriptor> {
let (resolved_path, display_path) = resolved_and_display_path(path); let (resolved_path, display_path) = resolved_and_display_path(path);
let state = self.query(Some(&resolved_path)); let state = self.query(Some(&resolved_path));
if state == PermissionState::Prompt { if state == PermissionState::Prompt {
if PromptResponse::Allow match permission_prompt(
== permission_prompt( &format!("write access to \"{}\"", display_path.display()),
&format!("write access to \"{}\"", display_path.display()), self.name,
self.name, Some("Deno.permissions.query()"),
Some("Deno.permissions.query()"), true,
) ) {
{ PromptResponse::Allow => {
self.granted_list.insert(WriteDescriptor(resolved_path)); self.granted_list.insert(WriteDescriptor(resolved_path));
PermissionState::Granted PermissionState::Granted
} else { }
self.denied_list.insert(WriteDescriptor(resolved_path)); PromptResponse::Deny => {
self.global_state = PermissionState::Denied; self.denied_list.insert(WriteDescriptor(resolved_path));
PermissionState::Denied 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 { } else if state == PermissionState::Granted {
self.granted_list.insert(WriteDescriptor(resolved_path)); self.granted_list.insert(WriteDescriptor(resolved_path));
@ -558,6 +592,7 @@ impl UnaryPermission<WriteDescriptor> {
"write access", "write access",
self.name, self.name,
Some("Deno.permissions.query()"), Some("Deno.permissions.query()"),
true,
) )
{ {
self.granted_list.clear(); self.granted_list.clear();
@ -594,7 +629,7 @@ impl UnaryPermission<WriteDescriptor> {
path: &Path, path: &Path,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let (result, prompted) = self.query(Some(path)).check2( let (result, prompted, is_allow_all) = self.query(Some(path)).check2(
self.name, self.name,
api_name, api_name,
|| Some(format!("\"{}\"", path.to_path_buf().display())), || Some(format!("\"{}\"", path.to_path_buf().display())),
@ -603,7 +638,12 @@ impl UnaryPermission<WriteDescriptor> {
if prompted { if prompted {
let resolved_path = resolve_from_cwd(path)?; let resolved_path = resolve_from_cwd(path)?;
if result.is_ok() { 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 { } else {
self.denied_list.insert(WriteDescriptor(resolved_path)); self.denied_list.insert(WriteDescriptor(resolved_path));
self.global_state = PermissionState::Denied; self.global_state = PermissionState::Denied;
@ -613,7 +653,7 @@ impl UnaryPermission<WriteDescriptor> {
} }
pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> {
let (result, prompted) = let (result, prompted, _) =
self self
.query(None) .query(None)
.check(self.name, api_name, Some("all"), self.prompt); .check(self.name, api_name, Some("all"), self.prompt);
@ -685,19 +725,26 @@ impl UnaryPermission<NetDescriptor> {
let state = self.query(Some(host)); let state = self.query(Some(host));
let host = NetDescriptor::new(&host); let host = NetDescriptor::new(&host);
if state == PermissionState::Prompt { if state == PermissionState::Prompt {
if PromptResponse::Allow match permission_prompt(
== permission_prompt( &format!("network access to \"{host}\""),
&format!("network access to \"{host}\""), self.name,
self.name, Some("Deno.permissions.query()"),
Some("Deno.permissions.query()"), true,
) ) {
{ PromptResponse::Allow => {
self.granted_list.insert(host); self.granted_list.insert(host);
PermissionState::Granted PermissionState::Granted
} else { }
self.denied_list.insert(host); PromptResponse::Deny => {
self.global_state = PermissionState::Denied; self.denied_list.insert(host);
PermissionState::Denied 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 { } else if state == PermissionState::Granted {
self.granted_list.insert(host); self.granted_list.insert(host);
@ -713,6 +760,7 @@ impl UnaryPermission<NetDescriptor> {
"network access", "network access",
self.name, self.name,
Some("Deno.permissions.query()"), Some("Deno.permissions.query()"),
true,
) )
{ {
self.granted_list.clear(); self.granted_list.clear();
@ -756,7 +804,7 @@ impl UnaryPermission<NetDescriptor> {
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let new_host = NetDescriptor::new(&host); 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, self.name,
api_name, api_name,
Some(&format!("\"{new_host}\"")), Some(&format!("\"{new_host}\"")),
@ -764,7 +812,12 @@ impl UnaryPermission<NetDescriptor> {
); );
if prompted { if prompted {
if result.is_ok() { 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 { } else {
self.denied_list.insert(new_host); self.denied_list.insert(new_host);
self.global_state = PermissionState::Denied; self.global_state = PermissionState::Denied;
@ -787,7 +840,7 @@ impl UnaryPermission<NetDescriptor> {
Some(port) => format!("{hostname}:{port}"), Some(port) => format!("{hostname}:{port}"),
}; };
let host = &(&hostname, url.port_or_known_default()); 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, self.name,
api_name, api_name,
Some(&format!("\"{display_host}\"")), Some(&format!("\"{display_host}\"")),
@ -795,7 +848,12 @@ impl UnaryPermission<NetDescriptor> {
); );
if prompted { if prompted {
if result.is_ok() { 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 { } else {
self.denied_list.insert(NetDescriptor::new(&host)); self.denied_list.insert(NetDescriptor::new(&host));
self.global_state = PermissionState::Denied; self.global_state = PermissionState::Denied;
@ -805,7 +863,7 @@ impl UnaryPermission<NetDescriptor> {
} }
pub fn check_all(&mut self) -> Result<(), AnyError> { pub fn check_all(&mut self) -> Result<(), AnyError> {
let (result, prompted) = let (result, prompted, _) =
self self
.query::<&str>(None) .query::<&str>(None)
.check(self.name, None, Some("all"), self.prompt); .check(self.name, None, Some("all"), self.prompt);
@ -859,19 +917,26 @@ impl UnaryPermission<EnvDescriptor> {
if let Some(env) = env { if let Some(env) = env {
let state = self.query(Some(env)); let state = self.query(Some(env));
if state == PermissionState::Prompt { if state == PermissionState::Prompt {
if PromptResponse::Allow match permission_prompt(
== permission_prompt( &format!("env access to \"{env}\""),
&format!("env access to \"{env}\""), self.name,
self.name, Some("Deno.permissions.query()"),
Some("Deno.permissions.query()"), true,
) ) {
{ PromptResponse::Allow => {
self.granted_list.insert(EnvDescriptor::new(env)); self.granted_list.insert(EnvDescriptor::new(env));
PermissionState::Granted PermissionState::Granted
} else { }
self.denied_list.insert(EnvDescriptor::new(env)); PromptResponse::Deny => {
self.global_state = PermissionState::Denied; self.denied_list.insert(EnvDescriptor::new(env));
PermissionState::Denied 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 { } else if state == PermissionState::Granted {
self.granted_list.insert(EnvDescriptor::new(env)); self.granted_list.insert(EnvDescriptor::new(env));
@ -887,6 +952,7 @@ impl UnaryPermission<EnvDescriptor> {
"env access", "env access",
self.name, self.name,
Some("Deno.permissions.query()"), Some("Deno.permissions.query()"),
true,
) )
{ {
self.granted_list.clear(); self.granted_list.clear();
@ -915,7 +981,7 @@ impl UnaryPermission<EnvDescriptor> {
} }
pub fn check(&mut self, env: &str) -> Result<(), AnyError> { 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, self.name,
None, None,
Some(&format!("\"{env}\"")), Some(&format!("\"{env}\"")),
@ -923,7 +989,12 @@ impl UnaryPermission<EnvDescriptor> {
); );
if prompted { if prompted {
if result.is_ok() { 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 { } else {
self.denied_list.insert(EnvDescriptor::new(env)); self.denied_list.insert(EnvDescriptor::new(env));
self.global_state = PermissionState::Denied; self.global_state = PermissionState::Denied;
@ -933,7 +1004,7 @@ impl UnaryPermission<EnvDescriptor> {
} }
pub fn check_all(&mut self) -> Result<(), AnyError> { pub fn check_all(&mut self) -> Result<(), AnyError> {
let (result, prompted) = let (result, prompted, _) =
self self
.query(None) .query(None)
.check(self.name, None, Some("all"), self.prompt); .check(self.name, None, Some("all"), self.prompt);
@ -993,19 +1064,26 @@ impl UnaryPermission<SysDescriptor> {
} }
if let Some(kind) = kind { if let Some(kind) = kind {
let desc = SysDescriptor(kind.to_string()); let desc = SysDescriptor(kind.to_string());
if PromptResponse::Allow match permission_prompt(
== permission_prompt( &format!("sys access to \"{kind}\""),
&format!("sys access to \"{kind}\""), self.name,
self.name, Some("Deno.permissions.query()"),
Some("Deno.permissions.query()"), true,
) ) {
{ PromptResponse::Allow => {
self.granted_list.insert(desc); self.granted_list.insert(desc);
PermissionState::Granted PermissionState::Granted
} else { }
self.denied_list.insert(desc); PromptResponse::Deny => {
self.global_state = PermissionState::Denied; self.denied_list.insert(desc);
PermissionState::Denied self.global_state = PermissionState::Denied;
PermissionState::Denied
}
PromptResponse::AllowAll => {
self.granted_list.clear();
self.global_state = PermissionState::Granted;
PermissionState::Granted
}
} }
} else { } else {
if PromptResponse::Allow if PromptResponse::Allow
@ -1013,6 +1091,7 @@ impl UnaryPermission<SysDescriptor> {
"sys access", "sys access",
self.name, self.name,
Some("Deno.permissions.query()"), Some("Deno.permissions.query()"),
true,
) )
{ {
self.global_state = PermissionState::Granted; self.global_state = PermissionState::Granted;
@ -1041,7 +1120,7 @@ impl UnaryPermission<SysDescriptor> {
kind: &str, kind: &str,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let (result, prompted) = self.query(Some(kind)).check( let (result, prompted, is_allow_all) = self.query(Some(kind)).check(
self.name, self.name,
api_name, api_name,
Some(&format!("\"{kind}\"")), Some(&format!("\"{kind}\"")),
@ -1049,7 +1128,12 @@ impl UnaryPermission<SysDescriptor> {
); );
if prompted { if prompted {
if result.is_ok() { 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 { } else {
self.denied_list.insert(SysDescriptor(kind.to_string())); self.denied_list.insert(SysDescriptor(kind.to_string()));
self.global_state = PermissionState::Denied; self.global_state = PermissionState::Denied;
@ -1059,7 +1143,7 @@ impl UnaryPermission<SysDescriptor> {
} }
pub fn check_all(&mut self) -> Result<(), AnyError> { pub fn check_all(&mut self) -> Result<(), AnyError> {
let (result, prompted) = let (result, prompted, _is_allow_all) =
self self
.query(None) .query(None)
.check(self.name, None, Some("all"), self.prompt); .check(self.name, None, Some("all"), self.prompt);
@ -1116,23 +1200,30 @@ impl UnaryPermission<RunDescriptor> {
if let Some(cmd) = cmd { if let Some(cmd) = cmd {
let state = self.query(Some(cmd)); let state = self.query(Some(cmd));
if state == PermissionState::Prompt { if state == PermissionState::Prompt {
if PromptResponse::Allow match permission_prompt(
== permission_prompt( &format!("run access to \"{cmd}\""),
&format!("run access to \"{cmd}\""), self.name,
self.name, Some("Deno.permissions.query()"),
Some("Deno.permissions.query()"), true,
) ) {
{ PromptResponse::Allow => {
self self
.granted_list .granted_list
.insert(RunDescriptor::from_str(cmd).unwrap()); .insert(RunDescriptor::from_str(cmd).unwrap());
PermissionState::Granted PermissionState::Granted
} else { }
self PromptResponse::Deny => {
.denied_list self
.insert(RunDescriptor::from_str(cmd).unwrap()); .denied_list
self.global_state = PermissionState::Denied; .insert(RunDescriptor::from_str(cmd).unwrap());
PermissionState::Denied 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 { } else if state == PermissionState::Granted {
self self
@ -1150,6 +1241,7 @@ impl UnaryPermission<RunDescriptor> {
"run access", "run access",
self.name, self.name,
Some("Deno.permissions.query()"), Some("Deno.permissions.query()"),
true,
) )
{ {
self.granted_list.clear(); self.granted_list.clear();
@ -1184,7 +1276,7 @@ impl UnaryPermission<RunDescriptor> {
cmd: &str, cmd: &str,
api_name: Option<&str>, api_name: Option<&str>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let (result, prompted) = self.query(Some(cmd)).check( let (result, prompted, is_allow_all) = self.query(Some(cmd)).check(
self.name, self.name,
api_name, api_name,
Some(&format!("\"{cmd}\"")), Some(&format!("\"{cmd}\"")),
@ -1192,9 +1284,14 @@ impl UnaryPermission<RunDescriptor> {
); );
if prompted { if prompted {
if result.is_ok() { if result.is_ok() {
self if is_allow_all {
.granted_list self.granted_list.clear();
.insert(RunDescriptor::from_str(cmd).unwrap()); self.global_state = PermissionState::Granted;
} else {
self
.granted_list
.insert(RunDescriptor::from_str(cmd).unwrap());
}
} else { } else {
self self
.denied_list .denied_list
@ -1206,7 +1303,7 @@ impl UnaryPermission<RunDescriptor> {
} }
pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> { pub fn check_all(&mut self, api_name: Option<&str>) -> Result<(), AnyError> {
let (result, prompted) = let (result, prompted, _) =
self self
.query(None) .query(None)
.check(self.name, api_name, Some("all"), self.prompt); .check(self.name, api_name, Some("all"), self.prompt);
@ -1267,19 +1364,26 @@ impl UnaryPermission<FfiDescriptor> {
let (resolved_path, display_path) = resolved_and_display_path(path); let (resolved_path, display_path) = resolved_and_display_path(path);
let state = self.query(Some(&resolved_path)); let state = self.query(Some(&resolved_path));
if state == PermissionState::Prompt { if state == PermissionState::Prompt {
if PromptResponse::Allow match permission_prompt(
== permission_prompt( &format!("ffi access to \"{}\"", display_path.display()),
&format!("ffi access to \"{}\"", display_path.display()), self.name,
self.name, Some("Deno.permissions.query()"),
Some("Deno.permissions.query()"), true,
) ) {
{ PromptResponse::Allow => {
self.granted_list.insert(FfiDescriptor(resolved_path)); self.granted_list.insert(FfiDescriptor(resolved_path));
PermissionState::Granted PermissionState::Granted
} else { }
self.denied_list.insert(FfiDescriptor(resolved_path)); PromptResponse::Deny => {
self.global_state = PermissionState::Denied; self.denied_list.insert(FfiDescriptor(resolved_path));
PermissionState::Denied 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 { } else if state == PermissionState::Granted {
self.granted_list.insert(FfiDescriptor(resolved_path)); self.granted_list.insert(FfiDescriptor(resolved_path));
@ -1295,6 +1399,7 @@ impl UnaryPermission<FfiDescriptor> {
"ffi access", "ffi access",
self.name, self.name,
Some("Deno.permissions.query()"), Some("Deno.permissions.query()"),
true,
) )
{ {
self.granted_list.clear(); self.granted_list.clear();
@ -1328,16 +1433,22 @@ impl UnaryPermission<FfiDescriptor> {
pub fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> { pub fn check(&mut self, path: Option<&Path>) -> Result<(), AnyError> {
if let Some(path) = path { if let Some(path) = path {
let (resolved_path, display_path) = resolved_and_display_path(path); let (resolved_path, display_path) = resolved_and_display_path(path);
let (result, prompted) = self.query(Some(&resolved_path)).check( let (result, prompted, is_allow_all) =
self.name, self.query(Some(&resolved_path)).check(
None, self.name,
Some(&format!("\"{}\"", display_path.display())), None,
self.prompt, Some(&format!("\"{}\"", display_path.display())),
); self.prompt,
);
if prompted { if prompted {
if result.is_ok() { 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 { } else {
self.denied_list.insert(FfiDescriptor(resolved_path)); self.denied_list.insert(FfiDescriptor(resolved_path));
self.global_state = PermissionState::Denied; self.global_state = PermissionState::Denied;
@ -1346,7 +1457,7 @@ impl UnaryPermission<FfiDescriptor> {
result result
} else { } else {
let (result, prompted) = let (result, prompted, _) =
self.query(None).check(self.name, None, None, self.prompt); self.query(None).check(self.name, None, None, self.prompt);
if prompted { if prompted {
@ -1362,7 +1473,7 @@ impl UnaryPermission<FfiDescriptor> {
} }
pub fn check_all(&mut self) -> Result<(), AnyError> { pub fn check_all(&mut self) -> Result<(), AnyError> {
let (result, prompted) = let (result, prompted, _) =
self self
.query(None) .query(None)
.check(self.name, None, Some("all"), self.prompt); .check(self.name, None, Some("all"), self.prompt);

View file

@ -11,6 +11,7 @@ pub const PERMISSION_EMOJI: &str = "⚠️";
pub enum PromptResponse { pub enum PromptResponse {
Allow, Allow,
Deny, Deny,
AllowAll,
} }
static PERMISSION_PROMPTER: Lazy<Mutex<Box<dyn PermissionPrompter>>> = static PERMISSION_PROMPTER: Lazy<Mutex<Box<dyn PermissionPrompter>>> =
@ -26,11 +27,14 @@ pub fn permission_prompt(
message: &str, message: &str,
flag: &str, flag: &str,
api_name: Option<&str>, api_name: Option<&str>,
is_unary: bool,
) -> PromptResponse { ) -> PromptResponse {
if let Some(before_callback) = MAYBE_BEFORE_PROMPT_CALLBACK.lock().as_mut() { if let Some(before_callback) = MAYBE_BEFORE_PROMPT_CALLBACK.lock().as_mut() {
before_callback(); 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() { if let Some(after_callback) = MAYBE_AFTER_PROMPT_CALLBACK.lock().as_mut() {
after_callback(); after_callback();
} }
@ -53,6 +57,7 @@ pub trait PermissionPrompter: Send + Sync {
message: &str, message: &str,
name: &str, name: &str,
api_name: Option<&str>, api_name: Option<&str>,
is_unary: bool,
) -> PromptResponse; ) -> PromptResponse;
} }
@ -64,6 +69,7 @@ impl PermissionPrompter for TtyPrompter {
message: &str, message: &str,
name: &str, name: &str,
api_name: Option<&str>, api_name: Option<&str>,
is_unary: bool,
) -> PromptResponse { ) -> PromptResponse {
if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) { if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
return PromptResponse::Deny; return PromptResponse::Deny;
@ -198,7 +204,11 @@ impl PermissionPrompter for TtyPrompter {
let _stderr_guard = std::io::stderr().lock(); let _stderr_guard = std::io::stderr().lock();
// print to stderr so that if stdout is piped this is still displayed. // 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!("{PERMISSION_EMOJI} ");
eprint!("{}", colors::bold("Deno requests ")); eprint!("{}", colors::bold("Deno requests "));
eprint!("{}", colors::bold(message)); eprint!("{}", colors::bold(message));
@ -209,7 +219,7 @@ impl PermissionPrompter for TtyPrompter {
let msg = format!("Run again with --allow-{name} to bypass this prompt."); let msg = format!("Run again with --allow-{name} to bypass this prompt.");
eprintln!("{}", colors::italic(&msg)); eprintln!("{}", colors::italic(&msg));
eprint!("{}", colors::bold("Allow?")); eprint!("{}", colors::bold("Allow?"));
eprint!(" {OPTS} > "); eprint!(" {opts} > ");
let value = loop { let value = loop {
let mut input = String::new(); let mut input = String::new();
let stdin = std::io::stdin(); let stdin = std::io::stdin();
@ -221,24 +231,30 @@ impl PermissionPrompter for TtyPrompter {
None => break PromptResponse::Deny, None => break PromptResponse::Deny,
Some(v) => v, Some(v) => v,
}; };
match ch.to_ascii_lowercase() { match ch {
'y' => { 'y' | 'Y' => {
clear_n_lines(if api_name.is_some() { 4 } else { 3 }); clear_n_lines(if api_name.is_some() { 4 } else { 3 });
let msg = format!("Granted {message}."); let msg = format!("Granted {message}.");
eprintln!("{}", colors::bold(&msg)); eprintln!("{}", colors::bold(&msg));
break PromptResponse::Allow; break PromptResponse::Allow;
} }
'n' => { 'n' | 'N' => {
clear_n_lines(if api_name.is_some() { 4 } else { 3 }); clear_n_lines(if api_name.is_some() { 4 } else { 3 });
let msg = format!("Denied {message}."); let msg = format!("Denied {message}.");
eprintln!("{}", colors::bold(&msg)); eprintln!("{}", colors::bold(&msg));
break PromptResponse::Deny; 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. // If we don't get a recognized option try again.
clear_n_lines(1); clear_n_lines(1);
eprint!("{}", colors::bold("Unrecognized option. Allow?")); eprint!("{}", colors::bold("Unrecognized option. Allow?"));
eprint!(" {OPTS} > "); eprint!(" {opts} > ");
} }
}; };
}; };
@ -264,6 +280,7 @@ pub mod tests {
_message: &str, _message: &str,
_name: &str, _name: &str,
_api_name: Option<&str>, _api_name: Option<&str>,
_is_unary: bool,
) -> PromptResponse { ) -> PromptResponse {
if STUB_PROMPT_VALUE.load(Ordering::SeqCst) { if STUB_PROMPT_VALUE.load(Ordering::SeqCst) {
PromptResponse::Allow PromptResponse::Allow