From efd7e78af3fc086dfdec51738905665d38d08eb4 Mon Sep 17 00:00:00 2001 From: Yoshiya Hinosawa Date: Mon, 28 Oct 2019 00:22:53 +0900 Subject: [PATCH] Use web standard Permissions API (#3200) --- cli/deno_error.rs | 4 + cli/js/deno.ts | 5 +- cli/js/dispatch.ts | 2 +- cli/js/errors.ts | 3 +- cli/js/lib.deno_runtime.d.ts | 84 ++++++---- cli/js/permissions.ts | 96 +++++++---- cli/js/permissions_test.ts | 41 +++-- cli/js/test_util.ts | 37 ++++- cli/js/unit_test_runner.ts | 10 +- cli/msg.rs | 1 + cli/ops/permissions.rs | 66 ++++---- cli/ops/timers.rs | 2 +- cli/permissions.rs | 300 ++++++++++++++--------------------- cli/tests/025_hrtime.ts | 8 +- std/manual.md | 9 +- 15 files changed, 362 insertions(+), 306 deletions(-) diff --git a/cli/deno_error.rs b/cli/deno_error.rs index 2c53c84f71..346149cd16 100644 --- a/cli/deno_error.rs +++ b/cli/deno_error.rs @@ -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; } diff --git a/cli/js/deno.ts b/cli/js/deno.ts index 4b0e3ff96e..cac730249e 100644 --- a/cli/js/deno.ts +++ b/cli/js/deno.ts @@ -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"; diff --git a/cli/js/dispatch.ts b/cli/js/dispatch.ts index 38405a866b..d66467011e 100644 --- a/cli/js/dispatch.ts +++ b/cli/js/dispatch.ts @@ -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; diff --git a/cli/js/errors.ts b/cli/js/errors.ts index 02ddfa2f28..8cd7a76be0 100644 --- a/cli/js/errors.ts +++ b/cli/js/errors.ts @@ -75,5 +75,6 @@ export enum ErrorKind { UnsupportedFetchScheme = 47, TooManyRedirects = 48, Diagnostic = 49, - JSError = 50 + JSError = 50, + TypeError = 51 } diff --git a/cli/js/lib.deno_runtime.d.ts b/cli/js/lib.deno_runtime.d.ts index 3036ea2d11..c0bebf4616 100644 --- a/cli/js/lib.deno_runtime.d.ts +++ b/cli/js/lib.deno_runtime.d.ts @@ -883,34 +883,64 @@ declare namespace Deno { } // @url js/permissions.d.ts - - /** Permissions as granted by the caller */ - export interface Permissions { - read: boolean; - write: boolean; - net: boolean; - env: boolean; - run: boolean; - hrtime: boolean; + /** 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; + /** Revokes the permission. + * const status = await Deno.permissions.revoke({ name: "run" }); + * assert(status.state !== "granted") + */ + revoke(d: PermissionDescriptor): Promise; + } + export const permissions: Permissions; + + /** https://w3c.github.io/permissions/#permissionstatus */ + export class PermissionStatus { + state: PermissionState; + constructor(state: PermissionState); } - 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; // @url js/truncate.d.ts diff --git a/cli/js/permissions.ts b/cli/js/permissions.ts index 4f393501c6..16ea3e5c27 100644 --- a/cli/js/permissions.ts +++ b/cli/js/permissions.ts @@ -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 -} - -export type Permission = keyof Permissions; - -/** Inspect granted permissions for the current program. - * - * if (Deno.permissions().read) { - * const file = await Deno.readFile("example.test"); - * // ... - * } +/** Permissions as granted by the caller + * See: https://w3c.github.io/permissions/#permission-registry */ -export function permissions(): Permissions { - return sendSync(dispatch.OP_PERMISSIONS) as Permissions; +export type PermissionName = + | "read" + | "write" + | "net" + | "env" + | "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 } -/** 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 }); +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 { + 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 { + const { state } = sendSync(dispatch.OP_REVOKE_PERMISSION, desc); + return new PermissionStatus(state); + } } + +export const permissions = new Permissions(); diff --git a/cli/js/permissions_test.ts b/cli/js/permissions_test.ts index 6511c2dcbe..d9ba538f0e 100644 --- a/cli/js/permissions_test.ts +++ b/cli/js/permissions_test.ts @@ -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", @@ -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 { + 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 { + 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 { + try { + // Invalid url causes TypeError. + await Deno.permissions.query({ name: "net", url: ":" }); + } catch (e) { + assert(e.name === "TypeError"); + } +}); diff --git a/cli/js/test_util.ts b/cli/js/test_util.ts index 2f2916e11e..85fffabe64 100644 --- a/cli/js/test_util.ts +++ b/cli/js/test_util.ts @@ -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 => + (await Deno.permissions.query({ name })).state === "granted"; + +async function getProcessPermissions(): Promise { + 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]) { @@ -44,9 +67,9 @@ function permissionsMatch( return true; } -export const permissionCombinations: Map = new Map(); +export const permissionCombinations: Map = 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; @@ -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, diff --git a/cli/js/unit_test_runner.ts b/cli/js/unit_test_runner.ts index 913c575b20..ae255c385d 100755 --- a/cli/js/unit_test_runner.ts +++ b/cli/js/unit_test_runner.ts @@ -1,7 +1,11 @@ #!/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; @@ -9,7 +13,7 @@ interface TestResult { result: number; } -function permsToCliFlags(perms: Deno.Permissions): string[] { +function permsToCliFlags(perms: Permissions): string[] { return Object.keys(perms) .map( (key): string => { @@ -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) { diff --git a/cli/msg.rs b/cli/msg.rs index 20ab9db130..206da76949 100644 --- a/cli/msg.rs +++ b/cli/msg.rs @@ -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 diff --git a/cli/ops/permissions.rs b/cli/ops/permissions.rs index 8303aa9cf7..823ab678b7 100644 --- a/cli/ops/permissions.rs +++ b/cli/ops/permissions.rs @@ -6,8 +6,8 @@ use deno::*; pub fn init(i: &mut Isolate, s: &ThreadSafeState) { i.register_op( - "permissions", - s.core_op(json_op(s.stateful_op(op_permissions))), + "query_permission", + s.core_op(json_op(s.stateful_op(op_query_permission))), ); i.register_op( "revoke_permission", @@ -15,24 +15,25 @@ pub fn init(i: &mut Isolate, s: &ThreadSafeState) { ); } -pub fn op_permissions( - state: &ThreadSafeState, - _args: Value, - _zero_copy: Option, -) -> Result { - 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)] +struct PermissionArgs { + name: String, + url: Option, + path: Option, } -#[derive(Deserialize)] -struct RevokePermissionArgs { - permission: String, +pub fn op_query_permission( + state: &ThreadSafeState, + args: Value, + _zero_copy: Option, +) -> Result { + 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( @@ -40,17 +41,20 @@ pub fn op_revoke_permission( args: Value, _zero_copy: Option, ) -> Result { - let args: RevokePermissionArgs = serde_json::from_value(args)?; - let permission = args.permission.as_ref(); - match permission { - "run" => state.permissions.revoke_run(), - "read" => state.permissions.revoke_read(), - "write" => state.permissions.revoke_write(), - "net" => state.permissions.revoke_net(), - "env" => state.permissions.revoke_env(), - "hrtime" => state.permissions.revoke_hrtime(), - _ => Ok(()), - }?; - - Ok(JsonOp::Sync(json!({}))) + let args: PermissionArgs = serde_json::from_value(args)?; + match args.name.as_ref() { + "run" => state.permissions.allow_run.revoke(), + "read" => state.permissions.allow_read.revoke(), + "write" => state.permissions.allow_write.revoke(), + "net" => state.permissions.allow_net.revoke(), + "env" => state.permissions.allow_env.revoke(), + "hrtime" => state.permissions.allow_hrtime.revoke(), + _ => {} + }; + 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() }))) } diff --git a/cli/ops/timers.rs b/cli/ops/timers.rs index f366797b42..9d87aaf5c5 100644 --- a/cli/ops/timers.rs +++ b/cli/ops/timers.rs @@ -70,7 +70,7 @@ fn op_now( // If the permission is not enabled // Round the nano result on 2 milliseconds // 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 } diff --git a/cli/permissions.rs b/cli/permissions.rs index 1dd2eb1e25..af4c960d7f 100644 --- a/cli/permissions.rs +++ b/cli/permissions.rs @@ -1,5 +1,5 @@ // 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 ansi_term::Style; use deno::ErrBox; @@ -9,10 +9,12 @@ use std::fmt; use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; +use url::Url; const PERMISSION_EMOJI: &str = "⚠️"; /// Tri-state value for storing permission state +#[derive(PartialEq)] pub enum PermissionAccessorState { Allow = 0, Ask = 1, @@ -43,9 +45,9 @@ impl From for PermissionAccessorState { impl fmt::Display for PermissionAccessorState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PermissionAccessorState::Allow => f.pad("Allow"), - PermissionAccessorState::Ask => f.pad("Ask"), - PermissionAccessorState::Deny => f.pad("Deny"), + PermissionAccessorState::Allow => f.pad("granted"), + PermissionAccessorState::Ask => f.pad("prompt"), + PermissionAccessorState::Deny => f.pad("denied"), } } } @@ -110,7 +112,7 @@ impl Default for PermissionAccessor { #[derive(Debug, Default)] pub struct DenoPermissions { - // Keep in sync with src/permissions.ts + // Keep in sync with cli/js/permissions.ts pub allow_read: PermissionAccessor, pub read_whitelist: Arc>, pub allow_write: PermissionAccessor, @@ -139,146 +141,94 @@ impl DenoPermissions { } } - pub fn check_run(&self) -> Result<(), ErrBox> { - let msg = "access to run a subprocess"; - - match self.allow_run.get_state() { - PermissionAccessorState::Allow => { - self.log_perm_access(msg); - Ok(()) - } - PermissionAccessorState::Ask => Err(permission_denied_msg( - "run again with the --allow-run flag".to_string(), - )), - PermissionAccessorState::Deny => Err(permission_denied_msg( - "run again with the --allow-run flag".to_string(), - )), + /** Checks the permission state and returns the result. */ + fn check_permission_state( + &self, + state: PermissionAccessorState, + msg: &str, + err_msg: &str, + ) -> Result<(), ErrBox> { + if state == PermissionAccessorState::Allow { + self.log_perm_access(msg); + return Ok(()); } + Err(permission_denied_msg(err_msg.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> { - let msg = &format!("read access to \"{}\"", filename); - match self.allow_read.get_state() { - PermissionAccessorState::Allow => { - self.log_perm_access(msg); - 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!(), - } - } - } + self.check_permission_state( + self.get_state_read(&Some(filename)), + &format!("read access to \"{}\"", filename), + "run again with the --allow-read flag", + ) + } + + 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> { - let msg = &format!("write access to \"{}\"", filename); - match self.allow_write.get_state() { - PermissionAccessorState::Allow => { - self.log_perm_access(msg); - 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!(), - } - } - } + self.check_permission_state( + self.get_state_write(&Some(filename)), + &format!("write access to \"{}\"", filename), + "run again with the --allow-write flag", + ) + } + + fn get_state_net( + &self, + host: &str, + port: Option, + ) -> 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> { - let msg = &format!("network access to \"{}:{}\"", hostname, port); - match self.allow_net.get_state() { - PermissionAccessorState::Allow => { - self.log_perm_access(msg); - 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(), - )) - } - } - } + self.check_permission_state( + self.get_state_net(hostname, Some(port)), + &format!("network access to \"{}:{}\"", hostname, port), + "run again with the --allow-net flag", + ) } pub fn check_net_url(&self, url: &url::Url) -> Result<(), ErrBox> { - let msg = &format!("network access to \"{}\"", url); - match self.allow_net.get_state() { - PermissionAccessorState::Allow => { - self.log_perm_access(msg); - 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(), - )) - } - } - } + self.check_permission_state( + self.get_state_net(&format!("{}", url.host().unwrap()), url.port()), + &format!("network access to \"{}\"", url), + "run again with the --allow-net flag", + ) } pub fn check_env(&self) -> Result<(), ErrBox> { - let msg = "access to environment variables"; - match self.allow_env.get_state() { - PermissionAccessorState::Allow => { - self.log_perm_access(msg); - 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(), - )), - } + self.check_permission_state( + self.allow_env.get_state(), + "access to environment variables", + "run again with the --allow-env flag", + ) } fn log_perm_access(&self, message: &str) { @@ -292,66 +242,44 @@ impl DenoPermissions { } } - pub fn allows_run(&self) -> bool { - self.allow_run.is_allow() - } - - pub fn allows_read(&self) -> bool { - self.allow_read.is_allow() - } - - pub fn allows_write(&self) -> bool { - self.allow_write.is_allow() - } - - 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(()) + pub fn get_permission_state( + &self, + name: &str, + url: &Option<&str>, + path: &Option<&str>, + ) -> Result { + 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(); + // If url is invalid, then throw a TypeError. + 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()), + "hrtime" => Ok(self.allow_hrtime.get_state()), + n => Err(type_error(format!("No such permission name: {}", n))), + } } } fn check_path_white_list( - filename: &str, + filename: &Option<&str>, white_list: &Arc>, ) -> bool { - let mut path_buf = PathBuf::from(filename); - + if filename.is_none() { + return false; + } + let mut path_buf = PathBuf::from(filename.unwrap()); loop { if white_list.contains(path_buf.to_str().unwrap()) { return true; @@ -363,6 +291,16 @@ fn check_path_white_list( false } +fn check_host_and_port_whitelist( + host: &str, + port: Option, + whitelist: &Arc>, +) -> bool { + whitelist.contains(host) + || (port.is_some() + && whitelist.contains(&format!("{}:{}", host, port.unwrap()))) +} + #[cfg(test)] mod tests { use super::*; diff --git a/cli/tests/025_hrtime.ts b/cli/tests/025_hrtime.ts index 417ca69821..9f60b7a77e 100644 --- a/cli/tests/025_hrtime.ts +++ b/cli/tests/025_hrtime.ts @@ -1,3 +1,5 @@ -console.log(performance.now() % 2 !== 0); -Deno.revokePermission("hrtime"); -console.log(performance.now() % 2 === 0); +window.onload = async (): Promise => { + console.log(performance.now() % 2 !== 0); + await Deno.permissions.revoke({ name: "hrtime" }); + console.log(performance.now() % 2 === 0); +}; diff --git a/std/manual.md b/std/manual.md index 6da2d0ec2d..70ecdc9acd 100644 --- a/std/manual.md +++ b/std/manual.md @@ -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. ```ts -const { permissions, revokePermission, open, remove } = Deno; +const { permissions, open, remove } = Deno; // lookup a permission -if (!permissions().write) { +const status = await permissions.query({ name: "write" }); +if (status.state !== "granted") { throw new Error("need write permission"); } const log = await open("request.log", "a+"); // revoke some permissions -revokePermission("read"); -revokePermission("write"); +await permissions.revoke({ name: "read" }); +await permissions.revoke({ name: "write" }); // use the log file const encoder = new TextEncoder();