Skip to content

Commit

Permalink
Use web standard Permissions API (#3200)
Browse files Browse the repository at this point in the history
  • Loading branch information
kt3k authored and ry committed Oct 27, 2019
1 parent 2598f9c commit efd7e78
Show file tree
Hide file tree
Showing 15 changed files with 358 additions and 302 deletions.
4 changes: 4 additions & 0 deletions cli/deno_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ pub fn too_many_redirects() -> ErrBox {
StaticError(ErrorKind::TooManyRedirects, "too many redirects").into()
}

pub fn type_error(msg: String) -> ErrBox {
DenoError::new(ErrorKind::TypeError, msg).into()
}

pub trait GetErrorKind {
fn kind(&self) -> ErrorKind;
}
Expand Down
5 changes: 3 additions & 2 deletions cli/js/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ export { applySourceMap } from "./error_stack.ts";
export { ErrorKind, DenoError } from "./errors.ts";
export {
permissions,
revokePermission,
Permission,
PermissionName,
PermissionState,
PermissionStatus,
Permissions
} from "./permissions.ts";
export { truncateSync, truncate } from "./truncate.ts";
Expand Down
2 changes: 1 addition & 1 deletion cli/js/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export let OP_GET_RANDOM_VALUES: number;
export let OP_GLOBAL_TIMER_STOP: number;
export let OP_GLOBAL_TIMER: number;
export let OP_NOW: number;
export let OP_PERMISSIONS: number;
export let OP_QUERY_PERMISSION: number;
export let OP_REVOKE_PERMISSION: number;
export let OP_CREATE_WORKER: number;
export let OP_HOST_GET_WORKER_CLOSED: number;
Expand Down
3 changes: 2 additions & 1 deletion cli/js/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,6 @@ export enum ErrorKind {
UnsupportedFetchScheme = 47,
TooManyRedirects = 48,
Diagnostic = 49,
JSError = 50
JSError = 50,
TypeError = 51
}
84 changes: 57 additions & 27 deletions cli/js/lib.deno_runtime.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -883,34 +883,64 @@ declare namespace Deno {
}

// @url js/permissions.d.ts
/** Permissions as granted by the caller
* See: https://w3c.github.io/permissions/#permission-registry
*/
export type PermissionName =
| "run"
| "read"
| "write"
| "net"
| "env"
| "hrtime";
/** https://w3c.github.io/permissions/#status-of-a-permission */
export type PermissionState = "granted" | "denied" | "prompt";
interface RunPermissionDescriptor {
name: "run";
}
interface ReadWritePermissionDescriptor {
name: "read" | "write";
path?: string;
}
interface NetPermissionDescriptor {
name: "net";
url?: string;
}
interface EnvPermissionDescriptor {
name: "env";
}
interface HrtimePermissionDescriptor {
name: "hrtime";
}
/** See: https://w3c.github.io/permissions/#permission-descriptor */
type PermissionDescriptor =
| RunPermissionDescriptor
| ReadWritePermissionDescriptor
| NetPermissionDescriptor
| EnvPermissionDescriptor
| HrtimePermissionDescriptor;

export class Permissions {
/** Queries the permission.
* const status = await Deno.permissions.query({ name: "read", path: "/etc" });
* if (status.state === "granted") {
* data = await Deno.readFile("/etc/passwd");
* }
*/
query(d: PermissionDescriptor): Promise<PermissionStatus>;
/** Revokes the permission.
* const status = await Deno.permissions.revoke({ name: "run" });
* assert(status.state !== "granted")
*/
revoke(d: PermissionDescriptor): Promise<PermissionStatus>;
}
export const permissions: Permissions;

/** Permissions as granted by the caller */
export interface Permissions {
read: boolean;
write: boolean;
net: boolean;
env: boolean;
run: boolean;
hrtime: boolean;
}
export type Permission = keyof Permissions;
/** Inspect granted permissions for the current program.
*
* if (Deno.permissions().read) {
* const file = await Deno.readFile("example.test");
* // ...
* }
*/
export function permissions(): Permissions;
/** Revoke a permission. When the permission was already revoked nothing changes
*
* if (Deno.permissions().read) {
* const file = await Deno.readFile("example.test");
* Deno.revokePermission('read');
* }
* Deno.readFile("example.test"); // -> error or permission prompt
*/
export function revokePermission(permission: Permission): void;
/** https://w3c.github.io/permissions/#permissionstatus */
export class PermissionStatus {
state: PermissionState;
constructor(state: PermissionState);
}

// @url js/truncate.d.ts

Expand Down
94 changes: 64 additions & 30 deletions cli/js/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,72 @@
import * as dispatch from "./dispatch.ts";
import { sendSync } from "./dispatch_json.ts";

/** Permissions as granted by the caller */
export interface Permissions {
read: boolean;
write: boolean;
net: boolean;
env: boolean;
run: boolean;
hrtime: boolean;
// NOTE: Keep in sync with src/permissions.rs
}
/** Permissions as granted by the caller
* See: https://w3c.github.io/permissions/#permission-registry
*/
export type PermissionName =
| "read"
| "write"
| "net"
| "env"
| "run"
| "hrtime";
// NOTE: Keep in sync with cli/permissions.rs

export type Permission = keyof Permissions;
/** https://w3c.github.io/permissions/#status-of-a-permission */
export type PermissionState = "granted" | "denied" | "prompt";

/** Inspect granted permissions for the current program.
*
* if (Deno.permissions().read) {
* const file = await Deno.readFile("example.test");
* // ...
* }
*/
export function permissions(): Permissions {
return sendSync(dispatch.OP_PERMISSIONS) as Permissions;
interface RunPermissionDescriptor {
name: "run";
}
interface ReadWritePermissionDescriptor {
name: "read" | "write";
path?: string;
}
interface NetPermissionDescriptor {
name: "net";
url?: string;
}
interface EnvPermissionDescriptor {
name: "env";
}
interface HrtimePermissionDescriptor {
name: "hrtime";
}
/** See: https://w3c.github.io/permissions/#permission-descriptor */
type PermissionDescriptor =
| RunPermissionDescriptor
| ReadWritePermissionDescriptor
| NetPermissionDescriptor
| EnvPermissionDescriptor
| HrtimePermissionDescriptor;

/** Revoke a permission. When the permission was already revoked nothing changes
*
* if (Deno.permissions().read) {
* const file = await Deno.readFile("example.test");
* Deno.revokePermission('read');
* }
* Deno.readFile("example.test"); // -> error or permission prompt
*/
export function revokePermission(permission: Permission): void {
sendSync(dispatch.OP_REVOKE_PERMISSION, { permission });
/** https://w3c.github.io/permissions/#permissionstatus */
export class PermissionStatus {
constructor(public state: PermissionState) {}
// TODO(kt3k): implement onchange handler
}

export class Permissions {
/** Queries the permission.
* const status = await Deno.permissions.query({ name: "read", path: "/etc" });
* if (status.state === "granted") {
* file = await Deno.readFile("/etc/passwd");
* }
*/
async query(desc: PermissionDescriptor): Promise<PermissionStatus> {
const { state } = sendSync(dispatch.OP_QUERY_PERMISSION, desc);
return new PermissionStatus(state);
}

/** Revokes the permission.
* const status = await Deno.permissions.revoke({ name: "run" });
* assert(status.state !== "granted")
*/
async revoke(desc: PermissionDescriptor): Promise<PermissionStatus> {
const { state } = sendSync(dispatch.OP_REVOKE_PERMISSION, desc);
return new PermissionStatus(state);
}
}

export const permissions = new Permissions();
41 changes: 27 additions & 14 deletions cli/js/permissions_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import { testPerm, assert, assertEquals } from "./test_util.ts";
import { test, testPerm, assert, assertEquals } from "./test_util.ts";

const knownPermissions: Deno.Permission[] = [
const knownPermissions: Deno.PermissionName[] = [
"run",
"read",
"write",
Expand All @@ -11,18 +11,31 @@ const knownPermissions: Deno.Permission[] = [
];

for (const grant of knownPermissions) {
testPerm({ [grant]: true }, function envGranted(): void {
const perms = Deno.permissions();
assert(perms !== null);
for (const perm in perms) {
assertEquals(perms[perm], perm === grant);
}
testPerm({ [grant]: true }, async function envGranted(): Promise<void> {
const status0 = await Deno.permissions.query({ name: grant });
assert(status0 != null);
assertEquals(status0.state, "granted");

Deno.revokePermission(grant);

const revoked = Deno.permissions();
for (const perm in revoked) {
assertEquals(revoked[perm], false);
}
const status1 = await Deno.permissions.revoke({ name: grant });
assert(status1 != null);
assertEquals(status1.state, "prompt");
});
}

test(async function permissionInvalidName(): Promise<void> {
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await Deno.permissions.query({ name: "foo" as any });
} catch (e) {
assert(e.name === "TypeError");
}
});

test(async function permissionNetInvalidUrl(): Promise<void> {
try {
// Invalid url causes TypeError.
await Deno.permissions.query({ name: "net", url: ":" });
} catch (e) {
assert(e.name === "TypeError");
}
});
37 changes: 30 additions & 7 deletions cli/js/test_util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,34 @@ interface TestPermissions {
hrtime?: boolean;
}

const processPerms = Deno.permissions();
export interface Permissions {
read: boolean;
write: boolean;
net: boolean;
env: boolean;
run: boolean;
hrtime: boolean;
}

const isGranted = async (name: Deno.PermissionName): Promise<boolean> =>
(await Deno.permissions.query({ name })).state === "granted";

async function getProcessPermissions(): Promise<Permissions> {
return {
run: await isGranted("run"),
read: await isGranted("read"),
write: await isGranted("write"),
net: await isGranted("net"),
env: await isGranted("env"),
hrtime: await isGranted("hrtime")
};
}

const processPerms = await getProcessPermissions();

function permissionsMatch(
processPerms: Deno.Permissions,
requiredPerms: Deno.Permissions
processPerms: Permissions,
requiredPerms: Permissions
): boolean {
for (const permName in processPerms) {
if (processPerms[permName] !== requiredPerms[permName]) {
Expand All @@ -44,9 +67,9 @@ function permissionsMatch(
return true;
}

export const permissionCombinations: Map<string, Deno.Permissions> = new Map();
export const permissionCombinations: Map<string, Permissions> = new Map();

function permToString(perms: Deno.Permissions): string {
function permToString(perms: Permissions): string {
const r = perms.read ? 1 : 0;
const w = perms.write ? 1 : 0;
const n = perms.net ? 1 : 0;
Expand All @@ -56,14 +79,14 @@ function permToString(perms: Deno.Permissions): string {
return `permR${r}W${w}N${n}E${e}U${u}H${h}`;
}

function registerPermCombination(perms: Deno.Permissions): void {
function registerPermCombination(perms: Permissions): void {
const key = permToString(perms);
if (!permissionCombinations.has(key)) {
permissionCombinations.set(key, perms);
}
}

function normalizeTestPermissions(perms: TestPermissions): Deno.Permissions {
function normalizeTestPermissions(perms: TestPermissions): Permissions {
return {
read: !!perms.read,
write: !!perms.write,
Expand Down
10 changes: 7 additions & 3 deletions cli/js/unit_test_runner.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
#!/usr/bin/env -S deno run --reload --allow-run
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import "./unit_tests.ts";
import { permissionCombinations, parseUnitTestOutput } from "./test_util.ts";
import {
permissionCombinations,
parseUnitTestOutput,
Permissions
} from "./test_util.ts";

interface TestResult {
perms: string;
output: string;
result: number;
}

function permsToCliFlags(perms: Deno.Permissions): string[] {
function permsToCliFlags(perms: Permissions): string[] {
return Object.keys(perms)
.map(
(key): string => {
Expand All @@ -25,7 +29,7 @@ function permsToCliFlags(perms: Deno.Permissions): string[] {
.filter((e): boolean => e.length > 0);
}

function fmtPerms(perms: Deno.Permissions): string {
function fmtPerms(perms: Permissions): string {
let fmt = permsToCliFlags(perms).join(" ");

if (!fmt) {
Expand Down
1 change: 1 addition & 0 deletions cli/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pub enum ErrorKind {
TooManyRedirects = 48,
Diagnostic = 49,
JSError = 50,
TypeError = 51,
}

// Warning! The values in this enum are duplicated in js/compiler.ts
Expand Down
Loading

0 comments on commit efd7e78

Please sign in to comment.