summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Menke <simon.menke@gmail.com>2019-03-04 17:04:19 +0100
committerRyan Dahl <ry@tinyclouds.org>2019-03-04 11:04:19 -0500
commit77d7ad61f39641b79a60a99da2f939cbc1d8fe39 (patch)
tree3bc46c49b0007fb83c435d6cf417dd0f844d947c
parent048a8a77753881936d7c6b32f4534ee364eb42ad (diff)
Allow inspection and revocation of permissions (#1875)
-rw-r--r--BUILD.gn1
-rw-r--r--js/deno.ts6
-rw-r--r--js/permissions.ts74
-rw-r--r--js/permissions_test.ts22
-rw-r--r--js/unit_tests.ts1
-rw-r--r--src/msg.fbs17
-rw-r--r--src/ops.rs53
-rw-r--r--src/permissions.rs46
-rw-r--r--website/manual.md29
9 files changed, 249 insertions, 0 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 756d0e9cf..91abcc80d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -91,6 +91,7 @@ ts_sources = [
"js/mock_builtin.js",
"js/net.ts",
"js/os.ts",
+ "js/permissions.ts",
"js/platform.ts",
"js/plugins.d.ts",
"js/process.ts",
diff --git a/js/deno.ts b/js/deno.ts
index 66d7d796c..d13ca81dd 100644
--- a/js/deno.ts
+++ b/js/deno.ts
@@ -50,6 +50,12 @@ export { symlinkSync, symlink } from "./symlink";
export { writeFileSync, writeFile, WriteFileOptions } from "./write_file";
export { ErrorKind, DenoError } from "./errors";
export { libdeno } from "./libdeno";
+export {
+ permissions,
+ revokePermission,
+ Permission,
+ Permissions
+} from "./permissions";
export { platform } from "./platform";
export { truncateSync, truncate } from "./truncate";
export { FileInfo } from "./file_info";
diff --git a/js/permissions.ts b/js/permissions.ts
new file mode 100644
index 000000000..6acb80b1f
--- /dev/null
+++ b/js/permissions.ts
@@ -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];
+}
diff --git a/js/permissions_test.ts b/js/permissions_test.ts
new file mode 100644
index 000000000..f4245c03b
--- /dev/null
+++ b/js/permissions_test.ts
@@ -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);
+ }
+ });
+}
diff --git a/js/unit_tests.ts b/js/unit_tests.ts
index 91c1745b6..5085deb5c 100644
--- a/js/unit_tests.ts
+++ b/js/unit_tests.ts
@@ -45,6 +45,7 @@ import "./url_test.ts";
import "./url_search_params_test.ts";
import "./write_file_test.ts";
import "./performance_test.ts";
+import "./permissions_test.ts";
import "./version_test.ts";
import "../website/app_test.js";
diff --git a/src/msg.fbs b/src/msg.fbs
index da03e00a4..1b57e72ef 100644
--- a/src/msg.fbs
+++ b/src/msg.fbs
@@ -12,6 +12,9 @@ union Any {
Exit,
Environ,
EnvironRes,
+ Permissions,
+ PermissionRevoke,
+ PermissionsRes,
Fetch,
FetchRes,
MakeTempDir,
@@ -231,6 +234,20 @@ table KeyValue {
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
// 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".
diff --git a/src/ops.rs b/src/ops.rs
index 5535ca1b9..ba309ca7a 100644
--- a/src/ops.rs
+++ b/src/ops.rs
@@ -130,6 +130,8 @@ pub fn dispatch(
msg::Any::Now => op_now,
msg::Any::IsTTY => op_is_tty,
msg::Any::Seek => op_seek,
+ msg::Any::Permissions => op_permissions,
+ msg::Any::PermissionRevoke => op_revoke_permission,
_ => panic!(format!(
"Unhandled message {}",
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(
isolate: &Isolate,
base: &msg::Base<'_>,
diff --git a/src/permissions.rs b/src/permissions.rs
index b40afb64e..03ffd20cb 100644
--- a/src/permissions.rs
+++ b/src/permissions.rs
@@ -12,6 +12,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
#[derive(Debug, Default)]
pub struct DenoPermissions {
+ // Keep in sync with src/permissions.ts
pub allow_read: AtomicBool,
pub allow_write: AtomicBool,
pub allow_net: AtomicBool,
@@ -91,6 +92,51 @@ impl DenoPermissions {
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 {
Self {
allow_read: AtomicBool::new(false),
diff --git a/website/manual.md b/website/manual.md
index 9081637a1..43b79d003 100644
--- a/website/manual.md
+++ b/website/manual.md
@@ -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
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
This one serves a local directory in HTTP.