mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 16:49:18 -05:00
First pass at permissions whitelist (#2129)
This commit is contained in:
parent
ac8c6fec5b
commit
2edee3367d
9 changed files with 850 additions and 105 deletions
|
@ -291,36 +291,14 @@ impl DenoDir {
|
||||||
referrer: &str,
|
referrer: &str,
|
||||||
) -> Result<Url, url::ParseError> {
|
) -> Result<Url, url::ParseError> {
|
||||||
let specifier = self.src_file_to_url(specifier);
|
let specifier = self.src_file_to_url(specifier);
|
||||||
let mut referrer = self.src_file_to_url(referrer);
|
let referrer = self.src_file_to_url(referrer);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"resolve_module specifier {} referrer {}",
|
"resolve_module specifier {} referrer {}",
|
||||||
specifier, referrer
|
specifier, referrer
|
||||||
);
|
);
|
||||||
|
|
||||||
if referrer.starts_with('.') {
|
resolve_file_url(specifier, referrer)
|
||||||
let cwd = std::env::current_dir().unwrap();
|
|
||||||
let referrer_path = cwd.join(referrer);
|
|
||||||
referrer = referrer_path.to_str().unwrap().to_string() + "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
let j = if is_remote(&specifier)
|
|
||||||
|| (Path::new(&specifier).is_absolute() && !is_remote(&referrer))
|
|
||||||
{
|
|
||||||
parse_local_or_remote(&specifier)?
|
|
||||||
} else if referrer.ends_with('/') {
|
|
||||||
let r = Url::from_directory_path(&referrer);
|
|
||||||
// TODO(ry) Properly handle error.
|
|
||||||
if r.is_err() {
|
|
||||||
error!("Url::from_directory_path error {}", referrer);
|
|
||||||
}
|
|
||||||
let base = r.unwrap();
|
|
||||||
base.join(specifier.as_ref())?
|
|
||||||
} else {
|
|
||||||
let base = parse_local_or_remote(&referrer)?;
|
|
||||||
base.join(specifier.as_ref())?
|
|
||||||
};
|
|
||||||
Ok(j)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns (module name, local filename)
|
/// Returns (module name, local filename)
|
||||||
|
@ -889,6 +867,35 @@ fn save_source_code_headers(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resolve_file_url(
|
||||||
|
specifier: String,
|
||||||
|
mut referrer: String,
|
||||||
|
) -> Result<Url, url::ParseError> {
|
||||||
|
if referrer.starts_with('.') {
|
||||||
|
let cwd = std::env::current_dir().unwrap();
|
||||||
|
let referrer_path = cwd.join(referrer);
|
||||||
|
referrer = referrer_path.to_str().unwrap().to_string() + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
let j = if is_remote(&specifier)
|
||||||
|
|| (Path::new(&specifier).is_absolute() && !is_remote(&referrer))
|
||||||
|
{
|
||||||
|
parse_local_or_remote(&specifier)?
|
||||||
|
} else if referrer.ends_with('/') {
|
||||||
|
let r = Url::from_directory_path(&referrer);
|
||||||
|
// TODO(ry) Properly handle error.
|
||||||
|
if r.is_err() {
|
||||||
|
error!("Url::from_directory_path error {}", referrer);
|
||||||
|
}
|
||||||
|
let base = r.unwrap();
|
||||||
|
base.join(specifier.as_ref())?
|
||||||
|
} else {
|
||||||
|
let base = parse_local_or_remote(&referrer)?;
|
||||||
|
base.join(specifier.as_ref())?
|
||||||
|
};
|
||||||
|
Ok(j)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
118
cli/flags.rs
118
cli/flags.rs
|
@ -16,8 +16,11 @@ pub struct DenoFlags {
|
||||||
/// the path passed on the command line, otherwise `None`.
|
/// the path passed on the command line, otherwise `None`.
|
||||||
pub config_path: Option<String>,
|
pub config_path: Option<String>,
|
||||||
pub allow_read: bool,
|
pub allow_read: bool,
|
||||||
|
pub read_whitelist: Vec<String>,
|
||||||
pub allow_write: bool,
|
pub allow_write: bool,
|
||||||
|
pub write_whitelist: Vec<String>,
|
||||||
pub allow_net: bool,
|
pub allow_net: bool,
|
||||||
|
pub net_whitelist: Vec<String>,
|
||||||
pub allow_env: bool,
|
pub allow_env: bool,
|
||||||
pub allow_run: bool,
|
pub allow_run: bool,
|
||||||
pub allow_high_precision: bool,
|
pub allow_high_precision: bool,
|
||||||
|
@ -193,17 +196,29 @@ ability to spawn subprocesses.
|
||||||
",
|
",
|
||||||
).arg(
|
).arg(
|
||||||
Arg::with_name("allow-read")
|
Arg::with_name("allow-read")
|
||||||
.long("allow-read")
|
.long("allow-read")
|
||||||
.help("Allow file system read access"),
|
.min_values(0)
|
||||||
).arg(
|
.takes_value(true)
|
||||||
Arg::with_name("allow-write")
|
.use_delimiter(true)
|
||||||
.long("allow-write")
|
.require_equals(true)
|
||||||
.help("Allow file system write access"),
|
.help("Allow file system read access"),
|
||||||
).arg(
|
).arg(
|
||||||
Arg::with_name("allow-net")
|
Arg::with_name("allow-write")
|
||||||
.long("allow-net")
|
.long("allow-write")
|
||||||
.help("Allow network access"),
|
.min_values(0)
|
||||||
).arg(
|
.takes_value(true)
|
||||||
|
.use_delimiter(true)
|
||||||
|
.require_equals(true)
|
||||||
|
.help("Allow file system write access"),
|
||||||
|
).arg(
|
||||||
|
Arg::with_name("allow-net")
|
||||||
|
.long("allow-net")
|
||||||
|
.min_values(0)
|
||||||
|
.takes_value(true)
|
||||||
|
.use_delimiter(true)
|
||||||
|
.require_equals(true)
|
||||||
|
.help("Allow network access"),
|
||||||
|
).arg(
|
||||||
Arg::with_name("allow-env")
|
Arg::with_name("allow-env")
|
||||||
.long("allow-env")
|
.long("allow-env")
|
||||||
.help("Allow environment access"),
|
.help("Allow environment access"),
|
||||||
|
@ -301,13 +316,31 @@ pub fn parse_flags(matches: ArgMatches) -> DenoFlags {
|
||||||
// flags specific to "run" subcommand
|
// flags specific to "run" subcommand
|
||||||
if let Some(run_matches) = matches.subcommand_matches("run") {
|
if let Some(run_matches) = matches.subcommand_matches("run") {
|
||||||
if run_matches.is_present("allow-read") {
|
if run_matches.is_present("allow-read") {
|
||||||
flags.allow_read = true;
|
if run_matches.value_of("allow-read").is_some() {
|
||||||
|
let read_wl = run_matches.values_of("allow-read").unwrap();
|
||||||
|
flags.read_whitelist =
|
||||||
|
read_wl.map(std::string::ToString::to_string).collect();
|
||||||
|
} else {
|
||||||
|
flags.allow_read = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if run_matches.is_present("allow-write") {
|
if run_matches.is_present("allow-write") {
|
||||||
flags.allow_write = true;
|
if run_matches.value_of("allow-write").is_some() {
|
||||||
|
let write_wl = run_matches.values_of("allow-write").unwrap();
|
||||||
|
flags.write_whitelist =
|
||||||
|
write_wl.map(std::string::ToString::to_string).collect();
|
||||||
|
} else {
|
||||||
|
flags.allow_write = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if run_matches.is_present("allow-net") {
|
if run_matches.is_present("allow-net") {
|
||||||
flags.allow_net = true;
|
if run_matches.value_of("allow-net").is_some() {
|
||||||
|
let net_wl = run_matches.values_of("allow-net").unwrap();
|
||||||
|
flags.net_whitelist =
|
||||||
|
net_wl.map(std::string::ToString::to_string).collect();
|
||||||
|
} else {
|
||||||
|
flags.allow_net = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if run_matches.is_present("allow-env") {
|
if run_matches.is_present("allow-env") {
|
||||||
flags.allow_env = true;
|
flags.allow_env = true;
|
||||||
|
@ -779,4 +812,61 @@ mod tests {
|
||||||
assert_eq!(subcommand, DenoSubcommand::Xeval);
|
assert_eq!(subcommand, DenoSubcommand::Xeval);
|
||||||
assert_eq!(argv, svec!["deno", "console.log(val)"]);
|
assert_eq!(argv, svec!["deno", "console.log(val)"]);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_flags_from_vec_19() {
|
||||||
|
let (flags, subcommand, argv) = flags_from_vec(svec![
|
||||||
|
"deno",
|
||||||
|
"run",
|
||||||
|
"--allow-read=/some/test/dir",
|
||||||
|
"script.ts"
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
flags,
|
||||||
|
DenoFlags {
|
||||||
|
allow_read: false,
|
||||||
|
read_whitelist: svec!["/some/test/dir"],
|
||||||
|
..DenoFlags::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(subcommand, DenoSubcommand::Run);
|
||||||
|
assert_eq!(argv, svec!["deno", "script.ts"]);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_flags_from_vec_20() {
|
||||||
|
let (flags, subcommand, argv) = flags_from_vec(svec![
|
||||||
|
"deno",
|
||||||
|
"run",
|
||||||
|
"--allow-write=/some/test/dir",
|
||||||
|
"script.ts"
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
flags,
|
||||||
|
DenoFlags {
|
||||||
|
allow_write: false,
|
||||||
|
write_whitelist: svec!["/some/test/dir"],
|
||||||
|
..DenoFlags::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(subcommand, DenoSubcommand::Run);
|
||||||
|
assert_eq!(argv, svec!["deno", "script.ts"]);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_flags_from_vec_21() {
|
||||||
|
let (flags, subcommand, argv) = flags_from_vec(svec![
|
||||||
|
"deno",
|
||||||
|
"run",
|
||||||
|
"--allow-net=127.0.0.1",
|
||||||
|
"script.ts"
|
||||||
|
]);
|
||||||
|
assert_eq!(
|
||||||
|
flags,
|
||||||
|
DenoFlags {
|
||||||
|
allow_net: false,
|
||||||
|
net_whitelist: svec!["127.0.0.1"],
|
||||||
|
..DenoFlags::default()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(subcommand, DenoSubcommand::Run);
|
||||||
|
assert_eq!(argv, svec!["deno", "script.ts"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
140
cli/ops.rs
140
cli/ops.rs
|
@ -2,6 +2,7 @@
|
||||||
use atty;
|
use atty;
|
||||||
use crate::ansi;
|
use crate::ansi;
|
||||||
use crate::compiler::get_compiler_config;
|
use crate::compiler::get_compiler_config;
|
||||||
|
use crate::deno_dir;
|
||||||
use crate::dispatch_minimal::dispatch_minimal;
|
use crate::dispatch_minimal::dispatch_minimal;
|
||||||
use crate::dispatch_minimal::parse_min_record;
|
use crate::dispatch_minimal::parse_min_record;
|
||||||
use crate::errors;
|
use crate::errors;
|
||||||
|
@ -43,7 +44,6 @@ use std;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::net::Shutdown;
|
use std::net::Shutdown;
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::time::{Duration, Instant, UNIX_EPOCH};
|
use std::time::{Duration, Instant, UNIX_EPOCH};
|
||||||
|
@ -241,6 +241,14 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option<OpCreator> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_path(path: &str) -> Result<(PathBuf, String), DenoError> {
|
||||||
|
let url = deno_dir::resolve_file_url(path.to_string(), ".".to_string())
|
||||||
|
.map_err(DenoError::from)?;
|
||||||
|
let path = url.to_file_path().unwrap();
|
||||||
|
let path_string = path.to_str().unwrap().to_string();
|
||||||
|
Ok((path, path_string))
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a milliseconds and nanoseconds subsec
|
// Returns a milliseconds and nanoseconds subsec
|
||||||
// since the start time of the deno runtime.
|
// since the start time of the deno runtime.
|
||||||
// If the High precision flag is not set, the
|
// If the High precision flag is not set, the
|
||||||
|
@ -697,7 +705,11 @@ fn op_fetch(
|
||||||
}
|
}
|
||||||
let req = maybe_req.unwrap();
|
let req = maybe_req.unwrap();
|
||||||
|
|
||||||
if let Err(e) = state.check_net(url) {
|
let url_ = match url::Url::parse(url) {
|
||||||
|
Err(err) => return odd_future(DenoError::from(err)),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
if let Err(e) = state.check_net_url(url_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -816,17 +828,20 @@ fn op_mkdir(
|
||||||
) -> Box<OpWithError> {
|
) -> Box<OpWithError> {
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_mkdir().unwrap();
|
let inner = base.inner_as_mkdir().unwrap();
|
||||||
let path = String::from(inner.path().unwrap());
|
let (path, path_) = match resolve_path(inner.path().unwrap()) {
|
||||||
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
let recursive = inner.recursive();
|
let recursive = inner.recursive();
|
||||||
let mode = inner.mode();
|
let mode = inner.mode();
|
||||||
|
|
||||||
if let Err(e) = state.check_write(&path) {
|
if let Err(e) = state.check_write(&path_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
blocking(base.sync(), move || {
|
blocking(base.sync(), move || {
|
||||||
debug!("op_mkdir {}", path);
|
debug!("op_mkdir {}", path_);
|
||||||
deno_fs::mkdir(Path::new(&path), mode, recursive)?;
|
deno_fs::mkdir(&path, mode, recursive)?;
|
||||||
Ok(empty_buf())
|
Ok(empty_buf())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -839,15 +854,17 @@ fn op_chmod(
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_chmod().unwrap();
|
let inner = base.inner_as_chmod().unwrap();
|
||||||
let _mode = inner.mode();
|
let _mode = inner.mode();
|
||||||
let path = String::from(inner.path().unwrap());
|
let (path, path_) = match resolve_path(inner.path().unwrap()) {
|
||||||
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = state.check_write(&path) {
|
if let Err(e) = state.check_write(&path_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
blocking(base.sync(), move || {
|
blocking(base.sync(), move || {
|
||||||
debug!("op_chmod {}", &path);
|
debug!("op_chmod {}", &path_);
|
||||||
let path = PathBuf::from(&path);
|
|
||||||
// Still check file/dir exists on windows
|
// Still check file/dir exists on windows
|
||||||
let _metadata = fs::metadata(&path)?;
|
let _metadata = fs::metadata(&path)?;
|
||||||
// Only work in unix
|
// Only work in unix
|
||||||
|
@ -902,8 +919,10 @@ fn op_open(
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let cmd_id = base.cmd_id();
|
let cmd_id = base.cmd_id();
|
||||||
let inner = base.inner_as_open().unwrap();
|
let inner = base.inner_as_open().unwrap();
|
||||||
let filename_str = inner.filename().unwrap();
|
let (filename, filename_) = match resolve_path(inner.filename().unwrap()) {
|
||||||
let filename = PathBuf::from(&filename_str);
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
let mode = inner.mode().unwrap();
|
let mode = inner.mode().unwrap();
|
||||||
|
|
||||||
let mut open_options = tokio::fs::OpenOptions::new();
|
let mut open_options = tokio::fs::OpenOptions::new();
|
||||||
|
@ -944,20 +963,20 @@ fn op_open(
|
||||||
|
|
||||||
match mode {
|
match mode {
|
||||||
"r" => {
|
"r" => {
|
||||||
if let Err(e) = state.check_read(&filename_str) {
|
if let Err(e) = state.check_read(&filename_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"w" | "a" | "x" => {
|
"w" | "a" | "x" => {
|
||||||
if let Err(e) = state.check_write(&filename_str) {
|
if let Err(e) = state.check_write(&filename_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&_ => {
|
&_ => {
|
||||||
if let Err(e) = state.check_read(&filename_str) {
|
if let Err(e) = state.check_read(&filename_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
if let Err(e) = state.check_write(&filename_str) {
|
if let Err(e) = state.check_write(&filename_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1146,11 +1165,13 @@ fn op_remove(
|
||||||
) -> Box<OpWithError> {
|
) -> Box<OpWithError> {
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_remove().unwrap();
|
let inner = base.inner_as_remove().unwrap();
|
||||||
let path_ = inner.path().unwrap();
|
let (path, path_) = match resolve_path(inner.path().unwrap()) {
|
||||||
let path = PathBuf::from(path_);
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
let recursive = inner.recursive();
|
let recursive = inner.recursive();
|
||||||
|
|
||||||
if let Err(e) = state.check_write(path.to_str().unwrap()) {
|
if let Err(e) = state.check_write(&path_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1175,10 +1196,14 @@ fn op_copy_file(
|
||||||
) -> Box<OpWithError> {
|
) -> Box<OpWithError> {
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_copy_file().unwrap();
|
let inner = base.inner_as_copy_file().unwrap();
|
||||||
let from_ = inner.from().unwrap();
|
let (from, from_) = match resolve_path(inner.from().unwrap()) {
|
||||||
let from = PathBuf::from(from_);
|
Err(err) => return odd_future(err),
|
||||||
let to_ = inner.to().unwrap();
|
Ok(v) => v,
|
||||||
let to = PathBuf::from(to_);
|
};
|
||||||
|
let (to, to_) = match resolve_path(inner.to().unwrap()) {
|
||||||
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = state.check_read(&from_) {
|
if let Err(e) = state.check_read(&from_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
|
@ -1258,8 +1283,10 @@ fn op_stat(
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_stat().unwrap();
|
let inner = base.inner_as_stat().unwrap();
|
||||||
let cmd_id = base.cmd_id();
|
let cmd_id = base.cmd_id();
|
||||||
let filename_ = inner.filename().unwrap();
|
let (filename, filename_) = match resolve_path(inner.filename().unwrap()) {
|
||||||
let filename = PathBuf::from(filename_);
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
let lstat = inner.lstat();
|
let lstat = inner.lstat();
|
||||||
|
|
||||||
if let Err(e) = state.check_read(&filename_) {
|
if let Err(e) = state.check_read(&filename_) {
|
||||||
|
@ -1275,6 +1302,8 @@ fn op_stat(
|
||||||
fs::metadata(&filename)?
|
fs::metadata(&filename)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let filename_str = builder.create_string(&filename_);
|
||||||
|
|
||||||
let inner = msg::StatRes::create(
|
let inner = msg::StatRes::create(
|
||||||
builder,
|
builder,
|
||||||
&msg::StatResArgs {
|
&msg::StatResArgs {
|
||||||
|
@ -1286,6 +1315,7 @@ fn op_stat(
|
||||||
created: to_seconds!(metadata.created()),
|
created: to_seconds!(metadata.created()),
|
||||||
mode: get_mode(&metadata.permissions()),
|
mode: get_mode(&metadata.permissions()),
|
||||||
has_mode: cfg!(target_family = "unix"),
|
has_mode: cfg!(target_family = "unix"),
|
||||||
|
path: Some(filename_str),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1310,16 +1340,19 @@ fn op_read_dir(
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_read_dir().unwrap();
|
let inner = base.inner_as_read_dir().unwrap();
|
||||||
let cmd_id = base.cmd_id();
|
let cmd_id = base.cmd_id();
|
||||||
let path = String::from(inner.path().unwrap());
|
let (path, path_) = match resolve_path(inner.path().unwrap()) {
|
||||||
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = state.check_read(&path) {
|
if let Err(e) = state.check_read(&path_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
blocking(base.sync(), move || -> OpResult {
|
blocking(base.sync(), move || -> OpResult {
|
||||||
debug!("op_read_dir {}", path);
|
debug!("op_read_dir {}", path.display());
|
||||||
let builder = &mut FlatBufferBuilder::new();
|
let builder = &mut FlatBufferBuilder::new();
|
||||||
let entries: Vec<_> = fs::read_dir(Path::new(&path))?
|
let entries: Vec<_> = fs::read_dir(path)?
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
let metadata = entry.metadata().unwrap();
|
let metadata = entry.metadata().unwrap();
|
||||||
|
@ -1370,9 +1403,15 @@ fn op_rename(
|
||||||
) -> Box<OpWithError> {
|
) -> Box<OpWithError> {
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_rename().unwrap();
|
let inner = base.inner_as_rename().unwrap();
|
||||||
let oldpath = PathBuf::from(inner.oldpath().unwrap());
|
let (oldpath, _) = match resolve_path(inner.oldpath().unwrap()) {
|
||||||
let newpath_ = inner.newpath().unwrap();
|
Err(err) => return odd_future(err),
|
||||||
let newpath = PathBuf::from(newpath_);
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
let (newpath, newpath_) = match resolve_path(inner.newpath().unwrap()) {
|
||||||
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = state.check_write(&newpath_) {
|
if let Err(e) = state.check_write(&newpath_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
|
@ -1390,9 +1429,14 @@ fn op_link(
|
||||||
) -> Box<OpWithError> {
|
) -> Box<OpWithError> {
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_link().unwrap();
|
let inner = base.inner_as_link().unwrap();
|
||||||
let oldname = PathBuf::from(inner.oldname().unwrap());
|
let (oldname, _) = match resolve_path(inner.oldname().unwrap()) {
|
||||||
let newname_ = inner.newname().unwrap();
|
Err(err) => return odd_future(err),
|
||||||
let newname = PathBuf::from(newname_);
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
let (newname, newname_) = match resolve_path(inner.newname().unwrap()) {
|
||||||
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = state.check_write(&newname_) {
|
if let Err(e) = state.check_write(&newname_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
|
@ -1412,9 +1456,14 @@ fn op_symlink(
|
||||||
) -> Box<OpWithError> {
|
) -> Box<OpWithError> {
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_symlink().unwrap();
|
let inner = base.inner_as_symlink().unwrap();
|
||||||
let oldname = PathBuf::from(inner.oldname().unwrap());
|
let (oldname, _) = match resolve_path(inner.oldname().unwrap()) {
|
||||||
let newname_ = inner.newname().unwrap();
|
Err(err) => return odd_future(err),
|
||||||
let newname = PathBuf::from(newname_);
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
let (newname, newname_) = match resolve_path(inner.newname().unwrap()) {
|
||||||
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = state.check_write(&newname_) {
|
if let Err(e) = state.check_write(&newname_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
|
@ -1442,8 +1491,10 @@ fn op_read_link(
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
let inner = base.inner_as_readlink().unwrap();
|
let inner = base.inner_as_readlink().unwrap();
|
||||||
let cmd_id = base.cmd_id();
|
let cmd_id = base.cmd_id();
|
||||||
let name_ = inner.name().unwrap();
|
let (name, name_) = match resolve_path(inner.name().unwrap()) {
|
||||||
let name = PathBuf::from(name_);
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = state.check_read(&name_) {
|
if let Err(e) = state.check_read(&name_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
|
@ -1547,15 +1598,18 @@ fn op_truncate(
|
||||||
assert!(data.is_none());
|
assert!(data.is_none());
|
||||||
|
|
||||||
let inner = base.inner_as_truncate().unwrap();
|
let inner = base.inner_as_truncate().unwrap();
|
||||||
let filename = String::from(inner.name().unwrap());
|
let (filename, filename_) = match resolve_path(inner.name().unwrap()) {
|
||||||
|
Err(err) => return odd_future(err),
|
||||||
|
Ok(v) => v,
|
||||||
|
};
|
||||||
let len = inner.len();
|
let len = inner.len();
|
||||||
|
|
||||||
if let Err(e) = state.check_write(&filename) {
|
if let Err(e) = state.check_write(&filename_) {
|
||||||
return odd_future(e);
|
return odd_future(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
blocking(base.sync(), move || {
|
blocking(base.sync(), move || {
|
||||||
debug!("op_truncate {} {}", filename, len);
|
debug!("op_truncate {} {}", filename_, len);
|
||||||
let f = fs::OpenOptions::new().write(true).open(&filename)?;
|
let f = fs::OpenOptions::new().write(true).open(&filename)?;
|
||||||
f.set_len(u64::from(len))?;
|
f.set_len(u64::from(len))?;
|
||||||
Ok(empty_buf())
|
Ok(empty_buf())
|
||||||
|
|
|
@ -6,8 +6,10 @@ use crate::flags::DenoFlags;
|
||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
use crate::errors::permission_denied;
|
use crate::errors::permission_denied;
|
||||||
use crate::errors::DenoResult;
|
use crate::errors::DenoResult;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -127,8 +129,11 @@ impl Default for PermissionAccessor {
|
||||||
pub struct DenoPermissions {
|
pub struct DenoPermissions {
|
||||||
// Keep in sync with src/permissions.ts
|
// Keep in sync with src/permissions.ts
|
||||||
pub allow_read: PermissionAccessor,
|
pub allow_read: PermissionAccessor,
|
||||||
|
pub read_whitelist: Arc<HashSet<String>>,
|
||||||
pub allow_write: PermissionAccessor,
|
pub allow_write: PermissionAccessor,
|
||||||
|
pub write_whitelist: Arc<HashSet<String>>,
|
||||||
pub allow_net: PermissionAccessor,
|
pub allow_net: PermissionAccessor,
|
||||||
|
pub net_whitelist: Arc<HashSet<String>>,
|
||||||
pub allow_env: PermissionAccessor,
|
pub allow_env: PermissionAccessor,
|
||||||
pub allow_run: PermissionAccessor,
|
pub allow_run: PermissionAccessor,
|
||||||
pub allow_high_precision: PermissionAccessor,
|
pub allow_high_precision: PermissionAccessor,
|
||||||
|
@ -139,9 +144,14 @@ impl DenoPermissions {
|
||||||
pub fn from_flags(flags: &DenoFlags) -> Self {
|
pub fn from_flags(flags: &DenoFlags) -> Self {
|
||||||
Self {
|
Self {
|
||||||
allow_read: PermissionAccessor::from(flags.allow_read),
|
allow_read: PermissionAccessor::from(flags.allow_read),
|
||||||
|
read_whitelist: Arc::new(flags.read_whitelist.iter().cloned().collect()),
|
||||||
allow_write: PermissionAccessor::from(flags.allow_write),
|
allow_write: PermissionAccessor::from(flags.allow_write),
|
||||||
allow_env: PermissionAccessor::from(flags.allow_env),
|
write_whitelist: Arc::new(
|
||||||
|
flags.write_whitelist.iter().cloned().collect(),
|
||||||
|
),
|
||||||
allow_net: PermissionAccessor::from(flags.allow_net),
|
allow_net: PermissionAccessor::from(flags.allow_net),
|
||||||
|
net_whitelist: Arc::new(flags.net_whitelist.iter().cloned().collect()),
|
||||||
|
allow_env: PermissionAccessor::from(flags.allow_env),
|
||||||
allow_run: PermissionAccessor::from(flags.allow_run),
|
allow_run: PermissionAccessor::from(flags.allow_run),
|
||||||
allow_high_precision: PermissionAccessor::from(
|
allow_high_precision: PermissionAccessor::from(
|
||||||
flags.allow_high_precision,
|
flags.allow_high_precision,
|
||||||
|
@ -170,42 +180,115 @@ impl DenoPermissions {
|
||||||
pub fn check_read(&self, filename: &str) -> DenoResult<()> {
|
pub fn check_read(&self, filename: &str) -> DenoResult<()> {
|
||||||
match self.allow_read.get_state() {
|
match self.allow_read.get_state() {
|
||||||
PermissionAccessorState::Allow => Ok(()),
|
PermissionAccessorState::Allow => Ok(()),
|
||||||
PermissionAccessorState::Ask => match self
|
state => {
|
||||||
.try_permissions_prompt(&format!("read access to \"{}\"", filename))
|
if check_path_white_list(filename, &self.read_whitelist) {
|
||||||
{
|
|
||||||
Err(e) => Err(e),
|
|
||||||
Ok(v) => {
|
|
||||||
self.allow_read.update_with_prompt_result(&v);
|
|
||||||
v.check()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
match state {
|
||||||
|
PermissionAccessorState::Ask => match self.try_permissions_prompt(
|
||||||
|
&format!("read access to \"{}\"", filename),
|
||||||
|
) {
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok(v) => {
|
||||||
|
self.allow_read.update_with_prompt_result(&v);
|
||||||
|
v.check()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PermissionAccessorState::Deny => Err(permission_denied()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
PermissionAccessorState::Deny => Err(permission_denied()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_write(&self, filename: &str) -> DenoResult<()> {
|
pub fn check_write(&self, filename: &str) -> DenoResult<()> {
|
||||||
match self.allow_write.get_state() {
|
match self.allow_write.get_state() {
|
||||||
PermissionAccessorState::Allow => Ok(()),
|
PermissionAccessorState::Allow => Ok(()),
|
||||||
PermissionAccessorState::Ask => match self
|
state => {
|
||||||
.try_permissions_prompt(&format!("write access to \"{}\"", filename))
|
if check_path_white_list(filename, &self.write_whitelist) {
|
||||||
{
|
|
||||||
Err(e) => Err(e),
|
|
||||||
Ok(v) => {
|
|
||||||
self.allow_write.update_with_prompt_result(&v);
|
|
||||||
v.check()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
} else {
|
||||||
|
match state {
|
||||||
|
PermissionAccessorState::Ask => match self.try_permissions_prompt(
|
||||||
|
&format!("write access to \"{}\"", filename),
|
||||||
|
) {
|
||||||
|
Err(e) => Err(e),
|
||||||
|
Ok(v) => {
|
||||||
|
self.allow_write.update_with_prompt_result(&v);
|
||||||
|
v.check()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PermissionAccessorState::Deny => Err(permission_denied()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
PermissionAccessorState::Deny => Err(permission_denied()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_net(&self, domain_name: &str) -> DenoResult<()> {
|
pub fn check_net(&self, host_and_port: &str) -> DenoResult<()> {
|
||||||
match self.allow_net.get_state() {
|
match self.allow_net.get_state() {
|
||||||
PermissionAccessorState::Allow => Ok(()),
|
PermissionAccessorState::Allow => Ok(()),
|
||||||
|
state => {
|
||||||
|
let parts = host_and_port.split(':').collect::<Vec<&str>>();
|
||||||
|
if match parts.len() {
|
||||||
|
2 => {
|
||||||
|
if self.net_whitelist.contains(parts[0]) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
.net_whitelist
|
||||||
|
.contains(&format!("{}:{}", parts[0], parts[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => self.net_whitelist.contains(parts[0]),
|
||||||
|
_ => panic!("Failed to parse origin string: {}", host_and_port),
|
||||||
|
} {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
self.check_net_inner(state, host_and_port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_net_url(&self, url: url::Url) -> DenoResult<()> {
|
||||||
|
match self.allow_net.get_state() {
|
||||||
|
PermissionAccessorState::Allow => Ok(()),
|
||||||
|
state => {
|
||||||
|
let host = url.host().unwrap();
|
||||||
|
let whitelist_result = {
|
||||||
|
if self.net_whitelist.contains(&format!("{}", host)) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
match url.port() {
|
||||||
|
Some(port) => {
|
||||||
|
self.net_whitelist.contains(&format!("{}:{}", host, port))
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if whitelist_result {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
self.check_net_inner(state, &url.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_net_inner(
|
||||||
|
&self,
|
||||||
|
state: PermissionAccessorState,
|
||||||
|
prompt_str: &str,
|
||||||
|
) -> DenoResult<()> {
|
||||||
|
match state {
|
||||||
PermissionAccessorState::Ask => match self.try_permissions_prompt(
|
PermissionAccessorState::Ask => match self.try_permissions_prompt(
|
||||||
&format!("network access to \"{}\"", domain_name),
|
&format!("network access to \"{}\"", prompt_str),
|
||||||
) {
|
) {
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
|
@ -215,6 +298,7 @@ impl DenoPermissions {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PermissionAccessorState::Deny => Err(permission_denied()),
|
PermissionAccessorState::Deny => Err(permission_denied()),
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,3 +438,281 @@ fn permission_prompt(message: &str) -> DenoResult<PromptResult> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_path_white_list(
|
||||||
|
filename: &str,
|
||||||
|
white_list: &Arc<HashSet<String>>,
|
||||||
|
) -> bool {
|
||||||
|
let mut path_buf = PathBuf::from(filename);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if white_list.contains(path_buf.to_str().unwrap()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if !path_buf.pop() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#![allow(clippy::cyclomatic_complexity)]
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Creates vector of strings, Vec<String>
|
||||||
|
macro_rules! svec {
|
||||||
|
($($x:expr),*) => (vec![$($x.to_string()),*]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_paths() {
|
||||||
|
let whitelist = svec!["/a/specific/dir/name", "/a/specific", "/b/c"];
|
||||||
|
|
||||||
|
let perms = DenoPermissions::from_flags(&DenoFlags {
|
||||||
|
read_whitelist: whitelist.clone(),
|
||||||
|
write_whitelist: whitelist.clone(),
|
||||||
|
no_prompts: true,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inside of /a/specific and /a/specific/dir/name
|
||||||
|
assert!(perms.check_read("/a/specific/dir/name").is_ok());
|
||||||
|
assert!(perms.check_write("/a/specific/dir/name").is_ok());
|
||||||
|
|
||||||
|
// Inside of /a/specific but outside of /a/specific/dir/name
|
||||||
|
assert!(perms.check_read("/a/specific/dir").is_ok());
|
||||||
|
assert!(perms.check_write("/a/specific/dir").is_ok());
|
||||||
|
|
||||||
|
// Inside of /a/specific and /a/specific/dir/name
|
||||||
|
assert!(perms.check_read("/a/specific/dir/name/inner").is_ok());
|
||||||
|
assert!(perms.check_write("/a/specific/dir/name/inner").is_ok());
|
||||||
|
|
||||||
|
// Inside of /a/specific but outside of /a/specific/dir/name
|
||||||
|
assert!(perms.check_read("/a/specific/other/dir").is_ok());
|
||||||
|
assert!(perms.check_write("/a/specific/other/dir").is_ok());
|
||||||
|
|
||||||
|
// Exact match with /b/c
|
||||||
|
assert!(perms.check_read("/b/c").is_ok());
|
||||||
|
assert!(perms.check_write("/b/c").is_ok());
|
||||||
|
|
||||||
|
// Sub path within /b/c
|
||||||
|
assert!(perms.check_read("/b/c/sub/path").is_ok());
|
||||||
|
assert!(perms.check_write("/b/c/sub/path").is_ok());
|
||||||
|
|
||||||
|
// Inside of /b but outside of /b/c
|
||||||
|
assert!(perms.check_read("/b/e").is_err());
|
||||||
|
assert!(perms.check_write("/b/e").is_err());
|
||||||
|
|
||||||
|
// Inside of /a but outside of /a/specific
|
||||||
|
assert!(perms.check_read("/a/b").is_err());
|
||||||
|
assert!(perms.check_write("/a/b").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_net() {
|
||||||
|
let perms = DenoPermissions::from_flags(&DenoFlags {
|
||||||
|
net_whitelist: svec![
|
||||||
|
"localhost",
|
||||||
|
"deno.land",
|
||||||
|
"github.com:3000",
|
||||||
|
"127.0.0.1",
|
||||||
|
"172.16.0.2:8000"
|
||||||
|
],
|
||||||
|
no_prompts: true,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Any protocol + port for localhost should be ok, since we don't specify
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("http://localhost").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("http://localhost:8080").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://localhost").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://localhost:4443").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://localhost:5000").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("udp://localhost:6000").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("localhost:1234").is_ok());
|
||||||
|
|
||||||
|
// Correct domain + any port and protocol should be ok incorrect shouldn't
|
||||||
|
assert!(perms.check_net("deno.land").is_ok());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(
|
||||||
|
url::Url::parse("https://deno.land/std/example/welcome.ts").unwrap()
|
||||||
|
).is_ok()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("deno.land:3000").is_ok());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(
|
||||||
|
url::Url::parse("https://deno.land:3000/std/example/welcome.ts")
|
||||||
|
.unwrap()
|
||||||
|
).is_ok()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("deno.lands").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(
|
||||||
|
url::Url::parse("https://deno.lands/std/example/welcome.ts").unwrap()
|
||||||
|
).is_err()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("deno.lands:3000").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(
|
||||||
|
url::Url::parse("https://deno.lands:3000/std/example/welcome.ts")
|
||||||
|
.unwrap()
|
||||||
|
).is_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Correct domain + port should be ok all other combinations should err
|
||||||
|
assert!(perms.check_net("github.com:3000").is_ok());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(
|
||||||
|
url::Url::parse("https://github.com:3000/denoland/deno").unwrap()
|
||||||
|
).is_ok()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("github.com").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(
|
||||||
|
url::Url::parse("https://github.com/denoland/deno").unwrap()
|
||||||
|
).is_err()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("github.com:2000").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(
|
||||||
|
url::Url::parse("https://github.com:2000/denoland/deno").unwrap()
|
||||||
|
).is_err()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("github.net:3000").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(
|
||||||
|
url::Url::parse("https://github.net:3000/denoland/deno").unwrap()
|
||||||
|
).is_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Correct ipv4 address + any port should be ok others should err
|
||||||
|
assert!(perms.check_net("127.0.0.1").is_ok());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://127.0.0.1").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://127.0.0.1").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("127.0.0.1:3000").is_ok());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://127.0.0.1:3000").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://127.0.0.1:3000").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("127.0.0.2").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://127.0.0.2").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://127.0.0.2").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("127.0.0.2:3000").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://127.0.0.2:3000").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://127.0.0.2:3000").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Correct address + port should be ok all other combinations should err
|
||||||
|
assert!(perms.check_net("172.16.0.2:8000").is_ok());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://172.16.0.2:8000").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://172.16.0.2:8000").unwrap())
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("172.16.0.2").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://172.16.0.2").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://172.16.0.2").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("172.16.0.2:6000").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://172.16.0.2:6000").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://172.16.0.2:6000").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(perms.check_net("172.16.0.1:8000").is_err());
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("tcp://172.16.0.1:8000").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
perms
|
||||||
|
.check_net_url(url::Url::parse("https://172.16.0.1:8000").unwrap())
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Just some random hosts that should err
|
||||||
|
assert!(perms.check_net("somedomain").is_err());
|
||||||
|
assert!(perms.check_net("192.168.0.1").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -189,8 +189,13 @@ impl ThreadSafeState {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn check_net(&self, filename: &str) -> DenoResult<()> {
|
pub fn check_net(&self, host_and_port: &str) -> DenoResult<()> {
|
||||||
self.permissions.check_net(filename)
|
self.permissions.check_net(host_and_port)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn check_net_url(&self, url: url::Url) -> DenoResult<()> {
|
||||||
|
self.permissions.check_net_url(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { testPerm, assert, assertEquals } from "./test_util.ts";
|
||||||
|
|
||||||
type FileInfo = Deno.FileInfo;
|
type FileInfo = Deno.FileInfo;
|
||||||
|
|
||||||
|
const isWin = Deno.build.os === "win";
|
||||||
|
|
||||||
function assertSameContent(files: FileInfo[]): void {
|
function assertSameContent(files: FileInfo[]): void {
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
|
|
||||||
|
@ -13,7 +15,11 @@ function assertSameContent(files: FileInfo[]): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.name === "002_hello.ts") {
|
if (file.name === "002_hello.ts") {
|
||||||
assertEquals(file.path, `tests/${file.name}`);
|
if (isWin) {
|
||||||
|
assert(file.path.endsWith(`tests\\${file.name}`));
|
||||||
|
} else {
|
||||||
|
assert(file.path.endsWith(`tests/${file.name}`));
|
||||||
|
}
|
||||||
assertEquals(file.mode!, Deno.statSync(`tests/${file.name}`).mode!);
|
assertEquals(file.mode!, Deno.statSync(`tests/${file.name}`).mode!);
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
195
tools/complex_permissions_test.py
Executable file
195
tools/complex_permissions_test.py
Executable file
|
@ -0,0 +1,195 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import os
|
||||||
|
import pty
|
||||||
|
import select
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
from util import build_path, root_path, executable_suffix, green_ok, red_failed
|
||||||
|
|
||||||
|
PERMISSIONS_PROMPT_TEST_TS = "tools/complex_permissions_test.ts"
|
||||||
|
|
||||||
|
PROMPT_PATTERN = b'⚠️'
|
||||||
|
PERMISSION_DENIED_PATTERN = b'PermissionDenied: permission denied'
|
||||||
|
|
||||||
|
|
||||||
|
# This function is copied from:
|
||||||
|
# https://gist.github.com/hayd/4f46a68fc697ba8888a7b517a414583e
|
||||||
|
# https://stackoverflow.com/q/52954248/1240268
|
||||||
|
def tty_capture(cmd, bytes_input, timeout=5):
|
||||||
|
"""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'}
|
||||||
|
|
||||||
|
timeout_exact = time.time() + timeout
|
||||||
|
p = subprocess.Popen(
|
||||||
|
cmd, bufsize=1, stdin=si, stdout=so, stderr=se, close_fds=True)
|
||||||
|
os.write(mi, bytes_input)
|
||||||
|
|
||||||
|
select_timeout = .04 #seconds
|
||||||
|
res = {'stdout': b'', 'stderr': b''}
|
||||||
|
while True:
|
||||||
|
ready, _, _ = select.select([mo, me], [], [], select_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 or time.time(
|
||||||
|
) > timeout_exact: # 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']
|
||||||
|
|
||||||
|
|
||||||
|
# Wraps a test in debug printouts
|
||||||
|
# so we have visual indicator of what test failed
|
||||||
|
def wrap_test(test_name, test_method, *argv):
|
||||||
|
sys.stdout.write(test_name + " ... ")
|
||||||
|
try:
|
||||||
|
test_method(*argv)
|
||||||
|
print green_ok()
|
||||||
|
except AssertionError:
|
||||||
|
print red_failed()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class Prompt(object):
|
||||||
|
def __init__(self, deno_exe, test_types):
|
||||||
|
self.deno_exe = deno_exe
|
||||||
|
self.test_types = test_types
|
||||||
|
|
||||||
|
def run(self, flags, args, bytes_input):
|
||||||
|
"Returns (return_code, stdout, stderr)."
|
||||||
|
cmd = [self.deno_exe, "run"] + flags + [PERMISSIONS_PROMPT_TEST_TS
|
||||||
|
] + args
|
||||||
|
print " ".join(cmd)
|
||||||
|
return tty_capture(cmd, bytes_input)
|
||||||
|
|
||||||
|
def warm_up(self):
|
||||||
|
# ignore the ts compiling message
|
||||||
|
self.run(["--allow-read"], ["read", "package.json"], b'')
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
for test_type in ["read", "write"]:
|
||||||
|
test_name_base = "test_" + test_type
|
||||||
|
wrap_test(test_name_base + "_inside_project_dir",
|
||||||
|
self.test_inside_project_dir, test_type)
|
||||||
|
wrap_test(test_name_base + "_outside_tests_dir",
|
||||||
|
self.test_outside_test_dir, test_type)
|
||||||
|
wrap_test(test_name_base + "_inside_tests_dir",
|
||||||
|
self.test_inside_test_dir, test_type)
|
||||||
|
wrap_test(test_name_base + "_outside_tests_and_js_dir",
|
||||||
|
self.test_outside_test_and_js_dir, test_type)
|
||||||
|
wrap_test(test_name_base + "_inside_tests_and_js_dir",
|
||||||
|
self.test_inside_test_and_js_dir, test_type)
|
||||||
|
wrap_test(test_name_base + "_allow_localhost_4545",
|
||||||
|
self.test_allow_localhost_4545)
|
||||||
|
wrap_test(test_name_base + "_allow_deno_land",
|
||||||
|
self.test_allow_deno_land)
|
||||||
|
wrap_test(test_name_base + "_allow_localhost_4545_fail",
|
||||||
|
self.test_allow_localhost_4545_fail)
|
||||||
|
wrap_test(test_name_base + "_allow_localhost",
|
||||||
|
self.test_allow_localhost)
|
||||||
|
|
||||||
|
def test_inside_project_dir(self, test_type):
|
||||||
|
code, _stdout, stderr = self.run(
|
||||||
|
["--no-prompt", "--allow-" + test_type + "=" + root_path],
|
||||||
|
[test_type, "package.json", "tests/subdir/config.json"], b'')
|
||||||
|
assert code == 0
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert not PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
def test_outside_test_dir(self, test_type):
|
||||||
|
code, _stdout, stderr = self.run([
|
||||||
|
"--no-prompt",
|
||||||
|
"--allow-" + test_type + "=" + os.path.join(root_path, "tests")
|
||||||
|
], [test_type, "package.json"], b'')
|
||||||
|
assert code == 1
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
def test_inside_test_dir(self, test_type):
|
||||||
|
code, _stdout, stderr = self.run([
|
||||||
|
"--no-prompt",
|
||||||
|
"--allow-" + test_type + "=" + os.path.join(root_path, "tests")
|
||||||
|
], [test_type, "tests/subdir/config.json"], b'')
|
||||||
|
assert code == 0
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert not PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
def test_outside_test_and_js_dir(self, test_type):
|
||||||
|
code, _stdout, stderr = self.run([
|
||||||
|
"--no-prompt", "--allow-" + test_type + "=" + os.path.join(
|
||||||
|
root_path, "tests") + "," + os.path.join(root_path, "js")
|
||||||
|
], [test_type, "package.json"], b'')
|
||||||
|
assert code == 1
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
def test_inside_test_and_js_dir(self, test_type):
|
||||||
|
code, _stdout, stderr = self.run([
|
||||||
|
"--no-prompt", "--allow-" + test_type + "=" + os.path.join(
|
||||||
|
root_path, "tests") + "," + os.path.join(root_path, "js")
|
||||||
|
], [test_type, "js/dir_test.ts", "tests/subdir/config.json"], b'')
|
||||||
|
assert code == 0
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert not PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
def test_allow_localhost_4545(self):
|
||||||
|
code, _stdout, stderr = self.run(
|
||||||
|
["--no-prompt", "--allow-net=localhost:4545"],
|
||||||
|
["net", "http://localhost:4545"], b'')
|
||||||
|
assert code == 0
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert not PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
def test_allow_deno_land(self):
|
||||||
|
code, _stdout, stderr = self.run(
|
||||||
|
["--no-prompt", "--allow-net=deno.land"],
|
||||||
|
["net", "http://localhost:4545"], b'')
|
||||||
|
assert code == 1
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
def test_allow_localhost_4545_fail(self):
|
||||||
|
code, _stdout, stderr = self.run(
|
||||||
|
["--no-prompt", "--allow-net=localhost:4545"],
|
||||||
|
["net", "http://localhost:4546"], b'')
|
||||||
|
assert code == 1
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
def test_allow_localhost(self):
|
||||||
|
code, _stdout, stderr = self.run(
|
||||||
|
["--no-prompt", "--allow-net=localhost"], [
|
||||||
|
"net", "http://localhost:4545", "http://localhost:4546",
|
||||||
|
"http://localhost:4547"
|
||||||
|
], b'')
|
||||||
|
assert code == 0
|
||||||
|
assert not PROMPT_PATTERN in stderr
|
||||||
|
assert not PERMISSION_DENIED_PATTERN in stderr
|
||||||
|
|
||||||
|
|
||||||
|
def complex_permissions_test(deno_exe):
|
||||||
|
p = Prompt(deno_exe, ["read", "write", "net"])
|
||||||
|
p.test()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print "Permissions prompt tests"
|
||||||
|
deno_exe = os.path.join(build_path(), "deno" + executable_suffix)
|
||||||
|
complex_permissions_test(deno_exe)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
24
tools/complex_permissions_test.ts
Normal file
24
tools/complex_permissions_test.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
const { args, readFileSync, writeFileSync, exit, dial } = Deno;
|
||||||
|
|
||||||
|
const name = args[1];
|
||||||
|
const test: (args: string[]) => void = {
|
||||||
|
read: (files: string[]): void => {
|
||||||
|
files.forEach((file): any => readFileSync(file));
|
||||||
|
},
|
||||||
|
write: (files: string[]): void => {
|
||||||
|
files.forEach(
|
||||||
|
(file): any => writeFileSync(file, new Uint8Array(), { append: true })
|
||||||
|
);
|
||||||
|
},
|
||||||
|
net: (hosts: string[]): void => {
|
||||||
|
hosts.forEach((host): any => fetch(host));
|
||||||
|
}
|
||||||
|
}[name];
|
||||||
|
|
||||||
|
if (!test) {
|
||||||
|
console.log("Unknown test:", name);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(args.slice(2));
|
|
@ -105,7 +105,9 @@ def main(argv):
|
||||||
if os.name != 'nt':
|
if os.name != 'nt':
|
||||||
from is_tty_test import is_tty_test
|
from is_tty_test import is_tty_test
|
||||||
from permission_prompt_test import permission_prompt_test
|
from permission_prompt_test import permission_prompt_test
|
||||||
|
from complex_permissions_test import complex_permissions_test
|
||||||
permission_prompt_test(deno_exe)
|
permission_prompt_test(deno_exe)
|
||||||
|
complex_permissions_test(deno_exe)
|
||||||
is_tty_test(deno_exe)
|
is_tty_test(deno_exe)
|
||||||
|
|
||||||
repl_tests(deno_exe)
|
repl_tests(deno_exe)
|
||||||
|
|
Loading…
Reference in a new issue