1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -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 {
name: "ffi";
path?: string | URL;
}
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 `true`, the global `ffi` permission will be requested.
* 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".
*/
ffi?: "inherit" | boolean;
ffi?: "inherit" | boolean | Array<string | URL>;
/** Specifies if the `read` permission should be requested or revoked.
* 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"]`.
*/
net?: "inherit" | boolean | string[];
ffi?: "inherit" | boolean;
ffi?: "inherit" | boolean | Array<string | URL>;
read?: "inherit" | boolean | Array<string | URL>;
run?: "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_hrtime: bool,
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_run: Option<Vec<String>>,
pub allow_write: Option<Vec<PathBuf>>,
@ -324,7 +324,7 @@ impl Flags {
args.push("--allow-ffi".to_string());
}
Some(ffi_allowlist) => {
let s = format!("--allow-ffi={}", ffi_allowlist.join(","));
let s = format!("--allow-ffi={}", join_paths(ffi_allowlist, ","));
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") {
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);
debug!("ffi allowlist: {:#?}", &flags.allow_ffi);
}

View file

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

View file

@ -3,7 +3,7 @@
use crate::itest;
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",
http_server: true,
});

View file

@ -18,7 +18,7 @@ for (const name of permissions) {
},
async fn() {
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 write = await Deno.permissions.query({ name: "write" });
self.postMessage(
hrtime.state === "denied" &&
net.state === "denied" &&
ffi.state === "denied" &&
read.state === "denied" &&
run.state === "denied" &&
write.state === "denied",
hrtime.state === "prompt" &&
net.state === "prompt" &&
ffi.state === "prompt" &&
read.state === "prompt" &&
run.state === "prompt" &&
write.state === "prompt",
);
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 { 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,
},
},
const worker = new Worker(
new URL("./read_check_granular_worker.js", import.meta.url).href,
{
type: "module",
deno: {
namespace: true,
permissions: "none",
},
);
},
);
worker.onmessage = ({ data: childHasPermission }) => {
postMessage({
parentHasPermission: state === "granted",
childHasPermission,
});
close();
};
worker.postMessage(null);
onmessage = ({ data }) => {
worker.postMessage(data);
};
worker.onmessage = ({ data }) => {
postMessage(data);
};

View file

@ -1,11 +1,29 @@
onmessage = async ({ data }) => {
const { state } = await Deno.permissions.query({
name: "read",
path: data.path,
});
postMessage({
hasPermission: state === "granted",
index: data.index,
});
};
// deno-fmt-ignore-file
postMessage({
envGlobal: (await Deno.permissions.query({ name: "env" })).state,
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,
netGlobal: (await Deno.permissions.query({ name: "net" })).state,
netFoo: (await Deno.permissions.query({ name: "net", host: "foo" })).state,
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,
} from "../../../../test_util/std/testing/asserts.ts";
import { deferred } from "../../../../test_util/std/async/deferred.ts";
import { fromFileUrl } from "../../../../test_util/std/path/mod.ts";
Deno.test({
name: "worker terminate",
@ -454,7 +453,6 @@ Deno.test("Worker limit children permissions", async function () {
});
Deno.test("Worker limit children permissions granularly", async function () {
const promise = deferred();
const worker = new Worker(
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: {
namespace: true,
permissions: {
read: [
new URL("./read_check_worker.js", import.meta.url),
],
env: ["foo"],
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"],
},
},
},
);
//Routes are relative to the spawned worker location
const routes = [
{
permission: false,
path: fromFileUrl(
new URL("read_check_granular_worker.js", import.meta.url),
),
},
{
permission: true,
path: fromFileUrl(new URL("read_check_worker.js", import.meta.url)),
},
];
let checked = 0;
worker.onmessage = ({ data }) => {
checked++;
assertEquals(data.hasPermission, routes[data.index].permission);
routes.shift();
if (checked === routes.length) {
promise.resolve();
}
};
routes.forEach(({ path }, index) =>
worker.postMessage({
index,
path,
})
);
await promise;
const promise = deferred();
worker.onmessage = ({ data }) => promise.resolve(data);
assertEquals(await promise, {
envGlobal: "prompt",
envFoo: "granted",
envAbsent: "prompt",
hrtime: "granted",
netGlobal: "prompt",
netFoo: "granted",
netFoo8000: "granted",
netBar: "prompt",
netBar8000: "granted",
ffiGlobal: "prompt",
ffiFoo: "granted",
ffiBar: "granted",
ffiAbsent: "prompt",
readGlobal: "prompt",
readFoo: "granted",
readBar: "granted",
readAbsent: "prompt",
runGlobal: "prompt",
runFoo: "granted",
runBar: "granted",
runBaz: "granted",
runAbsent: "prompt",
writeGlobal: "prompt",
writeFoo: "granted",
writeBar: "granted",
writeAbsent: "prompt",
});
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 */
/** This worker has permissions but doesn't grant them to its children */
const worker = new Worker(
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();
/** This worker has read permissions but doesn't grant them to its children */
const worker = new Worker(
new URL("./parent_read_check_granular_worker.js", import.meta.url)
.href,
{
type: "module",
deno: {
namespace: true,
permissions: {
read: [
new URL("./read_check_granular_worker.js", import.meta.url),
],
},
},
},
);
//Routes are relative to the spawned worker location
const routes = [
{
childHasPermission: false,
parentHasPermission: true,
path: fromFileUrl(
new URL("read_check_granular_worker.js", import.meta.url),
),
},
{
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.onmessage = ({ data }) => promise.resolve(data);
assertEquals(await promise, {
envGlobal: "prompt",
envFoo: "prompt",
envAbsent: "prompt",
hrtime: "prompt",
netGlobal: "prompt",
netFoo: "prompt",
netFoo8000: "prompt",
netBar: "prompt",
netBar8000: "prompt",
ffiGlobal: "prompt",
ffiFoo: "prompt",
ffiBar: "prompt",
ffiAbsent: "prompt",
readGlobal: "prompt",
readFoo: "prompt",
readBar: "prompt",
readAbsent: "prompt",
runGlobal: "prompt",
runFoo: "prompt",
runBar: "prompt",
runBaz: "prompt",
runAbsent: "prompt",
writeGlobal: "prompt",
writeFoo: "prompt",
writeBar: "prompt",
writeAbsent: "prompt",
});
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("./deno_worker.ts", import.meta.url).href,
{
type: "module",
deno: {
namespace: true,
permissions: {
env: true,
Deno.test({
name:
"Worker initialization throws on worker permissions greater than parent thread permissions",
permissions: { env: false },
fn: function () {
assertThrows(
() => {
const worker = new Worker(
new URL("./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",
);
);
worker.terminate();
},
Deno.errors.PermissionDenied,
"Can't escalate parent thread permissions",
);
},
});
Deno.test("Worker with disabled permissions", async function () {
@ -643,6 +601,19 @@ Deno.test("Worker with disabled permissions", async function () {
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({
name: "worker location",
fn: async function () {

View file

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

View file

@ -83,7 +83,10 @@ mod not_docs {
}
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!")
}
}

View file

@ -10,7 +10,10 @@
} = window;
const { pathFromURL } = window.__bootstrap.util;
const {
ArrayIsArray,
ArrayPrototypeIncludes,
ArrayPrototypeMap,
ArrayPrototypeSlice,
Map,
MapPrototypeGet,
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);
} else if (desc.name === "run") {
desc.command = pathFromURL(desc.command);
@ -213,7 +218,34 @@
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 = {
serializePermissions,
permissions,
Permissions,
PermissionStatus,

View file

@ -4,8 +4,6 @@
((window) => {
const core = window.Deno.core;
const {
ArrayIsArray,
ArrayPrototypeMap,
Error,
StringPrototypeStartsWith,
String,
@ -15,7 +13,8 @@
const webidl = window.__bootstrap.webidl;
const { URL } = window.__bootstrap.url;
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 { deserializeJsMessageData, serializeJsMessageData } =
window.__bootstrap.messagePort;
@ -32,7 +31,7 @@
return core.opSync("op_create_worker", {
hasSourceCode,
name,
permissions,
permissions: serializePermissions(permissions),
sourceCode,
specifier,
useDenoNamespace,
@ -56,87 +55,6 @@
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 {
#id = 0;
#name = "";
@ -152,43 +70,23 @@
super();
specifier = String(specifier);
const {
deno = {},
name = "unknown",
deno,
name,
type = "classic",
} = options;
// TODO(Soremwar)
// `deno: boolean` is kept for backwards compatibility with the previous
// worker options implementation. Remove for 2.0
let workerDenoAttributes;
if (typeof deno == "boolean") {
workerDenoAttributes = {
// Change this to enable the Deno namespace by default
namespace: deno,
permissions: null,
};
let namespace;
let permissions;
if (typeof deno == "object") {
namespace = deno.namespace ?? false;
permissions = deno.permissions ?? undefined;
} else {
workerDenoAttributes = {
// Change this to enable the Deno namespace by default
namespace: !!(deno?.namespace ?? false),
permissions: (deno?.permissions ?? "inherit") === "inherit"
? null
: deno?.permissions,
};
// If the permission option is set to "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,
};
}
// Assume `deno: boolean | undefined`.
// TODO(Soremwar)
// `deno: boolean` is kept for backwards compatibility with the previous
// worker options implementation. Remove for 2.0
namespace = !!deno;
permissions = undefined;
}
const workerType = webidl.converters["WorkerType"](type);
@ -218,17 +116,16 @@
specifier,
hasSourceCode,
sourceCode,
workerDenoAttributes.namespace,
workerDenoAttributes.permissions === null
? null
: parsePermissions(workerDenoAttributes.permissions),
options?.name,
namespace,
permissions,
name,
workerType,
);
this.#id = id;
this.#pollControl();
this.#pollMessages();
}
#handleError(e) {
const event = new ErrorEvent("error", {
cancelable: true,
@ -359,7 +256,6 @@
]);
window.__bootstrap.worker = {
parsePermissions,
Worker,
};
})(this);

View file

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

View file

@ -28,7 +28,6 @@ pub struct PermissionArgs {
host: Option<String>,
variable: Option<String>,
command: Option<String>,
library: Option<String>,
}
pub fn op_query_permission(
@ -50,7 +49,7 @@ pub fn op_query_permission(
),
"env" => permissions.env.query(args.variable.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(),
n => {
return Err(custom_error(
@ -81,7 +80,7 @@ pub fn op_revoke_permission(
),
"env" => permissions.env.revoke(args.variable.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(),
n => {
return Err(custom_error(
@ -112,7 +111,7 @@ pub fn op_request_permission(
),
"env" => permissions.env.request(args.variable.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(),
n => {
return Err(custom_error(

View file

@ -1,18 +1,9 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use crate::ops::TestingFeaturesEnabled;
use crate::permissions::resolve_read_allowlist;
use crate::permissions::resolve_write_allowlist;
use crate::permissions::EnvDescriptor;
use crate::permissions::FfiDescriptor;
use crate::permissions::NetDescriptor;
use crate::permissions::PermissionState;
use crate::permissions::create_child_permissions;
use crate::permissions::ChildPermissionsArg;
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::SendableWebWorkerHandle;
use crate::web_worker::WebWorker;
@ -20,14 +11,10 @@ use crate::web_worker::WebWorkerHandle;
use crate::web_worker::WebWorkerType;
use crate::web_worker::WorkerControlEvent;
use crate::web_worker::WorkerId;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::op_async;
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::Deserializer;
use deno_core::Extension;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
@ -35,10 +22,6 @@ use deno_web::JsMessageData;
use log::debug;
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::HashSet;
use std::convert::From;
use std::fmt;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use std::thread::JoinHandle;
@ -131,369 +114,12 @@ pub fn init(create_web_worker_cb: Arc<CreateWebWorkerCb>) -> Extension {
.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)]
#[serde(rename_all = "camelCase")]
pub struct CreateWorkerArgs {
has_source_code: bool,
name: Option<String>,
permissions: Option<PermissionsArg>,
permissions: Option<ChildPermissionsArg>,
source_code: String,
specifier: String,
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");
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 {
parent_permissions.clone()
};
let parent_permissions = parent_permissions.clone();
let worker_id = state.take::<WorkerId>();
let create_module_loader = state.take::<CreateWebWorkerCbHolder>();

File diff suppressed because it is too large Load diff