summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/dts/lib.deno.shared_globals.d.ts56
-rw-r--r--cli/main.rs5
-rw-r--r--cli/module_loader.rs22
-rw-r--r--cli/tests/unstable_worker.ts6
-rw-r--r--cli/tests/workers/bench_worker.ts (renamed from cli/tests/subdir/bench_worker.ts)0
-rw-r--r--cli/tests/workers/busy_worker.js (renamed from cli/tests/subdir/busy_worker.js)0
-rw-r--r--cli/tests/workers/deno_worker.ts (renamed from cli/tests/subdir/deno_worker.ts)0
-rw-r--r--cli/tests/workers/event_worker.js (renamed from cli/tests/subdir/event_worker.js)0
-rw-r--r--cli/tests/workers/event_worker_scope.js (renamed from cli/tests/subdir/event_worker_scope.js)0
-rw-r--r--cli/tests/workers/fetching_worker.js (renamed from cli/tests/subdir/fetching_worker.js)2
-rw-r--r--cli/tests/workers/immediately_close_worker.js (renamed from cli/tests/immediately_close_worker.js)0
-rw-r--r--cli/tests/workers/nested_worker.js (renamed from cli/tests/subdir/nested_worker.js)0
-rw-r--r--cli/tests/workers/no_permissions_worker.js17
-rw-r--r--cli/tests/workers/non_deno_worker.js (renamed from cli/tests/subdir/non_deno_worker.js)0
-rw-r--r--cli/tests/workers/parent_read_check_granular_worker.js43
-rw-r--r--cli/tests/workers/parent_read_check_worker.js27
-rw-r--r--cli/tests/workers/racy_worker.js (renamed from cli/tests/subdir/racy_worker.js)0
-rw-r--r--cli/tests/workers/read_check_granular_worker.js13
-rw-r--r--cli/tests/workers/read_check_worker.js7
-rw-r--r--cli/tests/workers/sibling_worker.js (renamed from cli/tests/subdir/sibling_worker.js)0
-rw-r--r--cli/tests/workers/test_worker.js (renamed from cli/tests/subdir/test_worker.js)0
-rw-r--r--cli/tests/workers/test_worker.ts (renamed from cli/tests/subdir/test_worker.ts)0
-rw-r--r--cli/tests/workers/throwing_worker.js (renamed from cli/tests/subdir/throwing_worker.js)0
-rw-r--r--cli/tests/workers/worker_crypto.js (renamed from cli/tests/subdir/worker_crypto.js)0
-rw-r--r--cli/tests/workers/worker_globals.ts (renamed from cli/tests/subdir/worker_globals.ts)0
-rw-r--r--cli/tests/workers/worker_unstable.ts (renamed from cli/tests/subdir/worker_unstable.ts)0
-rw-r--r--cli/tests/workers_round_robin_bench.ts2
-rw-r--r--cli/tests/workers_startup_bench.ts2
-rw-r--r--cli/tests/workers_test.ts280
-rw-r--r--docs/runtime/workers.md147
-rw-r--r--runtime/js/11_workers.js128
-rw-r--r--runtime/ops/worker_host.rs376
-rw-r--r--runtime/permissions.rs2
33 files changed, 1062 insertions, 73 deletions
diff --git a/cli/dts/lib.deno.shared_globals.d.ts b/cli/dts/lib.deno.shared_globals.d.ts
index e4d763ec0..74abbc95b 100644
--- a/cli/dts/lib.deno.shared_globals.d.ts
+++ b/cli/dts/lib.deno.shared_globals.d.ts
@@ -662,24 +662,33 @@ declare class Worker extends EventTarget {
options?: {
type?: "classic" | "module";
name?: string;
- /** UNSTABLE: New API. Expect many changes; most likely this
- * field will be made into an object for more granular
- * configuration of worker thread (permissions, import map, etc.).
+ /** UNSTABLE: New API.
*
- * Set to `true` to make `Deno` namespace and all of its methods
- * available to worker thread.
- *
- * Currently worker inherits permissions from main thread (permissions
- * given using `--allow-*` flags).
- * Configurable permissions are on the roadmap to be implemented.
+ * Set deno.namespace to `true` to make `Deno` namespace and all of its methods
+ * available to worker thread. The namespace is disabled by default.
+ *
+ * Configure deno.permissions options to change the level of access the worker will
+ * have. By default it will inherit the permissions of its parent thread. The permissions
+ * of a worker can't be extended beyond its parent's permissions reach.
+ * - "inherit" will take the permissions of the thread the worker is created in
+ * - You can disable/enable permissions all together by passing a boolean
+ * - You can provide a list of routes relative to the file the worker
+ * is created in to limit the access of the worker (read/write permissions only)
*
* Example:
*
* ```ts
* // mod.ts
* const worker = new Worker(
- * new URL("deno_worker.ts", import.meta.url).href,
- * { type: "module", deno: true }
+ * new URL("deno_worker.ts", import.meta.url).href, {
+ * type: "module",
+ * deno: {
+ * namespace: true,
+ * permissions: {
+ * read: true,
+ * },
+ * },
+ * }
* );
* worker.postMessage({ cmd: "readFile", fileName: "./log.txt" });
*
@@ -707,7 +716,30 @@ declare class Worker extends EventTarget {
* hello world2
*
*/
- deno?: boolean;
+ // TODO(Soremwar)
+ // `deno: true` is kept for backwards compatibility with the previous worker
+ // options implementation. Remove for 2.0
+ deno?: true | {
+ namespace?: boolean;
+ /** Set to false to disable all the permissions in the worker */
+ permissions?: "inherit" | false | {
+ env?: "inherit" | boolean;
+ hrtime?: "inherit" | boolean;
+ /**
+ * The format of the net access list must be `hostname[:port]`
+ * in order to be resolved
+ *
+ * ```
+ * net: ["https://deno.land", "localhost:8080"],
+ * ```
+ * */
+ net?: "inherit" | boolean | string[];
+ plugin?: "inherit" | boolean;
+ read?: "inherit" | boolean | Array<string | URL>;
+ run?: "inherit" | boolean;
+ write?: "inherit" | boolean | Array<string | URL>;
+ };
+ };
},
);
postMessage(message: any, transfer: ArrayBuffer[]): void;
diff --git a/cli/main.rs b/cli/main.rs
index b622cccf3..a247389d9 100644
--- a/cli/main.rs
+++ b/cli/main.rs
@@ -100,7 +100,10 @@ fn create_web_worker_callback(
|| program_state.coverage_dir.is_some();
let maybe_inspector_server = program_state.maybe_inspector_server.clone();
- let module_loader = CliModuleLoader::new_for_worker(program_state.clone());
+ let module_loader = CliModuleLoader::new_for_worker(
+ program_state.clone(),
+ args.parent_permissions.clone(),
+ );
let create_web_worker_cb =
create_web_worker_callback(program_state.clone());
diff --git a/cli/module_loader.rs b/cli/module_loader.rs
index aab951c4a..c91a2336b 100644
--- a/cli/module_loader.rs
+++ b/cli/module_loader.rs
@@ -22,6 +22,10 @@ pub struct CliModuleLoader {
/// import map file will be resolved and set.
pub import_map: Option<ImportMap>,
pub lib: TypeLib,
+ /// The initial set of permissions used to resolve the imports in the worker.
+ /// They are decoupled from the worker permissions since read access errors
+ /// must be raised based on the parent thread permissions
+ pub initial_permissions: Rc<RefCell<Option<Permissions>>>,
pub program_state: Arc<ProgramState>,
}
@@ -38,11 +42,15 @@ impl CliModuleLoader {
Rc::new(CliModuleLoader {
import_map,
lib,
+ initial_permissions: Rc::new(RefCell::new(None)),
program_state,
})
}
- pub fn new_for_worker(program_state: Arc<ProgramState>) -> Rc<Self> {
+ pub fn new_for_worker(
+ program_state: Arc<ProgramState>,
+ permissions: Permissions,
+ ) -> Rc<Self> {
let lib = if program_state.flags.unstable {
TypeLib::UnstableDenoWorker
} else {
@@ -52,6 +60,7 @@ impl CliModuleLoader {
Rc::new(CliModuleLoader {
import_map: None,
lib,
+ initial_permissions: Rc::new(RefCell::new(Some(permissions))),
program_state,
})
}
@@ -118,7 +127,16 @@ impl ModuleLoader for CliModuleLoader {
let state = op_state.borrow();
// The permissions that should be applied to any dynamically imported module
- let dynamic_permissions = state.borrow::<Permissions>().clone();
+ let dynamic_permissions =
+ // If there are initial permissions assigned to the loader take them
+ // and use only once for top level module load.
+ // Otherwise use permissions assigned to the current worker.
+ if let Some(permissions) = self.initial_permissions.borrow_mut().take() {
+ permissions
+ } else {
+ state.borrow::<Permissions>().clone()
+ };
+
let lib = self.lib.clone();
drop(state);
diff --git a/cli/tests/unstable_worker.ts b/cli/tests/unstable_worker.ts
index 6b5304edf..429754dfe 100644
--- a/cli/tests/unstable_worker.ts
+++ b/cli/tests/unstable_worker.ts
@@ -1,8 +1,10 @@
const w = new Worker(
- new URL("subdir/worker_unstable.ts", import.meta.url).href,
+ new URL("workers/worker_unstable.ts", import.meta.url).href,
{
type: "module",
- deno: true,
+ deno: {
+ namespace: true,
+ },
name: "Unstable Worker",
},
);
diff --git a/cli/tests/subdir/bench_worker.ts b/cli/tests/workers/bench_worker.ts
index 7e85eed03..7e85eed03 100644
--- a/cli/tests/subdir/bench_worker.ts
+++ b/cli/tests/workers/bench_worker.ts
diff --git a/cli/tests/subdir/busy_worker.js b/cli/tests/workers/busy_worker.js
index 7deba0321..7deba0321 100644
--- a/cli/tests/subdir/busy_worker.js
+++ b/cli/tests/workers/busy_worker.js
diff --git a/cli/tests/subdir/deno_worker.ts b/cli/tests/workers/deno_worker.ts
index 6a57c47f0..6a57c47f0 100644
--- a/cli/tests/subdir/deno_worker.ts
+++ b/cli/tests/workers/deno_worker.ts
diff --git a/cli/tests/subdir/event_worker.js b/cli/tests/workers/event_worker.js
index 849b6026c..849b6026c 100644
--- a/cli/tests/subdir/event_worker.js
+++ b/cli/tests/workers/event_worker.js
diff --git a/cli/tests/subdir/event_worker_scope.js b/cli/tests/workers/event_worker_scope.js
index 0381801a8..0381801a8 100644
--- a/cli/tests/subdir/event_worker_scope.js
+++ b/cli/tests/workers/event_worker_scope.js
diff --git a/cli/tests/subdir/fetching_worker.js b/cli/tests/workers/fetching_worker.js
index 3e33d1c9e..e1bcdf911 100644
--- a/cli/tests/subdir/fetching_worker.js
+++ b/cli/tests/workers/fetching_worker.js
@@ -1,5 +1,5 @@
const r = await fetch(
- "http://localhost:4545/cli/tests/subdir/fetching_worker.js",
+ "http://localhost:4545/cli/tests/workers/fetching_worker.js",
);
await r.text();
postMessage("Done!");
diff --git a/cli/tests/immediately_close_worker.js b/cli/tests/workers/immediately_close_worker.js
index 8fd27343a..8fd27343a 100644
--- a/cli/tests/immediately_close_worker.js
+++ b/cli/tests/workers/immediately_close_worker.js
diff --git a/cli/tests/subdir/nested_worker.js b/cli/tests/workers/nested_worker.js
index 4b51b8763..4b51b8763 100644
--- a/cli/tests/subdir/nested_worker.js
+++ b/cli/tests/workers/nested_worker.js
diff --git a/cli/tests/workers/no_permissions_worker.js b/cli/tests/workers/no_permissions_worker.js
new file mode 100644
index 000000000..8a4f79d57
--- /dev/null
+++ b/cli/tests/workers/no_permissions_worker.js
@@ -0,0 +1,17 @@
+self.onmessage = async () => {
+ const hrtime = await Deno.permissions.query({ name: "hrtime" });
+ const net = await Deno.permissions.query({ name: "net" });
+ const plugin = await Deno.permissions.query({ name: "plugin" });
+ const read = await Deno.permissions.query({ name: "read" });
+ const run = await Deno.permissions.query({ name: "run" });
+ const write = await Deno.permissions.query({ name: "write" });
+ self.postMessage(
+ hrtime.state === "denied" &&
+ net.state === "denied" &&
+ plugin.state === "denied" &&
+ read.state === "denied" &&
+ run.state === "denied" &&
+ write.state === "denied",
+ );
+ self.close();
+};
diff --git a/cli/tests/subdir/non_deno_worker.js b/cli/tests/workers/non_deno_worker.js
index 773721560..773721560 100644
--- a/cli/tests/subdir/non_deno_worker.js
+++ b/cli/tests/workers/non_deno_worker.js
diff --git a/cli/tests/workers/parent_read_check_granular_worker.js b/cli/tests/workers/parent_read_check_granular_worker.js
new file mode 100644
index 000000000..1a7182e17
--- /dev/null
+++ b/cli/tests/workers/parent_read_check_granular_worker.js
@@ -0,0 +1,43 @@
+import { fromFileUrl } from "../../../std/path/mod.ts";
+
+const worker = new Worker(
+ new URL("./read_check_granular_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: [],
+ },
+ },
+ },
+);
+
+let received = 0;
+const messages = [];
+
+worker.onmessage = ({ data: childResponse }) => {
+ received++;
+ postMessage({
+ childHasPermission: childResponse.hasPermission,
+ index: childResponse.index,
+ parentHasPermission: messages[childResponse.index],
+ });
+ if (received === messages.length) {
+ worker.terminate();
+ }
+};
+
+onmessage = async ({ data }) => {
+ const { state } = await Deno.permissions.query({
+ name: "read",
+ path: fromFileUrl(new URL(data.route, import.meta.url)),
+ });
+
+ messages[data.index] = state === "granted";
+
+ worker.postMessage({
+ index: data.index,
+ route: data.route,
+ });
+};
diff --git a/cli/tests/workers/parent_read_check_worker.js b/cli/tests/workers/parent_read_check_worker.js
new file mode 100644
index 000000000..ec92cca3f
--- /dev/null
+++ b/cli/tests/workers/parent_read_check_worker.js
@@ -0,0 +1,27 @@
+onmessage = async () => {
+ const { state } = await Deno.permissions.query({
+ name: "read",
+ });
+
+ const worker = new Worker(
+ new URL("./read_check_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: false,
+ },
+ },
+ },
+ );
+
+ worker.onmessage = ({ data: childHasPermission }) => {
+ postMessage({
+ parentHasPermission: state === "granted",
+ childHasPermission,
+ });
+ close();
+ };
+ worker.postMessage(null);
+};
diff --git a/cli/tests/subdir/racy_worker.js b/cli/tests/workers/racy_worker.js
index 83756b791..83756b791 100644
--- a/cli/tests/subdir/racy_worker.js
+++ b/cli/tests/workers/racy_worker.js
diff --git a/cli/tests/workers/read_check_granular_worker.js b/cli/tests/workers/read_check_granular_worker.js
new file mode 100644
index 000000000..4eddb7a75
--- /dev/null
+++ b/cli/tests/workers/read_check_granular_worker.js
@@ -0,0 +1,13 @@
+import { fromFileUrl } from "../../../std/path/mod.ts";
+
+onmessage = async ({ data }) => {
+ const { state } = await Deno.permissions.query({
+ name: "read",
+ path: fromFileUrl(new URL(data.route, import.meta.url)),
+ });
+
+ postMessage({
+ hasPermission: state === "granted",
+ index: data.index,
+ });
+};
diff --git a/cli/tests/workers/read_check_worker.js b/cli/tests/workers/read_check_worker.js
new file mode 100644
index 000000000..2ad01bf5b
--- /dev/null
+++ b/cli/tests/workers/read_check_worker.js
@@ -0,0 +1,7 @@
+onmessage = async () => {
+ const { state } = await Deno.permissions.query({
+ name: "read",
+ });
+ postMessage(state === "granted");
+ close();
+};
diff --git a/cli/tests/subdir/sibling_worker.js b/cli/tests/workers/sibling_worker.js
index 99707e5d6..99707e5d6 100644
--- a/cli/tests/subdir/sibling_worker.js
+++ b/cli/tests/workers/sibling_worker.js
diff --git a/cli/tests/subdir/test_worker.js b/cli/tests/workers/test_worker.js
index 4260975a6..4260975a6 100644
--- a/cli/tests/subdir/test_worker.js
+++ b/cli/tests/workers/test_worker.js
diff --git a/cli/tests/subdir/test_worker.ts b/cli/tests/workers/test_worker.ts
index ca79dcfe4..ca79dcfe4 100644
--- a/cli/tests/subdir/test_worker.ts
+++ b/cli/tests/workers/test_worker.ts
diff --git a/cli/tests/subdir/throwing_worker.js b/cli/tests/workers/throwing_worker.js
index 56ee4ff88..56ee4ff88 100644
--- a/cli/tests/subdir/throwing_worker.js
+++ b/cli/tests/workers/throwing_worker.js
diff --git a/cli/tests/subdir/worker_crypto.js b/cli/tests/workers/worker_crypto.js
index a86340005..a86340005 100644
--- a/cli/tests/subdir/worker_crypto.js
+++ b/cli/tests/workers/worker_crypto.js
diff --git a/cli/tests/subdir/worker_globals.ts b/cli/tests/workers/worker_globals.ts
index a9e7efd85..a9e7efd85 100644
--- a/cli/tests/subdir/worker_globals.ts
+++ b/cli/tests/workers/worker_globals.ts
diff --git a/cli/tests/subdir/worker_unstable.ts b/cli/tests/workers/worker_unstable.ts
index a5b5f7ba2..a5b5f7ba2 100644
--- a/cli/tests/subdir/worker_unstable.ts
+++ b/cli/tests/workers/worker_unstable.ts
diff --git a/cli/tests/workers_round_robin_bench.ts b/cli/tests/workers_round_robin_bench.ts
index 8467480b8..461e86f91 100644
--- a/cli/tests/workers_round_robin_bench.ts
+++ b/cli/tests/workers_round_robin_bench.ts
@@ -22,7 +22,7 @@ async function main(): Promise<void> {
const workers: Array<[Map<number, Deferred<string>>, Worker]> = [];
for (let i = 1; i <= workerCount; ++i) {
const worker = new Worker(
- new URL("subdir/bench_worker.ts", import.meta.url).href,
+ new URL("workers/bench_worker.ts", import.meta.url).href,
{ type: "module" },
);
const promise = deferred();
diff --git a/cli/tests/workers_startup_bench.ts b/cli/tests/workers_startup_bench.ts
index 0d58b7912..f85bd13a1 100644
--- a/cli/tests/workers_startup_bench.ts
+++ b/cli/tests/workers_startup_bench.ts
@@ -5,7 +5,7 @@ async function bench(): Promise<void> {
const workers: Worker[] = [];
for (let i = 1; i <= workerCount; ++i) {
const worker = new Worker(
- new URL("subdir/bench_worker.ts", import.meta.url).href,
+ new URL("workers/bench_worker.ts", import.meta.url).href,
{ type: "module" },
);
const promise = new Promise<void>((resolve): void => {
diff --git a/cli/tests/workers_test.ts b/cli/tests/workers_test.ts
index 3bfe0181a..2dfc7e26b 100644
--- a/cli/tests/workers_test.ts
+++ b/cli/tests/workers_test.ts
@@ -2,12 +2,12 @@
// Requires to be run with `--allow-net` flag
-// FIXME(bartlomieju): this file is an integration test only because
-// workers are leaking ops at the moment - `worker.terminate()` is not
-// yet implemented. Once it gets implemented this file should be
-// again moved to `cli/js/` as an unit test file.
-
-import { assert, assertEquals } from "../../std/testing/asserts.ts";
+import {
+ assert,
+ assertEquals,
+ assertThrows,
+ fail,
+} from "../../std/testing/asserts.ts";
import { deferred } from "../../std/async/deferred.ts";
Deno.test({
@@ -16,11 +16,11 @@ Deno.test({
const promise = deferred();
const jsWorker = new Worker(
- new URL("subdir/test_worker.js", import.meta.url).href,
+ new URL("workers/test_worker.js", import.meta.url).href,
{ type: "module" },
);
const tsWorker = new Worker(
- new URL("subdir/test_worker.ts", import.meta.url).href,
+ new URL("workers/test_worker.ts", import.meta.url).href,
{ type: "module", name: "tsWorker" },
);
@@ -73,7 +73,7 @@ Deno.test({
const promise = deferred();
const nestedWorker = new Worker(
- new URL("subdir/nested_worker.js", import.meta.url).href,
+ new URL("workers/nested_worker.js", import.meta.url).href,
{ type: "module", name: "nested" },
);
@@ -93,7 +93,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const throwingWorker = new Worker(
- new URL("subdir/throwing_worker.js", import.meta.url).href,
+ new URL("workers/throwing_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -114,7 +114,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const w = new Worker(
- new URL("subdir/worker_globals.ts", import.meta.url).href,
+ new URL("workers/worker_globals.ts", import.meta.url).href,
{ type: "module" },
);
w.onmessage = (e): void => {
@@ -133,7 +133,7 @@ Deno.test({
const promise = deferred();
const fetchingWorker = new Worker(
- new URL("subdir/fetching_worker.js", import.meta.url).href,
+ new URL("workers/fetching_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -160,7 +160,7 @@ Deno.test({
const promise = deferred();
const busyWorker = new Worker(
- new URL("subdir/busy_worker.js", import.meta.url).href,
+ new URL("workers/busy_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -193,7 +193,7 @@ Deno.test({
const promise = deferred();
const racyWorker = new Worker(
- new URL("subdir/racy_worker.js", import.meta.url).href,
+ new URL("workers/racy_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -221,7 +221,7 @@ Deno.test({
const promise2 = deferred();
const worker = new Worker(
- new URL("subdir/event_worker.js", import.meta.url).href,
+ new URL("workers/event_worker.js", import.meta.url).href,
{ type: "module" },
);
@@ -265,7 +265,7 @@ Deno.test({
const promise1 = deferred();
const worker = new Worker(
- new URL("subdir/event_worker_scope.js", import.meta.url).href,
+ new URL("workers/event_worker_scope.js", import.meta.url).href,
{ type: "module" },
);
@@ -294,12 +294,18 @@ Deno.test({
const promise2 = deferred();
const regularWorker = new Worker(
- new URL("subdir/non_deno_worker.js", import.meta.url).href,
+ new URL("workers/non_deno_worker.js", import.meta.url).href,
{ type: "module" },
);
const denoWorker = new Worker(
- new URL("subdir/deno_worker.ts", import.meta.url).href,
- { type: "module", deno: true },
+ new URL("workers/deno_worker.ts", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: "inherit",
+ },
+ },
);
regularWorker.onmessage = (e): void => {
@@ -326,7 +332,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const w = new Worker(
- new URL("subdir/worker_crypto.js", import.meta.url).href,
+ new URL("workers/worker_crypto.js", import.meta.url).href,
{ type: "module" },
);
w.onmessage = (e): void => {
@@ -344,7 +350,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const w = new Worker(
- new URL("subdir/test_worker.ts", import.meta.url).href,
+ new URL("workers/test_worker.ts", import.meta.url).href,
{ type: "module", name: "tsWorker" },
);
const arr: number[] = [];
@@ -368,7 +374,7 @@ Deno.test({
fn: async function (): Promise<void> {
const promise = deferred();
const w = new Worker(
- new URL("./immediately_close_worker.js", import.meta.url).href,
+ new URL("./workers/immediately_close_worker.js", import.meta.url).href,
{ type: "module" },
);
setTimeout(() => {
@@ -378,3 +384,233 @@ Deno.test({
w.terminate();
},
});
+
+Deno.test("Worker inherits permissions", async function () {
+ const promise = deferred();
+ const worker = new Worker(
+ new URL("./workers/read_check_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: "inherit",
+ },
+ },
+ );
+
+ worker.onmessage = ({ data: hasPermission }) => {
+ assert(hasPermission);
+ promise.resolve();
+ };
+
+ worker.postMessage(null);
+
+ await promise;
+ worker.terminate();
+});
+
+Deno.test("Worker limit children permissions", async function () {
+ const promise = deferred();
+ const worker = new Worker(
+ new URL("./workers/read_check_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: false,
+ },
+ },
+ },
+ );
+
+ worker.onmessage = ({ data: hasPermission }) => {
+ assert(!hasPermission);
+ promise.resolve();
+ };
+
+ worker.postMessage(null);
+
+ await promise;
+ worker.terminate();
+});
+
+Deno.test("Worker limit children permissions granularly", async function () {
+ const promise = deferred();
+ const worker = new Worker(
+ new URL("./workers/read_check_granular_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: [
+ new URL("./workers/read_check_worker.js", import.meta.url),
+ ],
+ },
+ },
+ },
+ );
+
+ //Routes are relative to the spawned worker location
+ const routes = [
+ { permission: false, route: "read_check_granular_worker.js" },
+ { permission: true, route: "read_check_worker.js" },
+ ];
+
+ let checked = 0;
+ worker.onmessage = ({ data }) => {
+ checked++;
+ assertEquals(data.hasPermission, routes[data.index].permission);
+ routes.shift();
+ if (checked === routes.length) {
+ promise.resolve();
+ }
+ };
+
+ routes.forEach(({ route }, index) =>
+ worker.postMessage({
+ index,
+ route,
+ })
+ );
+
+ await promise;
+ worker.terminate();
+});
+
+Deno.test("Nested worker limit children permissions", async function () {
+ const promise = deferred();
+
+ /** This worker has read permissions but doesn't grant them to its children */
+ const worker = new Worker(
+ new URL("./workers/parent_read_check_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: "inherit",
+ },
+ },
+ );
+
+ worker.onmessage = ({ data }) => {
+ assert(data.parentHasPermission);
+ assert(!data.childHasPermission);
+ promise.resolve();
+ };
+
+ worker.postMessage(null);
+
+ await promise;
+ worker.terminate();
+});
+
+Deno.test("Nested worker limit children permissions granularly", async function () {
+ const promise = deferred();
+
+ /** This worker has read permissions but doesn't grant them to its children */
+ const worker = new Worker(
+ new URL("./workers/parent_read_check_granular_worker.js", import.meta.url)
+ .href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ read: [
+ new URL("./workers/read_check_granular_worker.js", import.meta.url),
+ ],
+ },
+ },
+ },
+ );
+
+ //Routes are relative to the spawned worker location
+ const routes = [
+ {
+ childHasPermission: false,
+ parentHasPermission: true,
+ route: "read_check_granular_worker.js",
+ },
+ {
+ childHasPermission: false,
+ parentHasPermission: false,
+ route: "read_check_worker.js",
+ },
+ ];
+
+ let checked = 0;
+ worker.onmessage = ({ data }) => {
+ checked++;
+ assertEquals(
+ data.childHasPermission,
+ routes[data.index].childHasPermission,
+ );
+ assertEquals(
+ data.parentHasPermission,
+ routes[data.index].parentHasPermission,
+ );
+ if (checked === routes.length) {
+ promise.resolve();
+ }
+ };
+
+ // Index needed cause requests will be handled asynchronously
+ routes.forEach(({ route }, index) =>
+ worker.postMessage({
+ index,
+ route,
+ })
+ );
+
+ await promise;
+ worker.terminate();
+});
+
+// This test relies on env permissions not being granted on main thread
+Deno.test("Worker initialization throws on worker permissions greater than parent thread permissions", function () {
+ assertThrows(
+ () => {
+ const worker = new Worker(
+ new URL("./workers/deno_worker.ts", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ env: true,
+ },
+ },
+ },
+ );
+ worker.terminate();
+ },
+ Deno.errors.PermissionDenied,
+ "Can't escalate parent thread permissions",
+ );
+});
+
+Deno.test("Worker with disabled permissions", async function () {
+ const promise = deferred();
+
+ const worker = new Worker(
+ new URL("./workers/no_permissions_worker.js", import.meta.url).href,
+ {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: false,
+ },
+ },
+ );
+
+ worker.onmessage = ({ data: sandboxed }) => {
+ assert(sandboxed);
+ promise.resolve();
+ };
+
+ worker.postMessage(null);
+ await promise;
+ worker.terminate();
+});
diff --git a/docs/runtime/workers.md b/docs/runtime/workers.md
index 5dcf05915..caed638b0 100644
--- a/docs/runtime/workers.md
+++ b/docs/runtime/workers.md
@@ -16,15 +16,15 @@ specifier for some nearby script.
```ts
// Good
-new Worker(new URL("worker.js", import.meta.url).href, { type: "module" });
+new Worker(new URL("./worker.js", import.meta.url).href, { type: "module" });
// Bad
-new Worker(new URL("worker.js", import.meta.url).href);
-new Worker(new URL("worker.js", import.meta.url).href, { type: "classic" });
+new Worker(new URL("./worker.js", import.meta.url).href);
+new Worker(new URL("./worker.js", import.meta.url).href, { type: "classic" });
new Worker("./worker.js", { type: "module" });
```
-### Permissions
+### Instantiation permissions
Creating a new `Worker` instance is similar to a dynamic import; therefore Deno
requires appropriate permission for this action.
@@ -34,7 +34,7 @@ For workers using local modules; `--allow-read` permission is required:
**main.ts**
```ts
-new Worker(new URL("worker.ts", import.meta.url).href, { type: "module" });
+new Worker(new URL("./worker.ts", import.meta.url).href, { type: "module" });
```
**worker.ts**
@@ -82,14 +82,17 @@ hello world
By default the `Deno` namespace is not available in worker scope.
-To add the `Deno` namespace pass `deno: true` option when creating new worker:
+To enable the `Deno` namespace pass `deno.namespace = true` option when creating
+new worker:
**main.js**
```ts
-const worker = new Worker(new URL("worker.js", import.meta.url).href, {
+const worker = new Worker(new URL("./worker.js", import.meta.url).href, {
type: "module",
- deno: true,
+ deno: {
+ namespace: true,
+ },
});
worker.postMessage({ filename: "./log.txt" });
```
@@ -116,7 +119,129 @@ $ deno run --allow-read --unstable main.js
hello world
```
-When the `Deno` namespace is available in worker scope, the worker inherits its
-parent process' permissions (the ones specified using `--allow-*` flags).
+### Specifying worker permissions
-We intend to make permissions configurable for workers.
+> This is an unstable Deno feature. Learn more about
+> [unstable features](./stability.md).
+
+The permissions available for the worker are analogous to the CLI permission
+flags, meaning every permission enabled there can be disabled at the level of
+the Worker API. You can find a more detailed description of each of the
+permission options [here](../getting_started/permissions.md).
+
+By default a worker will inherit permissions from the thread it was created in,
+however in order to allow users to limit the access of this worker we provide
+the `deno.permissions` option in the worker API.
+
+- For permissions that support granular access you can pass in a list of the
+ desired resources the worker will have access to, and for those who only have
+ the on/off option you can pass true/false respectively.
+
+ ```ts
+ const worker = new Worker(new URL("./worker.js", import.meta.url).href, {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: [
+ net: [
+ "https://deno.land/",
+ ],
+ read: [
+ new URL("./file_1.txt", import.meta.url),
+ new URL("./file_2.txt", import.meta.url),
+ ],
+ write: false,
+ ],
+ },
+ });
+ ```
+
+- Granular access permissions receive both absolute and relative routes as
+ arguments, however take into account that relative routes will be resolved
+ relative to the file the worker is instantiated in, not the path the worker
+ file is currently in
+
+ ```ts
+ const worker = new Worker(new URL("./worker/worker.js", import.meta.url).href, {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: [
+ read: [
+ "/home/user/Documents/deno/worker/file_1.txt",
+ "./worker/file_2.txt",
+ ],
+ ],
+ },
+ });
+ ```
+
+- Both `deno.permissions` and its children support the option `"inherit"`, which
+ implies it will borrow its parent permissions.
+
+ ```ts
+ // This worker will inherit its parent permissions
+ const worker = new Worker(new URL("./worker.js", import.meta.url).href, {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: "inherit",
+ },
+ });
+ ```
+
+ ```ts
+ // This worker will inherit only the net permissions of its parent
+ const worker = new Worker(new URL("./worker.js", import.meta.url).href, {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ env: false,
+ hrtime: false,
+ net: "inherit",
+ plugin: false,
+ read: false,
+ run: false,
+ write: false,
+ },
+ },
+ });
+ ```
+
+- Not specifying the `deno.permissions` option or one of its children will cause
+ the worker to inherit by default.
+
+ ```ts
+ // This worker will inherit its parent permissions
+ const worker = new Worker(new URL("./worker.js", import.meta.url).href, {
+ type: "module",
+ });
+ ```
+
+ ```ts
+ // This worker will inherit all the permissions of its parent BUT net
+ const worker = new Worker(new URL("./worker.js", import.meta.url).href, {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: {
+ net: false,
+ },
+ },
+ });
+ ```
+
+- You can disable the permissions of the worker all together by passing false to
+ the `deno.permissions` option.
+
+ ```ts
+ // This worker will not have any permissions enabled
+ const worker = new Worker(new URL("./worker.js", import.meta.url).href, {
+ type: "module",
+ deno: {
+ namespace: true,
+ permissions: false,
+ },
+ });
+ ```
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())