0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

fix(runtime/ops/worker_host): move permission arg parsing to Rust (#12297)

This commit is contained in:
Nayeem Rahman 2021-10-13 18:04:44 +01:00 committed by GitHub
parent 43a63530ac
commit 7a22df9b76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1201 additions and 823 deletions

View file

@ -2178,6 +2178,7 @@ declare namespace Deno {
export interface FfiPermissionDescriptor { export interface FfiPermissionDescriptor {
name: "ffi"; name: "ffi";
path?: string | URL;
} }
export interface HrtimePermissionDescriptor { export interface HrtimePermissionDescriptor {

View file

@ -910,10 +910,12 @@ declare namespace Deno {
* If set to `"inherit"`, the current `ffi` permission will be inherited. * If set to `"inherit"`, the current `ffi` permission will be inherited.
* If set to `true`, the global `ffi` permission will be requested. * If set to `true`, the global `ffi` permission will be requested.
* If set to `false`, the global `ffi` permission will be revoked. * If set to `false`, the global `ffi` permission will be revoked.
* If set to `Array<string | URL>`, the `ffi` permission will be requested with the
* specified file paths.
* *
* Defaults to "inherit". * Defaults to "inherit".
*/ */
ffi?: "inherit" | boolean; ffi?: "inherit" | boolean | Array<string | URL>;
/** Specifies if the `read` permission should be requested or revoked. /** Specifies if the `read` permission should be requested or revoked.
* If set to `"inherit"`, the current `read` permission will be inherited. * If set to `"inherit"`, the current `read` permission will be inherited.
@ -1237,7 +1239,7 @@ declare interface WorkerOptions {
* For example: `["https://deno.land", "localhost:8080"]`. * For example: `["https://deno.land", "localhost:8080"]`.
*/ */
net?: "inherit" | boolean | string[]; net?: "inherit" | boolean | string[];
ffi?: "inherit" | boolean; ffi?: "inherit" | boolean | Array<string | URL>;
read?: "inherit" | boolean | Array<string | URL>; read?: "inherit" | boolean | Array<string | URL>;
run?: "inherit" | boolean | Array<string | URL>; run?: "inherit" | boolean | Array<string | URL>;
write?: "inherit" | boolean | Array<string | URL>; write?: "inherit" | boolean | Array<string | URL>;

View file

@ -198,7 +198,7 @@ pub struct Flags {
pub allow_env: Option<Vec<String>>, pub allow_env: Option<Vec<String>>,
pub allow_hrtime: bool, pub allow_hrtime: bool,
pub allow_net: Option<Vec<String>>, pub allow_net: Option<Vec<String>>,
pub allow_ffi: Option<Vec<String>>, pub allow_ffi: Option<Vec<PathBuf>>,
pub allow_read: Option<Vec<PathBuf>>, pub allow_read: Option<Vec<PathBuf>>,
pub allow_run: Option<Vec<String>>, pub allow_run: Option<Vec<String>>,
pub allow_write: Option<Vec<PathBuf>>, pub allow_write: Option<Vec<PathBuf>>,
@ -324,7 +324,7 @@ impl Flags {
args.push("--allow-ffi".to_string()); args.push("--allow-ffi".to_string());
} }
Some(ffi_allowlist) => { Some(ffi_allowlist) => {
let s = format!("--allow-ffi={}", ffi_allowlist.join(",")); let s = format!("--allow-ffi={}", join_paths(ffi_allowlist, ","));
args.push(s); args.push(s);
} }
_ => {} _ => {}
@ -1685,10 +1685,10 @@ fn config_arg<'a, 'b>() -> Arg<'a, 'b> {
.long_help( .long_help(
"Load configuration file. "Load configuration file.
Before 1.14 Deno only supported loading tsconfig.json that allowed Before 1.14 Deno only supported loading tsconfig.json that allowed
to customise TypeScript compiler settings. to customise TypeScript compiler settings.
Starting with 1.14 configuration file can be used to configure different Starting with 1.14 configuration file can be used to configure different
subcommands like `deno lint` or `deno fmt`. subcommands like `deno lint` or `deno fmt`.
It's recommended to use `deno.json` or `deno.jsonc` as a filename.", It's recommended to use `deno.json` or `deno.jsonc` as a filename.",
) )
@ -2202,7 +2202,7 @@ fn permission_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
} }
if let Some(ffi_wl) = matches.values_of("allow-ffi") { if let Some(ffi_wl) = matches.values_of("allow-ffi") {
let ffi_allowlist: Vec<String> = ffi_wl.map(ToString::to_string).collect(); let ffi_allowlist: Vec<PathBuf> = ffi_wl.map(PathBuf::from).collect();
flags.allow_ffi = Some(ffi_allowlist); flags.allow_ffi = Some(ffi_allowlist);
debug!("ffi allowlist: {:#?}", &flags.allow_ffi); debug!("ffi allowlist: {:#?}", &flags.allow_ffi);
} }

View file

@ -4,8 +4,8 @@ use deno_core::error::AnyError;
use deno_core::JsRuntime; use deno_core::JsRuntime;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_core::OpState; use deno_core::OpState;
use deno_runtime::ops::worker_host::create_worker_permissions; use deno_runtime::permissions::create_child_permissions;
use deno_runtime::ops::worker_host::PermissionsArg; use deno_runtime::permissions::ChildPermissionsArg;
use deno_runtime::permissions::Permissions; use deno_runtime::permissions::Permissions;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use uuid::Uuid; use uuid::Uuid;
@ -26,15 +26,15 @@ struct PermissionsHolder(Uuid, Permissions);
pub fn op_pledge_test_permissions( pub fn op_pledge_test_permissions(
state: &mut OpState, state: &mut OpState,
args: PermissionsArg, args: ChildPermissionsArg,
_: (), _: (),
) -> Result<Uuid, AnyError> { ) -> Result<Uuid, AnyError> {
deno_runtime::ops::check_unstable(state, "Deno.test.permissions"); deno_runtime::ops::check_unstable(state, "Deno.test.permissions");
let token = Uuid::new_v4(); let token = Uuid::new_v4();
let parent_permissions = state.borrow::<Permissions>().clone(); let parent_permissions = state.borrow_mut::<Permissions>();
let worker_permissions = let worker_permissions = create_child_permissions(parent_permissions, args)?;
create_worker_permissions(parent_permissions.clone(), args)?; let parent_permissions = parent_permissions.clone();
state.put::<PermissionsHolder>(PermissionsHolder(token, parent_permissions)); state.put::<PermissionsHolder>(PermissionsHolder(token, parent_permissions));

View file

@ -3,7 +3,7 @@
use crate::itest; use crate::itest;
itest!(workers { itest!(workers {
args: "test --reload --location http://127.0.0.1:4545/ --allow-net --allow-read --unstable workers/test.ts", args: "test --reload --location http://127.0.0.1:4545/ -A --unstable workers/test.ts",
output: "workers/test.ts.out", output: "workers/test.ts.out",
http_server: true, http_server: true,
}); });

View file

@ -18,7 +18,7 @@ for (const name of permissions) {
}, },
async fn() { async fn() {
const status = await Deno.permissions.query({ name }); const status = await Deno.permissions.query({ name });
assertEquals(status.state, "denied"); assertEquals(status.state, "prompt");
}, },
}); });

View file

@ -6,12 +6,12 @@ self.onmessage = async () => {
const run = await Deno.permissions.query({ name: "run" }); const run = await Deno.permissions.query({ name: "run" });
const write = await Deno.permissions.query({ name: "write" }); const write = await Deno.permissions.query({ name: "write" });
self.postMessage( self.postMessage(
hrtime.state === "denied" && hrtime.state === "prompt" &&
net.state === "denied" && net.state === "prompt" &&
ffi.state === "denied" && ffi.state === "prompt" &&
read.state === "denied" && read.state === "prompt" &&
run.state === "denied" && run.state === "prompt" &&
write.state === "denied", write.state === "prompt",
); );
self.close(); self.close();
}; };

View file

@ -1,41 +0,0 @@
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: data.path,
});
messages[data.index] = state === "granted";
worker.postMessage({
index: data.index,
route: data.route,
});
};

View file

@ -1,27 +1,18 @@
onmessage = async () => { const worker = new Worker(
const { state } = await Deno.permissions.query({ new URL("./read_check_granular_worker.js", import.meta.url).href,
name: "read", {
}); type: "module",
deno: {
const worker = new Worker( namespace: true,
new URL("./read_check_worker.js", import.meta.url).href, permissions: "none",
{
type: "module",
deno: {
namespace: true,
permissions: {
read: false,
},
},
}, },
); },
);
worker.onmessage = ({ data: childHasPermission }) => { onmessage = ({ data }) => {
postMessage({ worker.postMessage(data);
parentHasPermission: state === "granted", };
childHasPermission,
}); worker.onmessage = ({ data }) => {
close(); postMessage(data);
};
worker.postMessage(null);
}; };

View file

@ -1,11 +1,29 @@
onmessage = async ({ data }) => { // deno-fmt-ignore-file
const { state } = await Deno.permissions.query({ postMessage({
name: "read", envGlobal: (await Deno.permissions.query({ name: "env" })).state,
path: data.path, envFoo: (await Deno.permissions.query({ name: "env", variable: "foo" })).state,
}); envAbsent: (await Deno.permissions.query({ name: "env", variable: "absent" })).state,
hrtime: (await Deno.permissions.query({ name: "hrtime" })).state,
postMessage({ netGlobal: (await Deno.permissions.query({ name: "net" })).state,
hasPermission: state === "granted", netFoo: (await Deno.permissions.query({ name: "net", host: "foo" })).state,
index: data.index, netFoo8000: (await Deno.permissions.query({ name: "net", host: "foo:8000" })).state,
}); netBar: (await Deno.permissions.query({ name: "net", host: "bar" })).state,
}; netBar8000: (await Deno.permissions.query({ name: "net", host: "bar:8000" })).state,
ffiGlobal: (await Deno.permissions.query({ name: "ffi" })).state,
ffiFoo: (await Deno.permissions.query({ name: "ffi", path: new URL("foo", import.meta.url) })).state,
ffiBar: (await Deno.permissions.query({ name: "ffi", path: "bar" })).state,
ffiAbsent: (await Deno.permissions.query({ name: "ffi", path: "absent" })).state,
readGlobal: (await Deno.permissions.query({ name: "read" })).state,
readFoo: (await Deno.permissions.query({ name: "read", path: new URL("foo", import.meta.url) })).state,
readBar: (await Deno.permissions.query({ name: "read", path: "bar" })).state,
readAbsent: (await Deno.permissions.query({ name: "read", path: "absent" })).state,
runGlobal: (await Deno.permissions.query({ name: "run" })).state,
runFoo: (await Deno.permissions.query({ name: "run", command: new URL("foo", import.meta.url) })).state,
runBar: (await Deno.permissions.query({ name: "run", command: "bar" })).state,
runBaz: (await Deno.permissions.query({ name: "run", command: "./baz" })).state,
runAbsent: (await Deno.permissions.query({ name: "run", command: "absent" })).state,
writeGlobal: (await Deno.permissions.query({ name: "write" })).state,
writeFoo: (await Deno.permissions.query({ name: "write", path: new URL("foo", import.meta.url) })).state,
writeBar: (await Deno.permissions.query({ name: "write", path: "bar" })).state,
writeAbsent: (await Deno.permissions.query({ name: "write", path: "absent" })).state,
});

View file

@ -8,7 +8,6 @@ import {
assertThrows, assertThrows,
} from "../../../../test_util/std/testing/asserts.ts"; } from "../../../../test_util/std/testing/asserts.ts";
import { deferred } from "../../../../test_util/std/async/deferred.ts"; import { deferred } from "../../../../test_util/std/async/deferred.ts";
import { fromFileUrl } from "../../../../test_util/std/path/mod.ts";
Deno.test({ Deno.test({
name: "worker terminate", name: "worker terminate",
@ -454,7 +453,6 @@ Deno.test("Worker limit children permissions", async function () {
}); });
Deno.test("Worker limit children permissions granularly", async function () { Deno.test("Worker limit children permissions granularly", async function () {
const promise = deferred();
const worker = new Worker( const worker = new Worker(
new URL("./read_check_granular_worker.js", import.meta.url).href, new URL("./read_check_granular_worker.js", import.meta.url).href,
{ {
@ -462,53 +460,52 @@ Deno.test("Worker limit children permissions granularly", async function () {
deno: { deno: {
namespace: true, namespace: true,
permissions: { permissions: {
read: [ env: ["foo"],
new URL("./read_check_worker.js", import.meta.url), hrtime: true,
], net: ["foo", "bar:8000"],
ffi: [new URL("foo", import.meta.url), "bar"],
read: [new URL("foo", import.meta.url), "bar"],
run: [new URL("foo", import.meta.url), "bar", "./baz"],
write: [new URL("foo", import.meta.url), "bar"],
}, },
}, },
}, },
); );
const promise = deferred();
//Routes are relative to the spawned worker location worker.onmessage = ({ data }) => promise.resolve(data);
const routes = [ assertEquals(await promise, {
{ envGlobal: "prompt",
permission: false, envFoo: "granted",
path: fromFileUrl( envAbsent: "prompt",
new URL("read_check_granular_worker.js", import.meta.url), hrtime: "granted",
), netGlobal: "prompt",
}, netFoo: "granted",
{ netFoo8000: "granted",
permission: true, netBar: "prompt",
path: fromFileUrl(new URL("read_check_worker.js", import.meta.url)), netBar8000: "granted",
}, ffiGlobal: "prompt",
]; ffiFoo: "granted",
ffiBar: "granted",
let checked = 0; ffiAbsent: "prompt",
worker.onmessage = ({ data }) => { readGlobal: "prompt",
checked++; readFoo: "granted",
assertEquals(data.hasPermission, routes[data.index].permission); readBar: "granted",
routes.shift(); readAbsent: "prompt",
if (checked === routes.length) { runGlobal: "prompt",
promise.resolve(); runFoo: "granted",
} runBar: "granted",
}; runBaz: "granted",
runAbsent: "prompt",
routes.forEach(({ path }, index) => writeGlobal: "prompt",
worker.postMessage({ writeFoo: "granted",
index, writeBar: "granted",
path, writeAbsent: "prompt",
}) });
);
await promise;
worker.terminate(); worker.terminate();
}); });
Deno.test("Nested worker limit children permissions", async function () { Deno.test("Nested worker limit children permissions", async function () {
const promise = deferred(); /** This worker has permissions but doesn't grant them to its children */
/** This worker has read permissions but doesn't grant them to its children */
const worker = new Worker( const worker = new Worker(
new URL("./parent_read_check_worker.js", import.meta.url).href, new URL("./parent_read_check_worker.js", import.meta.url).href,
{ {
@ -519,104 +516,65 @@ Deno.test("Nested worker limit children permissions", async function () {
}, },
}, },
); );
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(); const promise = deferred();
worker.onmessage = ({ data }) => promise.resolve(data);
/** This worker has read permissions but doesn't grant them to its children */ assertEquals(await promise, {
const worker = new Worker( envGlobal: "prompt",
new URL("./parent_read_check_granular_worker.js", import.meta.url) envFoo: "prompt",
.href, envAbsent: "prompt",
{ hrtime: "prompt",
type: "module", netGlobal: "prompt",
deno: { netFoo: "prompt",
namespace: true, netFoo8000: "prompt",
permissions: { netBar: "prompt",
read: [ netBar8000: "prompt",
new URL("./read_check_granular_worker.js", import.meta.url), ffiGlobal: "prompt",
], ffiFoo: "prompt",
}, ffiBar: "prompt",
}, ffiAbsent: "prompt",
}, readGlobal: "prompt",
); readFoo: "prompt",
readBar: "prompt",
//Routes are relative to the spawned worker location readAbsent: "prompt",
const routes = [ runGlobal: "prompt",
{ runFoo: "prompt",
childHasPermission: false, runBar: "prompt",
parentHasPermission: true, runBaz: "prompt",
path: fromFileUrl( runAbsent: "prompt",
new URL("read_check_granular_worker.js", import.meta.url), writeGlobal: "prompt",
), writeFoo: "prompt",
}, writeBar: "prompt",
{ writeAbsent: "prompt",
childHasPermission: false, });
parentHasPermission: false,
path: fromFileUrl(new URL("read_check_worker.js", import.meta.url)),
},
];
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(({ path }, index) =>
worker.postMessage({
index,
path,
})
);
await promise;
worker.terminate(); worker.terminate();
}); });
// This test relies on env permissions not being granted on main thread // 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 () { Deno.test({
assertThrows( name:
() => { "Worker initialization throws on worker permissions greater than parent thread permissions",
const worker = new Worker( permissions: { env: false },
new URL("./deno_worker.ts", import.meta.url).href, fn: function () {
{ assertThrows(
type: "module", () => {
deno: { const worker = new Worker(
namespace: true, new URL("./deno_worker.ts", import.meta.url).href,
permissions: { {
env: true, type: "module",
deno: {
namespace: true,
permissions: {
env: true,
},
}, },
}, },
}, );
); worker.terminate();
worker.terminate(); },
}, Deno.errors.PermissionDenied,
Deno.errors.PermissionDenied, "Can't escalate parent thread permissions",
"Can't escalate parent thread permissions", );
); },
}); });
Deno.test("Worker with disabled permissions", async function () { Deno.test("Worker with disabled permissions", async function () {
@ -643,6 +601,19 @@ Deno.test("Worker with disabled permissions", async function () {
worker.terminate(); worker.terminate();
}); });
Deno.test("Worker with invalid permission arg", function () {
assertThrows(
() =>
new Worker(`data:,close();`, {
type: "module",
// @ts-expect-error invalid env value
deno: { permissions: { env: "foo" } },
}),
TypeError,
'Error parsing args: (deno.permissions.env) invalid value: string "foo", expected "inherit" or boolean or string[]',
);
});
Deno.test({ Deno.test({
name: "worker location", name: "worker location",
fn: async function () { fn: async function () {

View file

@ -20,6 +20,8 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ffi::c_void; use std::ffi::c_void;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
pub struct Unstable(pub bool); pub struct Unstable(pub bool);
@ -37,7 +39,7 @@ fn check_unstable(state: &OpState, api_name: &str) {
} }
pub trait FfiPermissions { pub trait FfiPermissions {
fn check(&mut self, path: &str) -> Result<(), AnyError>; fn check(&mut self, path: &Path) -> Result<(), AnyError>;
} }
#[derive(Clone)] #[derive(Clone)]
@ -366,7 +368,7 @@ where
check_unstable(state, "Deno.dlopen"); check_unstable(state, "Deno.dlopen");
let permissions = state.borrow_mut::<FP>(); let permissions = state.borrow_mut::<FP>();
permissions.check(&path)?; permissions.check(&PathBuf::from(&path))?;
let lib = Library::open(&path).map_err(|e| { let lib = Library::open(&path).map_err(|e| {
dlopen::Error::OpeningLibraryError(std::io::Error::new( dlopen::Error::OpeningLibraryError(std::io::Error::new(

View file

@ -83,7 +83,10 @@ mod not_docs {
} }
impl deno_ffi::FfiPermissions for Permissions { impl deno_ffi::FfiPermissions for Permissions {
fn check(&mut self, _path: &str) -> Result<(), deno_core::error::AnyError> { fn check(
&mut self,
_path: &Path,
) -> Result<(), deno_core::error::AnyError> {
unreachable!("snapshotting!") unreachable!("snapshotting!")
} }
} }

View file

@ -10,7 +10,10 @@
} = window; } = window;
const { pathFromURL } = window.__bootstrap.util; const { pathFromURL } = window.__bootstrap.util;
const { const {
ArrayIsArray,
ArrayPrototypeIncludes, ArrayPrototypeIncludes,
ArrayPrototypeMap,
ArrayPrototypeSlice,
Map, Map,
MapPrototypeGet, MapPrototypeGet,
MapPrototypeHas, MapPrototypeHas,
@ -162,7 +165,9 @@
); );
} }
if (desc.name === "read" || desc.name === "write") { if (
desc.name === "read" || desc.name === "write" || desc.name === "ffi"
) {
desc.path = pathFromURL(desc.path); desc.path = pathFromURL(desc.path);
} else if (desc.name === "run") { } else if (desc.name === "run") {
desc.command = pathFromURL(desc.command); desc.command = pathFromURL(desc.command);
@ -213,7 +218,34 @@
const permissions = new Permissions(illegalConstructorKey); const permissions = new Permissions(illegalConstructorKey);
/** Converts all file URLs in FS allowlists to paths. */
function serializePermissions(permissions) {
if (typeof permissions == "object" && permissions != null) {
const serializedPermissions = {};
for (const key of ["read", "write", "run", "ffi"]) {
if (ArrayIsArray(permissions[key])) {
serializedPermissions[key] = ArrayPrototypeMap(
permissions[key],
(path) => pathFromURL(path),
);
} else {
serializedPermissions[key] = permissions[key];
}
}
for (const key of ["env", "hrtime", "net"]) {
if (ArrayIsArray(permissions[key])) {
serializedPermissions[key] = ArrayPrototypeSlice(permissions[key]);
} else {
serializedPermissions[key] = permissions[key];
}
}
return serializedPermissions;
}
return permissions;
}
window.__bootstrap.permissions = { window.__bootstrap.permissions = {
serializePermissions,
permissions, permissions,
Permissions, Permissions,
PermissionStatus, PermissionStatus,

View file

@ -4,8 +4,6 @@
((window) => { ((window) => {
const core = window.Deno.core; const core = window.Deno.core;
const { const {
ArrayIsArray,
ArrayPrototypeMap,
Error, Error,
StringPrototypeStartsWith, StringPrototypeStartsWith,
String, String,
@ -15,7 +13,8 @@
const webidl = window.__bootstrap.webidl; const webidl = window.__bootstrap.webidl;
const { URL } = window.__bootstrap.url; const { URL } = window.__bootstrap.url;
const { getLocationHref } = window.__bootstrap.location; const { getLocationHref } = window.__bootstrap.location;
const { log, pathFromURL } = window.__bootstrap.util; const { serializePermissions } = window.__bootstrap.permissions;
const { log } = window.__bootstrap.util;
const { defineEventHandler } = window.__bootstrap.event; const { defineEventHandler } = window.__bootstrap.event;
const { deserializeJsMessageData, serializeJsMessageData } = const { deserializeJsMessageData, serializeJsMessageData } =
window.__bootstrap.messagePort; window.__bootstrap.messagePort;
@ -32,7 +31,7 @@
return core.opSync("op_create_worker", { return core.opSync("op_create_worker", {
hasSourceCode, hasSourceCode,
name, name,
permissions, permissions: serializePermissions(permissions),
sourceCode, sourceCode,
specifier, specifier,
useDenoNamespace, useDenoNamespace,
@ -56,87 +55,6 @@
return core.opAsync("op_host_recv_message", id); return core.opAsync("op_host_recv_message", id);
} }
/**
* @param {"inherit" | boolean} value
* @param {string} permission
* @return {boolean}
*/
function parseUnitPermission(
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 (!ArrayIsArray(value) && typeof value !== "boolean") {
throw new Error(
`Expected 'array' or 'boolean' for ${permission} permission, ${typeof value} received`,
);
//Casts URLs to absolute routes
} else if (ArrayIsArray(value)) {
value = ArrayPrototypeMap(value, (route) => {
if (route instanceof URL) {
if (permission === "net") {
throw new Error(
`Expected 'string' for net permission, received 'URL'`,
);
} else if (permission === "env") {
throw new Error(
`Expected 'string' for env permission, received 'URL'`,
);
} else {
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",
ffi = "inherit",
read = "inherit",
run = "inherit",
write = "inherit",
}) {
return {
env: parseArrayPermission(env, "env"),
hrtime: parseUnitPermission(hrtime, "hrtime"),
net: parseArrayPermission(net, "net"),
ffi: parseUnitPermission(ffi, "ffi"),
read: parseArrayPermission(read, "read"),
run: parseUnitPermission(run, "run"),
write: parseArrayPermission(write, "write"),
};
}
class Worker extends EventTarget { class Worker extends EventTarget {
#id = 0; #id = 0;
#name = ""; #name = "";
@ -152,43 +70,23 @@
super(); super();
specifier = String(specifier); specifier = String(specifier);
const { const {
deno = {}, deno,
name = "unknown", name,
type = "classic", type = "classic",
} = options; } = options;
// TODO(Soremwar) let namespace;
// `deno: boolean` is kept for backwards compatibility with the previous let permissions;
// worker options implementation. Remove for 2.0 if (typeof deno == "object") {
let workerDenoAttributes; namespace = deno.namespace ?? false;
if (typeof deno == "boolean") { permissions = deno.permissions ?? undefined;
workerDenoAttributes = {
// Change this to enable the Deno namespace by default
namespace: deno,
permissions: null,
};
} else { } else {
workerDenoAttributes = { // Assume `deno: boolean | undefined`.
// Change this to enable the Deno namespace by default // TODO(Soremwar)
namespace: !!(deno?.namespace ?? false), // `deno: boolean` is kept for backwards compatibility with the previous
permissions: (deno?.permissions ?? "inherit") === "inherit" // worker options implementation. Remove for 2.0
? null namespace = !!deno;
: deno?.permissions, permissions = undefined;
};
// If the permission option is set to "none", all permissions
// must be removed from the worker
if (workerDenoAttributes.permissions === "none") {
workerDenoAttributes.permissions = {
env: false,
hrtime: false,
net: false,
ffi: false,
read: false,
run: false,
write: false,
};
}
} }
const workerType = webidl.converters["WorkerType"](type); const workerType = webidl.converters["WorkerType"](type);
@ -218,17 +116,16 @@
specifier, specifier,
hasSourceCode, hasSourceCode,
sourceCode, sourceCode,
workerDenoAttributes.namespace, namespace,
workerDenoAttributes.permissions === null permissions,
? null name,
: parsePermissions(workerDenoAttributes.permissions),
options?.name,
workerType, workerType,
); );
this.#id = id; this.#id = id;
this.#pollControl(); this.#pollControl();
this.#pollMessages(); this.#pollMessages();
} }
#handleError(e) { #handleError(e) {
const event = new ErrorEvent("error", { const event = new ErrorEvent("error", {
cancelable: true, cancelable: true,
@ -359,7 +256,6 @@
]); ]);
window.__bootstrap.worker = { window.__bootstrap.worker = {
parsePermissions,
Worker, Worker,
}; };
})(this); })(this);

View file

@ -3,10 +3,10 @@
((window) => { ((window) => {
const core = window.Deno.core; const core = window.Deno.core;
const { parsePermissions } = window.__bootstrap.worker;
const { setExitHandler } = window.__bootstrap.os; const { setExitHandler } = window.__bootstrap.os;
const { Console, inspectArgs } = window.__bootstrap.console; const { Console, inspectArgs } = window.__bootstrap.console;
const { metrics } = core; const { metrics } = core;
const { serializePermissions } = window.__bootstrap.permissions;
const { assert } = window.__bootstrap.util; const { assert } = window.__bootstrap.util;
const { const {
ArrayPrototypeFilter, ArrayPrototypeFilter,
@ -230,7 +230,7 @@ finishing test case.`;
function pledgePermissions(permissions) { function pledgePermissions(permissions) {
return core.opSync( return core.opSync(
"op_pledge_test_permissions", "op_pledge_test_permissions",
parsePermissions(permissions), serializePermissions(permissions),
); );
} }
@ -289,7 +289,7 @@ finishing test case.`;
if (testDef.permissions) { if (testDef.permissions) {
testDef.fn = withPermissions( testDef.fn = withPermissions(
testDef.fn, testDef.fn,
parsePermissions(testDef.permissions), testDef.permissions,
); );
} }

View file

@ -28,7 +28,6 @@ pub struct PermissionArgs {
host: Option<String>, host: Option<String>,
variable: Option<String>, variable: Option<String>,
command: Option<String>, command: Option<String>,
library: Option<String>,
} }
pub fn op_query_permission( pub fn op_query_permission(
@ -50,7 +49,7 @@ pub fn op_query_permission(
), ),
"env" => permissions.env.query(args.variable.as_deref()), "env" => permissions.env.query(args.variable.as_deref()),
"run" => permissions.run.query(args.command.as_deref()), "run" => permissions.run.query(args.command.as_deref()),
"ffi" => permissions.ffi.query(args.library.as_deref()), "ffi" => permissions.ffi.query(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.query(), "hrtime" => permissions.hrtime.query(),
n => { n => {
return Err(custom_error( return Err(custom_error(
@ -81,7 +80,7 @@ pub fn op_revoke_permission(
), ),
"env" => permissions.env.revoke(args.variable.as_deref()), "env" => permissions.env.revoke(args.variable.as_deref()),
"run" => permissions.run.revoke(args.command.as_deref()), "run" => permissions.run.revoke(args.command.as_deref()),
"ffi" => permissions.ffi.revoke(args.library.as_deref()), "ffi" => permissions.ffi.revoke(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.revoke(), "hrtime" => permissions.hrtime.revoke(),
n => { n => {
return Err(custom_error( return Err(custom_error(
@ -112,7 +111,7 @@ pub fn op_request_permission(
), ),
"env" => permissions.env.request(args.variable.as_deref()), "env" => permissions.env.request(args.variable.as_deref()),
"run" => permissions.run.request(args.command.as_deref()), "run" => permissions.run.request(args.command.as_deref()),
"ffi" => permissions.ffi.request(args.library.as_deref()), "ffi" => permissions.ffi.request(args.path.as_deref().map(Path::new)),
"hrtime" => permissions.hrtime.request(), "hrtime" => permissions.hrtime.request(),
n => { n => {
return Err(custom_error( return Err(custom_error(

View file

@ -1,18 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ops::TestingFeaturesEnabled; use crate::ops::TestingFeaturesEnabled;
use crate::permissions::resolve_read_allowlist; use crate::permissions::create_child_permissions;
use crate::permissions::resolve_write_allowlist; use crate::permissions::ChildPermissionsArg;
use crate::permissions::EnvDescriptor;
use crate::permissions::FfiDescriptor;
use crate::permissions::NetDescriptor;
use crate::permissions::PermissionState;
use crate::permissions::Permissions; use crate::permissions::Permissions;
use crate::permissions::ReadDescriptor;
use crate::permissions::RunDescriptor;
use crate::permissions::UnaryPermission;
use crate::permissions::UnitPermission;
use crate::permissions::WriteDescriptor;
use crate::web_worker::run_web_worker; use crate::web_worker::run_web_worker;
use crate::web_worker::SendableWebWorkerHandle; use crate::web_worker::SendableWebWorkerHandle;
use crate::web_worker::WebWorker; use crate::web_worker::WebWorker;
@ -20,14 +11,10 @@ use crate::web_worker::WebWorkerHandle;
use crate::web_worker::WebWorkerType; use crate::web_worker::WebWorkerType;
use crate::web_worker::WorkerControlEvent; use crate::web_worker::WorkerControlEvent;
use crate::web_worker::WorkerId; use crate::web_worker::WorkerId;
use deno_core::error::custom_error;
use deno_core::error::AnyError; use deno_core::error::AnyError;
use deno_core::op_async; use deno_core::op_async;
use deno_core::op_sync; use deno_core::op_sync;
use deno_core::serde::de;
use deno_core::serde::de::SeqAccess;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
use deno_core::serde::Deserializer;
use deno_core::Extension; use deno_core::Extension;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_core::OpState; use deno_core::OpState;
@ -35,10 +22,6 @@ use deno_web::JsMessageData;
use log::debug; use log::debug;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; 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::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::thread::JoinHandle; use std::thread::JoinHandle;
@ -131,369 +114,12 @@ pub fn init(create_web_worker_cb: Arc<CreateWebWorkerCb>) -> Extension {
.build() .build()
} }
fn merge_boolean_permission(
mut main: UnitPermission,
worker: Option<PermissionState>,
) -> Result<UnitPermission, AnyError> {
if let Some(worker) = worker {
if worker < main.state {
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.state = worker;
}
}
Ok(main)
}
fn merge_net_permission(
mut main: UnaryPermission<NetDescriptor>,
worker: Option<UnaryPermission<NetDescriptor>>,
) -> Result<UnaryPermission<NetDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker
.granted_list
.iter()
.all(|x| main.check(&(&x.0, x.1)).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_read_permission(
mut main: UnaryPermission<ReadDescriptor>,
worker: Option<UnaryPermission<ReadDescriptor>>,
) -> Result<UnaryPermission<ReadDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker
.granted_list
.iter()
.all(|x| main.check(x.0.as_path()).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_write_permission(
mut main: UnaryPermission<WriteDescriptor>,
worker: Option<UnaryPermission<WriteDescriptor>>,
) -> Result<UnaryPermission<WriteDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker
.granted_list
.iter()
.all(|x| main.check(x.0.as_path()).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_env_permission(
mut main: UnaryPermission<EnvDescriptor>,
worker: Option<UnaryPermission<EnvDescriptor>>,
) -> Result<UnaryPermission<EnvDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker
.granted_list
.iter()
.all(|x| main.check(x.as_ref()).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_run_permission(
mut main: UnaryPermission<RunDescriptor>,
worker: Option<UnaryPermission<RunDescriptor>>,
) -> Result<UnaryPermission<RunDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker.granted_list.iter().all(|x| main.check(&x.0).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
fn merge_ffi_permission(
mut main: UnaryPermission<FfiDescriptor>,
worker: Option<UnaryPermission<FfiDescriptor>>,
) -> Result<UnaryPermission<FfiDescriptor>, AnyError> {
if let Some(worker) = worker {
if (worker.global_state < main.global_state)
|| !worker.granted_list.iter().all(|x| main.check(&x.0).is_ok())
{
return Err(custom_error(
"PermissionDenied",
"Can't escalate parent thread permissions",
));
} else {
main.global_state = worker.global_state;
main.granted_list = worker.granted_list;
}
}
Ok(main)
}
pub fn create_worker_permissions(
main_perms: Permissions,
worker_perms: PermissionsArg,
) -> Result<Permissions, AnyError> {
Ok(Permissions {
env: merge_env_permission(main_perms.env, worker_perms.env)?,
hrtime: merge_boolean_permission(main_perms.hrtime, worker_perms.hrtime)?,
net: merge_net_permission(main_perms.net, worker_perms.net)?,
ffi: merge_ffi_permission(main_perms.ffi, worker_perms.ffi)?,
read: merge_read_permission(main_perms.read, worker_perms.read)?,
run: merge_run_permission(main_perms.run, worker_perms.run)?,
write: merge_write_permission(main_perms.write, worker_perms.write)?,
})
}
#[derive(Debug, Deserialize)]
pub struct PermissionsArg {
#[serde(default, deserialize_with = "as_unary_env_permission")]
env: Option<UnaryPermission<EnvDescriptor>>,
#[serde(default, deserialize_with = "as_permission_state")]
hrtime: Option<PermissionState>,
#[serde(default, deserialize_with = "as_unary_net_permission")]
net: Option<UnaryPermission<NetDescriptor>>,
#[serde(default, deserialize_with = "as_unary_ffi_permission")]
ffi: Option<UnaryPermission<FfiDescriptor>>,
#[serde(default, deserialize_with = "as_unary_read_permission")]
read: Option<UnaryPermission<ReadDescriptor>>,
#[serde(default, deserialize_with = "as_unary_run_permission")]
run: Option<UnaryPermission<RunDescriptor>>,
#[serde(default, deserialize_with = "as_unary_write_permission")]
write: Option<UnaryPermission<WriteDescriptor>>,
}
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")
}
// visit_unit maps undefined/missing values to false
fn visit_unit<E>(self) -> Result<UnaryPermissionBase, E>
where
E: de::Error,
{
self.visit_bool(false)
}
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_net_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<NetDescriptor>>, D::Error>
where
D: Deserializer<'de>,
{
let value: UnaryPermissionBase =
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
let allowed: HashSet<NetDescriptor> = value
.paths
.into_iter()
.map(NetDescriptor::from_string)
.collect();
Ok(Some(UnaryPermission::<NetDescriptor> {
global_state: value.global_state,
granted_list: allowed,
..Default::default()
}))
}
fn as_unary_read_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<ReadDescriptor>>, 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::<ReadDescriptor> {
global_state: value.global_state,
granted_list: resolve_read_allowlist(&Some(paths)),
..Default::default()
}))
}
fn as_unary_write_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<WriteDescriptor>>, 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::<WriteDescriptor> {
global_state: value.global_state,
granted_list: resolve_write_allowlist(&Some(paths)),
..Default::default()
}))
}
fn as_unary_env_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<EnvDescriptor>>, D::Error>
where
D: Deserializer<'de>,
{
let value: UnaryPermissionBase =
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
Ok(Some(UnaryPermission::<EnvDescriptor> {
global_state: value.global_state,
granted_list: value.paths.into_iter().map(EnvDescriptor::new).collect(),
..Default::default()
}))
}
fn as_unary_run_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<RunDescriptor>>, D::Error>
where
D: Deserializer<'de>,
{
let value: UnaryPermissionBase =
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
Ok(Some(UnaryPermission::<RunDescriptor> {
global_state: value.global_state,
granted_list: value.paths.into_iter().map(RunDescriptor).collect(),
..Default::default()
}))
}
fn as_unary_ffi_permission<'de, D>(
deserializer: D,
) -> Result<Option<UnaryPermission<FfiDescriptor>>, D::Error>
where
D: Deserializer<'de>,
{
let value: UnaryPermissionBase =
deserializer.deserialize_any(ParseBooleanOrStringVec)?;
Ok(Some(UnaryPermission::<FfiDescriptor> {
global_state: value.global_state,
granted_list: value.paths.into_iter().map(FfiDescriptor).collect(),
..Default::default()
}))
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CreateWorkerArgs { pub struct CreateWorkerArgs {
has_source_code: bool, has_source_code: bool,
name: Option<String>, name: Option<String>,
permissions: Option<PermissionsArg>, permissions: Option<ChildPermissionsArg>,
source_code: String, source_code: String,
specifier: String, specifier: String,
use_deno_namespace: bool, use_deno_namespace: bool,
@ -528,13 +154,18 @@ fn op_create_worker(
); );
} }
} }
let parent_permissions = state.borrow::<Permissions>().clone();
let worker_permissions = if let Some(permissions) = args.permissions { if args.permissions.is_some() {
super::check_unstable(state, "Worker.deno.permissions"); super::check_unstable(state, "Worker.deno.permissions");
create_worker_permissions(parent_permissions.clone(), permissions)? }
let parent_permissions = state.borrow_mut::<Permissions>();
let worker_permissions = if let Some(child_permissions_arg) = args.permissions
{
create_child_permissions(parent_permissions, child_permissions_arg)?
} else { } else {
parent_permissions.clone() parent_permissions.clone()
}; };
let parent_permissions = parent_permissions.clone();
let worker_id = state.take::<WorkerId>(); let worker_id = state.take::<WorkerId>();
let create_module_loader = state.take::<CreateWebWorkerCbHolder>(); let create_module_loader = state.take::<CreateWebWorkerCbHolder>();

File diff suppressed because it is too large Load diff