summaryrefslogtreecommitdiff
path: root/runtime/ops/worker_host.rs
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/ops/worker_host.rs')
-rw-r--r--runtime/ops/worker_host.rs376
1 files changed, 365 insertions, 11 deletions
diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs
index 871e4b9fe..da00c6e6e 100644
--- a/runtime/ops/worker_host.rs
+++ b/runtime/ops/worker_host.rs
@@ -1,14 +1,22 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use crate::permissions::resolve_fs_allowlist;
+use crate::permissions::PermissionState;
use crate::permissions::Permissions;
+use crate::permissions::UnaryPermission;
use crate::web_worker::run_web_worker;
use crate::web_worker::WebWorker;
use crate::web_worker::WebWorkerHandle;
use crate::web_worker::WorkerEvent;
+use deno_core::error::custom_error;
use deno_core::error::generic_error;
use deno_core::error::AnyError;
use deno_core::error::JsError;
use deno_core::futures::channel::mpsc;
+use deno_core::serde::de;
+use deno_core::serde::de::SeqAccess;
+use deno_core::serde::Deserialize;
+use deno_core::serde::Deserializer;
use deno_core::serde_json;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
@@ -16,10 +24,12 @@ use deno_core::BufVec;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
-use serde::Deserialize;
use std::cell::RefCell;
use std::collections::HashMap;
+use std::collections::HashSet;
use std::convert::From;
+use std::fmt;
+use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use std::thread::JoinHandle;
@@ -27,6 +37,7 @@ use std::thread::JoinHandle;
pub struct CreateWebWorkerArgs {
pub name: String,
pub worker_id: u32,
+ pub parent_permissions: Permissions,
pub permissions: Permissions,
pub main_module: ModuleSpecifier,
pub use_deno_namespace: bool,
@@ -47,6 +58,14 @@ struct HostUnhandledErrorArgs {
message: String,
}
+pub struct WorkerThread {
+ join_handle: JoinHandle<Result<(), AnyError>>,
+ worker_handle: WebWorkerHandle,
+}
+
+pub type WorkersTable = HashMap<u32, WorkerThread>;
+pub type WorkerId = u32;
+
pub fn init(
rt: &mut deno_core::JsRuntime,
sender: Option<mpsc::Sender<WorkerEvent>>,
@@ -86,21 +105,348 @@ pub fn init(
);
}
-pub struct WorkerThread {
- join_handle: JoinHandle<Result<(), AnyError>>,
- worker_handle: WebWorkerHandle,
+fn merge_permission_state(
+ target: &PermissionState,
+ incoming: Option<PermissionState>,
+) -> Result<PermissionState, AnyError> {
+ match target {
+ PermissionState::Granted => match incoming {
+ Some(x) => Ok(x),
+ None => Ok(*target),
+ },
+ _ => match incoming {
+ Some(x) => match x {
+ PermissionState::Denied => Ok(x),
+ _ => Err(custom_error(
+ "PermissionDenied",
+ "Can't escalate parent thread permissions",
+ )),
+ },
+ None => Ok(*target),
+ },
+ }
}
-pub type WorkersTable = HashMap<u32, WorkerThread>;
-pub type WorkerId = u32;
+fn check_net_permission_contains(
+ a: &HashSet<String>,
+ b: &HashSet<String>,
+) -> bool {
+ b.iter().all(|x| a.contains(x))
+}
+
+fn merge_net_permissions(
+ target: &UnaryPermission<String>,
+ incoming: Option<UnaryPermission<String>>,
+) -> Result<UnaryPermission<String>, AnyError> {
+ if incoming.is_none() {
+ return Ok(target.clone());
+ };
+
+ let new_permissions = incoming.unwrap();
+ match &target.global_state {
+ PermissionState::Granted => Ok(UnaryPermission::<String> {
+ global_state: new_permissions.global_state,
+ granted_list: new_permissions.granted_list,
+ denied_list: new_permissions.denied_list,
+ }),
+ PermissionState::Prompt => match new_permissions.global_state {
+ //Throw
+ PermissionState::Granted => Err(custom_error(
+ "PermissionDenied",
+ "Can't escalate parent thread permissions",
+ )),
+ //Merge
+ PermissionState::Prompt => {
+ if check_net_permission_contains(
+ &target.granted_list,
+ &new_permissions.granted_list,
+ ) {
+ Ok(UnaryPermission::<String> {
+ global_state: new_permissions.global_state,
+ granted_list: new_permissions.granted_list,
+ denied_list: target.denied_list.clone(),
+ })
+ } else {
+ Err(custom_error(
+ "PermissionDenied",
+ "Can't escalate parent thread permissions",
+ ))
+ }
+ }
+ //Copy
+ PermissionState::Denied => Ok(UnaryPermission::<String> {
+ global_state: new_permissions.global_state,
+ granted_list: new_permissions.granted_list,
+ denied_list: new_permissions.denied_list,
+ }),
+ },
+ PermissionState::Denied => match new_permissions.global_state {
+ PermissionState::Denied => Ok(UnaryPermission::<String> {
+ global_state: new_permissions.global_state,
+ granted_list: new_permissions.granted_list,
+ denied_list: new_permissions.denied_list,
+ }),
+ _ => Err(custom_error(
+ "PermissionDenied",
+ "Can't escalate parent thread permissions",
+ )),
+ },
+ }
+}
+
+enum WorkerPermissionType {
+ READ,
+ WRITE,
+}
+
+fn check_read_permissions(
+ allow_list: &HashSet<PathBuf>,
+ current_permissions: &Permissions,
+) -> bool {
+ allow_list
+ .iter()
+ .all(|x| current_permissions.check_read(&x).is_ok())
+}
+
+fn check_write_permissions(
+ allow_list: &HashSet<PathBuf>,
+ current_permissions: &Permissions,
+) -> bool {
+ allow_list
+ .iter()
+ .all(|x| current_permissions.check_write(&x).is_ok())
+}
+
+fn merge_read_write_permissions(
+ permission_type: WorkerPermissionType,
+ target: &UnaryPermission<PathBuf>,
+ incoming: Option<UnaryPermission<PathBuf>>,
+ current_permissions: &Permissions,
+) -> Result<UnaryPermission<PathBuf>, AnyError> {
+ if incoming.is_none() {
+ return Ok(target.clone());
+ };
+
+ let new_permissions = incoming.unwrap();
+ match &target.global_state {
+ PermissionState::Granted => Ok(UnaryPermission::<PathBuf> {
+ global_state: new_permissions.global_state,
+ granted_list: new_permissions.granted_list,
+ denied_list: new_permissions.denied_list,
+ }),
+ PermissionState::Prompt => match new_permissions.global_state {
+ //Throw
+ PermissionState::Granted => Err(custom_error(
+ "PermissionDenied",
+ "Can't escalate parent thread permissions",
+ )),
+ //Merge
+ PermissionState::Prompt => {
+ if match permission_type {
+ WorkerPermissionType::READ => check_read_permissions(
+ &new_permissions.granted_list,
+ current_permissions,
+ ),
+ WorkerPermissionType::WRITE => check_write_permissions(
+ &new_permissions.granted_list,
+ current_permissions,
+ ),
+ } {
+ Ok(UnaryPermission::<PathBuf> {
+ global_state: new_permissions.global_state,
+ granted_list: new_permissions.granted_list,
+ denied_list: target.denied_list.clone(),
+ })
+ } else {
+ Err(custom_error(
+ "PermissionDenied",
+ "Can't escalate parent thread permissions",
+ ))
+ }
+ }
+ //Copy
+ PermissionState::Denied => Ok(UnaryPermission::<PathBuf> {
+ global_state: new_permissions.global_state,
+ granted_list: new_permissions.granted_list,
+ denied_list: new_permissions.denied_list,
+ }),
+ },
+ PermissionState::Denied => match new_permissions.global_state {
+ PermissionState::Denied => Ok(UnaryPermission::<PathBuf> {
+ global_state: new_permissions.global_state,
+ granted_list: new_permissions.granted_list,
+ denied_list: new_permissions.denied_list,
+ }),
+ _ => Err(custom_error(
+ "PermissionDenied",
+ "Can't escalate parent thread permissions",
+ )),
+ },
+ }
+}
+
+fn create_worker_permissions(
+ main_thread_permissions: &Permissions,
+ permission_args: PermissionsArg,
+) -> Result<Permissions, AnyError> {
+ Ok(Permissions {
+ env: merge_permission_state(
+ &main_thread_permissions.env,
+ permission_args.env,
+ )?,
+ hrtime: merge_permission_state(
+ &main_thread_permissions.hrtime,
+ permission_args.hrtime,
+ )?,
+ net: merge_net_permissions(
+ &main_thread_permissions.net,
+ permission_args.net,
+ )?,
+ plugin: merge_permission_state(
+ &main_thread_permissions.plugin,
+ permission_args.plugin,
+ )?,
+ read: merge_read_write_permissions(
+ WorkerPermissionType::READ,
+ &main_thread_permissions.read,
+ permission_args.read,
+ &main_thread_permissions,
+ )?,
+ run: merge_permission_state(
+ &main_thread_permissions.run,
+ permission_args.run,
+ )?,
+ write: merge_read_write_permissions(
+ WorkerPermissionType::WRITE,
+ &main_thread_permissions.write,
+ permission_args.write,
+ &main_thread_permissions,
+ )?,
+ })
+}
+
+#[derive(Debug, Deserialize)]
+struct PermissionsArg {
+ #[serde(default, deserialize_with = "as_permission_state")]
+ env: Option<PermissionState>,
+ #[serde(default, deserialize_with = "as_permission_state")]
+ hrtime: Option<PermissionState>,
+ #[serde(default, deserialize_with = "as_unary_string_permission")]
+ net: Option<UnaryPermission<String>>,
+ #[serde(default, deserialize_with = "as_permission_state")]
+ plugin: Option<PermissionState>,
+ #[serde(default, deserialize_with = "as_unary_path_permission")]
+ read: Option<UnaryPermission<PathBuf>>,
+ #[serde(default, deserialize_with = "as_permission_state")]
+ run: Option<PermissionState>,
+ #[serde(default, deserialize_with = "as_unary_path_permission")]
+ write: Option<UnaryPermission<PathBuf>>,
+}
+
+fn as_permission_state<'de, D>(
+ deserializer: D,
+) -> Result<Option<PermissionState>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let value: bool = Deserialize::deserialize(deserializer)?;
+
+ match value {
+ true => Ok(Some(PermissionState::Granted)),
+ false => Ok(Some(PermissionState::Denied)),
+ }
+}
+
+struct UnaryPermissionBase {
+ global_state: PermissionState,
+ paths: Vec<String>,
+}
+
+struct ParseBooleanOrStringVec;
+
+impl<'de> de::Visitor<'de> for ParseBooleanOrStringVec {
+ type Value = UnaryPermissionBase;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("a vector of strings or a boolean")
+ }
+
+ fn visit_bool<E>(self, v: bool) -> Result<UnaryPermissionBase, E>
+ where
+ E: de::Error,
+ {
+ Ok(UnaryPermissionBase {
+ global_state: match v {
+ true => PermissionState::Granted,
+ false => PermissionState::Denied,
+ },
+ paths: Vec::new(),
+ })
+ }
+
+ fn visit_seq<V>(self, mut visitor: V) -> Result<UnaryPermissionBase, V::Error>
+ where
+ V: SeqAccess<'de>,
+ {
+ let mut vec: Vec<String> = Vec::new();
+
+ let mut value = visitor.next_element::<String>()?;
+ while value.is_some() {
+ vec.push(value.unwrap());
+ value = visitor.next_element()?;
+ }
+ Ok(UnaryPermissionBase {
+ global_state: PermissionState::Prompt,
+ paths: vec,
+ })
+ }
+}
+
+fn as_unary_string_permission<'de, D>(
+ deserializer: D,
+) -> Result<Option<UnaryPermission<String>>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let value: UnaryPermissionBase =
+ deserializer.deserialize_any(ParseBooleanOrStringVec)?;
+
+ let allowed: HashSet<String> = value.paths.into_iter().collect();
+
+ Ok(Some(UnaryPermission::<String> {
+ global_state: value.global_state,
+ granted_list: allowed,
+ ..Default::default()
+ }))
+}
+
+fn as_unary_path_permission<'de, D>(
+ deserializer: D,
+) -> Result<Option<UnaryPermission<PathBuf>>, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let value: UnaryPermissionBase =
+ deserializer.deserialize_any(ParseBooleanOrStringVec)?;
+
+ let paths: Vec<PathBuf> =
+ value.paths.into_iter().map(PathBuf::from).collect();
+
+ Ok(Some(UnaryPermission::<PathBuf> {
+ global_state: value.global_state,
+ granted_list: resolve_fs_allowlist(&Some(paths)),
+ ..Default::default()
+ }))
+}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreateWorkerArgs {
- name: Option<String>,
- specifier: String,
has_source_code: bool,
+ name: Option<String>,
+ permissions: Option<PermissionsArg>,
source_code: String,
+ specifier: String,
use_deno_namespace: bool,
}
@@ -121,9 +467,16 @@ fn op_create_worker(
let args_name = args.name;
let use_deno_namespace = args.use_deno_namespace;
if use_deno_namespace {
- super::check_unstable(state, "Worker.deno");
+ super::check_unstable(state, "Worker.deno.namespace");
}
- let permissions = state.borrow::<Permissions>().clone();
+ let parent_permissions = state.borrow::<Permissions>().clone();
+ let worker_permissions = if let Some(permissions) = args.permissions {
+ super::check_unstable(state, "Worker.deno.permissions");
+ create_worker_permissions(&parent_permissions, permissions)?
+ } else {
+ parent_permissions.clone()
+ };
+
let worker_id = state.take::<WorkerId>();
let create_module_loader = state.take::<CreateWebWorkerCbHolder>();
state.put::<CreateWebWorkerCbHolder>(create_module_loader.clone());
@@ -149,7 +502,8 @@ fn op_create_worker(
let worker = (create_module_loader.0)(CreateWebWorkerArgs {
name: worker_name,
worker_id,
- permissions,
+ parent_permissions,
+ permissions: worker_permissions,
main_module: module_specifier.clone(),
use_deno_namespace,
});