summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/js/11_workers.js128
-rw-r--r--runtime/ops/worker_host.rs376
-rw-r--r--runtime/permissions.rs2
3 files changed, 486 insertions, 20 deletions
diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js
index 62210dfae..db0bbc3c7 100644
--- a/runtime/js/11_workers.js
+++ b/runtime/js/11_workers.js
@@ -3,21 +3,24 @@
((window) => {
const core = window.Deno.core;
const { Window } = window.__bootstrap.globalInterfaces;
- const { log } = window.__bootstrap.util;
+ const { log, pathFromURL } = window.__bootstrap.util;
const { defineEventHandler } = window.__bootstrap.webUtil;
+ const build = window.__bootstrap.build.build;
function createWorker(
specifier,
hasSourceCode,
sourceCode,
useDenoNamespace,
+ permissions,
name,
) {
return core.jsonOpSync("op_create_worker", {
- specifier,
hasSourceCode,
- sourceCode,
name,
+ permissions,
+ sourceCode,
+ specifier,
useDenoNamespace,
});
}
@@ -47,14 +50,122 @@
return JSON.parse(dataJson);
}
+ /**
+ * @param {string} permission
+ * @return {boolean}
+ */
+ function parseBooleanPermission(
+ value,
+ permission,
+ ) {
+ if (value !== "inherit" && typeof value !== "boolean") {
+ throw new Error(
+ `Expected 'boolean' for ${permission} permission, ${typeof value} received`,
+ );
+ }
+ return value === "inherit" ? undefined : value;
+ }
+
+ /**
+ * @param {string} permission
+ * @return {(boolean | string[])}
+ * */
+ function parseArrayPermission(
+ value,
+ permission,
+ ) {
+ if (typeof value === "string") {
+ if (value !== "inherit") {
+ throw new Error(
+ `Expected 'array' or 'boolean' for ${permission} permission, "${value}" received`,
+ );
+ }
+ } else if (!Array.isArray(value) && typeof value !== "boolean") {
+ throw new Error(
+ `Expected 'array' or 'boolean' for ${permission} permission, ${typeof value} received`,
+ );
+ //Casts URLs to absolute routes
+ } else if (Array.isArray(value)) {
+ value = value.map((route) => {
+ if (route instanceof URL) {
+ route = pathFromURL(route);
+ }
+ return route;
+ });
+ }
+
+ return value === "inherit" ? undefined : value;
+ }
+
+ /**
+ * Normalizes data, runs checks on parameters and deletes inherited permissions
+ */
+ function parsePermissions({
+ env = "inherit",
+ hrtime = "inherit",
+ net = "inherit",
+ plugin = "inherit",
+ read = "inherit",
+ run = "inherit",
+ write = "inherit",
+ }) {
+ return {
+ env: parseBooleanPermission(env, "env"),
+ hrtime: parseBooleanPermission(hrtime, "hrtime"),
+ net: parseArrayPermission(net, "net"),
+ plugin: parseBooleanPermission(plugin, "plugin"),
+ read: parseArrayPermission(read, "read"),
+ run: parseBooleanPermission(run, "run"),
+ write: parseArrayPermission(write, "write"),
+ };
+ }
+
class Worker extends EventTarget {
#id = 0;
#name = "";
#terminated = false;
- constructor(specifier, options) {
+ constructor(specifier, options = {}) {
super();
- const { type = "classic", name = "unknown" } = options ?? {};
+ const {
+ deno = {},
+ name = "unknown",
+ type = "classic",
+ } = options;
+
+ // TODO(Soremwar)
+ // `deno: true` is kept for backwards compatibility with the previous worker
+ // options implementation. Remove for 2.0
+ let workerDenoAttributes;
+ if (deno === true) {
+ workerDenoAttributes = {
+ // Change this to enable the Deno namespace by default
+ namespace: deno,
+ permissions: null,
+ };
+ } else {
+ workerDenoAttributes = {
+ // Change this to enable the Deno namespace by default
+ namespace: !!(deno?.namespace ?? false),
+ permissions: (deno?.permissions ?? "inherit") === "inherit"
+ ? null
+ : deno?.permissions,
+ };
+
+ // If the permission option is set to false, all permissions
+ // must be removed from the worker
+ if (workerDenoAttributes.permissions === false) {
+ workerDenoAttributes.permissions = {
+ env: false,
+ hrtime: false,
+ net: false,
+ plugin: false,
+ read: false,
+ run: false,
+ write: false,
+ };
+ }
+ }
if (type !== "module") {
throw new Error(
@@ -66,13 +177,14 @@
const hasSourceCode = false;
const sourceCode = decoder.decode(new Uint8Array());
- const useDenoNamespace = options ? !!options.deno : false;
-
const { id } = createWorker(
specifier,
hasSourceCode,
sourceCode,
- useDenoNamespace,
+ workerDenoAttributes.namespace,
+ workerDenoAttributes.permissions === null
+ ? null
+ : parsePermissions(workerDenoAttributes.permissions),
options?.name,
);
this.#id = id;
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,
});
diff --git a/runtime/permissions.rs b/runtime/permissions.rs
index c50783f9d..16a611690 100644
--- a/runtime/permissions.rs
+++ b/runtime/permissions.rs
@@ -78,7 +78,7 @@ pub struct Permissions {
pub hrtime: PermissionState,
}
-fn resolve_fs_allowlist(allow: &Option<Vec<PathBuf>>) -> HashSet<PathBuf> {
+pub fn resolve_fs_allowlist(allow: &Option<Vec<PathBuf>>) -> HashSet<PathBuf> {
if let Some(v) = allow {
v.iter()
.map(|raw_path| resolve_from_cwd(Path::new(&raw_path)).unwrap())