1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-26 09:10:40 -05:00

feat: Add sync APIs for "Deno.permissions" (#17019)

This commit adds sync versions of async APIs to "Deno.permissions"
namespace.

Following APIs were added:
- "Deno.permissions.querySync"
- "Deno.permissions.requestSync"
- "Deno.permissions.revokeSync"
This commit is contained in:
Asher Gomez 2023-01-25 08:42:44 +09:00 committed by GitHub
parent f14ea3d4d4
commit 900929f65c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 284 additions and 15 deletions

View file

@ -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";

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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" }),
);
});

View file

@ -4279,6 +4279,20 @@ declare namespace Deno {
*/
query(desc: PermissionDescriptor): Promise<PermissionStatus>;
/** 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<PermissionStatus>;
/** 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<PermissionStatus>;
/** 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
*/

View file

@ -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);
}
}