mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 23:34:47 -05:00
Allow inspection and revocation of permissions (#1875)
This commit is contained in:
parent
048a8a7775
commit
77d7ad61f3
9 changed files with 249 additions and 0 deletions
1
BUILD.gn
1
BUILD.gn
|
@ -91,6 +91,7 @@ ts_sources = [
|
||||||
"js/mock_builtin.js",
|
"js/mock_builtin.js",
|
||||||
"js/net.ts",
|
"js/net.ts",
|
||||||
"js/os.ts",
|
"js/os.ts",
|
||||||
|
"js/permissions.ts",
|
||||||
"js/platform.ts",
|
"js/platform.ts",
|
||||||
"js/plugins.d.ts",
|
"js/plugins.d.ts",
|
||||||
"js/process.ts",
|
"js/process.ts",
|
||||||
|
|
|
@ -50,6 +50,12 @@ export { symlinkSync, symlink } from "./symlink";
|
||||||
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file";
|
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file";
|
||||||
export { ErrorKind, DenoError } from "./errors";
|
export { ErrorKind, DenoError } from "./errors";
|
||||||
export { libdeno } from "./libdeno";
|
export { libdeno } from "./libdeno";
|
||||||
|
export {
|
||||||
|
permissions,
|
||||||
|
revokePermission,
|
||||||
|
Permission,
|
||||||
|
Permissions
|
||||||
|
} from "./permissions";
|
||||||
export { platform } from "./platform";
|
export { platform } from "./platform";
|
||||||
export { truncateSync, truncate } from "./truncate";
|
export { truncateSync, truncate } from "./truncate";
|
||||||
export { FileInfo } from "./file_info";
|
export { FileInfo } from "./file_info";
|
||||||
|
|
74
js/permissions.ts
Normal file
74
js/permissions.ts
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import * as msg from "gen/msg_generated";
|
||||||
|
import * as flatbuffers from "./flatbuffers";
|
||||||
|
import * as dispatch from "./dispatch";
|
||||||
|
import { assert } from "./util";
|
||||||
|
|
||||||
|
/** Permissions as granted by the caller */
|
||||||
|
export type Permissions = {
|
||||||
|
read: boolean;
|
||||||
|
write: boolean;
|
||||||
|
net: boolean;
|
||||||
|
env: boolean;
|
||||||
|
run: 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");
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export function permissions(): Permissions {
|
||||||
|
const baseRes = dispatch.sendSync(...getReq())!;
|
||||||
|
assert(msg.Any.PermissionsRes === baseRes.innerType());
|
||||||
|
const res = new msg.PermissionsRes();
|
||||||
|
assert(baseRes.inner(res) != null);
|
||||||
|
// TypeScript cannot track assertion above, therefore not null assertion
|
||||||
|
return createPermissions(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 {
|
||||||
|
dispatch.sendSync(...revokeReq(permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPermissions(inner: msg.PermissionsRes): Permissions {
|
||||||
|
return {
|
||||||
|
read: inner.read(),
|
||||||
|
write: inner.write(),
|
||||||
|
net: inner.net(),
|
||||||
|
env: inner.env(),
|
||||||
|
run: inner.run()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getReq(): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] {
|
||||||
|
const builder = flatbuffers.createBuilder();
|
||||||
|
msg.Permissions.startPermissions(builder);
|
||||||
|
const inner = msg.Permissions.endPermissions(builder);
|
||||||
|
return [builder, msg.Any.Permissions, inner];
|
||||||
|
}
|
||||||
|
|
||||||
|
function revokeReq(
|
||||||
|
permission: string
|
||||||
|
): [flatbuffers.Builder, msg.Any, flatbuffers.Offset] {
|
||||||
|
const builder = flatbuffers.createBuilder();
|
||||||
|
const permission_ = builder.createString(permission);
|
||||||
|
msg.PermissionRevoke.startPermissionRevoke(builder);
|
||||||
|
msg.PermissionRevoke.addPermission(builder, permission_);
|
||||||
|
const inner = msg.PermissionRevoke.endPermissionRevoke(builder);
|
||||||
|
return [builder, msg.Any.PermissionRevoke, inner];
|
||||||
|
}
|
22
js/permissions_test.ts
Normal file
22
js/permissions_test.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { testPerm, assert, assertEqual } from "./test_util.ts";
|
||||||
|
import { Permission } from "deno";
|
||||||
|
|
||||||
|
const knownPermissions: Permission[] = ["run", "read", "write", "net", "env"];
|
||||||
|
|
||||||
|
for (let grant of knownPermissions) {
|
||||||
|
testPerm({ [grant]: true }, function envGranted() {
|
||||||
|
const perms = Deno.permissions();
|
||||||
|
assert(perms !== null);
|
||||||
|
for (const perm in perms) {
|
||||||
|
assertEqual(perms[perm], perm === grant);
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.revokePermission(grant);
|
||||||
|
|
||||||
|
const revoked = Deno.permissions();
|
||||||
|
for (const perm in revoked) {
|
||||||
|
assertEqual(revoked[perm], false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ import "./url_test.ts";
|
||||||
import "./url_search_params_test.ts";
|
import "./url_search_params_test.ts";
|
||||||
import "./write_file_test.ts";
|
import "./write_file_test.ts";
|
||||||
import "./performance_test.ts";
|
import "./performance_test.ts";
|
||||||
|
import "./permissions_test.ts";
|
||||||
import "./version_test.ts";
|
import "./version_test.ts";
|
||||||
|
|
||||||
import "../website/app_test.js";
|
import "../website/app_test.js";
|
||||||
|
|
17
src/msg.fbs
17
src/msg.fbs
|
@ -12,6 +12,9 @@ union Any {
|
||||||
Exit,
|
Exit,
|
||||||
Environ,
|
Environ,
|
||||||
EnvironRes,
|
EnvironRes,
|
||||||
|
Permissions,
|
||||||
|
PermissionRevoke,
|
||||||
|
PermissionsRes,
|
||||||
Fetch,
|
Fetch,
|
||||||
FetchRes,
|
FetchRes,
|
||||||
MakeTempDir,
|
MakeTempDir,
|
||||||
|
@ -231,6 +234,20 @@ table KeyValue {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table Permissions {}
|
||||||
|
|
||||||
|
table PermissionRevoke {
|
||||||
|
permission: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
table PermissionsRes {
|
||||||
|
run: bool;
|
||||||
|
read: bool;
|
||||||
|
write: bool;
|
||||||
|
net: bool;
|
||||||
|
env: bool;
|
||||||
|
}
|
||||||
|
|
||||||
// Note this represents The WHOLE header of an http message, not just the key
|
// Note this represents The WHOLE header of an http message, not just the key
|
||||||
// value pairs. That means it includes method and url for Requests and status
|
// value pairs. That means it includes method and url for Requests and status
|
||||||
// for responses. This is why it is singular "Header" instead of "Headers".
|
// for responses. This is why it is singular "Header" instead of "Headers".
|
||||||
|
|
53
src/ops.rs
53
src/ops.rs
|
@ -130,6 +130,8 @@ pub fn dispatch(
|
||||||
msg::Any::Now => op_now,
|
msg::Any::Now => op_now,
|
||||||
msg::Any::IsTTY => op_is_tty,
|
msg::Any::IsTTY => op_is_tty,
|
||||||
msg::Any::Seek => op_seek,
|
msg::Any::Seek => op_seek,
|
||||||
|
msg::Any::Permissions => op_permissions,
|
||||||
|
msg::Any::PermissionRevoke => op_revoke_permission,
|
||||||
_ => panic!(format!(
|
_ => panic!(format!(
|
||||||
"Unhandled message {}",
|
"Unhandled message {}",
|
||||||
msg::enum_name_any(inner_type)
|
msg::enum_name_any(inner_type)
|
||||||
|
@ -503,6 +505,57 @@ fn op_env(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn op_permissions(
|
||||||
|
isolate: &Isolate,
|
||||||
|
base: &msg::Base<'_>,
|
||||||
|
data: libdeno::deno_buf,
|
||||||
|
) -> Box<Op> {
|
||||||
|
assert_eq!(data.len(), 0);
|
||||||
|
let cmd_id = base.cmd_id();
|
||||||
|
let builder = &mut FlatBufferBuilder::new();
|
||||||
|
let inner = msg::PermissionsRes::create(
|
||||||
|
builder,
|
||||||
|
&msg::PermissionsResArgs {
|
||||||
|
run: isolate.permissions.allows_run(),
|
||||||
|
read: isolate.permissions.allows_read(),
|
||||||
|
write: isolate.permissions.allows_write(),
|
||||||
|
net: isolate.permissions.allows_net(),
|
||||||
|
env: isolate.permissions.allows_env(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
ok_future(serialize_response(
|
||||||
|
cmd_id,
|
||||||
|
builder,
|
||||||
|
msg::BaseArgs {
|
||||||
|
inner: Some(inner.as_union_value()),
|
||||||
|
inner_type: msg::Any::PermissionsRes,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_revoke_permission(
|
||||||
|
isolate: &Isolate,
|
||||||
|
base: &msg::Base<'_>,
|
||||||
|
data: libdeno::deno_buf,
|
||||||
|
) -> Box<Op> {
|
||||||
|
assert_eq!(data.len(), 0);
|
||||||
|
let inner = base.inner_as_permission_revoke().unwrap();
|
||||||
|
let permission = inner.permission().unwrap();
|
||||||
|
let result = match permission {
|
||||||
|
"run" => isolate.permissions.revoke_run(),
|
||||||
|
"read" => isolate.permissions.revoke_read(),
|
||||||
|
"write" => isolate.permissions.revoke_write(),
|
||||||
|
"net" => isolate.permissions.revoke_net(),
|
||||||
|
"env" => isolate.permissions.revoke_env(),
|
||||||
|
_ => Ok(()),
|
||||||
|
};
|
||||||
|
if let Err(e) = result {
|
||||||
|
return odd_future(e);
|
||||||
|
}
|
||||||
|
ok_future(empty_buf())
|
||||||
|
}
|
||||||
|
|
||||||
fn op_fetch(
|
fn op_fetch(
|
||||||
isolate: &Isolate,
|
isolate: &Isolate,
|
||||||
base: &msg::Base<'_>,
|
base: &msg::Base<'_>,
|
||||||
|
|
|
@ -12,6 +12,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DenoPermissions {
|
pub struct DenoPermissions {
|
||||||
|
// Keep in sync with src/permissions.ts
|
||||||
pub allow_read: AtomicBool,
|
pub allow_read: AtomicBool,
|
||||||
pub allow_write: AtomicBool,
|
pub allow_write: AtomicBool,
|
||||||
pub allow_net: AtomicBool,
|
pub allow_net: AtomicBool,
|
||||||
|
@ -91,6 +92,51 @@ impl DenoPermissions {
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn allows_run(&self) -> bool {
|
||||||
|
return self.allow_run.load(Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allows_read(&self) -> bool {
|
||||||
|
return self.allow_read.load(Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allows_write(&self) -> bool {
|
||||||
|
return self.allow_write.load(Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allows_net(&self) -> bool {
|
||||||
|
return self.allow_net.load(Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allows_env(&self) -> bool {
|
||||||
|
return self.allow_env.load(Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke_run(&self) -> DenoResult<()> {
|
||||||
|
self.allow_run.store(false, Ordering::SeqCst);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke_read(&self) -> DenoResult<()> {
|
||||||
|
self.allow_read.store(false, Ordering::SeqCst);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke_write(&self) -> DenoResult<()> {
|
||||||
|
self.allow_write.store(false, Ordering::SeqCst);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke_net(&self) -> DenoResult<()> {
|
||||||
|
self.allow_net.store(false, Ordering::SeqCst);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke_env(&self) -> DenoResult<()> {
|
||||||
|
self.allow_env.store(false, Ordering::SeqCst);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn default() -> Self {
|
pub fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
allow_read: AtomicBool::new(false),
|
allow_read: AtomicBool::new(false),
|
||||||
|
|
|
@ -286,6 +286,35 @@ It's worth noting that like the `cat.ts` example, the `copy()` function here
|
||||||
also does not make unnecessary memory copies. It receives a packet from the
|
also does not make unnecessary memory copies. It receives a packet from the
|
||||||
kernel and sends back, without further complexity.
|
kernel and sends back, without further complexity.
|
||||||
|
|
||||||
|
### Inspecting and revoking permissions
|
||||||
|
|
||||||
|
Sometimes a program may want to revoke previously granted permissions. When a
|
||||||
|
program, at a later stage, needs those permissions, a new prompt will be
|
||||||
|
presented to the user.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { permissions, revokePermission, open, remove } = Deno;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
// lookup a permission
|
||||||
|
if (!permissions().write) {
|
||||||
|
throw new Error("need write permission");
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = await open("request.log", "a+");
|
||||||
|
|
||||||
|
// revoke some permissions
|
||||||
|
revokePermission("read");
|
||||||
|
revokePermission("write");
|
||||||
|
|
||||||
|
// use the log file
|
||||||
|
await log.write(encoder.encode("hello\n"));
|
||||||
|
|
||||||
|
// this will prompt for the write permission or fail.
|
||||||
|
await remove("request.log");
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
### File server
|
### File server
|
||||||
|
|
||||||
This one serves a local directory in HTTP.
|
This one serves a local directory in HTTP.
|
||||||
|
|
Loading…
Reference in a new issue