diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 65d567658a..d5a988bf43 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -558,6 +558,21 @@ fn _090_run_permissions_request() { ]); } +#[test] +fn _090_run_permissions_request_sync() { + let args = "run --quiet run/090_run_permissions_request_sync.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + Output("⚠️ ️Deno requests run access to \"ls\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), + Input("y\n"), + Output("⚠️ ️Deno requests run access to \"cat\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), + Input("n\n"), + Output("granted\r\n"), + Output("prompt\r\n"), + Output("denied\r\n"), + ]); +} + itest!(_091_use_define_for_class_fields { args: "run --check run/091_use_define_for_class_fields.ts", output: "run/091_use_define_for_class_fields.ts.out", @@ -2272,6 +2287,21 @@ mod permissions { ]); } + #[test] + fn _061_permissions_request_sync() { + let args = "run --quiet run/061_permissions_request_sync.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + Output("⚠️ ️Deno requests read access to \"foo\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "), + Input("y\n"), + Output("⚠️ ️Deno requests read access to \"bar\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), + Input("n\n"), + Output("granted\r\n"), + Output("prompt\r\n"), + Output("denied\r\n"), + ]); + } + #[test] fn _062_permissions_request_global() { let args = "run --quiet run/062_permissions_request_global.ts"; @@ -2285,16 +2315,39 @@ mod permissions { ]); } + #[test] + fn _062_permissions_request_global_sync() { + let args = "run --quiet run/062_permissions_request_global_sync.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + Output("⚠️ ️Deno requests read access. Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "), + Input("y\n"), + Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), + Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), + Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), + ]); + } + itest!(_063_permissions_revoke { args: "run --allow-read=foo,bar run/063_permissions_revoke.ts", output: "run/063_permissions_revoke.ts.out", }); + itest!(_063_permissions_revoke_sync { + args: "run --allow-read=foo,bar run/063_permissions_revoke_sync.ts", + output: "run/063_permissions_revoke.ts.out", + }); + itest!(_064_permissions_revoke_global { args: "run --allow-read=foo,bar run/064_permissions_revoke_global.ts", output: "run/064_permissions_revoke_global.ts.out", }); + itest!(_064_permissions_revoke_global_sync { + args: "run --allow-read=foo,bar run/064_permissions_revoke_global_sync.ts", + output: "run/064_permissions_revoke_global.ts.out", + }); + #[test] fn _066_prompt() { let args = "run --quiet --unstable run/066_prompt.ts"; diff --git a/cli/tests/testdata/run/061_permissions_request_sync.ts b/cli/tests/testdata/run/061_permissions_request_sync.ts new file mode 100644 index 0000000000..52d225c479 --- /dev/null +++ b/cli/tests/testdata/run/061_permissions_request_sync.ts @@ -0,0 +1,8 @@ +const status1 = + Deno.permissions.requestSync({ name: "read", path: "foo" }).state; +const status2 = Deno.permissions.querySync({ name: "read", path: "bar" }).state; +const status3 = + Deno.permissions.requestSync({ name: "read", path: "bar" }).state; +console.log(status1); +console.log(status2); +console.log(status3); diff --git a/cli/tests/testdata/run/062_permissions_request_global_sync.ts b/cli/tests/testdata/run/062_permissions_request_global_sync.ts new file mode 100644 index 0000000000..d0e17c7ed0 --- /dev/null +++ b/cli/tests/testdata/run/062_permissions_request_global_sync.ts @@ -0,0 +1,6 @@ +const status1 = Deno.permissions.requestSync({ name: "read" }); +console.log(status1); +const status2 = Deno.permissions.querySync({ name: "read", path: "foo" }); +console.log(status2); +const status3 = Deno.permissions.querySync({ name: "read", path: "bar" }); +console.log(status3); diff --git a/cli/tests/testdata/run/063_permissions_revoke_sync.ts b/cli/tests/testdata/run/063_permissions_revoke_sync.ts new file mode 100644 index 0000000000..267ef37859 --- /dev/null +++ b/cli/tests/testdata/run/063_permissions_revoke_sync.ts @@ -0,0 +1,6 @@ +const status1 = Deno.permissions.revokeSync({ name: "read", path: "foo" }); +console.log(status1); +const status2 = Deno.permissions.querySync({ name: "read", path: "bar" }); +console.log(status2); +const status3 = Deno.permissions.revokeSync({ name: "read", path: "bar" }); +console.log(status3); diff --git a/cli/tests/testdata/run/064_permissions_revoke_global_sync.ts b/cli/tests/testdata/run/064_permissions_revoke_global_sync.ts new file mode 100644 index 0000000000..597b1481d5 --- /dev/null +++ b/cli/tests/testdata/run/064_permissions_revoke_global_sync.ts @@ -0,0 +1,6 @@ +const status1 = Deno.permissions.revokeSync({ name: "read" }); +console.log(status1); +const status2 = Deno.permissions.querySync({ name: "read", path: "foo" }); +console.log(status2); +const status3 = Deno.permissions.querySync({ name: "read", path: "bar" }); +console.log(status3); diff --git a/cli/tests/testdata/run/090_run_permissions_request_sync.ts b/cli/tests/testdata/run/090_run_permissions_request_sync.ts new file mode 100644 index 0000000000..40ff845226 --- /dev/null +++ b/cli/tests/testdata/run/090_run_permissions_request_sync.ts @@ -0,0 +1,18 @@ +const status1 = + Deno.permissions.requestSync({ name: "run", command: "ls" }).state; +if (status1 != "granted") { + throw Error(`unexpected status1 ${status1}`); +} +const status2 = + Deno.permissions.querySync({ name: "run", command: "cat" }).state; +if (status2 != "prompt") { + throw Error(`unexpected status2 ${status2}`); +} +const status3 = + Deno.permissions.requestSync({ name: "run", command: "cat" }).state; +if (status3 != "denied") { + throw Error(`unexpected status3 ${status3}`); +} +console.log(status1); +console.log(status2); +console.log(status3); diff --git a/cli/tests/unit/permissions_test.ts b/cli/tests/unit/permissions_test.ts index 193e606896..f001a0e4e3 100644 --- a/cli/tests/unit/permissions_test.ts +++ b/cli/tests/unit/permissions_test.ts @@ -13,12 +13,25 @@ Deno.test(async function permissionInvalidName() { }, TypeError); }); +Deno.test(function permissionInvalidNameSync() { + assertThrows(() => { + // deno-lint-ignore no-explicit-any + Deno.permissions.querySync({ name: "foo" as any }); + }, TypeError); +}); + Deno.test(async function permissionNetInvalidHost() { await assertRejects(async () => { await Deno.permissions.query({ name: "net", host: ":" }); }, URIError); }); +Deno.test(function permissionNetInvalidHostSync() { + assertThrows(() => { + Deno.permissions.querySync({ name: "net", host: ":" }); + }, URIError); +}); + Deno.test(async function permissionSysValidKind() { await Deno.permissions.query({ name: "sys", kind: "loadavg" }); await Deno.permissions.query({ name: "sys", kind: "osRelease" }); @@ -30,6 +43,16 @@ Deno.test(async function permissionSysValidKind() { await Deno.permissions.query({ name: "sys", kind: "gid" }); }); +Deno.test(function permissionSysValidKindSync() { + Deno.permissions.querySync({ name: "sys", kind: "loadavg" }); + Deno.permissions.querySync({ name: "sys", kind: "osRelease" }); + Deno.permissions.querySync({ name: "sys", kind: "networkInterfaces" }); + Deno.permissions.querySync({ name: "sys", kind: "systemMemoryInfo" }); + Deno.permissions.querySync({ name: "sys", kind: "hostname" }); + Deno.permissions.querySync({ name: "sys", kind: "uid" }); + Deno.permissions.querySync({ name: "sys", kind: "gid" }); +}); + Deno.test(async function permissionSysInvalidKind() { await assertRejects(async () => { // deno-lint-ignore no-explicit-any @@ -37,6 +60,13 @@ Deno.test(async function permissionSysInvalidKind() { }, TypeError); }); +Deno.test(function permissionSysInvalidKindSync() { + assertThrows(() => { + // deno-lint-ignore no-explicit-any + Deno.permissions.querySync({ name: "sys", kind: "abc" as any }); + }, TypeError); +}); + Deno.test(async function permissionQueryReturnsEventTarget() { const status = await Deno.permissions.query({ name: "hrtime" }); assert(["granted", "denied", "prompt"].includes(status.state)); @@ -49,6 +79,18 @@ Deno.test(async function permissionQueryReturnsEventTarget() { assert(status === (await Deno.permissions.query({ name: "hrtime" }))); }); +Deno.test(function permissionQueryReturnsEventTargetSync() { + const status = Deno.permissions.querySync({ name: "hrtime" }); + assert(["granted", "denied", "prompt"].includes(status.state)); + let called = false; + status.addEventListener("change", () => { + called = true; + }); + status.dispatchEvent(new Event("change")); + assert(called); + assert(status === Deno.permissions.querySync({ name: "hrtime" })); +}); + Deno.test(async function permissionQueryForReadReturnsSameStatus() { const status1 = await Deno.permissions.query({ name: "read", @@ -61,6 +103,18 @@ Deno.test(async function permissionQueryForReadReturnsSameStatus() { assert(status1 === status2); }); +Deno.test(function permissionQueryForReadReturnsSameStatusSync() { + const status1 = Deno.permissions.querySync({ + name: "read", + path: ".", + }); + const status2 = Deno.permissions.querySync({ + name: "read", + path: ".", + }); + assert(status1 === status2); +}); + Deno.test(function permissionsIllegalConstructor() { assertThrows(() => new Deno.Permissions(), TypeError, "Illegal constructor."); assertEquals(Deno.Permissions.length, 0); @@ -85,6 +139,21 @@ Deno.test(async function permissionURL() { await Deno.permissions.query({ name: "run", command: path }); }); +Deno.test(function permissionURLSync() { + Deno.permissions.querySync({ + name: "read", + path: new URL(".", import.meta.url), + }); + Deno.permissions.querySync({ + name: "write", + path: new URL(".", import.meta.url), + }); + Deno.permissions.querySync({ + name: "run", + command: new URL(".", import.meta.url), + }); +}); + Deno.test(async function permissionDescriptorValidation() { for (const value of [undefined, null, {}]) { for (const method of ["query", "request", "revoke"]) { @@ -100,6 +169,21 @@ Deno.test(async function permissionDescriptorValidation() { } }); +Deno.test(function permissionDescriptorValidationSync() { + for (const value of [undefined, null, {}]) { + for (const method of ["querySync", "revokeSync", "requestSync"]) { + assertThrows( + () => { + // deno-lint-ignore no-explicit-any + (Deno.permissions as any)[method](value as any); + }, + TypeError, + '"undefined" is not a valid permission name', + ); + } + } +}); + // Regression test for https://github.com/denoland/deno/issues/15894. Deno.test(async function permissionStatusObjectsNotEqual() { assert( @@ -107,3 +191,10 @@ Deno.test(async function permissionStatusObjectsNotEqual() { await Deno.permissions.query({ name: "env", variable: "B" }), ); }); + +Deno.test(function permissionStatusObjectsNotEqualSync() { + assert( + Deno.permissions.querySync({ name: "env", variable: "A" }) != + Deno.permissions.querySync({ name: "env", variable: "B" }), + ); +}); diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index b6add6e4bb..84655a5f38 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -4279,6 +4279,20 @@ declare namespace Deno { */ query(desc: PermissionDescriptor): Promise; + /** Returns the current status of a permission. + * + * Note, if the permission is already granted, `request()` will not prompt + * the user again, therefore `querySync()` is only necessary if you are going + * to react differently existing permissions without wanting to modify them + * or prompt the user to modify them. + * + * ```ts + * const status = Deno.permissions.querySync({ name: "read", path: "/etc" }); + * console.log(status.state); + * ``` + */ + querySync(desc: PermissionDescriptor): PermissionStatus; + /** Revokes a permission, and resolves to the state of the permission. * * ```ts @@ -4290,6 +4304,17 @@ declare namespace Deno { */ revoke(desc: PermissionDescriptor): Promise; + /** Revokes a permission, and returns the state of the permission. + * + * ```ts + * import { assert } from "https://deno.land/std/testing/asserts.ts"; + * + * const status = Deno.permissions.revokeSync({ name: "run" }); + * assert(status.state !== "granted") + * ``` + */ + revokeSync(desc: PermissionDescriptor): PermissionStatus; + /** Requests the permission, and resolves to the state of the permission. * * If the permission is already granted, the user will not be prompted to @@ -4305,6 +4330,23 @@ declare namespace Deno { * ``` */ request(desc: PermissionDescriptor): Promise; + + + /** Requests the permission, and returns the state of the permission. + * + * If the permission is already granted, the user will not be prompted to + * grant the permission again. + * + * ```ts + * const status = Deno.permissions.requestSync({ name: "env" }); + * if (status.state === "granted") { + * console.log("'env' permission is granted."); + * } else { + * console.log("'env' permission is denied."); + * } + * ``` + */ + requestSync(desc: PermissionDescriptor): PermissionStatus; } /** Deno's permission management API. @@ -4335,6 +4377,11 @@ declare namespace Deno { * const status = await Deno.permissions.query({ name: "read", path: "/etc" }); * console.log(status.state); * ``` + * + * ```ts + * const status = Deno.permissions.querySync({ name: "read", path: "/etc" }); + * console.log(status.state); + * ``` * * ### Revoking * @@ -4344,6 +4391,13 @@ declare namespace Deno { * const status = await Deno.permissions.revoke({ name: "run" }); * assert(status.state !== "granted") * ``` + * + * ```ts + * import { assert } from "https://deno.land/std/testing/asserts.ts"; + * + * const status = Deno.permissions.revokeSync({ name: "run" }); + * assert(status.state !== "granted") + * ``` * * ### Requesting * @@ -4355,6 +4409,15 @@ declare namespace Deno { * console.log("'env' permission is denied."); * } * ``` + * + * ```ts + * const status = Deno.permissions.requestSync({ name: "env" }); + * if (status.state === "granted") { + * console.log("'env' permission is granted."); + * } else { + * console.log("'env' permission is denied."); + * } + * ``` * * @category Permissions */ diff --git a/runtime/js/10_permissions.js b/runtime/js/10_permissions.js index c2ce6cef0d..7c4af8ed0c 100644 --- a/runtime/js/10_permissions.js +++ b/runtime/js/10_permissions.js @@ -183,48 +183,66 @@ } query(desc) { + try { + return PromiseResolve(this.querySync(desc)); + } catch (error) { + return PromiseReject(error); + } + } + + querySync(desc) { if (!isValidDescriptor(desc)) { - return PromiseReject( - new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, - ), + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, ); } formDescriptor(desc); const state = opQuery(desc); - return PromiseResolve(cache(desc, state)); + return cache(desc, state); } revoke(desc) { + try { + return PromiseResolve(this.revokeSync(desc)); + } catch (error) { + return PromiseReject(error); + } + } + + revokeSync(desc) { if (!isValidDescriptor(desc)) { - return PromiseReject( - new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, - ), + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, ); } formDescriptor(desc); const state = opRevoke(desc); - return PromiseResolve(cache(desc, state)); + return cache(desc, state); } request(desc) { + try { + return PromiseResolve(this.requestSync(desc)); + } catch (error) { + return PromiseReject(error); + } + } + + requestSync(desc) { if (!isValidDescriptor(desc)) { - return PromiseReject( - new TypeError( - `The provided value "${desc?.name}" is not a valid permission name.`, - ), + throw new TypeError( + `The provided value "${desc?.name}" is not a valid permission name.`, ); } formDescriptor(desc); const state = opRequest(desc); - return PromiseResolve(cache(desc, state)); + return cache(desc, state); } }