remove --no-prompt flag, fail on missing permissions (#3183)

This commit is contained in:
Yoshiya Hinosawa 2019-10-23 08:35:43 +09:00 committed by Ry Dahl
parent edd6a90da8
commit 2804ba8674
9 changed files with 68 additions and 199 deletions

View file

@ -67,6 +67,10 @@ pub fn permission_denied() -> ErrBox {
StaticError(ErrorKind::PermissionDenied, "permission denied").into()
}
pub fn permission_denied_msg(msg: String) -> ErrBox {
DenoError::new(ErrorKind::PermissionDenied, msg).into()
}
pub fn op_not_implemented() -> ErrBox {
StaticError(ErrorKind::OpNotAvailable, "op not implemented").into()
}
@ -483,6 +487,14 @@ mod tests {
assert_eq!(err.to_string(), "permission denied");
}
#[test]
fn test_permission_denied_msg() {
let err =
permission_denied_msg("run again with the --allow-net flag".to_string());
assert_eq!(err.kind(), ErrorKind::PermissionDenied);
assert_eq!(err.to_string(), "run again with the --allow-net flag");
}
#[test]
fn test_op_not_implemented() {
let err = op_not_implemented();

View file

@ -118,11 +118,6 @@ fn add_run_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
.long("allow-all")
.help("Allow all permissions"),
)
.arg(
Arg::with_name("no-prompt")
.long("no-prompt")
.help("Do not use prompts"),
)
.arg(
Arg::with_name("no-fetch")
.long("no-fetch")
@ -707,9 +702,6 @@ fn parse_run_args(mut flags: DenoFlags, matches: &ArgMatches) -> DenoFlags {
flags.allow_write = true;
flags.allow_hrtime = true;
}
if matches.is_present("no-prompt") {
flags.no_prompts = true;
}
if matches.is_present("no-fetch") {
flags.no_fetch = true;
}

View file

@ -52,13 +52,7 @@ async function main(): Promise<void> {
console.log(`Running tests for: ${permsFmt}`);
const cliPerms = permsToCliFlags(perms);
// run subsequent tests using same deno executable
const args = [
Deno.execPath(),
"run",
"--no-prompt",
...cliPerms,
"cli/js/unit_tests.ts"
];
const args = [Deno.execPath(), "run", ...cliPerms, "cli/js/unit_tests.ts"];
const p = Deno.run({
args,

View file

@ -1,15 +1,13 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
use crate::deno_error::permission_denied;
use crate::deno_error::permission_denied_msg;
use crate::flags::DenoFlags;
use ansi_term::Style;
use atty;
use deno::ErrBox;
use log;
use std::collections::HashSet;
use std::fmt;
use std::io;
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
const PERMISSION_EMOJI: &str = "⚠️";
@ -87,21 +85,6 @@ impl PermissionAccessor {
self.set_state(PermissionAccessorState::Ask)
}
pub fn deny(&self) {
self.set_state(PermissionAccessorState::Deny)
}
/// Update this accessors state based on a PromptResult value
/// This will only update the state if the PromptResult value
/// is one of the "Always" values
pub fn update_with_prompt_result(&self, prompt_result: &PromptResult) {
match prompt_result {
PromptResult::AllowAlways => self.allow(),
PromptResult::DenyAlways => self.deny(),
_ => {}
}
}
#[inline]
pub fn get_state(&self) -> PermissionAccessorState {
self.state.load(Ordering::SeqCst).into()
@ -137,7 +120,6 @@ pub struct DenoPermissions {
pub allow_env: PermissionAccessor,
pub allow_run: PermissionAccessor,
pub allow_hrtime: PermissionAccessor,
pub no_prompts: AtomicBool,
}
impl DenoPermissions {
@ -154,7 +136,6 @@ impl DenoPermissions {
allow_env: PermissionAccessor::from(flags.allow_env),
allow_run: PermissionAccessor::from(flags.allow_run),
allow_hrtime: PermissionAccessor::from(flags.allow_hrtime),
no_prompts: AtomicBool::new(flags.no_prompts),
}
}
@ -166,16 +147,12 @@ impl DenoPermissions {
self.log_perm_access(msg);
Ok(())
}
PermissionAccessorState::Ask => match self.try_permissions_prompt(msg) {
Err(e) => Err(e),
Ok(v) => {
self.allow_run.update_with_prompt_result(&v);
v.check()?;
self.log_perm_access(msg);
Ok(())
}
},
PermissionAccessorState::Deny => Err(permission_denied()),
PermissionAccessorState::Ask => Err(permission_denied_msg(
"run again with the --allow-run flag".to_string(),
)),
PermissionAccessorState::Deny => Err(permission_denied_msg(
"run again with the --allow-run flag".to_string(),
)),
}
}
@ -192,18 +169,12 @@ impl DenoPermissions {
Ok(())
} else {
match state {
PermissionAccessorState::Ask => {
match self.try_permissions_prompt(msg) {
Err(e) => Err(e),
Ok(v) => {
self.allow_read.update_with_prompt_result(&v);
v.check()?;
self.log_perm_access(msg);
Ok(())
}
}
}
PermissionAccessorState::Deny => Err(permission_denied()),
PermissionAccessorState::Ask => Err(permission_denied_msg(
"run again with the --allow-read flag".to_string(),
)),
PermissionAccessorState::Deny => Err(permission_denied_msg(
"run again with the --allow-read flag".to_string(),
)),
_ => unreachable!(),
}
}
@ -224,18 +195,12 @@ impl DenoPermissions {
Ok(())
} else {
match state {
PermissionAccessorState::Ask => {
match self.try_permissions_prompt(msg) {
Err(e) => Err(e),
Ok(v) => {
self.allow_write.update_with_prompt_result(&v);
v.check()?;
self.log_perm_access(msg);
Ok(())
}
}
}
PermissionAccessorState::Deny => Err(permission_denied()),
PermissionAccessorState::Ask => Err(permission_denied_msg(
"run again with the --allow-write flag".to_string(),
)),
PermissionAccessorState::Deny => Err(permission_denied_msg(
"run again with the --allow-write flag".to_string(),
)),
_ => unreachable!(),
}
}
@ -250,7 +215,7 @@ impl DenoPermissions {
self.log_perm_access(msg);
Ok(())
}
state => {
_state => {
let parts = host_and_port.split(':').collect::<Vec<&str>>();
if match parts.len() {
2 => {
@ -268,7 +233,9 @@ impl DenoPermissions {
self.log_perm_access(msg);
Ok(())
} else {
self.check_net_inner(state, msg)
Err(permission_denied_msg(
"run again with the --allow-net flag".to_string(),
))
}
}
}
@ -281,7 +248,7 @@ impl DenoPermissions {
self.log_perm_access(msg);
Ok(())
}
state => {
_state => {
let host = url.host().unwrap();
let whitelist_result = {
if self.net_whitelist.contains(&format!("{}", host)) {
@ -299,34 +266,14 @@ impl DenoPermissions {
self.log_perm_access(msg);
Ok(())
} else {
self.check_net_inner(state, msg)
Err(permission_denied_msg(
"run again with the --allow-net flag".to_string(),
))
}
}
}
}
fn check_net_inner(
&self,
state: PermissionAccessorState,
prompt_str: &str,
) -> Result<(), ErrBox> {
match state {
PermissionAccessorState::Ask => {
match self.try_permissions_prompt(prompt_str) {
Err(e) => Err(e),
Ok(v) => {
self.allow_net.update_with_prompt_result(&v);
v.check()?;
self.log_perm_access(prompt_str);
Ok(())
}
}
}
PermissionAccessorState::Deny => Err(permission_denied()),
_ => unreachable!(),
}
}
pub fn check_env(&self) -> Result<(), ErrBox> {
let msg = "access to environment variables";
match self.allow_env.get_state() {
@ -334,34 +281,15 @@ impl DenoPermissions {
self.log_perm_access(msg);
Ok(())
}
PermissionAccessorState::Ask => match self.try_permissions_prompt(msg) {
Err(e) => Err(e),
Ok(v) => {
self.allow_env.update_with_prompt_result(&v);
v.check()?;
self.log_perm_access(msg);
Ok(())
}
},
PermissionAccessorState::Deny => Err(permission_denied()),
PermissionAccessorState::Ask => Err(permission_denied_msg(
"run again with the --allow-env flag".to_string(),
)),
PermissionAccessorState::Deny => Err(permission_denied_msg(
"run again with the --allow-env flag".to_string(),
)),
}
}
/// Try to present the user with a permission prompt
/// will error with permission_denied if no_prompts is enabled
fn try_permissions_prompt(
&self,
message: &str,
) -> Result<PromptResult, ErrBox> {
if self.no_prompts.load(Ordering::SeqCst) {
return Err(permission_denied());
}
if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
return Err(permission_denied());
};
permission_prompt(message)
}
fn log_perm_access(&self, message: &str) {
if log_enabled!(log::Level::Info) {
eprintln!(
@ -427,60 +355,6 @@ impl DenoPermissions {
}
}
/// Quad-state value for representing user input on permission prompt
#[derive(Debug, Clone)]
pub enum PromptResult {
AllowAlways = 0,
AllowOnce = 1,
DenyOnce = 2,
DenyAlways = 3,
}
impl PromptResult {
/// If value is any form of deny this will error with permission_denied
pub fn check(&self) -> Result<(), ErrBox> {
match self {
PromptResult::DenyOnce => Err(permission_denied()),
PromptResult::DenyAlways => Err(permission_denied()),
_ => Ok(()),
}
}
}
impl fmt::Display for PromptResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PromptResult::AllowAlways => f.pad("AllowAlways"),
PromptResult::AllowOnce => f.pad("AllowOnce"),
PromptResult::DenyOnce => f.pad("DenyOnce"),
PromptResult::DenyAlways => f.pad("DenyAlways"),
}
}
}
fn permission_prompt(message: &str) -> Result<PromptResult, ErrBox> {
let msg = format!("{} Deno requests {}. Grant? [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", PERMISSION_EMOJI, message);
// print to stderr so that if deno is > to a file this is still displayed.
eprint!("{}", Style::new().bold().paint(msg));
loop {
let mut input = String::new();
let stdin = io::stdin();
let _nread = stdin.read_line(&mut input)?;
let ch = input.chars().next().unwrap();
match ch.to_ascii_lowercase() {
'a' => return Ok(PromptResult::AllowAlways),
'y' => return Ok(PromptResult::AllowOnce),
'n' => return Ok(PromptResult::DenyOnce),
'd' => return Ok(PromptResult::DenyAlways),
_ => {
// If we don't get a recognized option try again.
let msg_again = format!("Unrecognized option '{}' [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)] ", ch);
eprint!("{}", Style::new().bold().paint(msg_again));
}
};
}
}
fn check_path_white_list(
filename: &str,
white_list: &Arc<HashSet<String>>,
@ -514,7 +388,6 @@ mod tests {
let perms = DenoPermissions::from_flags(&DenoFlags {
read_whitelist: whitelist.clone(),
write_whitelist: whitelist.clone(),
no_prompts: true,
..Default::default()
});
@ -561,7 +434,6 @@ mod tests {
"127.0.0.1",
"172.16.0.2:8000"
],
no_prompts: true,
..Default::default()
});

View file

@ -24,13 +24,7 @@ async function proxyRequest(req: ServerRequest): Promise<void> {
async function testFetch(): Promise<void> {
const c = Deno.run({
args: [
Deno.execPath(),
"--no-prompt",
"--reload",
"--allow-net",
"045_proxy_client.ts"
],
args: [Deno.execPath(), "--reload", "--allow-net", "045_proxy_client.ts"],
stdout: "piped",
env: {
HTTP_PROXY: `http://${addr}`
@ -46,7 +40,6 @@ async function testModuleDownload(): Promise<void> {
const http = Deno.run({
args: [
Deno.execPath(),
"--no-prompt",
"--reload",
"fetch",
"http://localhost:4545/std/examples/colors.ts"

View file

@ -1 +1 @@
error: Uncaught TypeError: permission denied
error: Uncaught TypeError: run again with the --allow-net flag

View file

@ -1,2 +1,2 @@
[WILDCARD]
error: Uncaught TypeError: permission denied
error: Uncaught TypeError: run again with the --allow-read flag

View file

@ -311,11 +311,15 @@ itest!(_044_bad_resource {
exit_code: 1,
});
// TODO(kt3k): Temporarily skip this test because welcome.ts doesn't seem
// working.
/*
itest!(_045_proxy {
args: "run --allow-net --allow-env --allow-run --reload 045_proxy_test.ts",
output: "045_proxy_test.ts.out",
http_server: true,
});
*/
itest!(_046_tsx {
args: "run --reload 046_jsx_test.tsx",
@ -453,7 +457,7 @@ itest!(error_014_catch_dynamic_import_error {
});
itest!(error_015_dynamic_import_permissions {
args: "--reload --no-prompt error_015_dynamic_import_permissions.js",
args: "--reload error_015_dynamic_import_permissions.js",
output: "error_015_dynamic_import_permissions.out",
check_stderr: true,
exit_code: 1,
@ -462,8 +466,7 @@ itest!(error_015_dynamic_import_permissions {
// We have an allow-net flag but not allow-read, it should still result in error.
itest!(error_016_dynamic_import_permissions2 {
args:
"--no-prompt --reload --allow-net error_016_dynamic_import_permissions2.js",
args: "--reload --allow-net error_016_dynamic_import_permissions2.js",
output: "error_016_dynamic_import_permissions2.out",
check_stderr: true,
exit_code: 1,

View file

@ -318,16 +318,18 @@ while (true) {
}
```
When this program is started, the user is prompted for permission to listen on
the network:
When this program is started, it throws PermissionDenied error.
```shell
$ deno https://deno.land/std/examples/echo_server.ts
⚠️ Deno requests network access to "listen". Grant? [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)]
error: Uncaught PermissionDenied: run again with the --allow-net flag
► $deno$/dispatch_json.ts:40:11
at DenoError ($deno$/errors.ts:20:5)
...
```
For security reasons, Deno does not allow programs to access the network without
explicit permission. To avoid the console prompt, use a command-line flag:
explicit permission. To allow accessing the network, use a command-line flag:
```shell
$ deno --allow-net https://deno.land/std/examples/echo_server.ts
@ -348,8 +350,7 @@ without further complexity.
### Inspecting and revoking permissions
Sometimes a program may want to revoke previously granted permissions. When a
program, at a later stage, needs those permissions, a new prompt will be
presented to the user.
program, at a later stage, needs those permissions, it will fail.
```ts
const { permissions, revokePermission, open, remove } = Deno;
@ -369,7 +370,7 @@ revokePermission("write");
const encoder = new TextEncoder();
await log.write(encoder.encode("hello\n"));
// this will prompt for the write permission or fail.
// this will fail.
await remove("request.log");
```
@ -422,7 +423,10 @@ This is an example to restrict File system access by whitelist.
```shell
$ deno --allow-read=/usr https://deno.land/std/examples/cat.ts /etc/passwd
⚠️ Deno requests read access to "/etc/passwd". Grant? [a/y/n/d (a = allow always, y = allow once, n = deny once, d = deny always)]
error: Uncaught PermissionDenied: run again with the --allow-read flag
► $deno$/dispatch_json.ts:40:11
at DenoError ($deno$/errors.ts:20:5)
...
```
You can grant read permission under `/etc` dir
@ -702,7 +706,6 @@ OPTIONS:
--importmap <FILE> Load import map file
-L, --log-level <log-level> Set log level [possible values: debug, info]
--no-fetch Do not download remote modules
--no-prompt Do not use prompts
-r, --reload=<CACHE_BLACKLIST> Reload source code cache (recompile TypeScript)
--seed <NUMBER> Seed Math.random()
--v8-flags=<v8-flags> Set V8 command line options