summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BUILD.gn15
-rw-r--r--Cargo.toml1
-rw-r--r--build_extra/rust/BUILD.gn14
-rw-r--r--src/isolate.rs19
-rw-r--r--src/main.rs1
-rw-r--r--src/ops.rs100
-rw-r--r--src/permissions.rs86
m---------third_party0
-rwxr-xr-xtools/permission_prompt_test.py143
-rw-r--r--tools/permission_prompt_test.ts21
-rwxr-xr-xtools/test.py7
-rw-r--r--tools/third_party.py4
12 files changed, 352 insertions, 59 deletions
diff --git a/BUILD.gn b/BUILD.gn
index cfd7227bf..68cb2c6db 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -44,24 +44,25 @@ config("deno_config") {
}
main_extern = [
+ "$rust_build:atty",
+ "$rust_build:dirs",
+ "$rust_build:futures",
+ "$rust_build:getopts",
"$rust_build:hyper",
"$rust_build:hyper_rustls",
- "$rust_build:futures",
"$rust_build:lazy_static",
"$rust_build:libc",
"$rust_build:log",
+ "$rust_build:rand",
+ "$rust_build:remove_dir_all",
"$rust_build:ring",
"$rust_build:tempfile",
- "$rust_build:rand",
"$rust_build:tokio",
- "$rust_build:tokio_io",
- "$rust_build:tokio_fs",
"$rust_build:tokio_executor",
+ "$rust_build:tokio_fs",
+ "$rust_build:tokio_io",
"$rust_build:tokio_threadpool",
"$rust_build:url",
- "$rust_build:remove_dir_all",
- "$rust_build:dirs",
- "$rust_build:getopts",
"//build_extra/flatbuffers/rust:flatbuffers",
":msg_rs",
]
diff --git a/Cargo.toml b/Cargo.toml
index 3172ba3c1..96499adbd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ name = "deno"
version = "0.0.0"
[dependencies]
+atty = "0.2.11"
dirs = "1.0.4"
flatbuffers = { path = "third_party/flatbuffers/rust/flatbuffers/" }
futures = "0.1.25"
diff --git a/build_extra/rust/BUILD.gn b/build_extra/rust/BUILD.gn
index e40a19b03..56e0aaa96 100644
--- a/build_extra/rust/BUILD.gn
+++ b/build_extra/rust/BUILD.gn
@@ -113,14 +113,15 @@ rust_crate("winapi") {
"basetsd",
"cfg",
"cfgmgr32",
+ "consoleapi",
"combaseapi",
"errhandlingapi",
"excpt",
"fileapi",
"guiddef",
"handleapi",
- "inaddr",
"in6addr",
+ "inaddr",
"knownfolders",
"ktmtypes",
"libloaderapi",
@@ -134,6 +135,7 @@ rust_crate("winapi") {
"objbase",
"objidl",
"objidlbase",
+ "processenv",
"processthreadsapi",
"profileapi",
"propidl",
@@ -152,8 +154,10 @@ rust_crate("winapi") {
"vadefs",
"vcruntime",
"winbase",
+ "wincon",
"wincred",
"windef",
+ "wingdi",
"winerror",
"winnt",
"winreg",
@@ -865,6 +869,14 @@ rust_crate("sct") {
]
}
+rust_crate("atty") {
+ source_root = "$registry_github/atty-0.2.11/src/lib.rs"
+ extern = [
+ ":libc",
+ ":winapi",
+ ]
+}
+
rust_crate("base64") {
source_root = "$registry_github/base64-0.9.2/src/lib.rs"
extern = [
diff --git a/src/isolate.rs b/src/isolate.rs
index e222d280f..6530c396f 100644
--- a/src/isolate.rs
+++ b/src/isolate.rs
@@ -6,8 +6,10 @@
use deno_dir;
use errors::DenoError;
+use errors::DenoResult;
use flags;
use libdeno;
+use permissions::DenoPermissions;
use snapshot;
use futures::Future;
@@ -56,6 +58,7 @@ pub struct Isolate {
pub struct IsolateState {
pub dir: deno_dir::DenoDir,
pub argv: Vec<String>,
+ pub permissions: Mutex<DenoPermissions>,
pub flags: flags::DenoFlags,
tx: Mutex<Option<mpsc::Sender<(i32, Buf)>>>,
pub metrics: Mutex<Metrics>,
@@ -71,6 +74,21 @@ impl IsolateState {
tx.send((req_id, buf)).expect("tx.send error");
}
+ pub fn check_write(&self, filename: &str) -> DenoResult<()> {
+ let mut perm = self.permissions.lock().unwrap();
+ perm.check_write(filename)
+ }
+
+ pub fn check_env(&self) -> DenoResult<()> {
+ let mut perm = self.permissions.lock().unwrap();
+ perm.check_env()
+ }
+
+ pub fn check_net(&self, filename: &str) -> DenoResult<()> {
+ let mut perm = self.permissions.lock().unwrap();
+ perm.check_net(filename)
+ }
+
fn metrics_op_dispatched(
&self,
bytes_sent_control: u64,
@@ -143,6 +161,7 @@ impl Isolate {
state: Arc::new(IsolateState {
dir: deno_dir::DenoDir::new(flags.reload, custom_root).unwrap(),
argv: argv_rest,
+ permissions: Mutex::new(DenoPermissions::new(&flags)),
flags,
tx: Mutex::new(Some(tx)),
metrics: Mutex::new(Metrics::default()),
diff --git a/src/main.rs b/src/main.rs
index 91f566523..d524b94ed 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -31,6 +31,7 @@ mod http_util;
mod isolate;
mod libdeno;
pub mod ops;
+mod permissions;
mod resources;
mod snapshot;
mod tokio_util;
diff --git a/src/ops.rs b/src/ops.rs
index 37cbd6826..b7a20a46e 100644
--- a/src/ops.rs
+++ b/src/ops.rs
@@ -1,6 +1,5 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
use errors;
-use errors::permission_denied;
use errors::{DenoError, DenoResult, ErrorKind};
use fs as deno_fs;
use http_util;
@@ -333,11 +332,9 @@ fn op_set_env(
let inner = base.inner_as_set_env().unwrap();
let key = inner.key().unwrap();
let value = inner.value().unwrap();
-
- if !state.flags.allow_env {
- return odd_future(permission_denied());
+ if let Err(e) = state.check_env() {
+ return odd_future(e);
}
-
std::env::set_var(key, value);
ok_future(empty_buf())
}
@@ -350,8 +347,8 @@ fn op_env(
assert_eq!(data.len(), 0);
let cmd_id = base.cmd_id();
- if !state.flags.allow_env {
- return odd_future(permission_denied());
+ if let Err(e) = state.check_env() {
+ return odd_future(e);
}
let builder = &mut FlatBufferBuilder::new();
@@ -399,8 +396,9 @@ fn op_fetch_req(
let id = inner.id();
let url = inner.url().unwrap();
- if !state.flags.allow_net {
- return odd_future(permission_denied());
+ // FIXME use domain (or use this inside check_net)
+ if let Err(e) = state.check_net(url) {
+ return odd_future(e);
}
let url = url.parse::<hyper::Uri>().unwrap();
@@ -513,8 +511,9 @@ fn op_make_temp_dir(
let inner = base.inner_as_make_temp_dir().unwrap();
let cmd_id = base.cmd_id();
- if !state.flags.allow_write {
- return odd_future(permission_denied());
+ // FIXME
+ if let Err(e) = state.check_write("make_temp") {
+ return odd_future(e);
}
let dir = inner.dir().map(PathBuf::from);
@@ -562,10 +561,9 @@ fn op_mkdir(
let mode = inner.mode();
let path = String::from(inner.path().unwrap());
- if !state.flags.allow_write {
- return odd_future(permission_denied());
+ if let Err(e) = state.check_write(&path) {
+ return odd_future(e);
}
-
blocking!(base.sync(), || {
debug!("op_mkdir {}", path);
deno_fs::mkdir(Path::new(&path), mode)?;
@@ -583,8 +581,8 @@ fn op_chmod(
let _mode = inner.mode();
let path = String::from(inner.path().unwrap());
- if !state.flags.allow_write {
- return odd_future(permission_denied());
+ if let Err(e) = state.check_write(&path) {
+ return odd_future(e);
}
blocking!(base.sync(), || {
@@ -766,11 +764,14 @@ fn op_remove(
) -> Box<Op> {
assert_eq!(data.len(), 0);
let inner = base.inner_as_remove().unwrap();
- let path = PathBuf::from(inner.path().unwrap());
+ let path_ = inner.path().unwrap();
+ let path = PathBuf::from(path_);
let recursive = inner.recursive();
- if !state.flags.allow_write {
- return odd_future(permission_denied());
+
+ if let Err(e) = state.check_write(path.to_str().unwrap()) {
+ return odd_future(e);
}
+
blocking!(base.sync(), || {
debug!("op_remove {}", path.display());
let metadata = fs::metadata(&path)?;
@@ -831,10 +832,11 @@ fn op_copy_file(
assert_eq!(data.len(), 0);
let inner = base.inner_as_copy_file().unwrap();
let from = PathBuf::from(inner.from().unwrap());
- let to = PathBuf::from(inner.to().unwrap());
+ let to_ = inner.to().unwrap();
+ let to = PathBuf::from(to_);
- if !state.flags.allow_write {
- return odd_future(permission_denied());
+ if let Err(e) = state.check_write(&to_) {
+ return odd_future(e);
}
debug!("op_copy_file {} {}", from.display(), to.display());
@@ -1015,14 +1017,13 @@ fn op_write_file(
data: &'static mut [u8],
) -> Box<Op> {
let inner = base.inner_as_write_file().unwrap();
-
- if !state.flags.allow_write {
- return odd_future(permission_denied());
- }
-
let filename = String::from(inner.filename().unwrap());
let perm = inner.perm();
+ if let Err(e) = state.check_write(&filename) {
+ return odd_future(e);
+ }
+
blocking!(base.sync(), || -> OpResult {
debug!("op_write_file {} {}", filename, data.len());
deno_fs::write_file(Path::new(&filename), data, perm)?;
@@ -1036,12 +1037,13 @@ fn op_rename(
data: &'static mut [u8],
) -> Box<Op> {
assert_eq!(data.len(), 0);
- if !state.flags.allow_write {
- return odd_future(permission_denied());
- }
let inner = base.inner_as_rename().unwrap();
let oldpath = PathBuf::from(inner.oldpath().unwrap());
- let newpath = PathBuf::from(inner.newpath().unwrap());
+ let newpath_ = inner.newpath().unwrap();
+ let newpath = PathBuf::from(newpath_);
+ if let Err(e) = state.check_write(&newpath_) {
+ return odd_future(e);
+ }
blocking!(base.sync(), || -> OpResult {
debug!("op_rename {} {}", oldpath.display(), newpath.display());
fs::rename(&oldpath, &newpath)?;
@@ -1055,8 +1057,13 @@ fn op_symlink(
data: &'static mut [u8],
) -> Box<Op> {
assert_eq!(data.len(), 0);
- if !state.flags.allow_write {
- return odd_future(permission_denied());
+ let inner = base.inner_as_symlink().unwrap();
+ let oldname = PathBuf::from(inner.oldname().unwrap());
+ let newname_ = inner.newname().unwrap();
+ let newname = PathBuf::from(newname_);
+
+ if let Err(e) = state.check_write(&newname_) {
+ return odd_future(e);
}
// TODO Use type for Windows.
if cfg!(windows) {
@@ -1065,10 +1072,6 @@ fn op_symlink(
"Not implemented".to_string(),
));
}
-
- let inner = base.inner_as_symlink().unwrap();
- let oldname = PathBuf::from(inner.oldname().unwrap());
- let newname = PathBuf::from(inner.newname().unwrap());
blocking!(base.sync(), || -> OpResult {
debug!("op_symlink {} {}", oldname.display(), newname.display());
#[cfg(any(unix))]
@@ -1118,13 +1121,14 @@ fn op_truncate(
) -> Box<Op> {
assert_eq!(data.len(), 0);
- if !state.flags.allow_write {
- return odd_future(permission_denied());
- }
-
let inner = base.inner_as_truncate().unwrap();
let filename = String::from(inner.name().unwrap());
let len = inner.len();
+
+ if let Err(e) = state.check_write(&filename) {
+ return odd_future(e);
+ }
+
blocking!(base.sync(), || {
debug!("op_truncate {} {}", filename, len);
let f = fs::OpenOptions::new().write(true).open(&filename)?;
@@ -1139,8 +1143,8 @@ fn op_listen(
data: &'static mut [u8],
) -> Box<Op> {
assert_eq!(data.len(), 0);
- if !state.flags.allow_net {
- return odd_future(permission_denied());
+ if let Err(e) = state.check_net("listen") {
+ return odd_future(e);
}
let cmd_id = base.cmd_id();
@@ -1205,10 +1209,9 @@ fn op_accept(
data: &'static mut [u8],
) -> Box<Op> {
assert_eq!(data.len(), 0);
- if !state.flags.allow_net {
- return odd_future(permission_denied());
+ if let Err(e) = state.check_net("accept") {
+ return odd_future(e);
}
-
let cmd_id = base.cmd_id();
let inner = base.inner_as_accept().unwrap();
let server_rid = inner.rid();
@@ -1232,10 +1235,9 @@ fn op_dial(
data: &'static mut [u8],
) -> Box<Op> {
assert_eq!(data.len(), 0);
- if !state.flags.allow_net {
- return odd_future(permission_denied());
+ if let Err(e) = state.check_net("dial") {
+ return odd_future(e);
}
-
let cmd_id = base.cmd_id();
let inner = base.inner_as_dial().unwrap();
let network = inner.network().unwrap();
diff --git a/src/permissions.rs b/src/permissions.rs
new file mode 100644
index 000000000..aeeb7df9a
--- /dev/null
+++ b/src/permissions.rs
@@ -0,0 +1,86 @@
+extern crate atty;
+
+use flags::DenoFlags;
+
+use errors::permission_denied;
+use errors::DenoResult;
+use std::io;
+
+#[derive(Debug, Default, PartialEq)]
+pub struct DenoPermissions {
+ pub allow_write: bool,
+ pub allow_net: bool,
+ pub allow_env: bool,
+}
+
+impl DenoPermissions {
+ pub fn new(flags: &DenoFlags) -> DenoPermissions {
+ DenoPermissions {
+ allow_write: flags.allow_write,
+ allow_env: flags.allow_env,
+ allow_net: flags.allow_net,
+ }
+ }
+
+ pub fn check_write(&mut self, filename: &str) -> DenoResult<()> {
+ if self.allow_write {
+ return Ok(());
+ };
+ // TODO get location (where access occurred)
+ let r = permission_prompt(format!(
+ "Deno requests write access to \"{}\".",
+ filename
+ ));;
+ if r.is_ok() {
+ self.allow_write = true;
+ }
+ r
+ }
+
+ pub fn check_net(&mut self, domain_name: &str) -> DenoResult<()> {
+ if self.allow_net {
+ return Ok(());
+ };
+ // TODO get location (where access occurred)
+ let r = permission_prompt(format!(
+ "Deno requests network access to \"{}\".",
+ domain_name
+ ));
+ if r.is_ok() {
+ self.allow_net = true;
+ }
+ r
+ }
+
+ pub fn check_env(&mut self) -> DenoResult<()> {
+ if self.allow_env {
+ return Ok(());
+ };
+ // TODO get location (where access occurred)
+ let r = permission_prompt(
+ "Deno requests access to environment variables.".to_string(),
+ );
+ if r.is_ok() {
+ self.allow_env = true;
+ }
+ r
+ }
+}
+
+fn permission_prompt(message: String) -> DenoResult<()> {
+ if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) {
+ return Err(permission_denied());
+ };
+ // print to stderr so that if deno is > to a file this is still displayed.
+ eprint!("{} Grant? [yN] ", message);
+ let mut input = String::new();
+ let stdin = io::stdin();
+ let _nread = stdin.read_line(&mut input)?;
+ let ch = input.chars().next().unwrap();
+ let is_yes = ch == 'y' || ch == 'Y';
+ if is_yes {
+ Ok(())
+ } else {
+ Err(permission_denied())
+ }
+}
diff --git a/third_party b/third_party
-Subproject b93f9c8bd39a2548d60167043da6b947c023a83
+Subproject 56c4acce2e8ffe979b2e7d52d2b3e6f613ed492
diff --git a/tools/permission_prompt_test.py b/tools/permission_prompt_test.py
new file mode 100755
index 000000000..2bc24d12c
--- /dev/null
+++ b/tools/permission_prompt_test.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python
+import os
+import pty
+import select
+import subprocess
+
+from util import build_path, executable_suffix
+
+PERMISSIONS_PROMPT_TEST_TS = "tools/permission_prompt_test.ts"
+
+
+# This function is copied from:
+# https://gist.github.com/hayd/4f46a68fc697ba8888a7b517a414583e
+# https://stackoverflow.com/q/52954248/1240268
+def tty_capture(cmd, bytes_input):
+ """Capture the output of cmd with bytes_input to stdin,
+ with stdin, stdout and stderr as TTYs."""
+ mo, so = pty.openpty() # provide tty to enable line-buffering
+ me, se = pty.openpty()
+ mi, si = pty.openpty()
+ fdmap = {mo: 'stdout', me: 'stderr', mi: 'stdin'}
+
+ p = subprocess.Popen(
+ cmd, bufsize=1, stdin=si, stdout=so, stderr=se, close_fds=True)
+ os.write(mi, bytes_input)
+
+ timeout = .04 # seconds
+ res = {'stdout': b'', 'stderr': b''}
+ while True:
+ ready, _, _ = select.select([mo, me], [], [], timeout)
+ if ready:
+ for fd in ready:
+ data = os.read(fd, 512)
+ if not data:
+ break
+ res[fdmap[fd]] += data
+ elif p.poll() is not None: # select timed-out
+ break # p exited
+ for fd in [si, so, se, mi, mo, me]:
+ os.close(fd) # can't do it sooner: it leads to errno.EIO error
+ p.wait()
+ return p.returncode, res['stdout'], res['stderr']
+
+
+class Prompt(object):
+ def __init__(self, deno_exe):
+ self.deno_exe = deno_exe
+
+ def run(self,
+ arg,
+ bytes_input,
+ allow_write=False,
+ allow_net=False,
+ allow_env=False):
+ "Returns (return_code, stdout, stderr)."
+ cmd = [self.deno_exe, PERMISSIONS_PROMPT_TEST_TS, arg]
+ if allow_write:
+ cmd.append("--allow-write")
+ if allow_net:
+ cmd.append("--allow-net")
+ if allow_env:
+ cmd.append("--allow-env")
+ return tty_capture(cmd, bytes_input)
+
+ def warm_up(self):
+ # ignore the ts compiling message
+ self.run('needsWrite', b'', allow_write=True)
+
+ def test_write_yes(self):
+ code, stdout, stderr = self.run('needsWrite', b'y\n')
+ assert code == 0
+ assert stdout == b''
+ assert b'Deno requests write access' in stderr
+
+ def test_write_arg(self):
+ code, stdout, stderr = self.run('needsWrite', b'', allow_write=True)
+ assert code == 0
+ assert stdout == b''
+ assert stderr == b''
+
+ def test_write_no(self):
+ code, stdout, stderr = self.run('needsWrite', b'N\n')
+ assert code == 1
+ # FIXME this error message should be in stderr
+ assert b'PermissionDenied: permission denied' in stdout
+ assert b'Deno requests write access' in stderr
+
+ def test_env_yes(self):
+ code, stdout, stderr = self.run('needsEnv', b'y\n')
+ assert code == 0
+ assert stdout == b''
+ assert b'Deno requests access to environment' in stderr
+
+ def test_env_arg(self):
+ code, stdout, stderr = self.run('needsEnv', b'', allow_env=True)
+ assert code == 0
+ assert stdout == b''
+ assert stderr == b''
+
+ def test_env_no(self):
+ code, stdout, stderr = self.run('needsEnv', b'N\n')
+ assert code == 1
+ # FIXME this error message should be in stderr
+ assert b'PermissionDenied: permission denied' in stdout
+ assert b'Deno requests access to environment' in stderr
+
+ def test_net_yes(self):
+ code, stdout, stderr = self.run('needsEnv', b'y\n')
+ assert code == 0
+ assert stdout == b''
+ assert b'Deno requests access to environment' in stderr
+
+ def test_net_arg(self):
+ code, stdout, stderr = self.run('needsNet', b'', allow_net=True)
+ assert code == 0
+ assert stdout == b''
+ assert stderr == b''
+
+ def test_net_no(self):
+ code, stdout, stderr = self.run('needsNet', b'N\n')
+ assert code == 1
+ # FIXME this error message should be in stderr
+ assert b'PermissionDenied: permission denied' in stdout
+ assert b'Deno requests network access' in stderr
+
+
+def permission_prompt_test(deno_exe):
+ p = Prompt(deno_exe)
+ p.warm_up()
+ p.test_write_yes()
+ p.test_write_arg()
+ p.test_write_no()
+ p.test_env_yes()
+ p.test_env_arg()
+ p.test_env_no()
+ p.test_net_yes()
+ p.test_net_arg()
+ p.test_net_no()
+
+
+if __name__ == "__main__":
+ deno_exe = os.path.join(build_path(), "deno" + executable_suffix)
+ permission_prompt_test(deno_exe)
diff --git a/tools/permission_prompt_test.ts b/tools/permission_prompt_test.ts
new file mode 100644
index 000000000..cf8a09805
--- /dev/null
+++ b/tools/permission_prompt_test.ts
@@ -0,0 +1,21 @@
+import { args, listen, env, exit, makeTempDirSync } from "deno";
+
+const name = args[1];
+const test = {
+ needsWrite: () => {
+ makeTempDirSync();
+ },
+ needsEnv: () => {
+ env().home;
+ },
+ needsNet: () => {
+ listen("tcp", "127.0.0.1:4540");
+ }
+}[name];
+
+if (!test) {
+ console.log("Unknown test:", name);
+ exit(1);
+}
+
+test();
diff --git a/tools/test.py b/tools/test.py
index fb0544256..e2d077245 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -60,6 +60,13 @@ def main(argv):
check_output_test(deno_exe)
+ # TODO We currently skip testing the prompt in Windows completely.
+ # Windows does not support the pty module used for testing the permission
+ # prompt.
+ if os.name != 'nt':
+ from permission_prompt_test import permission_prompt_test
+ permission_prompt_test(deno_exe)
+
rmtree(deno_dir)
deno_dir_test(deno_exe, deno_dir)
diff --git a/tools/third_party.py b/tools/third_party.py
index d6c97f13b..65ff437b7 100644
--- a/tools/third_party.py
+++ b/tools/third_party.py
@@ -125,8 +125,8 @@ def run_cargo():
# If the lockfile ends up in the git repo, it'll make cargo hang for everyone
# else who tries to run sync_third_party.
def delete_lockfile():
- lockfiles = find_exts(
- path.join(rust_crates_path, "registry/index"), '.cargo-index-lock')
+ lockfiles = find_exts([path.join(rust_crates_path, "registry/index")],
+ ['.cargo-index-lock'])
for lockfile in lockfiles:
os.remove(lockfile)