mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 16:49:18 -05:00
Use web standard Permissions API (#3200)
This commit is contained in:
parent
2598f9c68d
commit
efd7e78af3
15 changed files with 362 additions and 306 deletions
|
@ -96,6 +96,10 @@ pub fn too_many_redirects() -> ErrBox {
|
||||||
StaticError(ErrorKind::TooManyRedirects, "too many redirects").into()
|
StaticError(ErrorKind::TooManyRedirects, "too many redirects").into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn type_error(msg: String) -> ErrBox {
|
||||||
|
DenoError::new(ErrorKind::TypeError, msg).into()
|
||||||
|
}
|
||||||
|
|
||||||
pub trait GetErrorKind {
|
pub trait GetErrorKind {
|
||||||
fn kind(&self) -> ErrorKind;
|
fn kind(&self) -> ErrorKind;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,8 +68,9 @@ export { applySourceMap } from "./error_stack.ts";
|
||||||
export { ErrorKind, DenoError } from "./errors.ts";
|
export { ErrorKind, DenoError } from "./errors.ts";
|
||||||
export {
|
export {
|
||||||
permissions,
|
permissions,
|
||||||
revokePermission,
|
PermissionName,
|
||||||
Permission,
|
PermissionState,
|
||||||
|
PermissionStatus,
|
||||||
Permissions
|
Permissions
|
||||||
} from "./permissions.ts";
|
} from "./permissions.ts";
|
||||||
export { truncateSync, truncate } from "./truncate.ts";
|
export { truncateSync, truncate } from "./truncate.ts";
|
||||||
|
|
|
@ -36,7 +36,7 @@ export let OP_GET_RANDOM_VALUES: number;
|
||||||
export let OP_GLOBAL_TIMER_STOP: number;
|
export let OP_GLOBAL_TIMER_STOP: number;
|
||||||
export let OP_GLOBAL_TIMER: number;
|
export let OP_GLOBAL_TIMER: number;
|
||||||
export let OP_NOW: 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_REVOKE_PERMISSION: number;
|
||||||
export let OP_CREATE_WORKER: number;
|
export let OP_CREATE_WORKER: number;
|
||||||
export let OP_HOST_GET_WORKER_CLOSED: number;
|
export let OP_HOST_GET_WORKER_CLOSED: number;
|
||||||
|
|
|
@ -75,5 +75,6 @@ export enum ErrorKind {
|
||||||
UnsupportedFetchScheme = 47,
|
UnsupportedFetchScheme = 47,
|
||||||
TooManyRedirects = 48,
|
TooManyRedirects = 48,
|
||||||
Diagnostic = 49,
|
Diagnostic = 49,
|
||||||
JSError = 50
|
JSError = 50,
|
||||||
|
TypeError = 51
|
||||||
}
|
}
|
||||||
|
|
78
cli/js/lib.deno_runtime.d.ts
vendored
78
cli/js/lib.deno_runtime.d.ts
vendored
|
@ -883,34 +883,64 @@ declare namespace Deno {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @url js/permissions.d.ts
|
// @url js/permissions.d.ts
|
||||||
|
/** Permissions as granted by the caller
|
||||||
/** Permissions as granted by the caller */
|
* See: https://w3c.github.io/permissions/#permission-registry
|
||||||
export interface Permissions {
|
*/
|
||||||
read: boolean;
|
export type PermissionName =
|
||||||
write: boolean;
|
| "run"
|
||||||
net: boolean;
|
| "read"
|
||||||
env: boolean;
|
| "write"
|
||||||
run: boolean;
|
| "net"
|
||||||
hrtime: boolean;
|
| "env"
|
||||||
|
| "hrtime";
|
||||||
|
/** https://w3c.github.io/permissions/#status-of-a-permission */
|
||||||
|
export type PermissionState = "granted" | "denied" | "prompt";
|
||||||
|
interface RunPermissionDescriptor {
|
||||||
|
name: "run";
|
||||||
}
|
}
|
||||||
export type Permission = keyof Permissions;
|
interface ReadWritePermissionDescriptor {
|
||||||
/** Inspect granted permissions for the current program.
|
name: "read" | "write";
|
||||||
*
|
path?: string;
|
||||||
* if (Deno.permissions().read) {
|
}
|
||||||
* const file = await Deno.readFile("example.test");
|
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");
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export function permissions(): Permissions;
|
query(d: PermissionDescriptor): Promise<PermissionStatus>;
|
||||||
/** Revoke a permission. When the permission was already revoked nothing changes
|
/** Revokes the permission.
|
||||||
*
|
* const status = await Deno.permissions.revoke({ name: "run" });
|
||||||
* if (Deno.permissions().read) {
|
* assert(status.state !== "granted")
|
||||||
* const file = await Deno.readFile("example.test");
|
|
||||||
* Deno.revokePermission('read');
|
|
||||||
* }
|
|
||||||
* Deno.readFile("example.test"); // -> error or permission prompt
|
|
||||||
*/
|
*/
|
||||||
export function revokePermission(permission: Permission): void;
|
revoke(d: PermissionDescriptor): Promise<PermissionStatus>;
|
||||||
|
}
|
||||||
|
export const permissions: Permissions;
|
||||||
|
|
||||||
|
/** https://w3c.github.io/permissions/#permissionstatus */
|
||||||
|
export class PermissionStatus {
|
||||||
|
state: PermissionState;
|
||||||
|
constructor(state: PermissionState);
|
||||||
|
}
|
||||||
|
|
||||||
// @url js/truncate.d.ts
|
// @url js/truncate.d.ts
|
||||||
|
|
||||||
|
|
|
@ -2,38 +2,72 @@
|
||||||
import * as dispatch from "./dispatch.ts";
|
import * as dispatch from "./dispatch.ts";
|
||||||
import { sendSync } from "./dispatch_json.ts";
|
import { sendSync } from "./dispatch_json.ts";
|
||||||
|
|
||||||
/** Permissions as granted by the caller */
|
/** Permissions as granted by the caller
|
||||||
export interface Permissions {
|
* See: https://w3c.github.io/permissions/#permission-registry
|
||||||
read: boolean;
|
*/
|
||||||
write: boolean;
|
export type PermissionName =
|
||||||
net: boolean;
|
| "read"
|
||||||
env: boolean;
|
| "write"
|
||||||
run: boolean;
|
| "net"
|
||||||
hrtime: boolean;
|
| "env"
|
||||||
// NOTE: Keep in sync with src/permissions.rs
|
| "run"
|
||||||
|
| "hrtime";
|
||||||
|
// NOTE: Keep in sync with cli/permissions.rs
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
/** https://w3c.github.io/permissions/#permissionstatus */
|
||||||
|
export class PermissionStatus {
|
||||||
|
constructor(public state: PermissionState) {}
|
||||||
|
// TODO(kt3k): implement onchange handler
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Permission = keyof Permissions;
|
export class Permissions {
|
||||||
|
/** Queries the permission.
|
||||||
/** Inspect granted permissions for the current program.
|
* const status = await Deno.permissions.query({ name: "read", path: "/etc" });
|
||||||
*
|
* if (status.state === "granted") {
|
||||||
* if (Deno.permissions().read) {
|
* file = await Deno.readFile("/etc/passwd");
|
||||||
* const file = await Deno.readFile("example.test");
|
|
||||||
* // ...
|
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export function permissions(): Permissions {
|
async query(desc: PermissionDescriptor): Promise<PermissionStatus> {
|
||||||
return sendSync(dispatch.OP_PERMISSIONS) as Permissions;
|
const { state } = sendSync(dispatch.OP_QUERY_PERMISSION, desc);
|
||||||
|
return new PermissionStatus(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Revoke a permission. When the permission was already revoked nothing changes
|
/** Revokes the permission.
|
||||||
*
|
* const status = await Deno.permissions.revoke({ name: "run" });
|
||||||
* if (Deno.permissions().read) {
|
* assert(status.state !== "granted")
|
||||||
* const file = await Deno.readFile("example.test");
|
|
||||||
* Deno.revokePermission('read');
|
|
||||||
* }
|
|
||||||
* Deno.readFile("example.test"); // -> error or permission prompt
|
|
||||||
*/
|
*/
|
||||||
export function revokePermission(permission: Permission): void {
|
async revoke(desc: PermissionDescriptor): Promise<PermissionStatus> {
|
||||||
sendSync(dispatch.OP_REVOKE_PERMISSION, { permission });
|
const { state } = sendSync(dispatch.OP_REVOKE_PERMISSION, desc);
|
||||||
|
return new PermissionStatus(state);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const permissions = new Permissions();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
// 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",
|
"run",
|
||||||
"read",
|
"read",
|
||||||
"write",
|
"write",
|
||||||
|
@ -11,18 +11,31 @@ const knownPermissions: Deno.Permission[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const grant of knownPermissions) {
|
for (const grant of knownPermissions) {
|
||||||
testPerm({ [grant]: true }, function envGranted(): void {
|
testPerm({ [grant]: true }, async function envGranted(): Promise<void> {
|
||||||
const perms = Deno.permissions();
|
const status0 = await Deno.permissions.query({ name: grant });
|
||||||
assert(perms !== null);
|
assert(status0 != null);
|
||||||
for (const perm in perms) {
|
assertEquals(status0.state, "granted");
|
||||||
assertEquals(perms[perm], perm === grant);
|
|
||||||
}
|
|
||||||
|
|
||||||
Deno.revokePermission(grant);
|
const status1 = await Deno.permissions.revoke({ name: grant });
|
||||||
|
assert(status1 != null);
|
||||||
const revoked = Deno.permissions();
|
assertEquals(status1.state, "prompt");
|
||||||
for (const perm in revoked) {
|
|
||||||
assertEquals(revoked[perm], false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -29,11 +29,34 @@ interface TestPermissions {
|
||||||
hrtime?: boolean;
|
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(
|
function permissionsMatch(
|
||||||
processPerms: Deno.Permissions,
|
processPerms: Permissions,
|
||||||
requiredPerms: Deno.Permissions
|
requiredPerms: Permissions
|
||||||
): boolean {
|
): boolean {
|
||||||
for (const permName in processPerms) {
|
for (const permName in processPerms) {
|
||||||
if (processPerms[permName] !== requiredPerms[permName]) {
|
if (processPerms[permName] !== requiredPerms[permName]) {
|
||||||
|
@ -44,9 +67,9 @@ function permissionsMatch(
|
||||||
return true;
|
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 r = perms.read ? 1 : 0;
|
||||||
const w = perms.write ? 1 : 0;
|
const w = perms.write ? 1 : 0;
|
||||||
const n = perms.net ? 1 : 0;
|
const n = perms.net ? 1 : 0;
|
||||||
|
@ -56,14 +79,14 @@ function permToString(perms: Deno.Permissions): string {
|
||||||
return `permR${r}W${w}N${n}E${e}U${u}H${h}`;
|
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);
|
const key = permToString(perms);
|
||||||
if (!permissionCombinations.has(key)) {
|
if (!permissionCombinations.has(key)) {
|
||||||
permissionCombinations.set(key, perms);
|
permissionCombinations.set(key, perms);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeTestPermissions(perms: TestPermissions): Deno.Permissions {
|
function normalizeTestPermissions(perms: TestPermissions): Permissions {
|
||||||
return {
|
return {
|
||||||
read: !!perms.read,
|
read: !!perms.read,
|
||||||
write: !!perms.write,
|
write: !!perms.write,
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
#!/usr/bin/env -S deno run --reload --allow-run
|
#!/usr/bin/env -S deno run --reload --allow-run
|
||||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
import "./unit_tests.ts";
|
import "./unit_tests.ts";
|
||||||
import { permissionCombinations, parseUnitTestOutput } from "./test_util.ts";
|
import {
|
||||||
|
permissionCombinations,
|
||||||
|
parseUnitTestOutput,
|
||||||
|
Permissions
|
||||||
|
} from "./test_util.ts";
|
||||||
|
|
||||||
interface TestResult {
|
interface TestResult {
|
||||||
perms: string;
|
perms: string;
|
||||||
|
@ -9,7 +13,7 @@ interface TestResult {
|
||||||
result: number;
|
result: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function permsToCliFlags(perms: Deno.Permissions): string[] {
|
function permsToCliFlags(perms: Permissions): string[] {
|
||||||
return Object.keys(perms)
|
return Object.keys(perms)
|
||||||
.map(
|
.map(
|
||||||
(key): string => {
|
(key): string => {
|
||||||
|
@ -25,7 +29,7 @@ function permsToCliFlags(perms: Deno.Permissions): string[] {
|
||||||
.filter((e): boolean => e.length > 0);
|
.filter((e): boolean => e.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function fmtPerms(perms: Deno.Permissions): string {
|
function fmtPerms(perms: Permissions): string {
|
||||||
let fmt = permsToCliFlags(perms).join(" ");
|
let fmt = permsToCliFlags(perms).join(" ");
|
||||||
|
|
||||||
if (!fmt) {
|
if (!fmt) {
|
||||||
|
|
|
@ -57,6 +57,7 @@ pub enum ErrorKind {
|
||||||
TooManyRedirects = 48,
|
TooManyRedirects = 48,
|
||||||
Diagnostic = 49,
|
Diagnostic = 49,
|
||||||
JSError = 50,
|
JSError = 50,
|
||||||
|
TypeError = 51,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warning! The values in this enum are duplicated in js/compiler.ts
|
// Warning! The values in this enum are duplicated in js/compiler.ts
|
||||||
|
|
|
@ -6,8 +6,8 @@ use deno::*;
|
||||||
|
|
||||||
pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
|
pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
|
||||||
i.register_op(
|
i.register_op(
|
||||||
"permissions",
|
"query_permission",
|
||||||
s.core_op(json_op(s.stateful_op(op_permissions))),
|
s.core_op(json_op(s.stateful_op(op_query_permission))),
|
||||||
);
|
);
|
||||||
i.register_op(
|
i.register_op(
|
||||||
"revoke_permission",
|
"revoke_permission",
|
||||||
|
@ -15,24 +15,25 @@ pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn op_permissions(
|
#[derive(Deserialize)]
|
||||||
state: &ThreadSafeState,
|
struct PermissionArgs {
|
||||||
_args: Value,
|
name: String,
|
||||||
_zero_copy: Option<PinnedBuf>,
|
url: Option<String>,
|
||||||
) -> Result<JsonOp, ErrBox> {
|
path: Option<String>,
|
||||||
Ok(JsonOp::Sync(json!({
|
|
||||||
"run": state.permissions.allows_run(),
|
|
||||||
"read": state.permissions.allows_read(),
|
|
||||||
"write": state.permissions.allows_write(),
|
|
||||||
"net": state.permissions.allows_net(),
|
|
||||||
"env": state.permissions.allows_env(),
|
|
||||||
"hrtime": state.permissions.allows_hrtime(),
|
|
||||||
})))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
pub fn op_query_permission(
|
||||||
struct RevokePermissionArgs {
|
state: &ThreadSafeState,
|
||||||
permission: String,
|
args: Value,
|
||||||
|
_zero_copy: Option<PinnedBuf>,
|
||||||
|
) -> Result<JsonOp, ErrBox> {
|
||||||
|
let args: PermissionArgs = serde_json::from_value(args)?;
|
||||||
|
let perm = state.permissions.get_permission_state(
|
||||||
|
&args.name,
|
||||||
|
&args.url.as_ref().map(String::as_str),
|
||||||
|
&args.path.as_ref().map(String::as_str),
|
||||||
|
)?;
|
||||||
|
Ok(JsonOp::Sync(json!({ "state": perm.to_string() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn op_revoke_permission(
|
pub fn op_revoke_permission(
|
||||||
|
@ -40,17 +41,20 @@ pub fn op_revoke_permission(
|
||||||
args: Value,
|
args: Value,
|
||||||
_zero_copy: Option<PinnedBuf>,
|
_zero_copy: Option<PinnedBuf>,
|
||||||
) -> Result<JsonOp, ErrBox> {
|
) -> Result<JsonOp, ErrBox> {
|
||||||
let args: RevokePermissionArgs = serde_json::from_value(args)?;
|
let args: PermissionArgs = serde_json::from_value(args)?;
|
||||||
let permission = args.permission.as_ref();
|
match args.name.as_ref() {
|
||||||
match permission {
|
"run" => state.permissions.allow_run.revoke(),
|
||||||
"run" => state.permissions.revoke_run(),
|
"read" => state.permissions.allow_read.revoke(),
|
||||||
"read" => state.permissions.revoke_read(),
|
"write" => state.permissions.allow_write.revoke(),
|
||||||
"write" => state.permissions.revoke_write(),
|
"net" => state.permissions.allow_net.revoke(),
|
||||||
"net" => state.permissions.revoke_net(),
|
"env" => state.permissions.allow_env.revoke(),
|
||||||
"env" => state.permissions.revoke_env(),
|
"hrtime" => state.permissions.allow_hrtime.revoke(),
|
||||||
"hrtime" => state.permissions.revoke_hrtime(),
|
_ => {}
|
||||||
_ => Ok(()),
|
};
|
||||||
}?;
|
let perm = state.permissions.get_permission_state(
|
||||||
|
&args.name,
|
||||||
Ok(JsonOp::Sync(json!({})))
|
&args.url.as_ref().map(String::as_str),
|
||||||
|
&args.path.as_ref().map(String::as_str),
|
||||||
|
)?;
|
||||||
|
Ok(JsonOp::Sync(json!({ "state": perm.to_string() })))
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ fn op_now(
|
||||||
// If the permission is not enabled
|
// If the permission is not enabled
|
||||||
// Round the nano result on 2 milliseconds
|
// Round the nano result on 2 milliseconds
|
||||||
// see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision
|
// see: https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp#Reduced_time_precision
|
||||||
if !state.permissions.allows_hrtime() {
|
if !state.permissions.allow_hrtime.is_allow() {
|
||||||
subsec_nanos -= subsec_nanos % reduced_time_precision
|
subsec_nanos -= subsec_nanos % reduced_time_precision
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
use crate::deno_error::permission_denied_msg;
|
use crate::deno_error::{permission_denied_msg, type_error};
|
||||||
use crate::flags::DenoFlags;
|
use crate::flags::DenoFlags;
|
||||||
use ansi_term::Style;
|
use ansi_term::Style;
|
||||||
use deno::ErrBox;
|
use deno::ErrBox;
|
||||||
|
@ -9,10 +9,12 @@ use std::fmt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
const PERMISSION_EMOJI: &str = "⚠️";
|
const PERMISSION_EMOJI: &str = "⚠️";
|
||||||
|
|
||||||
/// Tri-state value for storing permission state
|
/// Tri-state value for storing permission state
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum PermissionAccessorState {
|
pub enum PermissionAccessorState {
|
||||||
Allow = 0,
|
Allow = 0,
|
||||||
Ask = 1,
|
Ask = 1,
|
||||||
|
@ -43,9 +45,9 @@ impl From<bool> for PermissionAccessorState {
|
||||||
impl fmt::Display for PermissionAccessorState {
|
impl fmt::Display for PermissionAccessorState {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
PermissionAccessorState::Allow => f.pad("Allow"),
|
PermissionAccessorState::Allow => f.pad("granted"),
|
||||||
PermissionAccessorState::Ask => f.pad("Ask"),
|
PermissionAccessorState::Ask => f.pad("prompt"),
|
||||||
PermissionAccessorState::Deny => f.pad("Deny"),
|
PermissionAccessorState::Deny => f.pad("denied"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +112,7 @@ impl Default for PermissionAccessor {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DenoPermissions {
|
pub struct DenoPermissions {
|
||||||
// Keep in sync with src/permissions.ts
|
// Keep in sync with cli/js/permissions.ts
|
||||||
pub allow_read: PermissionAccessor,
|
pub allow_read: PermissionAccessor,
|
||||||
pub read_whitelist: Arc<HashSet<String>>,
|
pub read_whitelist: Arc<HashSet<String>>,
|
||||||
pub allow_write: PermissionAccessor,
|
pub allow_write: PermissionAccessor,
|
||||||
|
@ -139,146 +141,94 @@ impl DenoPermissions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_run(&self) -> Result<(), ErrBox> {
|
/** Checks the permission state and returns the result. */
|
||||||
let msg = "access to run a subprocess";
|
fn check_permission_state(
|
||||||
|
&self,
|
||||||
match self.allow_run.get_state() {
|
state: PermissionAccessorState,
|
||||||
PermissionAccessorState::Allow => {
|
msg: &str,
|
||||||
|
err_msg: &str,
|
||||||
|
) -> Result<(), ErrBox> {
|
||||||
|
if state == PermissionAccessorState::Allow {
|
||||||
self.log_perm_access(msg);
|
self.log_perm_access(msg);
|
||||||
Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
PermissionAccessorState::Ask => Err(permission_denied_msg(
|
Err(permission_denied_msg(err_msg.to_string()))
|
||||||
"run again with the --allow-run flag".to_string(),
|
|
||||||
)),
|
|
||||||
PermissionAccessorState::Deny => Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-run flag".to_string(),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn check_run(&self) -> Result<(), ErrBox> {
|
||||||
|
self.check_permission_state(
|
||||||
|
self.allow_run.get_state(),
|
||||||
|
"access to run a subprocess",
|
||||||
|
"run again with the --allow-run flag",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state_read(&self, filename: &Option<&str>) -> PermissionAccessorState {
|
||||||
|
if check_path_white_list(filename, &self.read_whitelist) {
|
||||||
|
return PermissionAccessorState::Allow;
|
||||||
|
}
|
||||||
|
self.allow_read.get_state()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_read(&self, filename: &str) -> Result<(), ErrBox> {
|
pub fn check_read(&self, filename: &str) -> Result<(), ErrBox> {
|
||||||
let msg = &format!("read access to \"{}\"", filename);
|
self.check_permission_state(
|
||||||
match self.allow_read.get_state() {
|
self.get_state_read(&Some(filename)),
|
||||||
PermissionAccessorState::Allow => {
|
&format!("read access to \"{}\"", filename),
|
||||||
self.log_perm_access(msg);
|
"run again with the --allow-read flag",
|
||||||
Ok(())
|
)
|
||||||
}
|
|
||||||
state => {
|
|
||||||
if check_path_white_list(filename, &self.read_whitelist) {
|
|
||||||
self.log_perm_access(msg);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
match state {
|
|
||||||
PermissionAccessorState::Ask => Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-read flag".to_string(),
|
|
||||||
)),
|
|
||||||
PermissionAccessorState::Deny => Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-read flag".to_string(),
|
|
||||||
)),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_state_write(
|
||||||
|
&self,
|
||||||
|
filename: &Option<&str>,
|
||||||
|
) -> PermissionAccessorState {
|
||||||
|
if check_path_white_list(filename, &self.write_whitelist) {
|
||||||
|
return PermissionAccessorState::Allow;
|
||||||
}
|
}
|
||||||
|
self.allow_write.get_state()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_write(&self, filename: &str) -> Result<(), ErrBox> {
|
pub fn check_write(&self, filename: &str) -> Result<(), ErrBox> {
|
||||||
let msg = &format!("write access to \"{}\"", filename);
|
self.check_permission_state(
|
||||||
match self.allow_write.get_state() {
|
self.get_state_write(&Some(filename)),
|
||||||
PermissionAccessorState::Allow => {
|
&format!("write access to \"{}\"", filename),
|
||||||
self.log_perm_access(msg);
|
"run again with the --allow-write flag",
|
||||||
Ok(())
|
)
|
||||||
}
|
|
||||||
state => {
|
|
||||||
if check_path_white_list(filename, &self.write_whitelist) {
|
|
||||||
self.log_perm_access(msg);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
match state {
|
|
||||||
PermissionAccessorState::Ask => Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-write flag".to_string(),
|
|
||||||
)),
|
|
||||||
PermissionAccessorState::Deny => Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-write flag".to_string(),
|
|
||||||
)),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_state_net(
|
||||||
|
&self,
|
||||||
|
host: &str,
|
||||||
|
port: Option<u16>,
|
||||||
|
) -> PermissionAccessorState {
|
||||||
|
if check_host_and_port_whitelist(host, port, &self.net_whitelist) {
|
||||||
|
return PermissionAccessorState::Allow;
|
||||||
}
|
}
|
||||||
|
self.allow_net.get_state()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_net(&self, hostname: &str, port: u16) -> Result<(), ErrBox> {
|
pub fn check_net(&self, hostname: &str, port: u16) -> Result<(), ErrBox> {
|
||||||
let msg = &format!("network access to \"{}:{}\"", hostname, port);
|
self.check_permission_state(
|
||||||
match self.allow_net.get_state() {
|
self.get_state_net(hostname, Some(port)),
|
||||||
PermissionAccessorState::Allow => {
|
&format!("network access to \"{}:{}\"", hostname, port),
|
||||||
self.log_perm_access(msg);
|
"run again with the --allow-net flag",
|
||||||
Ok(())
|
)
|
||||||
}
|
|
||||||
_state => {
|
|
||||||
if self.net_whitelist.contains(hostname)
|
|
||||||
|| self
|
|
||||||
.net_whitelist
|
|
||||||
.contains(&format!("{}:{}", hostname, port))
|
|
||||||
{
|
|
||||||
self.log_perm_access(msg);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-net flag".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_net_url(&self, url: &url::Url) -> Result<(), ErrBox> {
|
pub fn check_net_url(&self, url: &url::Url) -> Result<(), ErrBox> {
|
||||||
let msg = &format!("network access to \"{}\"", url);
|
self.check_permission_state(
|
||||||
match self.allow_net.get_state() {
|
self.get_state_net(&format!("{}", url.host().unwrap()), url.port()),
|
||||||
PermissionAccessorState::Allow => {
|
&format!("network access to \"{}\"", url),
|
||||||
self.log_perm_access(msg);
|
"run again with the --allow-net flag",
|
||||||
Ok(())
|
)
|
||||||
}
|
|
||||||
_state => {
|
|
||||||
let host = url.host().unwrap();
|
|
||||||
let whitelist_result = {
|
|
||||||
if self.net_whitelist.contains(&format!("{}", host)) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
match url.port() {
|
|
||||||
Some(port) => {
|
|
||||||
self.net_whitelist.contains(&format!("{}:{}", host, port))
|
|
||||||
}
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if whitelist_result {
|
|
||||||
self.log_perm_access(msg);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-net flag".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_env(&self) -> Result<(), ErrBox> {
|
pub fn check_env(&self) -> Result<(), ErrBox> {
|
||||||
let msg = "access to environment variables";
|
self.check_permission_state(
|
||||||
match self.allow_env.get_state() {
|
self.allow_env.get_state(),
|
||||||
PermissionAccessorState::Allow => {
|
"access to environment variables",
|
||||||
self.log_perm_access(msg);
|
"run again with the --allow-env flag",
|
||||||
Ok(())
|
)
|
||||||
}
|
|
||||||
PermissionAccessorState::Ask => Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-env flag".to_string(),
|
|
||||||
)),
|
|
||||||
PermissionAccessorState::Deny => Err(permission_denied_msg(
|
|
||||||
"run again with the --allow-env flag".to_string(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log_perm_access(&self, message: &str) {
|
fn log_perm_access(&self, message: &str) {
|
||||||
|
@ -292,66 +242,44 @@ impl DenoPermissions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allows_run(&self) -> bool {
|
pub fn get_permission_state(
|
||||||
self.allow_run.is_allow()
|
&self,
|
||||||
|
name: &str,
|
||||||
|
url: &Option<&str>,
|
||||||
|
path: &Option<&str>,
|
||||||
|
) -> Result<PermissionAccessorState, ErrBox> {
|
||||||
|
match name {
|
||||||
|
"run" => Ok(self.allow_run.get_state()),
|
||||||
|
"read" => Ok(self.get_state_read(path)),
|
||||||
|
"write" => Ok(self.get_state_write(path)),
|
||||||
|
"net" => {
|
||||||
|
// If url is not given, then just check the entire net permission
|
||||||
|
if url.is_none() {
|
||||||
|
return Ok(self.allow_net.get_state());
|
||||||
}
|
}
|
||||||
|
let url: &str = url.unwrap();
|
||||||
pub fn allows_read(&self) -> bool {
|
// If url is invalid, then throw a TypeError.
|
||||||
self.allow_read.is_allow()
|
let parsed = Url::parse(url)
|
||||||
|
.map_err(|_| type_error(format!("Invalid url: {}", url)))?;
|
||||||
|
let state = self
|
||||||
|
.get_state_net(&format!("{}", parsed.host().unwrap()), parsed.port());
|
||||||
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
"env" => Ok(self.allow_env.get_state()),
|
||||||
pub fn allows_write(&self) -> bool {
|
"hrtime" => Ok(self.allow_hrtime.get_state()),
|
||||||
self.allow_write.is_allow()
|
n => Err(type_error(format!("No such permission name: {}", n))),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allows_net(&self) -> bool {
|
|
||||||
self.allow_net.is_allow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn allows_env(&self) -> bool {
|
|
||||||
self.allow_env.is_allow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn allows_hrtime(&self) -> bool {
|
|
||||||
self.allow_hrtime.is_allow()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn revoke_run(&self) -> Result<(), ErrBox> {
|
|
||||||
self.allow_run.revoke();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn revoke_read(&self) -> Result<(), ErrBox> {
|
|
||||||
self.allow_read.revoke();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn revoke_write(&self) -> Result<(), ErrBox> {
|
|
||||||
self.allow_write.revoke();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn revoke_net(&self) -> Result<(), ErrBox> {
|
|
||||||
self.allow_net.revoke();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn revoke_env(&self) -> Result<(), ErrBox> {
|
|
||||||
self.allow_env.revoke();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn revoke_hrtime(&self) -> Result<(), ErrBox> {
|
|
||||||
self.allow_hrtime.revoke();
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_path_white_list(
|
fn check_path_white_list(
|
||||||
filename: &str,
|
filename: &Option<&str>,
|
||||||
white_list: &Arc<HashSet<String>>,
|
white_list: &Arc<HashSet<String>>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut path_buf = PathBuf::from(filename);
|
if filename.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut path_buf = PathBuf::from(filename.unwrap());
|
||||||
loop {
|
loop {
|
||||||
if white_list.contains(path_buf.to_str().unwrap()) {
|
if white_list.contains(path_buf.to_str().unwrap()) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -363,6 +291,16 @@ fn check_path_white_list(
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn check_host_and_port_whitelist(
|
||||||
|
host: &str,
|
||||||
|
port: Option<u16>,
|
||||||
|
whitelist: &Arc<HashSet<String>>,
|
||||||
|
) -> bool {
|
||||||
|
whitelist.contains(host)
|
||||||
|
|| (port.is_some()
|
||||||
|
&& whitelist.contains(&format!("{}:{}", host, port.unwrap())))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
window.onload = async (): Promise<void> => {
|
||||||
console.log(performance.now() % 2 !== 0);
|
console.log(performance.now() % 2 !== 0);
|
||||||
Deno.revokePermission("hrtime");
|
await Deno.permissions.revoke({ name: "hrtime" });
|
||||||
console.log(performance.now() % 2 === 0);
|
console.log(performance.now() % 2 === 0);
|
||||||
|
};
|
||||||
|
|
|
@ -353,18 +353,19 @@ Sometimes a program may want to revoke previously granted permissions. When a
|
||||||
program, at a later stage, needs those permissions, it will fail.
|
program, at a later stage, needs those permissions, it will fail.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const { permissions, revokePermission, open, remove } = Deno;
|
const { permissions, open, remove } = Deno;
|
||||||
|
|
||||||
// lookup a permission
|
// lookup a permission
|
||||||
if (!permissions().write) {
|
const status = await permissions.query({ name: "write" });
|
||||||
|
if (status.state !== "granted") {
|
||||||
throw new Error("need write permission");
|
throw new Error("need write permission");
|
||||||
}
|
}
|
||||||
|
|
||||||
const log = await open("request.log", "a+");
|
const log = await open("request.log", "a+");
|
||||||
|
|
||||||
// revoke some permissions
|
// revoke some permissions
|
||||||
revokePermission("read");
|
await permissions.revoke({ name: "read" });
|
||||||
revokePermission("write");
|
await permissions.revoke({ name: "write" });
|
||||||
|
|
||||||
// use the log file
|
// use the log file
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
|
|
Loading…
Reference in a new issue