summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/dts/lib.deno.ns.d.ts1
-rw-r--r--cli/dts/lib.deno.unstable.d.ts6
-rw-r--r--cli/flags.rs12
-rw-r--r--cli/ops/testing.rs12
-rw-r--r--cli/tests/integration/worker_tests.rs2
-rw-r--r--cli/tests/testdata/test/allow_all.ts2
-rw-r--r--cli/tests/testdata/workers/no_permissions_worker.js12
-rw-r--r--cli/tests/testdata/workers/parent_read_check_granular_worker.js41
-rw-r--r--cli/tests/testdata/workers/parent_read_check_worker.js39
-rw-r--r--cli/tests/testdata/workers/read_check_granular_worker.js40
-rw-r--r--cli/tests/testdata/workers/test.ts235
-rw-r--r--ext/ffi/lib.rs6
-rw-r--r--runtime/build.rs5
-rw-r--r--runtime/js/10_permissions.js (renamed from runtime/js/40_permissions.js)34
-rw-r--r--runtime/js/11_workers.js144
-rw-r--r--runtime/js/40_testing.js6
-rw-r--r--runtime/ops/permissions.rs7
-rw-r--r--runtime/ops/worker_host.rs391
-rw-r--r--runtime/permissions.rs1029
19 files changed, 1201 insertions, 823 deletions
diff --git a/cli/dts/lib.deno.ns.d.ts b/cli/dts/lib.deno.ns.d.ts
index eb91d6fa4..3fff1c61e 100644
--- a/cli/dts/lib.deno.ns.d.ts
+++ b/cli/dts/lib.deno.ns.d.ts
@@ -2178,6 +2178,7 @@ declare namespace Deno {
export interface FfiPermissionDescriptor {
name: "ffi";
+ path?: string | URL;
}
export interface HrtimePermissionDescriptor {
diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts
index 3bea165e5..fd33e1a74 100644
--- a/cli/dts/lib.deno.unstable.d.ts
+++ b/cli/dts/lib.deno.unstable.d.ts
@@ -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>;
diff --git a/cli/flags.rs b/cli/flags.rs
index df17c08da..67c76244d 100644
--- a/cli/flags.rs
+++ b/cli/flags.rs
@@ -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);
}
_ => {}
@@ -1685,10 +1685,10 @@ fn config_arg<'a, 'b>() -> Arg<'a, 'b> {
.long_help(
"Load configuration file.
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
-subcommands like `deno lint` or `deno fmt`.
+Starting with 1.14 configuration file can be used to configure different
+subcommands like `deno lint` or `deno fmt`.
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") {
- 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);
}
diff --git a/cli/ops/testing.rs b/cli/ops/testing.rs
index 99cfc670e..31b60b480 100644
--- a/cli/ops/testing.rs
+++ b/cli/ops/testing.rs
@@ -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));
diff --git a/cli/tests/integration/worker_tests.rs b/cli/tests/integration/worker_tests.rs
index bf1057821..c17b63af9 100644
--- a/cli/tests/integration/worker_tests.rs
+++ b/cli/tests/integration/worker_tests.rs
@@ -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,
});
diff --git a/cli/tests/testdata/test/allow_all.ts b/cli/tests/testdata/test/allow_all.ts
index abe55a8d5..e70ac46b0 100644
--- a/cli/tests/testdata/test/allow_all.ts
+++ b/cli/tests/testdata/test/allow_all.ts
@@ -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");
},
});
diff --git a/cli/tests/testdata/workers/no_permissions_worker.js b/cli/tests/testdata/workers/no_permissions_worker.js
index db0d911ac..f49f690ab 100644
--- a/cli/tests/testdata/workers/no_permissions_worker.js
+++ b/cli/tests/testdata/workers/no_permissions_worker.js
@@ -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();
};
diff --git a/cli/tests/testdata/workers/parent_read_check_granular_worker.js b/cli/tests/testdata/workers/parent_read_check_granular_worker.js
deleted file mode 100644
index 1391190cd..000000000
--- a/cli/tests/testdata/workers/parent_read_check_granular_worker.js
+++ /dev/null
@@ -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,
- });
-};
diff --git a/cli/tests/testdata/workers/parent_read_check_worker.js b/cli/tests/testdata/workers/parent_read_check_worker.js
index ec92cca3f..87ea6bded 100644
--- a/cli/tests/testdata/workers/parent_read_check_worker.js
+++ b/cli/tests/testdata/workers/parent_read_check_worker.js
@@ -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",
},
- );
+ },
+);
+
+onmessage = ({ data }) => {
+ worker.postMessage(data);
+};
- worker.onmessage = ({ data: childHasPermission }) => {
- postMessage({
- parentHasPermission: state === "granted",
- childHasPermission,
- });
- close();
- };
- worker.postMessage(null);
+worker.onmessage = ({ data }) => {
+ postMessage(data);
};
diff --git a/cli/tests/testdata/workers/read_check_granular_worker.js b/cli/tests/testdata/workers/read_check_granular_worker.js
index 25f2058b3..d40fac876 100644
--- a/cli/tests/testdata/workers/read_check_granular_worker.js
+++ b/cli/tests/testdata/workers/read_check_granular_worker.js
@@ -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,
+});
diff --git a/cli/tests/testdata/workers/test.ts b/cli/tests/testdata/workers/test.ts
index effd104f5..4a6575863 100644
--- a/cli/tests/testdata/workers/test.ts
+++ b/cli/tests/testdata/workers/test.ts
@@ -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 () {
diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs
index 4b83070d3..f1e593296 100644
--- a/ext/ffi/lib.rs
+++ b/ext/ffi/lib.rs
@@ -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(
diff --git a/runtime/build.rs b/runtime/build.rs
index 920aafc9f..b1d4fa8cb 100644
--- a/runtime/build.rs
+++ b/runtime/build.rs
@@ -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!")
}
}
diff --git a/runtime/js/40_permissions.js b/runtime/js/10_permissions.js
index 147ae92f0..a6884aab9 100644
--- a/runtime/js/40_permissions.js
+++ b/runtime/js/10_permissions.js
@@ -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,
diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js
index 53b256334..8f0095056 100644
--- a/runtime/js/11_workers.js
+++ b/runtime/js/11_workers.js
@@ -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);
diff --git a/runtime/js/40_testing.js b/runtime/js/40_testing.js
index 5b404766e..b4f7c847c 100644
--- a/runtime/js/40_testing.js
+++ b/runtime/js/40_testing.js
@@ -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,
);
}
diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs
index d9f341633..05f4b75d7 100644
--- a/runtime/ops/permissions.rs
+++ b/runtime/ops/permissions.rs
@@ -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(
diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs
index c1ce782da..e9eb380e0 100644
--- a/runtime/ops/worker_host.rs
+++ b/runtime/ops/worker_host.rs
@@ -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>();
diff --git a/runtime/permissions.rs b/runtime/permissions.rs
index 081268aa9..e62ce4eb1 100644
--- a/runtime/permissions.rs
+++ b/runtime/permissions.rs
@@ -7,8 +7,11 @@ use deno_core::error::uri_error;
use deno_core::error::AnyError;
#[cfg(test)]
use deno_core::parking_lot::Mutex;
+use deno_core::serde::de;
use deno_core::serde::Deserialize;
+use deno_core::serde::Deserializer;
use deno_core::serde::Serialize;
+use deno_core::serde_json;
use deno_core::url;
use deno_core::ModuleSpecifier;
use deno_core::OpState;
@@ -17,6 +20,8 @@ use std::collections::HashSet;
use std::fmt;
use std::hash::Hash;
use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use std::string::ToString;
#[cfg(test)]
use std::sync::atomic::AtomicBool;
#[cfg(test)]
@@ -115,7 +120,7 @@ impl Default for PermissionState {
}
}
-#[derive(Clone, Debug, Default, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct UnitPermission {
pub name: &'static str,
pub description: &'static str,
@@ -161,7 +166,7 @@ impl UnitPermission {
/// A normalized environment variable name. On Windows this will
/// be uppercase and on other platforms it will stay as-is.
-#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct EnvVarName {
inner: String,
}
@@ -184,7 +189,7 @@ impl AsRef<str> for EnvVarName {
}
}
-#[derive(Clone, Debug, Default, PartialEq)]
+#[derive(Clone, Debug, PartialEq)]
pub struct UnaryPermission<T: Eq + Hash> {
pub name: &'static str,
pub description: &'static str,
@@ -194,13 +199,13 @@ pub struct UnaryPermission<T: Eq + Hash> {
pub prompt: bool,
}
-#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct ReadDescriptor(pub PathBuf);
-#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct WriteDescriptor(pub PathBuf);
-#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct NetDescriptor(pub String, pub Option<u16>);
impl NetDescriptor {
@@ -225,7 +230,7 @@ impl fmt::Display for NetDescriptor {
}
}
-#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct EnvDescriptor(EnvVarName);
impl EnvDescriptor {
@@ -240,11 +245,38 @@ impl AsRef<str> for EnvDescriptor {
}
}
-#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
-pub struct RunDescriptor(pub String);
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
+pub enum RunDescriptor {
+ Name(String),
+ Path(PathBuf),
+}
+
+impl FromStr for RunDescriptor {
+ type Err = ();
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let is_path = s.contains('/');
+ #[cfg(windows)]
+ let is_path = is_path || s.contains('\\') || Path::new(s).is_absolute();
+ if is_path {
+ Ok(Self::Path(resolve_from_cwd(Path::new(s)).unwrap()))
+ } else {
+ Ok(Self::Name(s.to_string()))
+ }
+ }
+}
+
+impl ToString for RunDescriptor {
+ fn to_string(&self) -> String {
+ match self {
+ RunDescriptor::Name(s) => s.clone(),
+ RunDescriptor::Path(p) => p.to_string_lossy().to_string(),
+ }
+ }
+}
-#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
-pub struct FfiDescriptor(pub String);
+#[derive(Clone, Eq, PartialEq, Hash, Debug)]
+pub struct FfiDescriptor(pub PathBuf);
impl UnaryPermission<ReadDescriptor> {
pub fn query(&self, path: Option<&Path>) -> PermissionState {
@@ -369,6 +401,32 @@ impl UnaryPermission<ReadDescriptor> {
}
result
}
+
+ pub fn check_all(&mut self) -> Result<(), AnyError> {
+ let (result, prompted) =
+ self.query(None).check(self.name, Some("all"), self.prompt);
+ if prompted {
+ if result.is_ok() {
+ self.global_state = PermissionState::Granted;
+ } else {
+ self.global_state = PermissionState::Denied;
+ }
+ }
+ result
+ }
+}
+
+impl Default for UnaryPermission<ReadDescriptor> {
+ fn default() -> Self {
+ UnaryPermission::<ReadDescriptor> {
+ name: "read",
+ description: "read the file system",
+ global_state: Default::default(),
+ granted_list: Default::default(),
+ denied_list: Default::default(),
+ prompt: false,
+ }
+ }
}
impl UnaryPermission<WriteDescriptor> {
@@ -470,6 +528,32 @@ impl UnaryPermission<WriteDescriptor> {
}
result
}
+
+ pub fn check_all(&mut self) -> Result<(), AnyError> {
+ let (result, prompted) =
+ self.query(None).check(self.name, Some("all"), self.prompt);
+ if prompted {
+ if result.is_ok() {
+ self.global_state = PermissionState::Granted;
+ } else {
+ self.global_state = PermissionState::Denied;
+ }
+ }
+ result
+ }
+}
+
+impl Default for UnaryPermission<WriteDescriptor> {
+ fn default() -> Self {
+ UnaryPermission::<WriteDescriptor> {
+ name: "write",
+ description: "write to the file system",
+ global_state: Default::default(),
+ granted_list: Default::default(),
+ denied_list: Default::default(),
+ prompt: false,
+ }
+ }
}
impl UnaryPermission<NetDescriptor> {
@@ -615,6 +699,34 @@ impl UnaryPermission<NetDescriptor> {
}
result
}
+
+ pub fn check_all(&mut self) -> Result<(), AnyError> {
+ let (result, prompted) =
+ self
+ .query::<&str>(None)
+ .check(self.name, Some("all"), self.prompt);
+ if prompted {
+ if result.is_ok() {
+ self.global_state = PermissionState::Granted;
+ } else {
+ self.global_state = PermissionState::Denied;
+ }
+ }
+ result
+ }
+}
+
+impl Default for UnaryPermission<NetDescriptor> {
+ fn default() -> Self {
+ UnaryPermission::<NetDescriptor> {
+ name: "net",
+ description: "network",
+ global_state: Default::default(),
+ granted_list: Default::default(),
+ denied_list: Default::default(),
+ prompt: false,
+ }
+ }
}
impl UnaryPermission<EnvDescriptor> {
@@ -623,14 +735,14 @@ impl UnaryPermission<EnvDescriptor> {
if self.global_state == PermissionState::Denied
&& match env.as_ref() {
None => true,
- Some(env) => self.denied_list.iter().any(|env_| &env_.0 == env),
+ Some(env) => self.denied_list.contains(&EnvDescriptor::new(env)),
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
|| match env.as_ref() {
None => false,
- Some(env) => self.granted_list.iter().any(|env_| &env_.0 == env),
+ Some(env) => self.granted_list.contains(&EnvDescriptor::new(env)),
}
{
PermissionState::Granted
@@ -717,19 +829,36 @@ impl UnaryPermission<EnvDescriptor> {
}
}
+impl Default for UnaryPermission<EnvDescriptor> {
+ fn default() -> Self {
+ UnaryPermission::<EnvDescriptor> {
+ name: "env",
+ description: "environment variables",
+ global_state: Default::default(),
+ granted_list: Default::default(),
+ denied_list: Default::default(),
+ prompt: false,
+ }
+ }
+}
+
impl UnaryPermission<RunDescriptor> {
pub fn query(&self, cmd: Option<&str>) -> PermissionState {
if self.global_state == PermissionState::Denied
&& match cmd {
None => true,
- Some(cmd) => self.denied_list.iter().any(|cmd_| cmd_.0 == cmd),
+ Some(cmd) => self
+ .denied_list
+ .contains(&RunDescriptor::from_str(cmd).unwrap()),
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
|| match cmd {
None => false,
- Some(cmd) => self.granted_list.iter().any(|cmd_| cmd_.0 == cmd),
+ Some(cmd) => self
+ .granted_list
+ .contains(&RunDescriptor::from_str(cmd).unwrap()),
}
{
PermissionState::Granted
@@ -743,15 +872,21 @@ impl UnaryPermission<RunDescriptor> {
let state = self.query(Some(cmd));
if state == PermissionState::Prompt {
if permission_prompt(&format!("run access to \"{}\"", cmd)) {
- self.granted_list.insert(RunDescriptor(cmd.to_string()));
+ self
+ .granted_list
+ .insert(RunDescriptor::from_str(cmd).unwrap());
PermissionState::Granted
} else {
- self.denied_list.insert(RunDescriptor(cmd.to_string()));
+ self
+ .denied_list
+ .insert(RunDescriptor::from_str(cmd).unwrap());
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else if state == PermissionState::Granted {
- self.granted_list.insert(RunDescriptor(cmd.to_string()));
+ self
+ .granted_list
+ .insert(RunDescriptor::from_str(cmd).unwrap());
PermissionState::Granted
} else {
state
@@ -775,7 +910,9 @@ impl UnaryPermission<RunDescriptor> {
pub fn revoke(&mut self, cmd: Option<&str>) -> PermissionState {
if let Some(cmd) = cmd {
- self.granted_list.remove(&RunDescriptor(cmd.to_string()));
+ self
+ .granted_list
+ .remove(&RunDescriptor::from_str(cmd).unwrap());
} else {
self.granted_list.clear();
}
@@ -793,9 +930,13 @@ impl UnaryPermission<RunDescriptor> {
);
if prompted {
if result.is_ok() {
- self.granted_list.insert(RunDescriptor(cmd.to_string()));
+ self
+ .granted_list
+ .insert(RunDescriptor::from_str(cmd).unwrap());
} else {
- self.denied_list.insert(RunDescriptor(cmd.to_string()));
+ self
+ .denied_list
+ .insert(RunDescriptor::from_str(cmd).unwrap());
self.global_state = PermissionState::Denied;
}
}
@@ -816,19 +957,33 @@ impl UnaryPermission<RunDescriptor> {
}
}
+impl Default for UnaryPermission<RunDescriptor> {
+ fn default() -> Self {
+ UnaryPermission::<RunDescriptor> {
+ name: "run",
+ description: "run a subprocess",
+ global_state: Default::default(),
+ granted_list: Default::default(),
+ denied_list: Default::default(),
+ prompt: false,
+ }
+ }
+}
+
impl UnaryPermission<FfiDescriptor> {
- pub fn query(&self, lib: Option<&str>) -> PermissionState {
+ pub fn query(&self, path: Option<&Path>) -> PermissionState {
+ let path = path.map(|p| resolve_from_cwd(p).unwrap());
if self.global_state == PermissionState::Denied
- && match lib {
+ && match path.as_ref() {
None => true,
- Some(lib) => self.denied_list.iter().any(|lib_| lib_.0 == lib),
+ Some(path) => self.denied_list.contains(&FfiDescriptor(path.clone())),
}
{
PermissionState::Denied
} else if self.global_state == PermissionState::Granted
- || match lib {
+ || match path.as_ref() {
None => false,
- Some(lib) => self.granted_list.iter().any(|lib_| lib_.0 == lib),
+ Some(path) => self.granted_list.contains(&FfiDescriptor(path.clone())),
}
{
PermissionState::Granted
@@ -837,20 +992,24 @@ impl UnaryPermission<FfiDescriptor> {
}
}
- pub fn request(&mut self, lib: Option<&str>) -> PermissionState {
- if let Some(lib) = lib {
- let state = self.query(Some(lib));
+ pub fn request(&mut self, path: Option<&Path>) -> PermissionState {
+ if let Some(path) = path {
+ let (resolved_path, display_path) = resolved_and_display_path(path);
+ let state = self.query(Some(&resolved_path));
if state == PermissionState::Prompt {
- if permission_prompt(&format!("ffi access to \"{}\"", lib)) {
- self.granted_list.insert(FfiDescriptor(lib.to_string()));
+ if permission_prompt(&format!(
+ "ffi access to \"{}\"",
+ display_path.display()
+ )) {
+ self.granted_list.insert(FfiDescriptor(resolved_path));
PermissionState::Granted
} else {
- self.denied_list.insert(FfiDescriptor(lib.to_string()));
+ self.denied_list.insert(FfiDescriptor(resolved_path));
self.global_state = PermissionState::Denied;
PermissionState::Denied
}
} else if state == PermissionState::Granted {
- self.granted_list.insert(FfiDescriptor(lib.to_string()));
+ self.granted_list.insert(FfiDescriptor(resolved_path));
PermissionState::Granted
} else {
state
@@ -872,29 +1031,31 @@ impl UnaryPermission<FfiDescriptor> {
}
}
- pub fn revoke(&mut self, lib: Option<&str>) -> PermissionState {
- if let Some(lib) = lib {
- self.granted_list.remove(&FfiDescriptor(lib.to_string()));
+ pub fn revoke(&mut self, path: Option<&Path>) -> PermissionState {
+ if let Some(path) = path {
+ let path = resolve_from_cwd(path).unwrap();
+ self.granted_list.remove(&FfiDescriptor(path));
} else {
self.granted_list.clear();
}
if self.global_state == PermissionState::Granted {
self.global_state = PermissionState::Prompt;
}
- self.query(lib)
+ self.query(path)
}
- pub fn check(&mut self, lib: &str) -> Result<(), AnyError> {
- let (result, prompted) = self.query(Some(lib)).check(
+ pub fn check(&mut self, path: &Path) -> Result<(), AnyError> {
+ let (resolved_path, display_path) = resolved_and_display_path(path);
+ let (result, prompted) = self.query(Some(&resolved_path)).check(
self.name,
- Some(&format!("\"{}\"", lib)),
+ Some(&format!("\"{}\"", display_path.display())),
self.prompt,
);
if prompted {
if result.is_ok() {
- self.granted_list.insert(FfiDescriptor(lib.to_string()));
+ self.granted_list.insert(FfiDescriptor(resolved_path));
} else {
- self.denied_list.insert(FfiDescriptor(lib.to_string()));
+ self.denied_list.insert(FfiDescriptor(resolved_path));
self.global_state = PermissionState::Denied;
}
}
@@ -915,7 +1076,20 @@ impl UnaryPermission<FfiDescriptor> {
}
}
-#[derive(Clone, Debug, Default, PartialEq)]
+impl Default for UnaryPermission<FfiDescriptor> {
+ fn default() -> Self {
+ UnaryPermission::<FfiDescriptor> {
+ name: "ffi",
+ description: "load a dynamic library",
+ global_state: Default::default(),
+ granted_list: Default::default(),
+ denied_list: Default::default(),
+ prompt: false,
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
pub struct Permissions {
pub read: UnaryPermission<ReadDescriptor>,
pub write: UnaryPermission<WriteDescriptor>,
@@ -926,12 +1100,26 @@ pub struct Permissions {
pub hrtime: UnitPermission,
}
+impl Default for Permissions {
+ fn default() -> Self {
+ Self {
+ read: Permissions::new_read(&None, false),
+ write: Permissions::new_write(&None, false),
+ net: Permissions::new_net(&None, false),
+ env: Permissions::new_env(&None, false),
+ run: Permissions::new_run(&None, false),
+ ffi: Permissions::new_ffi(&None, false),
+ hrtime: Permissions::new_hrtime(false, false),
+ }
+ }
+}
+
#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct PermissionsOptions {
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>>,
@@ -944,12 +1132,10 @@ impl Permissions {
prompt: bool,
) -> UnaryPermission<ReadDescriptor> {
UnaryPermission::<ReadDescriptor> {
- name: "read",
- description: "read the file system",
global_state: global_state_from_option(state),
granted_list: resolve_read_allowlist(state),
- denied_list: Default::default(),
prompt,
+ ..Default::default()
}
}
@@ -958,12 +1144,10 @@ impl Permissions {
prompt: bool,
) -> UnaryPermission<WriteDescriptor> {
UnaryPermission::<WriteDescriptor> {
- name: "write",
- description: "write to the file system",
global_state: global_state_from_option(state),
granted_list: resolve_write_allowlist(state),
- denied_list: Default::default(),
prompt,
+ ..Default::default()
}
}
@@ -972,8 +1156,6 @@ impl Permissions {
prompt: bool,
) -> UnaryPermission<NetDescriptor> {
UnaryPermission::<NetDescriptor> {
- name: "net",
- description: "network",
global_state: global_state_from_option(state),
granted_list: state
.as_ref()
@@ -983,8 +1165,8 @@ impl Permissions {
.collect()
})
.unwrap_or_else(HashSet::new),
- denied_list: Default::default(),
prompt,
+ ..Default::default()
}
}
@@ -993,15 +1175,13 @@ impl Permissions {
prompt: bool,
) -> UnaryPermission<EnvDescriptor> {
UnaryPermission::<EnvDescriptor> {
- name: "env",
- description: "environment variables",
global_state: global_state_from_option(state),
granted_list: state
.as_ref()
.map(|v| v.iter().map(EnvDescriptor::new).collect())
.unwrap_or_else(HashSet::new),
- denied_list: Default::default(),
prompt,
+ ..Default::default()
}
}
@@ -1010,32 +1190,29 @@ impl Permissions {
prompt: bool,
) -> UnaryPermission<RunDescriptor> {
UnaryPermission::<RunDescriptor> {
- name: "run",
- description: "run a subprocess",
global_state: global_state_from_option(state),
granted_list: state
.as_ref()
- .map(|v| v.iter().map(|x| RunDescriptor(x.clone())).collect())
+ .map(|v| {
+ v.iter()
+ .map(|x| RunDescriptor::from_str(x).unwrap())
+ .collect()
+ })
.unwrap_or_else(HashSet::new),
- denied_list: Default::default(),
prompt,
+ ..Default::default()
}
}
pub fn new_ffi(
- state: &Option<Vec<String>>,
+ state: &Option<Vec<PathBuf>>,
prompt: bool,
) -> UnaryPermission<FfiDescriptor> {
UnaryPermission::<FfiDescriptor> {
- name: "ffi",
- description: "load a dynamic library",
global_state: global_state_from_option(state),
- granted_list: state
- .as_ref()
- .map(|v| v.iter().map(|x| FfiDescriptor(x.clone())).collect())
- .unwrap_or_else(HashSet::new),
- denied_list: Default::default(),
+ granted_list: resolve_ffi_allowlist(state),
prompt,
+ ..Default::default()
}
}
@@ -1137,7 +1314,7 @@ impl deno_websocket::WebSocketPermissions for Permissions {
}
impl deno_ffi::FfiPermissions for Permissions {
- fn check(&mut self, path: &str) -> Result<(), AnyError> {
+ fn check(&mut self, path: &Path) -> Result<(), AnyError> {
self.ffi.check(path)
}
}
@@ -1196,6 +1373,20 @@ pub fn resolve_write_allowlist(
}
}
+pub fn resolve_ffi_allowlist(
+ allow: &Option<Vec<PathBuf>>,
+) -> HashSet<FfiDescriptor> {
+ if let Some(v) = allow {
+ v.iter()
+ .map(|raw_path| {
+ FfiDescriptor(resolve_from_cwd(Path::new(&raw_path)).unwrap())
+ })
+ .collect()
+ } else {
+ HashSet::new()
+ }
+}
+
/// Arbitrary helper. Resolves the path from CWD, and also gets a path that
/// can be displayed without leaking the CWD when not allowed.
fn resolved_and_display_path(path: &Path) -> (PathBuf, PathBuf) {
@@ -1204,6 +1395,458 @@ fn resolved_and_display_path(path: &Path) -> (PathBuf, PathBuf) {
(resolved_path, display_path)
}
+fn escalation_error() -> AnyError {
+ custom_error(
+ "PermissionDenied",
+ "Can't escalate parent thread permissions",
+ )
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ChildUnitPermissionArg {
+ Inherit,
+ Granted,
+ NotGranted,
+}
+
+impl Default for ChildUnitPermissionArg {
+ fn default() -> Self {
+ ChildUnitPermissionArg::Inherit
+ }
+}
+
+impl<'de> Deserialize<'de> for ChildUnitPermissionArg {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct ChildUnitPermissionArgVisitor;
+ impl<'de> de::Visitor<'de> for ChildUnitPermissionArgVisitor {
+ type Value = ChildUnitPermissionArg;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("\"inherit\" or boolean")
+ }
+
+ fn visit_unit<E>(self) -> Result<ChildUnitPermissionArg, E>
+ where
+ E: de::Error,
+ {
+ Ok(ChildUnitPermissionArg::Inherit)
+ }
+
+ fn visit_str<E>(self, v: &str) -> Result<ChildUnitPermissionArg, E>
+ where
+ E: de::Error,
+ {
+ if v == "inherit" {
+ Ok(ChildUnitPermissionArg::Inherit)
+ } else {
+ Err(de::Error::invalid_value(de::Unexpected::Str(v), &self))
+ }
+ }
+
+ fn visit_bool<E>(self, v: bool) -> Result<ChildUnitPermissionArg, E>
+ where
+ E: de::Error,
+ {
+ match v {
+ true => Ok(ChildUnitPermissionArg::Granted),
+ false => Ok(ChildUnitPermissionArg::NotGranted),
+ }
+ }
+ }
+ deserializer.deserialize_any(ChildUnitPermissionArgVisitor)
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum ChildUnaryPermissionArg {
+ Inherit,
+ Granted,
+ NotGranted,
+ GrantedList(Vec<String>),
+}
+
+impl Default for ChildUnaryPermissionArg {
+ fn default() -> Self {
+ ChildUnaryPermissionArg::Inherit
+ }
+}
+
+impl<'de> Deserialize<'de> for ChildUnaryPermissionArg {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct ChildUnaryPermissionArgVisitor;
+ impl<'de> de::Visitor<'de> for ChildUnaryPermissionArgVisitor {
+ type Value = ChildUnaryPermissionArg;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("\"inherit\" or boolean or string[]")
+ }
+
+ fn visit_unit<E>(self) -> Result<ChildUnaryPermissionArg, E>
+ where
+ E: de::Error,
+ {
+ Ok(ChildUnaryPermissionArg::Inherit)
+ }
+
+ fn visit_str<E>(self, v: &str) -> Result<ChildUnaryPermissionArg, E>
+ where
+ E: de::Error,
+ {
+ if v == "inherit" {
+ Ok(ChildUnaryPermissionArg::Inherit)
+ } else {
+ Err(de::Error::invalid_value(de::Unexpected::Str(v), &self))
+ }
+ }
+
+ fn visit_bool<E>(self, v: bool) -> Result<ChildUnaryPermissionArg, E>
+ where
+ E: de::Error,
+ {
+ match v {
+ true => Ok(ChildUnaryPermissionArg::Granted),
+ false => Ok(ChildUnaryPermissionArg::NotGranted),
+ }
+ }
+
+ fn visit_seq<V>(
+ self,
+ mut v: V,
+ ) -> Result<ChildUnaryPermissionArg, V::Error>
+ where
+ V: de::SeqAccess<'de>,
+ {
+ let mut granted_list = vec![];
+ while let Some(value) = v.next_element::<String>()? {
+ granted_list.push(value);
+ }
+ Ok(ChildUnaryPermissionArg::GrantedList(granted_list))
+ }
+ }
+ deserializer.deserialize_any(ChildUnaryPermissionArgVisitor)
+ }
+}
+
+/// Directly deserializable from JS worker and test permission options.
+#[derive(Debug, Default, PartialEq)]
+pub struct ChildPermissionsArg {
+ env: ChildUnaryPermissionArg,
+ hrtime: ChildUnitPermissionArg,
+ net: ChildUnaryPermissionArg,
+ ffi: ChildUnaryPermissionArg,
+ read: ChildUnaryPermissionArg,
+ run: ChildUnaryPermissionArg,
+ write: ChildUnaryPermissionArg,
+}
+
+impl<'de> Deserialize<'de> for ChildPermissionsArg {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct ChildPermissionsArgVisitor;
+ impl<'de> de::Visitor<'de> for ChildPermissionsArgVisitor {
+ type Value = ChildPermissionsArg;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("\"inherit\" or \"none\" or object")
+ }
+
+ fn visit_unit<E>(self) -> Result<ChildPermissionsArg, E>
+ where
+ E: de::Error,
+ {
+ Ok(ChildPermissionsArg::default())
+ }
+
+ fn visit_str<E>(self, v: &str) -> Result<ChildPermissionsArg, E>
+ where
+ E: de::Error,
+ {
+ if v == "inherit" {
+ Ok(ChildPermissionsArg::default())
+ } else if v == "none" {
+ Ok(ChildPermissionsArg {
+ env: ChildUnaryPermissionArg::NotGranted,
+ hrtime: ChildUnitPermissionArg::NotGranted,
+ net: ChildUnaryPermissionArg::NotGranted,
+ ffi: ChildUnaryPermissionArg::NotGranted,
+ read: ChildUnaryPermissionArg::NotGranted,
+ run: ChildUnaryPermissionArg::NotGranted,
+ write: ChildUnaryPermissionArg::NotGranted,
+ })
+ } else {
+ Err(de::Error::invalid_value(de::Unexpected::Str(v), &self))
+ }
+ }
+
+ fn visit_map<V>(self, mut v: V) -> Result<ChildPermissionsArg, V::Error>
+ where
+ V: de::MapAccess<'de>,
+ {
+ let mut child_permissions_arg = ChildPermissionsArg::default();
+ while let Some((key, value)) =
+ v.next_entry::<String, serde_json::Value>()?
+ {
+ if key == "env" {
+ let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value);
+ child_permissions_arg.env = arg.map_err(|e| {
+ de::Error::custom(format!("(deno.permissions.env) {}", e))
+ })?;
+ } else if key == "hrtime" {
+ let arg = serde_json::from_value::<ChildUnitPermissionArg>(value);
+ child_permissions_arg.hrtime = arg.map_err(|e| {
+ de::Error::custom(format!("(deno.permissions.hrtime) {}", e))
+ })?;
+ } else if key == "net" {
+ let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value);
+ child_permissions_arg.net = arg.map_err(|e| {
+ de::Error::custom(format!("(deno.permissions.net) {}", e))
+ })?;
+ } else if key == "ffi" {
+ let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value);
+ child_permissions_arg.ffi = arg.map_err(|e| {
+ de::Error::custom(format!("(deno.permissions.ffi) {}", e))
+ })?;
+ } else if key == "read" {
+ let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value);
+ child_permissions_arg.read = arg.map_err(|e| {
+ de::Error::custom(format!("(deno.permissions.read) {}", e))
+ })?;
+ } else if key == "run" {
+ let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value);
+ child_permissions_arg.run = arg.map_err(|e| {
+ de::Error::custom(format!("(deno.permissions.run) {}", e))
+ })?;
+ } else if key == "write" {
+ let arg = serde_json::from_value::<ChildUnaryPermissionArg>(value);
+ child_permissions_arg.write = arg.map_err(|e| {
+ de::Error::custom(format!("(deno.permissions.write) {}", e))
+ })?;
+ } else {
+ return Err(de::Error::custom("unknown permission name"));
+ }
+ }
+ Ok(child_permissions_arg)
+ }
+ }
+ deserializer.deserialize_any(ChildPermissionsArgVisitor)
+ }
+}
+
+pub fn create_child_permissions(
+ main_perms: &mut Permissions,
+ child_permissions_arg: ChildPermissionsArg,
+) -> Result<Permissions, AnyError> {
+ let mut worker_perms = Permissions::default();
+ match child_permissions_arg.env {
+ ChildUnaryPermissionArg::Inherit => {
+ worker_perms.env = main_perms.env.clone();
+ }
+ ChildUnaryPermissionArg::Granted => {
+ if main_perms.env.check_all().is_err() {
+ return Err(escalation_error());
+ }
+ worker_perms.env.global_state = PermissionState::Granted;
+ }
+ ChildUnaryPermissionArg::NotGranted => {}
+ ChildUnaryPermissionArg::GrantedList(granted_list) => {
+ worker_perms.env.granted_list =
+ Permissions::new_env(&Some(granted_list), false).granted_list;
+ if !worker_perms
+ .env
+ .granted_list
+ .iter()
+ .all(|desc| main_perms.env.check(desc.as_ref()).is_ok())
+ {
+ return Err(escalation_error());
+ }
+ }
+ }
+ worker_perms.env.denied_list = main_perms.env.denied_list.clone();
+ if main_perms.env.global_state == PermissionState::Denied {
+ worker_perms.env.global_state = PermissionState::Denied;
+ }
+ worker_perms.env.prompt = main_perms.env.prompt;
+ match child_permissions_arg.hrtime {
+ ChildUnitPermissionArg::Inherit => {
+ worker_perms.hrtime = main_perms.hrtime.clone();
+ }
+ ChildUnitPermissionArg::Granted => {
+ if main_perms.hrtime.check().is_err() {
+ return Err(escalation_error());
+ }
+ worker_perms.hrtime.state = PermissionState::Granted;
+ }
+ ChildUnitPermissionArg::NotGranted => {}
+ }
+ if main_perms.hrtime.state == PermissionState::Denied {
+ worker_perms.hrtime.state = PermissionState::Denied;
+ }
+ worker_perms.hrtime.prompt = main_perms.hrtime.prompt;
+ match child_permissions_arg.net {
+ ChildUnaryPermissionArg::Inherit => {
+ worker_perms.net = main_perms.net.clone();
+ }
+ ChildUnaryPermissionArg::Granted => {
+ if main_perms.net.check_all().is_err() {
+ return Err(escalation_error());
+ }
+ worker_perms.net.global_state = PermissionState::Granted;
+ }
+ ChildUnaryPermissionArg::NotGranted => {}
+ ChildUnaryPermissionArg::GrantedList(granted_list) => {
+ worker_perms.net.granted_list =
+ Permissions::new_net(&Some(granted_list), false).granted_list;
+ if !worker_perms
+ .net
+ .granted_list
+ .iter()
+ .all(|desc| main_perms.net.check(&(&desc.0, desc.1)).is_ok())
+ {
+ return Err(escalation_error());
+ }
+ }
+ }
+ worker_perms.net.denied_list = main_perms.net.denied_list.clone();
+ if main_perms.net.global_state == PermissionState::Denied {
+ worker_perms.net.global_state = PermissionState::Denied;
+ }
+ worker_perms.net.prompt = main_perms.net.prompt;
+ match child_permissions_arg.ffi {
+ ChildUnaryPermissionArg::Inherit => {
+ worker_perms.ffi = main_perms.ffi.clone();
+ }
+ ChildUnaryPermissionArg::Granted => {
+ if main_perms.ffi.check_all().is_err() {
+ return Err(escalation_error());
+ }
+ worker_perms.ffi.global_state = PermissionState::Granted;
+ }
+ ChildUnaryPermissionArg::NotGranted => {}
+ ChildUnaryPermissionArg::GrantedList(granted_list) => {
+ worker_perms.ffi.granted_list = Permissions::new_ffi(
+ &Some(granted_list.iter().map(PathBuf::from).collect()),
+ false,
+ )
+ .granted_list;
+ if !worker_perms
+ .ffi
+ .granted_list
+ .iter()
+ .all(|desc| main_perms.ffi.check(&desc.0).is_ok())
+ {
+ return Err(escalation_error());
+ }
+ }
+ }
+ worker_perms.ffi.denied_list = main_perms.ffi.denied_list.clone();
+ if main_perms.ffi.global_state == PermissionState::Denied {
+ worker_perms.ffi.global_state = PermissionState::Denied;
+ }
+ worker_perms.ffi.prompt = main_perms.ffi.prompt;
+ match child_permissions_arg.read {
+ ChildUnaryPermissionArg::Inherit => {
+ worker_perms.read = main_perms.read.clone();
+ }
+ ChildUnaryPermissionArg::Granted => {
+ if main_perms.read.check_all().is_err() {
+ return Err(escalation_error());
+ }
+ worker_perms.read.global_state = PermissionState::Granted;
+ }
+ ChildUnaryPermissionArg::NotGranted => {}
+ ChildUnaryPermissionArg::GrantedList(granted_list) => {
+ worker_perms.read.granted_list = Permissions::new_read(
+ &Some(granted_list.iter().map(PathBuf::from).collect()),
+ false,
+ )
+ .granted_list;
+ if !worker_perms
+ .read
+ .granted_list
+ .iter()
+ .all(|desc| main_perms.read.check(&desc.0).is_ok())
+ {
+ return Err(escalation_error());
+ }
+ }
+ }
+ worker_perms.read.denied_list = main_perms.read.denied_list.clone();
+ if main_perms.read.global_state == PermissionState::Denied {
+ worker_perms.read.global_state = PermissionState::Denied;
+ }
+ worker_perms.read.prompt = main_perms.read.prompt;
+ match child_permissions_arg.run {
+ ChildUnaryPermissionArg::Inherit => {
+ worker_perms.run = main_perms.run.clone();
+ }
+ ChildUnaryPermissionArg::Granted => {
+ if main_perms.run.check_all().is_err() {
+ return Err(escalation_error());
+ }
+ worker_perms.run.global_state = PermissionState::Granted;
+ }
+ ChildUnaryPermissionArg::NotGranted => {}
+ ChildUnaryPermissionArg::GrantedList(granted_list) => {
+ worker_perms.run.granted_list =
+ Permissions::new_run(&Some(granted_list), false).granted_list;
+ if !worker_perms
+ .run
+ .granted_list
+ .iter()
+ .all(|desc| main_perms.run.check(&desc.to_string()).is_ok())
+ {
+ return Err(escalation_error());
+ }
+ }
+ }
+ worker_perms.run.denied_list = main_perms.run.denied_list.clone();
+ if main_perms.run.global_state == PermissionState::Denied {
+ worker_perms.run.global_state = PermissionState::Denied;
+ }
+ worker_perms.run.prompt = main_perms.run.prompt;
+ match child_permissions_arg.write {
+ ChildUnaryPermissionArg::Inherit => {
+ worker_perms.write = main_perms.write.clone();
+ }
+ ChildUnaryPermissionArg::Granted => {
+ if main_perms.write.check_all().is_err() {
+ return Err(escalation_error());
+ }
+ worker_perms.write.global_state = PermissionState::Granted;
+ }
+ ChildUnaryPermissionArg::NotGranted => {}
+ ChildUnaryPermissionArg::GrantedList(granted_list) => {
+ worker_perms.write.granted_list = Permissions::new_write(
+ &Some(granted_list.iter().map(PathBuf::from).collect()),
+ false,
+ )
+ .granted_list;
+ if !worker_perms
+ .write
+ .granted_list
+ .iter()
+ .all(|desc| main_perms.write.check(&desc.0).is_ok())
+ {
+ return Err(escalation_error());
+ }
+ }
+ }
+ worker_perms.write.denied_list = main_perms.write.denied_list.clone();
+ if main_perms.write.global_state == PermissionState::Denied {
+ worker_perms.write.global_state = PermissionState::Denied;
+ }
+ worker_perms.write.prompt = main_perms.write.prompt;
+ Ok(worker_perms)
+}
+
/// Shows the permission prompt and returns the answer according to the user input.
/// This loops until the user gives the proper input.
#[cfg(not(test))]
@@ -1371,6 +2014,7 @@ fn set_prompt_result(value: bool) {
mod tests {
use super::*;
use deno_core::resolve_url_or_path;
+ use deno_core::serde_json::json;
// Creates vector of strings, Vec<String>
macro_rules! svec {
@@ -1701,11 +2345,11 @@ mod tests {
},
ffi: UnaryPermission {
global_state: PermissionState::Prompt,
- ..Permissions::new_ffi(&Some(svec!["deno"]), false)
+ ..Permissions::new_ffi(&Some(vec![PathBuf::from("deno")]), false)
},
hrtime: UnitPermission {
state: PermissionState::Prompt,
- ..Default::default()
+ ..Permissions::new_hrtime(false, false)
},
};
#[rustfmt::skip]
@@ -1733,9 +2377,9 @@ mod tests {
assert_eq!(perms2.run.query(None), PermissionState::Prompt);
assert_eq!(perms2.run.query(Some(&"deno".to_string())), PermissionState::Granted);
assert_eq!(perms1.ffi.query(None), PermissionState::Granted);
- assert_eq!(perms1.ffi.query(Some(&"deno".to_string())), PermissionState::Granted);
+ assert_eq!(perms1.ffi.query(Some(Path::new("deno"))), PermissionState::Granted);
assert_eq!(perms2.ffi.query(None), PermissionState::Prompt);
- assert_eq!(perms2.ffi.query(Some(&"deno".to_string())), PermissionState::Granted);
+ assert_eq!(perms2.ffi.query(Some(Path::new("deno"))), PermissionState::Granted);
assert_eq!(perms1.hrtime.query(), PermissionState::Granted);
assert_eq!(perms2.hrtime.query(), PermissionState::Prompt);
};
@@ -1772,10 +2416,10 @@ mod tests {
set_prompt_result(false);
assert_eq!(perms.run.request(Some(&"deno".to_string())), PermissionState::Granted);
set_prompt_result(true);
- assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted);
+ assert_eq!(perms.ffi.request(Some(Path::new("deno"))), PermissionState::Granted);
assert_eq!(perms.ffi.query(None), PermissionState::Prompt);
set_prompt_result(false);
- assert_eq!(perms.ffi.request(Some(&"deno".to_string())), PermissionState::Granted);
+ assert_eq!(perms.ffi.request(Some(Path::new("deno"))), PermissionState::Granted);
set_prompt_result(false);
assert_eq!(perms.hrtime.request(), PermissionState::Denied);
set_prompt_result(true);
@@ -1817,11 +2461,11 @@ mod tests {
},
ffi: UnaryPermission {
global_state: PermissionState::Prompt,
- ..Permissions::new_ffi(&Some(svec!["deno"]), false)
+ ..Permissions::new_ffi(&Some(vec![PathBuf::from("deno")]), false)
},
hrtime: UnitPermission {
state: PermissionState::Denied,
- ..Default::default()
+ ..Permissions::new_hrtime(false, false)
},
};
#[rustfmt::skip]
@@ -1837,7 +2481,7 @@ mod tests {
assert_eq!(perms.net.query(Some(&("127.0.0.1", Some(8000)))), PermissionState::Granted);
assert_eq!(perms.env.revoke(Some(&"HOME".to_string())), PermissionState::Prompt);
assert_eq!(perms.run.revoke(Some(&"deno".to_string())), PermissionState::Prompt);
- assert_eq!(perms.ffi.revoke(Some(&"deno".to_string())), PermissionState::Prompt);
+ assert_eq!(perms.ffi.revoke(Some(Path::new("deno"))), PermissionState::Prompt);
assert_eq!(perms.hrtime.revoke(), PermissionState::Denied);
};
}
@@ -1977,4 +2621,233 @@ mod tests {
PermissionState::Prompt
);
}
+
+ #[test]
+ fn test_deserialize_child_permissions_arg() {
+ assert_eq!(
+ ChildPermissionsArg::default(),
+ ChildPermissionsArg {
+ env: ChildUnaryPermissionArg::Inherit,
+ hrtime: ChildUnitPermissionArg::Inherit,
+ net: ChildUnaryPermissionArg::Inherit,
+ ffi: ChildUnaryPermissionArg::Inherit,
+ read: ChildUnaryPermissionArg::Inherit,
+ run: ChildUnaryPermissionArg::Inherit,
+ write: ChildUnaryPermissionArg::Inherit,
+ }
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!("inherit")).unwrap(),
+ ChildPermissionsArg::default()
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!("none")).unwrap(),
+ ChildPermissionsArg {
+ env: ChildUnaryPermissionArg::NotGranted,
+ hrtime: ChildUnitPermissionArg::NotGranted,
+ net: ChildUnaryPermissionArg::NotGranted,
+ ffi: ChildUnaryPermissionArg::NotGranted,
+ read: ChildUnaryPermissionArg::NotGranted,
+ run: ChildUnaryPermissionArg::NotGranted,
+ write: ChildUnaryPermissionArg::NotGranted,
+ }
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!({})).unwrap(),
+ ChildPermissionsArg::default()
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!({
+ "env": ["foo", "bar"],
+ }))
+ .unwrap(),
+ ChildPermissionsArg {
+ env: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar"]),
+ ..Default::default()
+ }
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!({
+ "hrtime": true,
+ }))
+ .unwrap(),
+ ChildPermissionsArg {
+ hrtime: ChildUnitPermissionArg::Granted,
+ ..Default::default()
+ }
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!({
+ "hrtime": false,
+ }))
+ .unwrap(),
+ ChildPermissionsArg {
+ hrtime: ChildUnitPermissionArg::NotGranted,
+ ..Default::default()
+ }
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!({
+ "env": true,
+ "net": true,
+ "ffi": true,
+ "read": true,
+ "run": true,
+ "write": true,
+ }))
+ .unwrap(),
+ ChildPermissionsArg {
+ env: ChildUnaryPermissionArg::Granted,
+ net: ChildUnaryPermissionArg::Granted,
+ ffi: ChildUnaryPermissionArg::Granted,
+ read: ChildUnaryPermissionArg::Granted,
+ run: ChildUnaryPermissionArg::Granted,
+ write: ChildUnaryPermissionArg::Granted,
+ ..Default::default()
+ }
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!({
+ "env": false,
+ "net": false,
+ "ffi": false,
+ "read": false,
+ "run": false,
+ "write": false,
+ }))
+ .unwrap(),
+ ChildPermissionsArg {
+ env: ChildUnaryPermissionArg::NotGranted,
+ net: ChildUnaryPermissionArg::NotGranted,
+ ffi: ChildUnaryPermissionArg::NotGranted,
+ read: ChildUnaryPermissionArg::NotGranted,
+ run: ChildUnaryPermissionArg::NotGranted,
+ write: ChildUnaryPermissionArg::NotGranted,
+ ..Default::default()
+ }
+ );
+ assert_eq!(
+ serde_json::from_value::<ChildPermissionsArg>(json!({
+ "env": ["foo", "bar"],
+ "net": ["foo", "bar:8000"],
+ "ffi": ["foo", "file:///bar/baz"],
+ "read": ["foo", "file:///bar/baz"],
+ "run": ["foo", "file:///bar/baz", "./qux"],
+ "write": ["foo", "file:///bar/baz"],
+ }))
+ .unwrap(),
+ ChildPermissionsArg {
+ env: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar"]),
+ net: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar:8000"]),
+ ffi: ChildUnaryPermissionArg::GrantedList(svec![
+ "foo",
+ "file:///bar/baz"
+ ]),
+ read: ChildUnaryPermissionArg::GrantedList(svec![
+ "foo",
+ "file:///bar/baz"
+ ]),
+ run: ChildUnaryPermissionArg::GrantedList(svec![
+ "foo",
+ "file:///bar/baz",
+ "./qux"
+ ]),
+ write: ChildUnaryPermissionArg::GrantedList(svec![
+ "foo",
+ "file:///bar/baz"
+ ]),
+ ..Default::default()
+ }
+ );
+ }
+
+ #[test]
+ fn test_create_child_permissions() {
+ let mut main_perms = Permissions {
+ env: Permissions::new_env(&Some(vec![]), false),
+ hrtime: Permissions::new_hrtime(true, false),
+ net: Permissions::new_net(&Some(svec!["foo", "bar"]), false),
+ ..Default::default()
+ };
+ assert_eq!(
+ create_child_permissions(
+ &mut main_perms.clone(),
+ ChildPermissionsArg {
+ env: ChildUnaryPermissionArg::Inherit,
+ hrtime: ChildUnitPermissionArg::NotGranted,
+ net: ChildUnaryPermissionArg::GrantedList(svec!["foo"]),
+ ffi: ChildUnaryPermissionArg::NotGranted,
+ ..Default::default()
+ }
+ )
+ .unwrap(),
+ Permissions {
+ env: Permissions::new_env(&Some(vec![]), false),
+ net: Permissions::new_net(&Some(svec!["foo"]), false),
+ ..Default::default()
+ }
+ );
+ assert!(create_child_permissions(
+ &mut main_perms.clone(),
+ ChildPermissionsArg {
+ net: ChildUnaryPermissionArg::Granted,
+ ..Default::default()
+ }
+ )
+ .is_err());
+ assert!(create_child_permissions(
+ &mut main_perms.clone(),
+ ChildPermissionsArg {
+ net: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar", "baz"]),
+ ..Default::default()
+ }
+ )
+ .is_err());
+ assert!(create_child_permissions(
+ &mut main_perms,
+ ChildPermissionsArg {
+ ffi: ChildUnaryPermissionArg::GrantedList(svec!["foo"]),
+ ..Default::default()
+ }
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn test_create_child_permissions_with_prompt() {
+ let _guard = PERMISSION_PROMPT_GUARD.lock();
+ let mut main_perms = Permissions::from_options(&PermissionsOptions {
+ prompt: true,
+ ..Default::default()
+ });
+ set_prompt_result(true);
+ let worker_perms = create_child_permissions(
+ &mut main_perms,
+ ChildPermissionsArg {
+ hrtime: ChildUnitPermissionArg::Granted,
+ read: ChildUnaryPermissionArg::Granted,
+ run: ChildUnaryPermissionArg::GrantedList(svec!["foo", "bar"]),
+ ..Default::default()
+ },
+ )
+ .unwrap();
+ assert_eq!(main_perms, worker_perms);
+ }
+
+ #[test]
+ fn test_create_child_permissions_with_inherited_denied_list() {
+ let _guard = PERMISSION_PROMPT_GUARD.lock();
+ let mut main_perms = Permissions::from_options(&PermissionsOptions {
+ prompt: true,
+ ..Default::default()
+ });
+ set_prompt_result(false);
+ assert!(main_perms.write.check(&PathBuf::from("foo")).is_err());
+ let worker_perms = create_child_permissions(
+ &mut main_perms.clone(),
+ ChildPermissionsArg::default(),
+ )
+ .unwrap();
+ assert_eq!(worker_perms.write.denied_list, main_perms.write.denied_list);
+ }
}