1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -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);
} }
_ => {} _ => {}
@ -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