From adc2f08c178f51b3ddd5f1c2e3d7f5603424521e Mon Sep 17 00:00:00 2001 From: Steven Guerrero Date: Wed, 6 Jan 2021 15:31:16 -0500 Subject: feat: Add configurable permissions for Workers (#8215) This commit adds new option to "Worker" Web API that allows to configure permissions. New "Worker.deno.permissions" option can be used to define limited permissions to the worker thread by either: - inherit set of parent thread permissions - use limited subset of parent thread permissions - revoke all permissions (full sandbox) In order to achieve this functionality "CliModuleLoader" was modified to accept "initial permissions", which are used for top module loading (ie. uses parent thread permission set to load top level module of a worker). --- cli/tests/immediately_close_worker.js | 1 - cli/tests/subdir/bench_worker.ts | 21 -- cli/tests/subdir/busy_worker.js | 8 - cli/tests/subdir/deno_worker.ts | 7 - cli/tests/subdir/event_worker.js | 7 - cli/tests/subdir/event_worker_scope.js | 43 ---- cli/tests/subdir/fetching_worker.js | 6 - cli/tests/subdir/nested_worker.js | 18 -- cli/tests/subdir/non_deno_worker.js | 7 - cli/tests/subdir/racy_worker.js | 21 -- cli/tests/subdir/sibling_worker.js | 4 - cli/tests/subdir/test_worker.js | 19 -- cli/tests/subdir/test_worker.ts | 8 - cli/tests/subdir/throwing_worker.js | 2 - cli/tests/subdir/worker_crypto.js | 3 - cli/tests/subdir/worker_globals.ts | 10 - cli/tests/subdir/worker_unstable.ts | 5 - cli/tests/unstable_worker.ts | 6 +- cli/tests/workers/bench_worker.ts | 21 ++ cli/tests/workers/busy_worker.js | 8 + cli/tests/workers/deno_worker.ts | 7 + cli/tests/workers/event_worker.js | 7 + cli/tests/workers/event_worker_scope.js | 43 ++++ cli/tests/workers/fetching_worker.js | 6 + cli/tests/workers/immediately_close_worker.js | 1 + cli/tests/workers/nested_worker.js | 18 ++ cli/tests/workers/no_permissions_worker.js | 17 ++ cli/tests/workers/non_deno_worker.js | 7 + .../workers/parent_read_check_granular_worker.js | 43 ++++ cli/tests/workers/parent_read_check_worker.js | 27 ++ cli/tests/workers/racy_worker.js | 21 ++ cli/tests/workers/read_check_granular_worker.js | 13 + cli/tests/workers/read_check_worker.js | 7 + cli/tests/workers/sibling_worker.js | 4 + cli/tests/workers/test_worker.js | 19 ++ cli/tests/workers/test_worker.ts | 8 + cli/tests/workers/throwing_worker.js | 2 + cli/tests/workers/worker_crypto.js | 3 + cli/tests/workers/worker_globals.ts | 10 + cli/tests/workers/worker_unstable.ts | 5 + cli/tests/workers_round_robin_bench.ts | 2 +- cli/tests/workers_startup_bench.ts | 2 +- cli/tests/workers_test.ts | 280 +++++++++++++++++++-- 43 files changed, 561 insertions(+), 216 deletions(-) delete mode 100644 cli/tests/immediately_close_worker.js delete mode 100644 cli/tests/subdir/bench_worker.ts delete mode 100644 cli/tests/subdir/busy_worker.js delete mode 100644 cli/tests/subdir/deno_worker.ts delete mode 100644 cli/tests/subdir/event_worker.js delete mode 100644 cli/tests/subdir/event_worker_scope.js delete mode 100644 cli/tests/subdir/fetching_worker.js delete mode 100644 cli/tests/subdir/nested_worker.js delete mode 100644 cli/tests/subdir/non_deno_worker.js delete mode 100644 cli/tests/subdir/racy_worker.js delete mode 100644 cli/tests/subdir/sibling_worker.js delete mode 100644 cli/tests/subdir/test_worker.js delete mode 100644 cli/tests/subdir/test_worker.ts delete mode 100644 cli/tests/subdir/throwing_worker.js delete mode 100644 cli/tests/subdir/worker_crypto.js delete mode 100644 cli/tests/subdir/worker_globals.ts delete mode 100644 cli/tests/subdir/worker_unstable.ts create mode 100644 cli/tests/workers/bench_worker.ts create mode 100644 cli/tests/workers/busy_worker.js create mode 100644 cli/tests/workers/deno_worker.ts create mode 100644 cli/tests/workers/event_worker.js create mode 100644 cli/tests/workers/event_worker_scope.js create mode 100644 cli/tests/workers/fetching_worker.js create mode 100644 cli/tests/workers/immediately_close_worker.js create mode 100644 cli/tests/workers/nested_worker.js create mode 100644 cli/tests/workers/no_permissions_worker.js create mode 100644 cli/tests/workers/non_deno_worker.js create mode 100644 cli/tests/workers/parent_read_check_granular_worker.js create mode 100644 cli/tests/workers/parent_read_check_worker.js create mode 100644 cli/tests/workers/racy_worker.js create mode 100644 cli/tests/workers/read_check_granular_worker.js create mode 100644 cli/tests/workers/read_check_worker.js create mode 100644 cli/tests/workers/sibling_worker.js create mode 100644 cli/tests/workers/test_worker.js create mode 100644 cli/tests/workers/test_worker.ts create mode 100644 cli/tests/workers/throwing_worker.js create mode 100644 cli/tests/workers/worker_crypto.js create mode 100644 cli/tests/workers/worker_globals.ts create mode 100644 cli/tests/workers/worker_unstable.ts (limited to 'cli/tests') diff --git a/cli/tests/immediately_close_worker.js b/cli/tests/immediately_close_worker.js deleted file mode 100644 index 8fd27343a..000000000 --- a/cli/tests/immediately_close_worker.js +++ /dev/null @@ -1 +0,0 @@ -self.close(); diff --git a/cli/tests/subdir/bench_worker.ts b/cli/tests/subdir/bench_worker.ts deleted file mode 100644 index 7e85eed03..000000000 --- a/cli/tests/subdir/bench_worker.ts +++ /dev/null @@ -1,21 +0,0 @@ -onmessage = function (e): void { - const { cmdId, action, data } = e.data; - switch (action) { - case 0: // Static response - postMessage({ - cmdId, - data: "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n", - }); - break; - case 1: // Respond with request data - postMessage({ cmdId, data }); - break; - case 2: // Ping - postMessage({ cmdId }); - break; - case 3: // Close - postMessage({ cmdId: 3 }); - close(); - break; - } -}; diff --git a/cli/tests/subdir/busy_worker.js b/cli/tests/subdir/busy_worker.js deleted file mode 100644 index 7deba0321..000000000 --- a/cli/tests/subdir/busy_worker.js +++ /dev/null @@ -1,8 +0,0 @@ -self.onmessage = function (_evt) { - // infinite loop - for (let i = 0; true; i++) { - if (i % 1000 == 0) { - postMessage(i); - } - } -}; diff --git a/cli/tests/subdir/deno_worker.ts b/cli/tests/subdir/deno_worker.ts deleted file mode 100644 index 6a57c47f0..000000000 --- a/cli/tests/subdir/deno_worker.ts +++ /dev/null @@ -1,7 +0,0 @@ -onmessage = function (e): void { - if (typeof self.Deno === "undefined") { - throw new Error("Deno namespace not available in worker"); - } - - postMessage(e.data); -}; diff --git a/cli/tests/subdir/event_worker.js b/cli/tests/subdir/event_worker.js deleted file mode 100644 index 849b6026c..000000000 --- a/cli/tests/subdir/event_worker.js +++ /dev/null @@ -1,7 +0,0 @@ -onmessage = function (e) { - if (e.data === "boom") { - throw new Error("boom error!"); - } - - postMessage(e.data); -}; diff --git a/cli/tests/subdir/event_worker_scope.js b/cli/tests/subdir/event_worker_scope.js deleted file mode 100644 index 0381801a8..000000000 --- a/cli/tests/subdir/event_worker_scope.js +++ /dev/null @@ -1,43 +0,0 @@ -let messageHandlersCalled = 0; -let errorHandlersCalled = 0; - -onmessage = function (e) { - if (e.data === "boom") { - throw new Error("boom error!"); - } - messageHandlersCalled++; -}; - -self.addEventListener("message", (_e) => { - messageHandlersCalled++; -}); - -self.addEventListener("message", (_e) => { - messageHandlersCalled++; -}); - -self.addEventListener("message", (_e) => { - messageHandlersCalled++; - - postMessage({ - messageHandlersCalled, - errorHandlersCalled, - }); -}); - -onerror = function (_e) { - errorHandlersCalled++; -}; - -self.addEventListener("error", (_e) => { - errorHandlersCalled++; -}); - -self.addEventListener("error", (_e) => { - errorHandlersCalled++; -}); - -self.addEventListener("error", (e) => { - errorHandlersCalled++; - e.preventDefault(); -}); diff --git a/cli/tests/subdir/fetching_worker.js b/cli/tests/subdir/fetching_worker.js deleted file mode 100644 index 3e33d1c9e..000000000 --- a/cli/tests/subdir/fetching_worker.js +++ /dev/null @@ -1,6 +0,0 @@ -const r = await fetch( - "http://localhost:4545/cli/tests/subdir/fetching_worker.js", -); -await r.text(); -postMessage("Done!"); -close(); diff --git a/cli/tests/subdir/nested_worker.js b/cli/tests/subdir/nested_worker.js deleted file mode 100644 index 4b51b8763..000000000 --- a/cli/tests/subdir/nested_worker.js +++ /dev/null @@ -1,18 +0,0 @@ -// Specifier should be resolved relative to current file -const jsWorker = new Worker( - new URL("sibling_worker.js", import.meta.url).href, - { type: "module", name: "sibling" }, -); - -jsWorker.onerror = (_e) => { - postMessage({ type: "error" }); -}; - -jsWorker.onmessage = (e) => { - postMessage({ type: "msg", text: e }); - close(); -}; - -onmessage = function (e) { - jsWorker.postMessage(e.data); -}; diff --git a/cli/tests/subdir/non_deno_worker.js b/cli/tests/subdir/non_deno_worker.js deleted file mode 100644 index 773721560..000000000 --- a/cli/tests/subdir/non_deno_worker.js +++ /dev/null @@ -1,7 +0,0 @@ -onmessage = function (e) { - if (typeof self.Deno !== "undefined") { - throw new Error("Deno namespace unexpectedly available in worker"); - } - - postMessage(e.data); -}; diff --git a/cli/tests/subdir/racy_worker.js b/cli/tests/subdir/racy_worker.js deleted file mode 100644 index 83756b791..000000000 --- a/cli/tests/subdir/racy_worker.js +++ /dev/null @@ -1,21 +0,0 @@ -// See issue for details -// https://github.com/denoland/deno/issues/4080 -// -// After first call to `postMessage() this worker schedules -// [close(), postMessage()] ops on the same turn of microtask queue -// (because message is rather big). -// Only single `postMessage()` call should make it -// to host, ie. after calling `close()` no more code should be run. - -setTimeout(() => { - close(); -}, 50); - -while (true) { - await new Promise((done) => { - setTimeout(() => { - postMessage({ buf: new Array(999999) }); - done(); - }); - }); -} diff --git a/cli/tests/subdir/sibling_worker.js b/cli/tests/subdir/sibling_worker.js deleted file mode 100644 index 99707e5d6..000000000 --- a/cli/tests/subdir/sibling_worker.js +++ /dev/null @@ -1,4 +0,0 @@ -onmessage = (e) => { - postMessage(e.data); - close(); -}; diff --git a/cli/tests/subdir/test_worker.js b/cli/tests/subdir/test_worker.js deleted file mode 100644 index 4260975a6..000000000 --- a/cli/tests/subdir/test_worker.js +++ /dev/null @@ -1,19 +0,0 @@ -let thrown = false; - -if (self.name !== "") { - throw Error(`Bad worker name: ${self.name}, expected empty string.`); -} - -onmessage = function (e) { - if (thrown === false) { - thrown = true; - throw new SyntaxError("[test error]"); - } - - postMessage(e.data); - close(); -}; - -onerror = function () { - return false; -}; diff --git a/cli/tests/subdir/test_worker.ts b/cli/tests/subdir/test_worker.ts deleted file mode 100644 index ca79dcfe4..000000000 --- a/cli/tests/subdir/test_worker.ts +++ /dev/null @@ -1,8 +0,0 @@ -if (self.name !== "tsWorker") { - throw Error(`Invalid worker name: ${self.name}, expected tsWorker`); -} - -onmessage = function (e): void { - postMessage(e.data); - close(); -}; diff --git a/cli/tests/subdir/throwing_worker.js b/cli/tests/subdir/throwing_worker.js deleted file mode 100644 index 56ee4ff88..000000000 --- a/cli/tests/subdir/throwing_worker.js +++ /dev/null @@ -1,2 +0,0 @@ -// This worker just throws error when it's being executed -throw Error("Thrown error"); diff --git a/cli/tests/subdir/worker_crypto.js b/cli/tests/subdir/worker_crypto.js deleted file mode 100644 index a86340005..000000000 --- a/cli/tests/subdir/worker_crypto.js +++ /dev/null @@ -1,3 +0,0 @@ -onmessage = function () { - postMessage(!!self.crypto); -}; diff --git a/cli/tests/subdir/worker_globals.ts b/cli/tests/subdir/worker_globals.ts deleted file mode 100644 index a9e7efd85..000000000 --- a/cli/tests/subdir/worker_globals.ts +++ /dev/null @@ -1,10 +0,0 @@ -onmessage = function (): void { - postMessage( - [ - self instanceof DedicatedWorkerGlobalScope, - self instanceof WorkerGlobalScope, - self instanceof EventTarget, - ].join(", "), - ); - close(); -}; diff --git a/cli/tests/subdir/worker_unstable.ts b/cli/tests/subdir/worker_unstable.ts deleted file mode 100644 index a5b5f7ba2..000000000 --- a/cli/tests/subdir/worker_unstable.ts +++ /dev/null @@ -1,5 +0,0 @@ -console.log(Deno.permissions.query); -console.log(Deno.emit); -self.onmessage = () => { - self.close(); -}; 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/workers/bench_worker.ts b/cli/tests/workers/bench_worker.ts new file mode 100644 index 000000000..7e85eed03 --- /dev/null +++ b/cli/tests/workers/bench_worker.ts @@ -0,0 +1,21 @@ +onmessage = function (e): void { + const { cmdId, action, data } = e.data; + switch (action) { + case 0: // Static response + postMessage({ + cmdId, + data: "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nHello World\n", + }); + break; + case 1: // Respond with request data + postMessage({ cmdId, data }); + break; + case 2: // Ping + postMessage({ cmdId }); + break; + case 3: // Close + postMessage({ cmdId: 3 }); + close(); + break; + } +}; diff --git a/cli/tests/workers/busy_worker.js b/cli/tests/workers/busy_worker.js new file mode 100644 index 000000000..7deba0321 --- /dev/null +++ b/cli/tests/workers/busy_worker.js @@ -0,0 +1,8 @@ +self.onmessage = function (_evt) { + // infinite loop + for (let i = 0; true; i++) { + if (i % 1000 == 0) { + postMessage(i); + } + } +}; diff --git a/cli/tests/workers/deno_worker.ts b/cli/tests/workers/deno_worker.ts new file mode 100644 index 000000000..6a57c47f0 --- /dev/null +++ b/cli/tests/workers/deno_worker.ts @@ -0,0 +1,7 @@ +onmessage = function (e): void { + if (typeof self.Deno === "undefined") { + throw new Error("Deno namespace not available in worker"); + } + + postMessage(e.data); +}; diff --git a/cli/tests/workers/event_worker.js b/cli/tests/workers/event_worker.js new file mode 100644 index 000000000..849b6026c --- /dev/null +++ b/cli/tests/workers/event_worker.js @@ -0,0 +1,7 @@ +onmessage = function (e) { + if (e.data === "boom") { + throw new Error("boom error!"); + } + + postMessage(e.data); +}; diff --git a/cli/tests/workers/event_worker_scope.js b/cli/tests/workers/event_worker_scope.js new file mode 100644 index 000000000..0381801a8 --- /dev/null +++ b/cli/tests/workers/event_worker_scope.js @@ -0,0 +1,43 @@ +let messageHandlersCalled = 0; +let errorHandlersCalled = 0; + +onmessage = function (e) { + if (e.data === "boom") { + throw new Error("boom error!"); + } + messageHandlersCalled++; +}; + +self.addEventListener("message", (_e) => { + messageHandlersCalled++; +}); + +self.addEventListener("message", (_e) => { + messageHandlersCalled++; +}); + +self.addEventListener("message", (_e) => { + messageHandlersCalled++; + + postMessage({ + messageHandlersCalled, + errorHandlersCalled, + }); +}); + +onerror = function (_e) { + errorHandlersCalled++; +}; + +self.addEventListener("error", (_e) => { + errorHandlersCalled++; +}); + +self.addEventListener("error", (_e) => { + errorHandlersCalled++; +}); + +self.addEventListener("error", (e) => { + errorHandlersCalled++; + e.preventDefault(); +}); diff --git a/cli/tests/workers/fetching_worker.js b/cli/tests/workers/fetching_worker.js new file mode 100644 index 000000000..e1bcdf911 --- /dev/null +++ b/cli/tests/workers/fetching_worker.js @@ -0,0 +1,6 @@ +const r = await fetch( + "http://localhost:4545/cli/tests/workers/fetching_worker.js", +); +await r.text(); +postMessage("Done!"); +close(); diff --git a/cli/tests/workers/immediately_close_worker.js b/cli/tests/workers/immediately_close_worker.js new file mode 100644 index 000000000..8fd27343a --- /dev/null +++ b/cli/tests/workers/immediately_close_worker.js @@ -0,0 +1 @@ +self.close(); diff --git a/cli/tests/workers/nested_worker.js b/cli/tests/workers/nested_worker.js new file mode 100644 index 000000000..4b51b8763 --- /dev/null +++ b/cli/tests/workers/nested_worker.js @@ -0,0 +1,18 @@ +// Specifier should be resolved relative to current file +const jsWorker = new Worker( + new URL("sibling_worker.js", import.meta.url).href, + { type: "module", name: "sibling" }, +); + +jsWorker.onerror = (_e) => { + postMessage({ type: "error" }); +}; + +jsWorker.onmessage = (e) => { + postMessage({ type: "msg", text: e }); + close(); +}; + +onmessage = function (e) { + jsWorker.postMessage(e.data); +}; 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/workers/non_deno_worker.js b/cli/tests/workers/non_deno_worker.js new file mode 100644 index 000000000..773721560 --- /dev/null +++ b/cli/tests/workers/non_deno_worker.js @@ -0,0 +1,7 @@ +onmessage = function (e) { + if (typeof self.Deno !== "undefined") { + throw new Error("Deno namespace unexpectedly available in worker"); + } + + postMessage(e.data); +}; 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/workers/racy_worker.js b/cli/tests/workers/racy_worker.js new file mode 100644 index 000000000..83756b791 --- /dev/null +++ b/cli/tests/workers/racy_worker.js @@ -0,0 +1,21 @@ +// See issue for details +// https://github.com/denoland/deno/issues/4080 +// +// After first call to `postMessage() this worker schedules +// [close(), postMessage()] ops on the same turn of microtask queue +// (because message is rather big). +// Only single `postMessage()` call should make it +// to host, ie. after calling `close()` no more code should be run. + +setTimeout(() => { + close(); +}, 50); + +while (true) { + await new Promise((done) => { + setTimeout(() => { + postMessage({ buf: new Array(999999) }); + done(); + }); + }); +} 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/workers/sibling_worker.js b/cli/tests/workers/sibling_worker.js new file mode 100644 index 000000000..99707e5d6 --- /dev/null +++ b/cli/tests/workers/sibling_worker.js @@ -0,0 +1,4 @@ +onmessage = (e) => { + postMessage(e.data); + close(); +}; diff --git a/cli/tests/workers/test_worker.js b/cli/tests/workers/test_worker.js new file mode 100644 index 000000000..4260975a6 --- /dev/null +++ b/cli/tests/workers/test_worker.js @@ -0,0 +1,19 @@ +let thrown = false; + +if (self.name !== "") { + throw Error(`Bad worker name: ${self.name}, expected empty string.`); +} + +onmessage = function (e) { + if (thrown === false) { + thrown = true; + throw new SyntaxError("[test error]"); + } + + postMessage(e.data); + close(); +}; + +onerror = function () { + return false; +}; diff --git a/cli/tests/workers/test_worker.ts b/cli/tests/workers/test_worker.ts new file mode 100644 index 000000000..ca79dcfe4 --- /dev/null +++ b/cli/tests/workers/test_worker.ts @@ -0,0 +1,8 @@ +if (self.name !== "tsWorker") { + throw Error(`Invalid worker name: ${self.name}, expected tsWorker`); +} + +onmessage = function (e): void { + postMessage(e.data); + close(); +}; diff --git a/cli/tests/workers/throwing_worker.js b/cli/tests/workers/throwing_worker.js new file mode 100644 index 000000000..56ee4ff88 --- /dev/null +++ b/cli/tests/workers/throwing_worker.js @@ -0,0 +1,2 @@ +// This worker just throws error when it's being executed +throw Error("Thrown error"); diff --git a/cli/tests/workers/worker_crypto.js b/cli/tests/workers/worker_crypto.js new file mode 100644 index 000000000..a86340005 --- /dev/null +++ b/cli/tests/workers/worker_crypto.js @@ -0,0 +1,3 @@ +onmessage = function () { + postMessage(!!self.crypto); +}; diff --git a/cli/tests/workers/worker_globals.ts b/cli/tests/workers/worker_globals.ts new file mode 100644 index 000000000..a9e7efd85 --- /dev/null +++ b/cli/tests/workers/worker_globals.ts @@ -0,0 +1,10 @@ +onmessage = function (): void { + postMessage( + [ + self instanceof DedicatedWorkerGlobalScope, + self instanceof WorkerGlobalScope, + self instanceof EventTarget, + ].join(", "), + ); + close(); +}; diff --git a/cli/tests/workers/worker_unstable.ts b/cli/tests/workers/worker_unstable.ts new file mode 100644 index 000000000..a5b5f7ba2 --- /dev/null +++ b/cli/tests/workers/worker_unstable.ts @@ -0,0 +1,5 @@ +console.log(Deno.permissions.query); +console.log(Deno.emit); +self.onmessage = () => { + self.close(); +}; 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 { const workers: Array<[Map>, 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 { 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((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 { 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 { 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 { 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 { 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 { 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(); +}); -- cgit v1.2.3