1
0
Fork 0
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:
Yoshiya Hinosawa 2019-10-28 00:22:53 +09:00 committed by Ry Dahl
parent 2598f9c68d
commit efd7e78af3
15 changed files with 362 additions and 306 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,

View file

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

View file

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

View file

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

View file

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

View file

@ -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::*;

View file

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

View file

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