From fefe93c91b63e35bf88f5f432f0cca09948d0623 Mon Sep 17 00:00:00 2001 From: crowlKats <13135287+crowlKats@users.noreply.github.com> Date: Mon, 12 Apr 2021 04:15:43 +0200 Subject: [PATCH] feat(runtime/permissions): prompt fallback (#9376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yoshiya Hinosawa Co-authored-by: Bartek Iwańczuk --- cli/file_fetcher.rs | 58 ++--- cli/flags.rs | 11 +- cli/lsp/registries.rs | 4 +- cli/ops/runtime_compiler.rs | 4 +- cli/program_state.rs | 4 +- cli/specifier_handler.rs | 4 +- cli/tools/standalone.rs | 2 +- op_crates/fetch/lib.rs | 12 +- op_crates/websocket/lib.rs | 10 +- runtime/ops/fs.rs | 104 ++++----- runtime/ops/fs_events.rs | 2 +- runtime/ops/net.rs | 35 +-- runtime/ops/os.rs | 20 +- runtime/ops/plugin.rs | 2 +- runtime/ops/process.rs | 4 +- runtime/ops/runtime.rs | 2 +- runtime/ops/timers.rs | 2 +- runtime/ops/tls.rs | 10 +- runtime/permissions.rs | 421 ++++++++++++++++++++++++++---------- 19 files changed, 460 insertions(+), 251 deletions(-) diff --git a/cli/file_fetcher.rs b/cli/file_fetcher.rs index 187ac05dcc..b5cc1c838d 100644 --- a/cli/file_fetcher.rs +++ b/cli/file_fetcher.rs @@ -516,7 +516,7 @@ impl FileFetcher { fn fetch_remote( &self, specifier: &ModuleSpecifier, - permissions: &Permissions, + permissions: &mut Permissions, redirect_limit: i64, ) -> Pin> + Send>> { debug!("FileFetcher::fetch_remote() - specifier: {}", specifier); @@ -560,7 +560,7 @@ impl FileFetcher { }; let maybe_auth_token = self.auth_tokens.get(&specifier); let specifier = specifier.clone(); - let permissions = permissions.clone(); + let mut permissions = permissions.clone(); let client = self.http_client.clone(); let file_fetcher = self.clone(); // A single pass of fetch either yields code or yields a redirect. @@ -580,7 +580,7 @@ impl FileFetcher { FetchOnceResult::Redirect(redirect_url, headers) => { file_fetcher.http_cache.set(&specifier, headers, &[])?; file_fetcher - .fetch_remote(&redirect_url, &permissions, redirect_limit - 1) + .fetch_remote(&redirect_url, &mut permissions, redirect_limit - 1) .await } FetchOnceResult::Code(bytes, headers) => { @@ -600,7 +600,7 @@ impl FileFetcher { pub async fn fetch( &self, specifier: &ModuleSpecifier, - permissions: &Permissions, + permissions: &mut Permissions, ) -> Result { debug!("FileFetcher::fetch() - specifier: {}", specifier); let scheme = get_validated_scheme(specifier)?; @@ -718,7 +718,7 @@ mod tests { async fn test_fetch(specifier: &ModuleSpecifier) -> (File, FileFetcher) { let (file_fetcher, _) = setup(CacheSetting::ReloadAll, None); let result = file_fetcher - .fetch(specifier, &Permissions::allow_all()) + .fetch(specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); (result.unwrap(), file_fetcher) @@ -730,7 +730,7 @@ mod tests { let _http_server_guard = test_util::http_server(); let (file_fetcher, _) = setup(CacheSetting::ReloadAll, None); let result: Result = file_fetcher - .fetch_remote(specifier, &Permissions::allow_all(), 1) + .fetch_remote(specifier, &mut Permissions::allow_all(), 1) .await; assert!(result.is_ok()); let (_, headers) = file_fetcher.http_cache.get(specifier).unwrap(); @@ -1011,7 +1011,7 @@ mod tests { file_fetcher.insert_cached(file.clone()); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let result_file = result.unwrap(); @@ -1028,7 +1028,7 @@ mod tests { .unwrap(); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); @@ -1059,7 +1059,7 @@ mod tests { let specifier = resolve_url("data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=").unwrap(); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1089,7 +1089,7 @@ mod tests { ); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1113,7 +1113,7 @@ mod tests { .unwrap(); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1136,7 +1136,7 @@ mod tests { metadata.write(&cache_filename).unwrap(); let result = file_fetcher_01 - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1157,7 +1157,7 @@ mod tests { metadata.write(&cache_filename).unwrap(); let result = file_fetcher_02 - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1179,7 +1179,7 @@ mod tests { ) .expect("setup failed"); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1214,7 +1214,7 @@ mod tests { .unwrap(); let result = file_fetcher_01 - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); @@ -1234,7 +1234,7 @@ mod tests { ) .expect("could not create file fetcher"); let result = file_fetcher_02 - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); @@ -1273,7 +1273,7 @@ mod tests { .unwrap(); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1334,7 +1334,7 @@ mod tests { .unwrap(); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1406,7 +1406,7 @@ mod tests { .unwrap(); let result = file_fetcher_01 - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); @@ -1426,7 +1426,7 @@ mod tests { ) .expect("could not create file fetcher"); let result = file_fetcher_02 - .fetch(&redirected_specifier, &Permissions::allow_all()) + .fetch(&redirected_specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); @@ -1453,12 +1453,12 @@ mod tests { .unwrap(); let result = file_fetcher - .fetch_remote(&specifier, &Permissions::allow_all(), 2) + .fetch_remote(&specifier, &mut Permissions::allow_all(), 2) .await; assert!(result.is_ok()); let result = file_fetcher - .fetch_remote(&specifier, &Permissions::allow_all(), 1) + .fetch_remote(&specifier, &mut Permissions::allow_all(), 1) .await; assert!(result.is_err()); @@ -1491,7 +1491,7 @@ mod tests { .unwrap(); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1539,7 +1539,7 @@ mod tests { resolve_url("http://localhost:4545/cli/tests/002_hello.ts").unwrap(); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_err()); let err = result.unwrap_err(); @@ -1574,7 +1574,7 @@ mod tests { resolve_url("http://localhost:4545/cli/tests/002_hello.ts").unwrap(); let result = file_fetcher_01 - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_err()); let err = result.unwrap_err(); @@ -1582,12 +1582,12 @@ mod tests { assert_eq!(err.to_string(), "Specifier not found in cache: \"http://localhost:4545/cli/tests/002_hello.ts\", --cached-only is specified."); let result = file_fetcher_02 - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let result = file_fetcher_01 - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); @@ -1605,7 +1605,7 @@ mod tests { fs::write(fixture_path.clone(), r#"console.log("hello deno");"#) .expect("could not write file"); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); @@ -1614,7 +1614,7 @@ mod tests { fs::write(fixture_path, r#"console.log("goodbye deno");"#) .expect("could not write file"); let result = file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await; assert!(result.is_ok()); let file = result.unwrap(); diff --git a/cli/flags.rs b/cli/flags.rs index eb4bc86411..5feb463711 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -149,7 +149,7 @@ pub struct Flags { pub lock_write: bool, pub log_level: Option, pub no_check: bool, - pub no_prompts: bool, + pub prompt: bool, pub no_remote: bool, pub reload: bool, pub repl: bool, @@ -244,6 +244,7 @@ impl From for PermissionsOptions { allow_read: flags.allow_read, allow_run: flags.allow_run, allow_write: flags.allow_write, + prompt: flags.prompt, } } } @@ -1428,6 +1429,11 @@ fn permission_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { .long("allow-all") .help("Allow all permissions"), ) + .arg( + Arg::with_name("prompt") + .long("prompt") + .help("Fallback to prompt if required permission wasn't passed"), + ) } fn run_subcommand<'a, 'b>() -> App<'a, 'b> { @@ -1844,6 +1850,9 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.allow_plugin = true; flags.allow_hrtime = true; } + if matches.is_present("prompt") { + flags.prompt = true; + } } // TODO(ry) move this to utility module and add test. diff --git a/cli/lsp/registries.rs b/cli/lsp/registries.rs index 614d7f6e45..8a9ac20c65 100644 --- a/cli/lsp/registries.rs +++ b/cli/lsp/registries.rs @@ -347,7 +347,7 @@ impl ModuleRegistry { let specifier = origin_url.join(CONFIG_PATH)?; let file = self .file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await?; let config: RegistryConfigurationJson = serde_json::from_str(&file.source)?; validate_config(&config)?; @@ -609,7 +609,7 @@ impl ModuleRegistry { .ok()?; let file = self .file_fetcher - .fetch(&specifier, &Permissions::allow_all()) + .fetch(&specifier, &mut Permissions::allow_all()) .await .map_err(|err| { error!( diff --git a/cli/ops/runtime_compiler.rs b/cli/ops/runtime_compiler.rs index 81b400d13a..acd307f2f9 100644 --- a/cli/ops/runtime_compiler.rs +++ b/cli/ops/runtime_compiler.rs @@ -60,7 +60,7 @@ async fn op_emit( let args: EmitArgs = serde_json::from_value(args)?; let root_specifier = args.root_specifier; let program_state = state.borrow().borrow::>().clone(); - let runtime_permissions = { + let mut runtime_permissions = { let state = state.borrow(); state.borrow::().clone() }; @@ -86,7 +86,7 @@ async fn op_emit( } else { let file = program_state .file_fetcher - .fetch(&import_map_specifier, &runtime_permissions) + .fetch(&import_map_specifier, &mut runtime_permissions) .await?; ImportMap::from_json(import_map_specifier.as_str(), &file.source)? }; diff --git a/cli/program_state.rs b/cli/program_state.rs index a41acdec37..056928e7b2 100644 --- a/cli/program_state.rs +++ b/cli/program_state.rs @@ -108,7 +108,7 @@ impl ProgramState { format!("Bad URL (\"{}\") for import map.", import_map_url), )?; let file = file_fetcher - .fetch(&import_map_specifier, &Permissions::allow_all()) + .fetch(&import_map_specifier, &mut Permissions::allow_all()) .await?; let import_map = ImportMap::from_json(import_map_specifier.as_str(), &file.source)?; @@ -149,7 +149,7 @@ impl ProgramState { self: &Arc, specifier: ModuleSpecifier, lib: TypeLib, - runtime_permissions: Permissions, + mut runtime_permissions: Permissions, is_dynamic: bool, maybe_import_map: Option, ) -> Result<(), AnyError> { diff --git a/cli/specifier_handler.rs b/cli/specifier_handler.rs index 58815e15c7..066ed87f4d 100644 --- a/cli/specifier_handler.rs +++ b/cli/specifier_handler.rs @@ -257,7 +257,7 @@ impl SpecifierHandler for FetchHandler { // When the module graph fetches dynamic modules, the set of dynamic // permissions need to be applied. Other static imports have all // permissions. - let permissions = if is_dynamic { + let mut permissions = if is_dynamic { self.runtime_permissions.clone() } else { Permissions::allow_all() @@ -267,7 +267,7 @@ impl SpecifierHandler for FetchHandler { async move { let source_file = file_fetcher - .fetch(&requested_specifier, &permissions) + .fetch(&requested_specifier, &mut permissions) .await .map_err(|err| { let err = if let Some(e) = err.downcast_ref::() { diff --git a/cli/tools/standalone.rs b/cli/tools/standalone.rs index 062b807325..180d0bf523 100644 --- a/cli/tools/standalone.rs +++ b/cli/tools/standalone.rs @@ -220,7 +220,7 @@ pub fn compile_to_runtime_flags( lock_write: false, log_level: flags.log_level, no_check: false, - no_prompts: flags.no_prompts, + prompt: flags.prompt, no_remote: false, reload: false, repl: false, diff --git a/op_crates/fetch/lib.rs b/op_crates/fetch/lib.rs index 69c0c46ad7..ae716af417 100644 --- a/op_crates/fetch/lib.rs +++ b/op_crates/fetch/lib.rs @@ -86,19 +86,19 @@ pub struct HttpClientDefaults { } pub trait FetchPermissions { - fn check_net_url(&self, _url: &Url) -> Result<(), AnyError>; - fn check_read(&self, _p: &Path) -> Result<(), AnyError>; + fn check_net_url(&mut self, _url: &Url) -> Result<(), AnyError>; + fn check_read(&mut self, _p: &Path) -> Result<(), AnyError>; } /// For use with `op_fetch` when the user does not want permissions. pub struct NoFetchPermissions; impl FetchPermissions for NoFetchPermissions { - fn check_net_url(&self, _url: &Url) -> Result<(), AnyError> { + fn check_net_url(&mut self, _url: &Url) -> Result<(), AnyError> { Ok(()) } - fn check_read(&self, _p: &Path) -> Result<(), AnyError> { + fn check_read(&mut self, _p: &Path) -> Result<(), AnyError> { Ok(()) } } @@ -161,7 +161,7 @@ where let scheme = url.scheme(); let (request_rid, request_body_rid) = match scheme { "http" | "https" => { - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); permissions.check_net_url(&url)?; let mut request = client.request(method, url); @@ -442,7 +442,7 @@ where FP: FetchPermissions + 'static, { if let Some(ca_file) = args.ca_file.clone() { - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); permissions.check_read(&PathBuf::from(ca_file))?; } diff --git a/op_crates/websocket/lib.rs b/op_crates/websocket/lib.rs index 59fa5acdb1..930424aab7 100644 --- a/op_crates/websocket/lib.rs +++ b/op_crates/websocket/lib.rs @@ -49,14 +49,14 @@ pub struct WsCaData(pub Vec); pub struct WsUserAgent(pub String); pub trait WebSocketPermissions { - fn check_net_url(&self, _url: &url::Url) -> Result<(), AnyError>; + fn check_net_url(&mut self, _url: &url::Url) -> Result<(), AnyError>; } /// For use with `op_websocket_*` when the user does not want permissions. pub struct NoWebSocketPermissions; impl WebSocketPermissions for NoWebSocketPermissions { - fn check_net_url(&self, _url: &url::Url) -> Result<(), AnyError> { + fn check_net_url(&mut self, _url: &url::Url) -> Result<(), AnyError> { Ok(()) } } @@ -91,7 +91,7 @@ where WP: WebSocketPermissions + 'static, { state - .borrow::() + .borrow_mut::() .check_net_url(&url::Url::parse(&url)?)?; Ok(()) @@ -113,8 +113,8 @@ where WP: WebSocketPermissions + 'static, { { - let s = state.borrow(); - s.borrow::() + let mut s = state.borrow_mut(); + s.borrow_mut::() .check_net_url(&url::Url::parse(&args.url)?) .expect( "Permission check should have been done in op_ws_check_permission", diff --git a/runtime/ops/fs.rs b/runtime/ops/fs.rs index a084fdb15b..0753ea0fbb 100644 --- a/runtime/ops/fs.rs +++ b/runtime/ops/fs.rs @@ -149,7 +149,7 @@ fn open_helper( let _ = mode; // avoid unused warning } - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); let options = args.options; if options.read { @@ -409,7 +409,7 @@ fn op_chdir( _zero_copy: Option, ) -> Result<(), AnyError> { let d = PathBuf::from(&directory); - state.borrow::().read.check(&d)?; + state.borrow_mut::().read.check(&d)?; set_current_dir(&d)?; Ok(()) } @@ -429,7 +429,7 @@ fn op_mkdir_sync( ) -> Result<(), AnyError> { let path = Path::new(&args.path).to_path_buf(); let mode = args.mode.unwrap_or(0o777) & 0o777; - state.borrow::().write.check(&path)?; + state.borrow_mut::().write.check(&path)?; debug!("op_mkdir {} {:o} {}", path.display(), mode, args.recursive); let mut builder = std::fs::DirBuilder::new(); builder.recursive(args.recursive); @@ -451,8 +451,8 @@ async fn op_mkdir_async( let mode = args.mode.unwrap_or(0o777) & 0o777; { - let state = state.borrow(); - state.borrow::().write.check(&path)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { @@ -486,7 +486,7 @@ fn op_chmod_sync( let path = Path::new(&args.path).to_path_buf(); let mode = args.mode & 0o777; - state.borrow::().write.check(&path)?; + state.borrow_mut::().write.check(&path)?; debug!("op_chmod_sync {} {:o}", path.display(), mode); #[cfg(unix)] { @@ -513,8 +513,8 @@ async fn op_chmod_async( let mode = args.mode & 0o777; { - let state = state.borrow(); - state.borrow::().write.check(&path)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { @@ -552,7 +552,7 @@ fn op_chown_sync( _zero_copy: Option, ) -> Result<(), AnyError> { let path = Path::new(&args.path).to_path_buf(); - state.borrow::().write.check(&path)?; + state.borrow_mut::().write.check(&path)?; debug!( "op_chown_sync {} {:?} {:?}", path.display(), @@ -582,8 +582,8 @@ async fn op_chown_async( let path = Path::new(&args.path).to_path_buf(); { - let state = state.borrow(); - state.borrow::().write.check(&path)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { @@ -624,7 +624,7 @@ fn op_remove_sync( let path = PathBuf::from(&args.path); let recursive = args.recursive; - state.borrow::().write.check(&path)?; + state.borrow_mut::().write.check(&path)?; #[cfg(not(unix))] use std::os::windows::prelude::MetadataExt; @@ -667,8 +667,8 @@ async fn op_remove_async( let recursive = args.recursive; { - let state = state.borrow(); - state.borrow::().write.check(&path)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { @@ -722,7 +722,7 @@ fn op_copy_file_sync( let from = PathBuf::from(&args.from); let to = PathBuf::from(&args.to); - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); permissions.read.check(&from)?; permissions.write.check(&to)?; @@ -748,8 +748,8 @@ async fn op_copy_file_async( let to = PathBuf::from(&args.to); { - let state = state.borrow(); - let permissions = state.borrow::(); + let mut state = state.borrow_mut(); + let permissions = state.borrow_mut::(); permissions.read.check(&from)?; permissions.write.check(&to)?; } @@ -861,7 +861,7 @@ fn op_stat_sync( ) -> Result { let path = PathBuf::from(&args.path); let lstat = args.lstat; - state.borrow::().read.check(&path)?; + state.borrow_mut::().read.check(&path)?; debug!("op_stat_sync {} {}", path.display(), lstat); let metadata = if lstat { std::fs::symlink_metadata(&path)? @@ -880,8 +880,8 @@ async fn op_stat_async( let lstat = args.lstat; { - let state = state.borrow(); - state.borrow::().read.check(&path)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().read.check(&path)?; } tokio::task::spawn_blocking(move || { @@ -904,7 +904,7 @@ fn op_realpath_sync( ) -> Result { let path = PathBuf::from(&path); - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); permissions.read.check(&path)?; if path.is_relative() { permissions.read.check_blind(¤t_dir()?, "CWD")?; @@ -926,8 +926,8 @@ async fn op_realpath_async( let path = PathBuf::from(&path); { - let state = state.borrow(); - let permissions = state.borrow::(); + let mut state = state.borrow_mut(); + let permissions = state.borrow_mut::(); permissions.read.check(&path)?; if path.is_relative() { permissions.read.check_blind(¤t_dir()?, "CWD")?; @@ -962,7 +962,7 @@ fn op_read_dir_sync( ) -> Result, AnyError> { let path = PathBuf::from(&path); - state.borrow::().read.check(&path)?; + state.borrow_mut::().read.check(&path)?; debug!("op_read_dir_sync {}", path.display()); let entries: Vec<_> = std::fs::read_dir(path)? @@ -998,8 +998,8 @@ async fn op_read_dir_async( ) -> Result, AnyError> { let path = PathBuf::from(&path); { - let state = state.borrow(); - state.borrow::().read.check(&path)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().read.check(&path)?; } tokio::task::spawn_blocking(move || { debug!("op_read_dir_async {}", path.display()); @@ -1047,7 +1047,7 @@ fn op_rename_sync( let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); permissions.read.check(&oldpath)?; permissions.write.check(&oldpath)?; permissions.write.check(&newpath)?; @@ -1064,8 +1064,8 @@ async fn op_rename_async( let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); { - let state = state.borrow(); - let permissions = state.borrow::(); + let mut state = state.borrow_mut(); + let permissions = state.borrow_mut::(); permissions.read.check(&oldpath)?; permissions.write.check(&oldpath)?; permissions.write.check(&newpath)?; @@ -1098,7 +1098,7 @@ fn op_link_sync( let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); permissions.read.check(&oldpath)?; permissions.write.check(&oldpath)?; permissions.read.check(&newpath)?; @@ -1118,8 +1118,8 @@ async fn op_link_async( let newpath = PathBuf::from(&args.newpath); { - let state = state.borrow(); - let permissions = state.borrow::(); + let mut state = state.borrow_mut(); + let permissions = state.borrow_mut::(); permissions.read.check(&oldpath)?; permissions.write.check(&oldpath)?; permissions.read.check(&newpath)?; @@ -1159,7 +1159,7 @@ fn op_symlink_sync( let oldpath = PathBuf::from(&args.oldpath); let newpath = PathBuf::from(&args.newpath); - state.borrow::().write.check(&newpath)?; + state.borrow_mut::().write.check(&newpath)?; debug!( "op_symlink_sync {} {}", @@ -1209,8 +1209,8 @@ async fn op_symlink_async( let newpath = PathBuf::from(&args.newpath); { - let state = state.borrow(); - state.borrow::().write.check(&newpath)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().write.check(&newpath)?; } tokio::task::spawn_blocking(move || { @@ -1259,7 +1259,7 @@ fn op_read_link_sync( ) -> Result { let path = PathBuf::from(&path); - state.borrow::().read.check(&path)?; + state.borrow_mut::().read.check(&path)?; debug!("op_read_link_value {}", path.display()); let target = std::fs::read_link(&path)?.into_os_string(); @@ -1274,8 +1274,8 @@ async fn op_read_link_async( ) -> Result { let path = PathBuf::from(&path); { - let state = state.borrow(); - state.borrow::().read.check(&path)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().read.check(&path)?; } tokio::task::spawn_blocking(move || { debug!("op_read_link_async {}", path.display()); @@ -1349,7 +1349,7 @@ fn op_truncate_sync( let path = PathBuf::from(&args.path); let len = args.len; - state.borrow::().write.check(&path)?; + state.borrow_mut::().write.check(&path)?; debug!("op_truncate_sync {} {}", path.display(), len); let f = std::fs::OpenOptions::new().write(true).open(&path)?; @@ -1365,8 +1365,8 @@ async fn op_truncate_async( let path = PathBuf::from(&args.path); let len = args.len; { - let state = state.borrow(); - state.borrow::().write.check(&path)?; + let mut state = state.borrow_mut(); + state.borrow_mut::().write.check(&path)?; } tokio::task::spawn_blocking(move || { debug!("op_truncate_async {} {}", path.display(), len); @@ -1441,7 +1441,7 @@ fn op_make_temp_dir_sync( let suffix = args.suffix.map(String::from); state - .borrow::() + .borrow_mut::() .write .check(dir.clone().unwrap_or_else(temp_dir).as_path())?; @@ -1469,9 +1469,9 @@ async fn op_make_temp_dir_async( let prefix = args.prefix.map(String::from); let suffix = args.suffix.map(String::from); { - let state = state.borrow(); + let mut state = state.borrow_mut(); state - .borrow::() + .borrow_mut::() .write .check(dir.clone().unwrap_or_else(temp_dir).as_path())?; } @@ -1504,7 +1504,7 @@ fn op_make_temp_file_sync( let suffix = args.suffix.map(String::from); state - .borrow::() + .borrow_mut::() .write .check(dir.clone().unwrap_or_else(temp_dir).as_path())?; @@ -1532,9 +1532,9 @@ async fn op_make_temp_file_async( let prefix = args.prefix.map(String::from); let suffix = args.suffix.map(String::from); { - let state = state.borrow(); + let mut state = state.borrow_mut(); state - .borrow::() + .borrow_mut::() .write .check(dir.clone().unwrap_or_else(temp_dir).as_path())?; } @@ -1648,7 +1648,7 @@ fn op_utime_sync( let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); - state.borrow::().write.check(&path)?; + state.borrow_mut::().write.check(&path)?; filetime::set_file_times(path, atime, mtime)?; Ok(()) } @@ -1664,7 +1664,11 @@ async fn op_utime_async( let atime = filetime::FileTime::from_unix_time(args.atime.0, args.atime.1); let mtime = filetime::FileTime::from_unix_time(args.mtime.0, args.mtime.1); - state.borrow().borrow::().write.check(&path)?; + state + .borrow_mut() + .borrow_mut::() + .write + .check(&path)?; tokio::task::spawn_blocking(move || { filetime::set_file_times(path, atime, mtime)?; @@ -1681,7 +1685,7 @@ fn op_cwd( ) -> Result { let path = current_dir()?; state - .borrow::() + .borrow_mut::() .read .check_blind(&path, "CWD")?; let path_str = into_string(path.into_os_string())?; diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs index a64f31a333..30ab69ba58 100644 --- a/runtime/ops/fs_events.rs +++ b/runtime/ops/fs_events.rs @@ -109,7 +109,7 @@ fn op_fs_events_open( }; for path in &args.paths { state - .borrow::() + .borrow_mut::() .read .check(&PathBuf::from(path))?; watcher.watch(path, recursive_mode)?; diff --git a/runtime/ops/net.rs b/runtime/ops/net.rs index 4c38d2293e..934ff79265 100644 --- a/runtime/ops/net.rs +++ b/runtime/ops/net.rs @@ -224,8 +224,8 @@ async fn op_datagram_send( transport_args: ArgsEnum::Ip(args), } if transport == "udp" => { { - let s = state.borrow(); - s.borrow::() + let mut s = state.borrow_mut(); + s.borrow_mut::() .net .check(&(&args.hostname, Some(args.port)))?; } @@ -251,8 +251,8 @@ async fn op_datagram_send( } if transport == "unixpacket" => { let address_path = Path::new(&args.path); { - let s = state.borrow(); - s.borrow::().write.check(&address_path)?; + let mut s = state.borrow_mut(); + s.borrow_mut::().write.check(&address_path)?; } let resource = state .borrow() @@ -289,9 +289,9 @@ async fn op_connect( transport_args: ArgsEnum::Ip(args), } if transport == "tcp" => { { - let state_ = state.borrow(); + let mut state_ = state.borrow_mut(); state_ - .borrow::() + .borrow_mut::() .net .check(&(&args.hostname, Some(args.port)))?; } @@ -327,9 +327,15 @@ async fn op_connect( let address_path = Path::new(&args.path); super::check_unstable2(&state, "Deno.connect"); { - let state_ = state.borrow(); - state_.borrow::().read.check(&address_path)?; - state_.borrow::().write.check(&address_path)?; + let mut state_ = state.borrow_mut(); + state_ + .borrow_mut::() + .read + .check(&address_path)?; + state_ + .borrow_mut::() + .write + .check(&address_path)?; } let path = args.path; let unix_stream = net_unix::UnixStream::connect(Path::new(&path)).await?; @@ -443,7 +449,6 @@ fn op_listen( args: ListenArgs, _zero_copy: Option, ) -> Result { - let permissions = state.borrow::(); match args { ListenArgs { transport, @@ -453,7 +458,10 @@ fn op_listen( if transport == "udp" { super::check_unstable(state, "Deno.listenDatagram"); } - permissions.net.check(&(&args.hostname, Some(args.port)))?; + state + .borrow_mut::() + .net + .check(&(&args.hostname, Some(args.port)))?; } let addr = resolve_addr_sync(&args.hostname, args.port)? .next() @@ -497,6 +505,7 @@ fn op_listen( if transport == "unixpacket" { super::check_unstable(state, "Deno.listenDatagram"); } + let permissions = state.borrow_mut::(); permissions.read.check(&address_path)?; permissions.write.check(&address_path)?; } @@ -604,8 +613,8 @@ async fn op_dns_resolve( }; { - let s = state.borrow(); - let perm = s.borrow::(); + let mut s = state.borrow_mut(); + let perm = s.borrow_mut::(); // Checks permission against the name servers which will be actually queried. for ns in config.name_servers() { diff --git a/runtime/ops/os.rs b/runtime/ops/os.rs index 5f265bf20c..b13b54d22e 100644 --- a/runtime/ops/os.rs +++ b/runtime/ops/os.rs @@ -32,7 +32,7 @@ fn op_exec_path( ) -> Result { let current_exe = env::current_exe().unwrap(); state - .borrow::() + .borrow_mut::() .read .check_blind(¤t_exe, "exec_path")?; // Now apply URL parser to current exe to get fully resolved path, otherwise @@ -54,7 +54,7 @@ fn op_set_env( args: SetEnv, _zero_copy: Option, ) -> Result<(), AnyError> { - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; let invalid_key = args.key.is_empty() || args.key.contains(&['=', '\0'] as &[char]); let invalid_value = args.value.contains('\0'); @@ -70,7 +70,7 @@ fn op_env( _args: (), _zero_copy: Option, ) -> Result, AnyError> { - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; Ok(env::vars().collect()) } @@ -79,7 +79,7 @@ fn op_get_env( key: String, _zero_copy: Option, ) -> Result, AnyError> { - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(type_error("Key contains invalid characters.")); } @@ -94,7 +94,7 @@ fn op_delete_env( key: String, _zero_copy: Option, ) -> Result<(), AnyError> { - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(type_error("Key contains invalid characters.")); } @@ -116,7 +116,7 @@ fn op_loadavg( _zero_copy: Option, ) -> Result<(f64, f64, f64), AnyError> { super::check_unstable(state, "Deno.loadavg"); - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; match sys_info::loadavg() { Ok(loadavg) => Ok((loadavg.one, loadavg.five, loadavg.fifteen)), Err(_) => Ok((0.0, 0.0, 0.0)), @@ -129,7 +129,7 @@ fn op_hostname( _zero_copy: Option, ) -> Result { super::check_unstable(state, "Deno.hostname"); - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; let hostname = sys_info::hostname().unwrap_or_else(|_| "".to_string()); Ok(hostname) } @@ -140,7 +140,7 @@ fn op_os_release( _zero_copy: Option, ) -> Result { super::check_unstable(state, "Deno.osRelease"); - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; let release = sys_info::os_release().unwrap_or_else(|_| "".to_string()); Ok(release) } @@ -164,7 +164,7 @@ fn op_system_memory_info( _zero_copy: Option, ) -> Result, AnyError> { super::check_unstable(state, "Deno.systemMemoryInfo"); - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; match sys_info::mem_info() { Ok(info) => Ok(Some(MemInfo { total: info.total, @@ -191,7 +191,7 @@ fn op_system_cpu_info( _zero_copy: Option, ) -> Result { super::check_unstable(state, "Deno.systemCpuInfo"); - state.borrow::().env.check()?; + state.borrow_mut::().env.check()?; let cores = sys_info::cpu_num().ok(); let speed = sys_info::cpu_speed().ok(); diff --git a/runtime/ops/plugin.rs b/runtime/ops/plugin.rs index 0397dbca31..d0fc989f46 100644 --- a/runtime/ops/plugin.rs +++ b/runtime/ops/plugin.rs @@ -34,7 +34,7 @@ pub fn op_open_plugin( let filename = PathBuf::from(&filename); super::check_unstable(state, "Deno.openPlugin"); - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); permissions.plugin.check()?; debug!("Loading Plugin: {:#?}", filename); diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index 625bc204ce..4b49e21f3f 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -97,7 +97,7 @@ fn op_run( _zero_copy: Option, ) -> Result { let args = run_args.cmd; - state.borrow::().run.check(&args[0])?; + state.borrow_mut::().run.check(&args[0])?; let env = run_args.env; let cwd = run_args.cwd; @@ -286,7 +286,7 @@ fn op_kill( _zero_copy: Option, ) -> Result<(), AnyError> { super::check_unstable(state, "Deno.kill"); - state.borrow::().run.check_all()?; + state.borrow_mut::().run.check_all()?; kill(args.pid, args.signo)?; Ok(()) diff --git a/runtime/ops/runtime.rs b/runtime/ops/runtime.rs index ef7445b119..d694cb9c93 100644 --- a/runtime/ops/runtime.rs +++ b/runtime/ops/runtime.rs @@ -31,7 +31,7 @@ fn op_main_module( if main_url.scheme() == "file" { let main_path = std::env::current_dir().unwrap().join(main_url.to_string()); state - .borrow::() + .borrow_mut::() .read .check_blind(&main_path, "main_module")?; } diff --git a/runtime/ops/timers.rs b/runtime/ops/timers.rs index 428d4ecea7..aee38cf8d3 100644 --- a/runtime/ops/timers.rs +++ b/runtime/ops/timers.rs @@ -140,7 +140,7 @@ fn op_now( // If the permission is not enabled // Round the nano result on 2 milliseconds // see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision - if op_state.borrow::().hrtime.check().is_err() { + if op_state.borrow_mut::().hrtime.check().is_err() { subsec_nanos -= subsec_nanos % reduced_time_precision; } diff --git a/runtime/ops/tls.rs b/runtime/ops/tls.rs index 83dbbfcd1d..36762d66c6 100644 --- a/runtime/ops/tls.rs +++ b/runtime/ops/tls.rs @@ -107,8 +107,8 @@ async fn op_start_tls( } { super::check_unstable2(&state, "Deno.startTls"); - let s = state.borrow(); - let permissions = s.borrow::(); + let mut s = state.borrow_mut(); + let permissions = s.borrow_mut::(); permissions.net.check(&(&domain, Some(0)))?; if let Some(path) = &args.cert_file { permissions.read.check(Path::new(&path))?; @@ -170,8 +170,8 @@ async fn op_connect_tls( assert_eq!(args.transport, "tcp"); { - let s = state.borrow(); - let permissions = s.borrow::(); + let mut s = state.borrow_mut(); + let permissions = s.borrow_mut::(); permissions.net.check(&(&args.hostname, Some(args.port)))?; if let Some(path) = &args.cert_file { permissions.read.check(Path::new(&path))?; @@ -313,7 +313,7 @@ fn op_listen_tls( let cert_file = args.cert_file; let key_file = args.key_file; { - let permissions = state.borrow::(); + let permissions = state.borrow_mut::(); permissions.net.check(&(&args.hostname, Some(args.port)))?; permissions.read.check(Path::new(&cert_file))?; permissions.read.check(Path::new(&key_file))?; diff --git a/runtime/permissions.rs b/runtime/permissions.rs index dba6dc1a5c..f2a6e48e53 100644 --- a/runtime/permissions.rs +++ b/runtime/permissions.rs @@ -1,4 +1,5 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + use crate::colors; use crate::fs_util::resolve_from_cwd; use deno_core::error::custom_error; @@ -33,23 +34,35 @@ pub enum PermissionState { } impl PermissionState { - /// Check the permission state. - fn check(self, name: &str, info: Option<&str>) -> Result<(), AnyError> { - if self == PermissionState::Granted { - log_perm_access(&format!( - "{} access{}", - name, - info.map_or(Default::default(), |info| { format!(" to {}", info) }), - )); - return Ok(()); - } - let message = format!( - "Requires {} access{}, run again with the --allow-{} flag", + /// Check the permission state. bool is whether a prompt was issued. + fn check( + self, + name: &str, + info: Option<&str>, + prompt: bool, + ) -> (Result<(), AnyError>, bool) { + let access = format!( + "{} access{}", name, - info.map_or(Default::default(), |info| { format!(" to {}", info) }), - name + info.map_or(String::new(), |info| { format!(" to {}", info) }), ); - Err(custom_error("PermissionDenied", message)) + let result = if self == PermissionState::Granted + || (prompt + && self == PermissionState::Prompt + && permission_prompt(&access)) + { + log_perm_access(&access); + Ok(()) + } else { + Err(custom_error( + "PermissionDenied", + format!( + "Requires {}, run again with the --allow-{} flag", + access, name + ), + )) + }; + (result, prompt && self == PermissionState::Prompt) } } @@ -74,6 +87,7 @@ pub struct UnitPermission { pub name: &'static str, pub description: &'static str, pub state: PermissionState, + pub prompt: bool, } impl UnitPermission { @@ -99,8 +113,16 @@ impl UnitPermission { self.state } - pub fn check(&self) -> Result<(), AnyError> { - self.state.check(self.name, None) + pub fn check(&mut self) -> Result<(), AnyError> { + let (result, prompted) = self.state.check(self.name, None, self.prompt); + if prompted { + if result.is_ok() { + self.state = PermissionState::Granted; + } else { + self.state = PermissionState::Denied; + } + } + result } } @@ -113,6 +135,8 @@ pub struct UnaryPermission { pub global_state: PermissionState, pub granted_list: HashSet, pub denied_list: HashSet, + #[serde(skip)] + pub prompt: bool, } #[derive(Clone, Eq, PartialEq, Hash, Debug, Default, Deserialize)] @@ -234,24 +258,46 @@ impl UnaryPermission { self.query(path) } - pub fn check(&self, path: &Path) -> Result<(), AnyError> { + pub fn check(&mut self, path: &Path) -> Result<(), AnyError> { let (resolved_path, display_path) = resolved_and_display_path(path); - self - .query(Some(&resolved_path)) - .check(self.name, Some(&format!("\"{}\"", display_path.display()))) + let (result, prompted) = self.query(Some(&resolved_path)).check( + self.name, + Some(&format!("\"{}\"", display_path.display())), + self.prompt, + ); + if prompted { + if result.is_ok() { + self.granted_list.insert(ReadDescriptor(resolved_path)); + } else { + self.denied_list.insert(ReadDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + } + } + result } /// As `check()`, but permission error messages will anonymize the path /// by replacing it with the given `display`. pub fn check_blind( - &self, + &mut self, path: &Path, display: &str, ) -> Result<(), AnyError> { let resolved_path = resolve_from_cwd(path).unwrap(); - self - .query(Some(&resolved_path)) - .check(self.name, Some(&format!("<{}>", display))) + let (result, prompted) = self.query(Some(&resolved_path)).check( + self.name, + Some(&format!("<{}>", display)), + self.prompt, + ); + if prompted { + if result.is_ok() { + self.granted_list.insert(ReadDescriptor(resolved_path)); + } else { + self.denied_list.insert(ReadDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + } + } + result } } @@ -340,11 +386,22 @@ impl UnaryPermission { self.query(path) } - pub fn check(&self, path: &Path) -> Result<(), AnyError> { + pub fn check(&mut self, path: &Path) -> Result<(), AnyError> { let (resolved_path, display_path) = resolved_and_display_path(path); - self - .query(Some(&resolved_path)) - .check(self.name, Some(&format!("\"{}\"", display_path.display()))) + let (result, prompted) = self.query(Some(&resolved_path)).check( + self.name, + Some(&format!("\"{}\"", display_path.display())), + self.prompt, + ); + if prompted { + if result.is_ok() { + self.granted_list.insert(WriteDescriptor(resolved_path)); + } else { + self.denied_list.insert(WriteDescriptor(resolved_path)); + self.global_state = PermissionState::Denied; + } + } + result } } @@ -445,16 +502,27 @@ impl UnaryPermission { } pub fn check>( - &self, + &mut self, host: &(T, Option), ) -> Result<(), AnyError> { - self.query(Some(host)).check( + let new_host = NetDescriptor::new(&host); + let (result, prompted) = self.query(Some(host)).check( self.name, - Some(&format!("\"{}\"", NetDescriptor::new(&host))), - ) + Some(&format!("\"{}\"", new_host)), + self.prompt, + ); + if prompted { + if result.is_ok() { + self.granted_list.insert(new_host); + } else { + self.denied_list.insert(new_host); + self.global_state = PermissionState::Denied; + } + } + result } - pub fn check_url(&self, url: &url::Url) -> Result<(), AnyError> { + pub fn check_url(&mut self, url: &url::Url) -> Result<(), AnyError> { let hostname = url .host_str() .ok_or_else(|| uri_error("Missing host"))? @@ -463,9 +531,21 @@ impl UnaryPermission { None => hostname.clone(), Some(port) => format!("{}:{}", hostname, port), }; - self - .query(Some(&(hostname, url.port_or_known_default()))) - .check(self.name, Some(&format!("\"{}\"", display_host))) + let host = &(&hostname, url.port_or_known_default()); + let (result, prompted) = self.query(Some(host)).check( + self.name, + Some(&format!("\"{}\"", display_host)), + self.prompt, + ); + if prompted { + if result.is_ok() { + self.granted_list.insert(NetDescriptor::new(&host)); + } else { + self.denied_list.insert(NetDescriptor::new(&host)); + self.global_state = PermissionState::Denied; + } + } + result } } @@ -536,49 +616,34 @@ impl UnaryPermission { self.query(cmd) } - pub fn check(&self, cmd: &str) -> Result<(), AnyError> { - self - .query(Some(cmd)) - .check(self.name, Some(&format!("\"{}\"", cmd))) - } - - pub fn check_all(&self) -> Result<(), AnyError> { - self.query(None).check(self.name, Some("all")) - } -} - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct BooleanPermission { - pub name: &'static str, - pub description: &'static str, - pub state: PermissionState, -} - -impl BooleanPermission { - pub fn query(&self) -> PermissionState { - self.state - } - - pub fn request(&mut self) -> PermissionState { - if self.state == PermissionState::Prompt { - if permission_prompt(&format!("access to {}", self.description)) { - self.state = PermissionState::Granted; + pub fn check(&mut self, cmd: &str) -> Result<(), AnyError> { + let (result, prompted) = self.query(Some(cmd)).check( + self.name, + Some(&format!("\"{}\"", cmd)), + self.prompt, + ); + if prompted { + if result.is_ok() { + self.granted_list.insert(RunDescriptor(cmd.to_string())); } else { - self.state = PermissionState::Denied; + self.denied_list.insert(RunDescriptor(cmd.to_string())); + self.global_state = PermissionState::Denied; } } - self.state + result } - pub fn revoke(&mut self) -> PermissionState { - if self.state == PermissionState::Granted { - self.state = PermissionState::Prompt; + pub fn check_all(&mut self) -> Result<(), AnyError> { + let (result, prompted) = + self.query(None).check(self.name, Some("all"), self.prompt); + if prompted { + if result.is_ok() { + self.global_state = PermissionState::Granted; + } else { + self.global_state = PermissionState::Denied; + } } - self.state - } - - pub fn check(&self) -> Result<(), AnyError> { - self.state.check(self.name, None) + result } } @@ -602,11 +667,13 @@ pub struct PermissionsOptions { pub allow_read: Option>, pub allow_run: Option>, pub allow_write: Option>, + pub prompt: bool, } impl Permissions { pub fn new_read( state: &Option>, + prompt: bool, ) -> UnaryPermission { UnaryPermission:: { name: "read", @@ -614,11 +681,13 @@ impl Permissions { global_state: global_state_from_option(state), granted_list: resolve_read_allowlist(&state), denied_list: Default::default(), + prompt, } } pub fn new_write( state: &Option>, + prompt: bool, ) -> UnaryPermission { UnaryPermission:: { name: "write", @@ -626,11 +695,13 @@ impl Permissions { global_state: global_state_from_option(state), granted_list: resolve_write_allowlist(&state), denied_list: Default::default(), + prompt, } } pub fn new_net( state: &Option>, + prompt: bool, ) -> UnaryPermission { UnaryPermission:: { name: "net", @@ -645,15 +716,22 @@ impl Permissions { }) .unwrap_or_else(HashSet::new), denied_list: Default::default(), + prompt, } } - pub fn new_env(state: bool) -> UnitPermission { - boolean_permission_from_flag_bool(state, "env", "environment variables") + pub fn new_env(state: bool, prompt: bool) -> UnitPermission { + boolean_permission_from_flag_bool( + state, + "env", + "environment variables", + prompt, + ) } pub fn new_run( state: &Option>, + prompt: bool, ) -> UnaryPermission { UnaryPermission:: { name: "run", @@ -664,45 +742,51 @@ impl Permissions { .map(|v| v.iter().map(|x| RunDescriptor(x.clone())).collect()) .unwrap_or_else(HashSet::new), denied_list: Default::default(), + prompt, } } - pub fn new_plugin(state: bool) -> UnitPermission { - boolean_permission_from_flag_bool(state, "plugin", "open a plugin") + pub fn new_plugin(state: bool, prompt: bool) -> UnitPermission { + boolean_permission_from_flag_bool(state, "plugin", "open a plugin", prompt) } - pub fn new_hrtime(state: bool) -> UnitPermission { - boolean_permission_from_flag_bool(state, "hrtime", "high precision time") + pub fn new_hrtime(state: bool, prompt: bool) -> UnitPermission { + boolean_permission_from_flag_bool( + state, + "hrtime", + "high precision time", + prompt, + ) } pub fn from_options(opts: &PermissionsOptions) -> Self { Self { - read: Permissions::new_read(&opts.allow_read), - write: Permissions::new_write(&opts.allow_write), - net: Permissions::new_net(&opts.allow_net), - env: Permissions::new_env(opts.allow_env), - run: Permissions::new_run(&opts.allow_run), - plugin: Permissions::new_plugin(opts.allow_plugin), - hrtime: Permissions::new_hrtime(opts.allow_hrtime), + read: Permissions::new_read(&opts.allow_read, opts.prompt), + write: Permissions::new_write(&opts.allow_write, opts.prompt), + net: Permissions::new_net(&opts.allow_net, opts.prompt), + env: Permissions::new_env(opts.allow_env, opts.prompt), + run: Permissions::new_run(&opts.allow_run, opts.prompt), + plugin: Permissions::new_plugin(opts.allow_plugin, opts.prompt), + hrtime: Permissions::new_hrtime(opts.allow_hrtime, opts.prompt), } } pub fn allow_all() -> Self { Self { - read: Permissions::new_read(&Some(vec![])), - write: Permissions::new_write(&Some(vec![])), - net: Permissions::new_net(&Some(vec![])), - env: Permissions::new_env(true), - run: Permissions::new_run(&Some(vec![])), - plugin: Permissions::new_plugin(true), - hrtime: Permissions::new_hrtime(true), + read: Permissions::new_read(&Some(vec![]), false), + write: Permissions::new_write(&Some(vec![]), false), + net: Permissions::new_net(&Some(vec![]), false), + env: Permissions::new_env(true, false), + run: Permissions::new_run(&Some(vec![]), false), + plugin: Permissions::new_plugin(true, false), + hrtime: Permissions::new_hrtime(true, false), } } /// A helper function that determines if the module specifier is a local or /// remote, and performs a read or net check for the specifier. pub fn check_specifier( - &self, + &mut self, specifier: &ModuleSpecifier, ) -> Result<(), AnyError> { match specifier.scheme() { @@ -721,17 +805,17 @@ impl Permissions { } impl deno_fetch::FetchPermissions for Permissions { - fn check_net_url(&self, url: &url::Url) -> Result<(), AnyError> { + fn check_net_url(&mut self, url: &url::Url) -> Result<(), AnyError> { self.net.check_url(url) } - fn check_read(&self, path: &Path) -> Result<(), AnyError> { + fn check_read(&mut self, path: &Path) -> Result<(), AnyError> { self.read.check(path) } } impl deno_websocket::WebSocketPermissions for Permissions { - fn check_net_url(&self, url: &url::Url) -> Result<(), AnyError> { + fn check_net_url(&mut self, url: &url::Url) -> Result<(), AnyError> { self.net.check_url(url) } } @@ -747,6 +831,7 @@ fn boolean_permission_from_flag_bool( flag: bool, name: &'static str, description: &'static str, + prompt: bool, ) -> UnitPermission { UnitPermission { name, @@ -756,6 +841,7 @@ fn boolean_permission_from_flag_bool( } else { PermissionState::Prompt }, + prompt, } } @@ -810,9 +896,10 @@ fn permission_prompt(message: &str) -> bool { if !atty::is(atty::Stream::Stdin) || !atty::is(atty::Stream::Stderr) { return false; }; + let opts = "[g/d (g = grant, d = deny)] "; let msg = format!( - "{} ️Deno requests {}. Grant? [g/d (g = grant, d = deny)] ", - PERMISSION_EMOJI, message + "{} ️Deno requests {}. Grant? {}", + PERMISSION_EMOJI, message, opts ); // print to stderr so that if deno is > to a file this is still displayed. eprint!("{}", colors::bold(&msg)); @@ -832,8 +919,7 @@ fn permission_prompt(message: &str) -> bool { 'd' => return false, _ => { // If we don't get a recognized option try again. - let msg_again = - format!("Unrecognized option '{}' [g/d (g = grant, d = deny)] ", ch); + let msg_again = format!("Unrecognized option '{}' {}", ch, opts); eprint!("{}", colors::bold(&msg_again)); } }; @@ -879,7 +965,7 @@ mod tests { PathBuf::from("/b/c"), ]; - let perms = Permissions::from_options(&PermissionsOptions { + let mut perms = Permissions::from_options(&PermissionsOptions { allow_read: Some(allowlist.clone()), allow_write: Some(allowlist), ..Default::default() @@ -939,7 +1025,7 @@ mod tests { #[test] fn test_check_net_with_values() { - let perms = Permissions::from_options(&PermissionsOptions { + let mut perms = Permissions::from_options(&PermissionsOptions { allow_net: Some(svec![ "localhost", "deno.land", @@ -981,7 +1067,7 @@ mod tests { #[test] fn test_check_net_only_flag() { - let perms = Permissions::from_options(&PermissionsOptions { + let mut perms = Permissions::from_options(&PermissionsOptions { allow_net: Some(svec![]), // this means `--allow-net` is present without values following `=` sign ..Default::default() }); @@ -1015,7 +1101,7 @@ mod tests { #[test] fn test_check_net_no_flag() { - let perms = Permissions::from_options(&PermissionsOptions { + let mut perms = Permissions::from_options(&PermissionsOptions { allow_net: None, ..Default::default() }); @@ -1049,7 +1135,7 @@ mod tests { #[test] fn test_check_net_url() { - let perms = Permissions::from_options(&PermissionsOptions { + let mut perms = Permissions::from_options(&PermissionsOptions { allow_net: Some(svec![ "localhost", "deno.land", @@ -1113,7 +1199,7 @@ mod tests { } else { vec![PathBuf::from("/a")] }; - let perms = Permissions::from_options(&PermissionsOptions { + let mut perms = Permissions::from_options(&PermissionsOptions { allow_read: Some(read_allowlist), allow_net: Some(svec!["localhost"]), ..Default::default() @@ -1151,7 +1237,7 @@ mod tests { #[test] fn check_invalid_specifiers() { - let perms = Permissions::allow_all(); + let mut perms = Permissions::allow_all(); let mut test_cases = vec![]; @@ -1175,15 +1261,15 @@ mod tests { let perms2 = Permissions { read: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_read(&Some(vec![PathBuf::from("/foo")])) + ..Permissions::new_read(&Some(vec![PathBuf::from("/foo")]), false) }, write: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_write(&Some(vec![PathBuf::from("/foo")])) + ..Permissions::new_write(&Some(vec![PathBuf::from("/foo")]), false) }, net: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_net(&Some(svec!["127.0.0.1:8000"])) + ..Permissions::new_net(&Some(svec!["127.0.0.1:8000"]), false) }, env: UnitPermission { state: PermissionState::Prompt, @@ -1191,7 +1277,7 @@ mod tests { }, run: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_run(&Some(svec!["deno"])) + ..Permissions::new_run(&Some(svec!["deno"]), false) }, plugin: UnitPermission { state: PermissionState::Prompt, @@ -1276,15 +1362,15 @@ mod tests { let mut perms = Permissions { read: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_read(&Some(vec![PathBuf::from("/foo")])) + ..Permissions::new_read(&Some(vec![PathBuf::from("/foo")]), false) }, write: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_write(&Some(vec![PathBuf::from("/foo")])) + ..Permissions::new_write(&Some(vec![PathBuf::from("/foo")]), false) }, net: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_net(&Some(svec!["127.0.0.1"])) + ..Permissions::new_net(&Some(svec!["127.0.0.1"]), false) }, env: UnitPermission { state: PermissionState::Granted, @@ -1292,7 +1378,7 @@ mod tests { }, run: UnaryPermission { global_state: PermissionState::Prompt, - ..Permissions::new_run(&Some(svec!["deno"])) + ..Permissions::new_run(&Some(svec!["deno"]), false) }, plugin: UnitPermission { state: PermissionState::Prompt, @@ -1319,4 +1405,105 @@ mod tests { assert_eq!(perms.hrtime.revoke(), PermissionState::Denied); }; } + + #[test] + fn test_check() { + let mut perms = Permissions { + read: Permissions::new_read(&None, true), + write: Permissions::new_write(&None, true), + net: Permissions::new_net(&None, true), + env: Permissions::new_env(false, true), + run: Permissions::new_run(&None, true), + plugin: Permissions::new_plugin(false, true), + hrtime: Permissions::new_hrtime(false, true), + }; + + let _guard = PERMISSION_PROMPT_GUARD.lock().unwrap(); + + set_prompt_result(true); + assert!(perms.read.check(&Path::new("/foo")).is_ok()); + set_prompt_result(false); + assert!(perms.read.check(&Path::new("/foo")).is_ok()); + assert!(perms.read.check(&Path::new("/bar")).is_err()); + + set_prompt_result(true); + assert!(perms.write.check(&Path::new("/foo")).is_ok()); + set_prompt_result(false); + assert!(perms.write.check(&Path::new("/foo")).is_ok()); + assert!(perms.write.check(&Path::new("/bar")).is_err()); + + set_prompt_result(true); + assert!(perms.net.check(&("127.0.0.1", Some(8000))).is_ok()); + set_prompt_result(false); + assert!(perms.net.check(&("127.0.0.1", Some(8000))).is_ok()); + assert!(perms.net.check(&("127.0.0.1", Some(8001))).is_err()); + assert!(perms.net.check(&("127.0.0.1", None)).is_err()); + assert!(perms.net.check(&("deno.land", Some(8000))).is_err()); + assert!(perms.net.check(&("deno.land", None)).is_err()); + + set_prompt_result(true); + assert!(perms.run.check("cat").is_ok()); + set_prompt_result(false); + assert!(perms.run.check("cat").is_ok()); + assert!(perms.run.check("ls").is_err()); + + set_prompt_result(true); + assert!(perms.hrtime.check().is_ok()); + set_prompt_result(false); + assert!(perms.hrtime.check().is_ok()); + } + + #[test] + fn test_check_fail() { + let mut perms = Permissions { + read: Permissions::new_read(&None, true), + write: Permissions::new_write(&None, true), + net: Permissions::new_net(&None, true), + env: Permissions::new_env(false, true), + run: Permissions::new_run(&None, true), + plugin: Permissions::new_plugin(false, true), + hrtime: Permissions::new_hrtime(false, true), + }; + + let _guard = PERMISSION_PROMPT_GUARD.lock().unwrap(); + + set_prompt_result(false); + assert!(perms.read.check(&Path::new("/foo")).is_err()); + set_prompt_result(true); + assert!(perms.read.check(&Path::new("/foo")).is_err()); + assert!(perms.read.check(&Path::new("/bar")).is_ok()); + set_prompt_result(false); + assert!(perms.read.check(&Path::new("/bar")).is_ok()); + + set_prompt_result(false); + assert!(perms.write.check(&Path::new("/foo")).is_err()); + set_prompt_result(true); + assert!(perms.write.check(&Path::new("/foo")).is_err()); + assert!(perms.write.check(&Path::new("/bar")).is_ok()); + set_prompt_result(false); + assert!(perms.write.check(&Path::new("/bar")).is_ok()); + + set_prompt_result(false); + assert!(perms.net.check(&("127.0.0.1", Some(8000))).is_err()); + set_prompt_result(true); + assert!(perms.net.check(&("127.0.0.1", Some(8000))).is_err()); + assert!(perms.net.check(&("127.0.0.1", Some(8001))).is_ok()); + assert!(perms.net.check(&("deno.land", Some(8000))).is_ok()); + set_prompt_result(false); + assert!(perms.net.check(&("127.0.0.1", Some(8001))).is_ok()); + assert!(perms.net.check(&("deno.land", Some(8000))).is_ok()); + + set_prompt_result(false); + assert!(perms.run.check("cat").is_err()); + set_prompt_result(true); + assert!(perms.run.check("cat").is_err()); + assert!(perms.run.check("ls").is_ok()); + set_prompt_result(false); + assert!(perms.run.check("ls").is_ok()); + + set_prompt_result(false); + assert!(perms.hrtime.check().is_err()); + set_prompt_result(true); + assert!(perms.hrtime.check().is_err()); + } }