mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
refactor: permissions (#7074)
This commit is contained in:
parent
f6e9150b33
commit
015fa0bd41
24 changed files with 1051 additions and 724 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -346,7 +346,6 @@ dependencies = [
|
|||
"nix",
|
||||
"notify",
|
||||
"os_pipe",
|
||||
"pty",
|
||||
"rand 0.7.3",
|
||||
"regex",
|
||||
"reqwest",
|
||||
|
@ -2348,6 +2347,7 @@ dependencies = [
|
|||
"futures",
|
||||
"lazy_static",
|
||||
"os_pipe",
|
||||
"pty",
|
||||
"regex",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
|
|
|
@ -83,9 +83,6 @@ os_pipe = "0.9.2"
|
|||
tokio-tungstenite = { version = "0.10.1", features = ["connect"] }
|
||||
test_util = { path = "../test_util" }
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
pty = "0.2.2"
|
||||
|
||||
[package.metadata.winres]
|
||||
# This section defines the metadata that appears in the deno.exe PE header.
|
||||
OriginalFilename = "deno.exe"
|
||||
|
|
|
@ -174,7 +174,13 @@ impl OpError {
|
|||
}
|
||||
|
||||
pub fn invalid_domain_error() -> OpError {
|
||||
OpError::new(ErrorKind::TypeError, "Invalid domain.".to_string())
|
||||
OpError::type_error("Invalid domain.".to_string())
|
||||
}
|
||||
|
||||
pub fn permission_escalation_error() -> OpError {
|
||||
OpError::permission_denied(
|
||||
"Arguments escalate parent permissions.".to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,12 +35,18 @@ pub fn op_query_permission(
|
|||
) -> Result<JsonOp, OpError> {
|
||||
let args: PermissionArgs = serde_json::from_value(args)?;
|
||||
let state = state.borrow();
|
||||
let permissions = &state.permissions;
|
||||
let path = args.path.as_deref();
|
||||
let perm = state.permissions.get_permission_state(
|
||||
&args.name,
|
||||
&args.url.as_deref(),
|
||||
&path.as_deref().map(Path::new),
|
||||
)?;
|
||||
let perm = match args.name.as_ref() {
|
||||
"read" => permissions.query_read(&path.as_deref().map(Path::new)),
|
||||
"write" => permissions.query_write(&path.as_deref().map(Path::new)),
|
||||
"net" => permissions.query_net_url(&args.url.as_deref())?,
|
||||
"env" => permissions.query_env(),
|
||||
"run" => permissions.query_run(),
|
||||
"plugin" => permissions.query_plugin(),
|
||||
"hrtime" => permissions.query_hrtime(),
|
||||
n => return Err(OpError::other(format!("No such permission name: {}", n))),
|
||||
};
|
||||
Ok(JsonOp::Sync(json!({ "state": perm.to_string() })))
|
||||
}
|
||||
|
||||
|
@ -52,22 +58,17 @@ pub fn op_revoke_permission(
|
|||
let args: PermissionArgs = serde_json::from_value(args)?;
|
||||
let mut state = state.borrow_mut();
|
||||
let permissions = &mut state.permissions;
|
||||
match args.name.as_ref() {
|
||||
"run" => permissions.allow_run.revoke(),
|
||||
"read" => permissions.allow_read.revoke(),
|
||||
"write" => permissions.allow_write.revoke(),
|
||||
"net" => permissions.allow_net.revoke(),
|
||||
"env" => permissions.allow_env.revoke(),
|
||||
"plugin" => permissions.allow_plugin.revoke(),
|
||||
"hrtime" => permissions.allow_hrtime.revoke(),
|
||||
_ => {}
|
||||
};
|
||||
let path = args.path.as_deref();
|
||||
let perm = permissions.get_permission_state(
|
||||
&args.name,
|
||||
&args.url.as_deref(),
|
||||
&path.as_deref().map(Path::new),
|
||||
)?;
|
||||
let perm = match args.name.as_ref() {
|
||||
"read" => permissions.revoke_read(&path.as_deref().map(Path::new)),
|
||||
"write" => permissions.revoke_write(&path.as_deref().map(Path::new)),
|
||||
"net" => permissions.revoke_net(&args.url.as_deref())?,
|
||||
"env" => permissions.revoke_env(),
|
||||
"run" => permissions.revoke_run(),
|
||||
"plugin" => permissions.revoke_plugin(),
|
||||
"hrtime" => permissions.revoke_hrtime(),
|
||||
n => return Err(OpError::other(format!("No such permission name: {}", n))),
|
||||
};
|
||||
Ok(JsonOp::Sync(json!({ "state": perm.to_string() })))
|
||||
}
|
||||
|
||||
|
@ -81,14 +82,14 @@ pub fn op_request_permission(
|
|||
let permissions = &mut state.permissions;
|
||||
let path = args.path.as_deref();
|
||||
let perm = match args.name.as_ref() {
|
||||
"run" => Ok(permissions.request_run()),
|
||||
"read" => Ok(permissions.request_read(&path.as_deref().map(Path::new))),
|
||||
"write" => Ok(permissions.request_write(&path.as_deref().map(Path::new))),
|
||||
"net" => permissions.request_net(&args.url.as_deref()),
|
||||
"env" => Ok(permissions.request_env()),
|
||||
"plugin" => Ok(permissions.request_plugin()),
|
||||
"hrtime" => Ok(permissions.request_hrtime()),
|
||||
n => Err(OpError::other(format!("No such permission name: {}", n))),
|
||||
}?;
|
||||
"read" => permissions.request_read(&path.as_deref().map(Path::new)),
|
||||
"write" => permissions.request_write(&path.as_deref().map(Path::new)),
|
||||
"net" => permissions.request_net(&args.url.as_deref())?,
|
||||
"env" => permissions.request_env(),
|
||||
"run" => permissions.request_run(),
|
||||
"plugin" => permissions.request_plugin(),
|
||||
"hrtime" => permissions.request_hrtime(),
|
||||
n => return Err(OpError::other(format!("No such permission name: {}", n))),
|
||||
};
|
||||
Ok(JsonOp::Sync(json!({ "state": perm.to_string() })))
|
||||
}
|
||||
|
|
|
@ -59,16 +59,20 @@ fn op_now(
|
|||
_args: Value,
|
||||
_zero_copy: &mut [ZeroCopyBuf],
|
||||
) -> Result<JsonOp, OpError> {
|
||||
let state = state.borrow();
|
||||
let seconds = state.start_time.elapsed().as_secs();
|
||||
let mut subsec_nanos = state.start_time.elapsed().subsec_nanos();
|
||||
let inner_state = state.borrow();
|
||||
let seconds = inner_state.start_time.elapsed().as_secs();
|
||||
let mut subsec_nanos = inner_state.start_time.elapsed().subsec_nanos();
|
||||
let reduced_time_precision = 2_000_000; // 2ms in nanoseconds
|
||||
|
||||
// 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.allow_hrtime.is_allow() {
|
||||
subsec_nanos -= subsec_nanos % reduced_time_precision
|
||||
if let Err(op_error) = state.check_hrtime() {
|
||||
if op_error.kind_str == "PermissionDenied" {
|
||||
subsec_nanos -= subsec_nanos % reduced_time_precision;
|
||||
} else {
|
||||
return Err(op_error);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(JsonOp::Sync(json!({
|
||||
|
|
1296
cli/permissions.rs
1296
cli/permissions.rs
File diff suppressed because it is too large
Load diff
|
@ -560,6 +560,11 @@ impl State {
|
|||
self.borrow().permissions.check_run()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_hrtime(&self) -> Result<(), OpError> {
|
||||
self.borrow().permissions.check_hrtime()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn check_plugin(&self, filename: &Path) -> Result<(), OpError> {
|
||||
self.borrow().permissions.check_plugin(filename)
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
[WILDCARD]
|
||||
running 7 tests
|
||||
test runGranted ... ok [WILDCARD]
|
||||
test readGranted ... ok [WILDCARD]
|
||||
test writeGranted ... ok [WILDCARD]
|
||||
test netGranted ... ok [WILDCARD]
|
||||
test envGranted ... ok [WILDCARD]
|
||||
test pluginGranted ... ok [WILDCARD]
|
||||
test hrtimeGranted ... ok [WILDCARD]
|
||||
|
||||
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]
|
|
@ -1,36 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
const knownPermissions: Deno.PermissionName[] = [
|
||||
"run",
|
||||
"read",
|
||||
"write",
|
||||
"net",
|
||||
"env",
|
||||
"plugin",
|
||||
"hrtime",
|
||||
];
|
||||
|
||||
export function assert(cond: unknown): asserts cond {
|
||||
if (!cond) {
|
||||
throw Error("Assertion failed");
|
||||
}
|
||||
}
|
||||
|
||||
function genFunc(grant: Deno.PermissionName): [string, () => Promise<void>] {
|
||||
const gen: () => Promise<void> = async function Granted(): Promise<void> {
|
||||
const status0 = await Deno.permissions.query({ name: grant });
|
||||
assert(status0 != null);
|
||||
assert(status0.state === "granted");
|
||||
|
||||
const status1 = await Deno.permissions.revoke({ name: grant });
|
||||
assert(status1 != null);
|
||||
assert(status1.state === "prompt");
|
||||
};
|
||||
const name = grant + "Granted";
|
||||
return [name, gen];
|
||||
}
|
||||
|
||||
for (const grant of knownPermissions) {
|
||||
const [name, fn] = genFunc(grant);
|
||||
Deno.test(name, fn);
|
||||
}
|
6
cli/tests/061_permissions_request.ts
Normal file
6
cli/tests/061_permissions_request.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const status1 = await Deno.permissions.request({ name: "read", path: "foo" });
|
||||
const status2 = await Deno.permissions.query({ name: "read", path: "bar" });
|
||||
const status3 = await Deno.permissions.request({ name: "read", path: "bar" });
|
||||
console.log(status1);
|
||||
console.log(status2);
|
||||
console.log(status3);
|
3
cli/tests/061_permissions_request.ts.out
Normal file
3
cli/tests/061_permissions_request.ts.out
Normal file
|
@ -0,0 +1,3 @@
|
|||
[WILDCARD]PermissionStatus { state: "granted" }
|
||||
PermissionStatus { state: "prompt" }
|
||||
PermissionStatus { state: "denied" }
|
6
cli/tests/062_permissions_request_global.ts
Normal file
6
cli/tests/062_permissions_request_global.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const status1 = await Deno.permissions.request({ name: "read" });
|
||||
const status2 = await Deno.permissions.query({ name: "read", path: "foo" });
|
||||
const status3 = await Deno.permissions.query({ name: "read", path: "bar" });
|
||||
console.log(status1);
|
||||
console.log(status2);
|
||||
console.log(status3);
|
3
cli/tests/062_permissions_request_global.ts.out
Normal file
3
cli/tests/062_permissions_request_global.ts.out
Normal file
|
@ -0,0 +1,3 @@
|
|||
[WILDCARD]PermissionStatus { state: "granted" }
|
||||
PermissionStatus { state: "granted" }
|
||||
PermissionStatus { state: "granted" }
|
6
cli/tests/063_permissions_revoke.ts
Normal file
6
cli/tests/063_permissions_revoke.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const status1 = await Deno.permissions.revoke({ name: "read", path: "foo" });
|
||||
const status2 = await Deno.permissions.query({ name: "read", path: "bar" });
|
||||
const status3 = await Deno.permissions.revoke({ name: "read", path: "bar" });
|
||||
console.log(status1);
|
||||
console.log(status2);
|
||||
console.log(status3);
|
3
cli/tests/063_permissions_revoke.ts.out
Normal file
3
cli/tests/063_permissions_revoke.ts.out
Normal file
|
@ -0,0 +1,3 @@
|
|||
[WILDCARD]PermissionStatus { state: "prompt" }
|
||||
PermissionStatus { state: "granted" }
|
||||
PermissionStatus { state: "prompt" }
|
6
cli/tests/064_permissions_revoke_global.ts
Normal file
6
cli/tests/064_permissions_revoke_global.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
const status1 = await Deno.permissions.revoke({ name: "read" });
|
||||
const status2 = await Deno.permissions.query({ name: "read", path: "foo" });
|
||||
const status3 = await Deno.permissions.query({ name: "read", path: "bar" });
|
||||
console.log(status1);
|
||||
console.log(status2);
|
||||
console.log(status3);
|
3
cli/tests/064_permissions_revoke_global.ts.out
Normal file
3
cli/tests/064_permissions_revoke_global.ts.out
Normal file
|
@ -0,0 +1,3 @@
|
|||
[WILDCARD]PermissionStatus { state: "prompt" }
|
||||
PermissionStatus { state: "prompt" }
|
||||
PermissionStatus { state: "prompt" }
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
#[cfg(unix)]
|
||||
extern crate nix;
|
||||
#[cfg(unix)]
|
||||
extern crate pty;
|
||||
extern crate tempfile;
|
||||
|
||||
use test_util as util;
|
||||
|
@ -166,8 +164,8 @@ fn no_color() {
|
|||
#[test]
|
||||
#[ignore]
|
||||
pub fn test_raw_tty() {
|
||||
use pty::fork::*;
|
||||
use std::io::{Read, Write};
|
||||
use util::pty::fork::*;
|
||||
|
||||
let fork = Fork::from_ptmx().unwrap();
|
||||
|
||||
|
@ -1581,12 +1579,6 @@ itest!(_056_make_temp_file_write_perm {
|
|||
output: "056_make_temp_file_write_perm.out",
|
||||
});
|
||||
|
||||
// TODO(lucacasonato): remove --unstable when permissions goes stable
|
||||
itest!(_057_revoke_permissions {
|
||||
args: "test -A --unstable 057_revoke_permissions.ts",
|
||||
output: "057_revoke_permissions.out",
|
||||
});
|
||||
|
||||
itest!(_058_tasks_microtasks_close {
|
||||
args: "run --quiet 058_tasks_microtasks_close.ts",
|
||||
output: "058_tasks_microtasks_close.ts.out",
|
||||
|
@ -1603,6 +1595,36 @@ itest!(_060_deno_doc_displays_all_overloads_in_details_view {
|
|||
output: "060_deno_doc_displays_all_overloads_in_details_view.ts.out",
|
||||
});
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn _061_permissions_request() {
|
||||
let args = "run --unstable 061_permissions_request.ts";
|
||||
let output = "061_permissions_request.ts.out";
|
||||
let input = b"g\nd\n";
|
||||
|
||||
util::test_pty(args, output, input);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn _062_permissions_request_global() {
|
||||
let args = "run --unstable 062_permissions_request_global.ts";
|
||||
let output = "062_permissions_request_global.ts.out";
|
||||
let input = b"g\n";
|
||||
|
||||
util::test_pty(args, output, input);
|
||||
}
|
||||
|
||||
itest!(_063_permissions_revoke {
|
||||
args: "run --unstable --allow-read=foo,bar 063_permissions_revoke.ts",
|
||||
output: "063_permissions_revoke.ts.out",
|
||||
});
|
||||
|
||||
itest!(_064_permissions_revoke_global {
|
||||
args: "run --unstable --allow-read=foo,bar 064_permissions_revoke_global.ts",
|
||||
output: "064_permissions_revoke_global.ts.out",
|
||||
});
|
||||
|
||||
itest!(js_import_detect {
|
||||
args: "run --quiet --reload js_import_detect.ts",
|
||||
output: "js_import_detect.ts.out",
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
## Inspecting and revoking permissions
|
||||
|
||||
> This program makes use of an unstable Deno feature. Learn more about
|
||||
> [unstable features](../runtime/stability.md).
|
||||
|
||||
Sometimes a program may want to revoke previously granted permissions. When a
|
||||
program, at a later stage, needs those permissions, it will fail.
|
||||
|
||||
```ts
|
||||
// lookup a permission
|
||||
const status = await Deno.permissions.query({ name: "write" });
|
||||
if (status.state !== "granted") {
|
||||
throw new Error("need write permission");
|
||||
}
|
||||
|
||||
const log = await Deno.open("request.log", { write: true, append: true });
|
||||
|
||||
// revoke some permissions
|
||||
await Deno.permissions.revoke({ name: "read" });
|
||||
await Deno.permissions.revoke({ name: "write" });
|
||||
|
||||
// use the log file
|
||||
const encoder = new TextEncoder();
|
||||
await log.write(encoder.encode("hello\n"));
|
||||
|
||||
// this will fail.
|
||||
await Deno.remove("request.log");
|
||||
```
|
|
@ -1,7 +1,7 @@
|
|||
## Compiler API
|
||||
## Compiler APIs
|
||||
|
||||
> This is an unstable Deno feature. Learn more about
|
||||
> [unstable features](./stability.md).
|
||||
> This API is unstable. Learn more about
|
||||
> [unstable features](../runtime/stability.md).
|
||||
|
||||
Deno supports runtime access to the built-in TypeScript compiler. There are
|
||||
three methods in the `Deno` namespace that provide this access.
|
||||
|
|
189
docs/runtime/permission_apis.md
Normal file
189
docs/runtime/permission_apis.md
Normal file
|
@ -0,0 +1,189 @@
|
|||
## Permission APIs
|
||||
|
||||
> This API is unstable. Learn more about
|
||||
> [unstable features](../runtime/stability.md).
|
||||
|
||||
Permissions are granted from the CLI when running the `deno` command. User code
|
||||
will often assume its own set of required permissions, but there is no guarantee
|
||||
during execution that the set of _granted_ permissions will align with this.
|
||||
|
||||
In some cases, ensuring a fault-tolerant program requires a way to interact with
|
||||
the permission system at runtime.
|
||||
|
||||
### Permission descriptors
|
||||
|
||||
On the CLI, read permission for `/foo/bar` is represented as
|
||||
`--allow-read=/foo/bar`. In runtime JS, it is represented as the following:
|
||||
|
||||
```ts
|
||||
const desc = { name: "read", path: "/foo/bar" };
|
||||
```
|
||||
|
||||
Other examples:
|
||||
|
||||
```ts
|
||||
// Global write permission.
|
||||
const desc1 = { name: "write" };
|
||||
|
||||
// Write permission to `$PWD/foo/bar`.
|
||||
const desc2 = { name: "write", path: "foo/bar" };
|
||||
|
||||
// Global net permission.
|
||||
const desc3 = { name: "net" };
|
||||
|
||||
// Net permission to 127.0.0.1:8000.
|
||||
const desc4 = { name: "net", url: "127.0.0.1:8000" };
|
||||
|
||||
// High-resolution time permission.
|
||||
const desc5 = { name: "hrtime" };
|
||||
```
|
||||
|
||||
### Query permissions
|
||||
|
||||
Check, by descriptor, if a permission is granted or not.
|
||||
|
||||
```ts
|
||||
// deno run --unstable --allow-read=/foo main.ts
|
||||
|
||||
const desc1 = { name: "read", path: "/foo" };
|
||||
console.log(await Deno.permissions.query(desc1));
|
||||
// PermissionStatus { state: "granted" }
|
||||
|
||||
const desc2 = { name: "read", path: "/foo/bar" };
|
||||
console.log(await Deno.permissions.query(desc2));
|
||||
// PermissionStatus { state: "granted" }
|
||||
|
||||
const desc3 = { name: "read", path: "/bar" };
|
||||
console.log(await Deno.permissions.query(desc3));
|
||||
// PermissionStatus { state: "prompt" }
|
||||
```
|
||||
|
||||
### Permission states
|
||||
|
||||
A permission state can be either "granted", "prompt" or "denied". Permissions
|
||||
which have been granted from the CLI will query to `{ state: "granted" }`. Those
|
||||
which have not been granted query to `{ state: "prompt" }` by default, while
|
||||
`{ state: "denied" }` reserved for those which have been explicitly refused.
|
||||
This will come up in [Request permissions](#request-permissions).
|
||||
|
||||
### Permission strength
|
||||
|
||||
The intuitive understanding behind the result of the second query in
|
||||
[Query permissions](#query-permissions) is that read access was granted to
|
||||
`/foo` and `/foo/bar` is within `/foo` so `/foo/bar` is allowed to be read.
|
||||
|
||||
We can also say that `desc1` is
|
||||
_[stronger than](https://www.w3.org/TR/permissions/#ref-for-permissiondescriptor-stronger-than)_
|
||||
`desc2`. This means that for any set of CLI-granted permissions:
|
||||
|
||||
1. If `desc1` queries to `{ state: "granted" }` then so must `desc2`.
|
||||
2. If `desc2` queries to `{ state: "denied" }` then so must `desc1`.
|
||||
|
||||
More examples:
|
||||
|
||||
```ts
|
||||
const desc1 = { name: "write" };
|
||||
// is stronger than
|
||||
const desc2 = { name: "write", path: "/foo" };
|
||||
|
||||
const desc3 = { name: "net" };
|
||||
// is stronger than
|
||||
const desc4 = { name: "net", url: "127.0.0.1:8000" };
|
||||
```
|
||||
|
||||
### Request permissions
|
||||
|
||||
Request an ungranted permission from the user via CLI prompt.
|
||||
|
||||
```ts
|
||||
// deno run --unstable main.ts
|
||||
|
||||
const desc1 = { name: "read", path: "/foo" };
|
||||
const status1 = await Deno.permissions.request(desc1);
|
||||
// ⚠️ Deno requests read access to "/foo". Grant? [g/d (g = grant, d = deny)] g
|
||||
console.log(status1);
|
||||
// PermissionStatus { state: "granted" }
|
||||
|
||||
const desc2 = { name: "read", path: "/bar" };
|
||||
const status2 = await Deno.permissions.request(desc2);
|
||||
// ⚠️ Deno requests read access to "/bar". Grant? [g/d (g = grant, d = deny)] d
|
||||
console.log(status2);
|
||||
// PermissionStatus { state: "denied" }
|
||||
```
|
||||
|
||||
If the current permission state is "prompt", a prompt will appear on the user's
|
||||
terminal asking them if they would like to grant the request. The request for
|
||||
`desc1` was granted so its new status is returned and execution will continue as
|
||||
if `--allow-read=/foo` was specified on the CLI. The request for `desc2` was
|
||||
denied so its permission state is downgraded from "prompt" to "denied".
|
||||
|
||||
If the current permission state is already either "granted" or "denied", the
|
||||
request will behave like a query and just return the current status. This
|
||||
prevents prompts both for already granted permissions and previously denied
|
||||
requests.
|
||||
|
||||
### Revoke permissions
|
||||
|
||||
Downgrade a permission from "granted" to "prompt".
|
||||
|
||||
```ts
|
||||
// deno run --unstable --allow-read=/foo main.ts
|
||||
|
||||
const desc = { name: "read", path: "/foo" };
|
||||
console.log(await Deno.permissions.revoke(desc));
|
||||
// PermissionStatus { state: "prompt" }
|
||||
```
|
||||
|
||||
However, what happens when you try to revoke a permission which is _partial_ to
|
||||
one granted on the CLI?
|
||||
|
||||
```ts
|
||||
// deno run --unstable --allow-read=/foo main.ts
|
||||
|
||||
const desc = { name: "read", path: "/foo/bar" };
|
||||
console.log(await Deno.permissions.revoke(desc));
|
||||
// PermissionStatus { state: "granted" }
|
||||
```
|
||||
|
||||
It was not revoked.
|
||||
|
||||
To understand this behaviour, imagine that Deno stores an internal set of
|
||||
_explicitly granted permission descriptors_. Specifying `--allow-read=/foo,/bar`
|
||||
on the CLI initializes this set to:
|
||||
|
||||
```ts
|
||||
[
|
||||
{ name: "read", path: "/foo" },
|
||||
{ name: "read", path: "/bar" },
|
||||
];
|
||||
```
|
||||
|
||||
Granting a runtime request for `{ name: "write", path: "/foo" }` updates the set
|
||||
to:
|
||||
|
||||
```ts
|
||||
[
|
||||
{ name: "read", path: "/foo" },
|
||||
{ name: "read", path: "/bar" },
|
||||
{ name: "write", path: "/foo" },
|
||||
];
|
||||
```
|
||||
|
||||
Deno's permission revocation algorithm works by removing every element from this
|
||||
set which the argument permission descriptor is _stronger than_. So to ensure
|
||||
`desc` is not longer granted, pass an argument descriptor _stronger than_
|
||||
whichever _explicitly granted permission descriptor_ is _stronger than_ `desc`.
|
||||
|
||||
```ts
|
||||
// deno run --unstable --allow-read=/foo main.ts
|
||||
|
||||
const desc = { name: "read", path: "/foo/bar" };
|
||||
console.log(await Deno.permissions.revoke(desc)); // Insufficient.
|
||||
// PermissionStatus { state: "granted" }
|
||||
|
||||
const strongDesc = { name: "read", path: "/foo" };
|
||||
await Deno.permissions.revoke(strongDesc); // Good.
|
||||
|
||||
console.log(await Deno.permissions.query(desc));
|
||||
// PermissionStatus { state: "prompt" }
|
||||
```
|
|
@ -19,6 +19,7 @@
|
|||
"children": {
|
||||
"stability": "Stability",
|
||||
"program_lifecycle": "Program lifecycle",
|
||||
"permission_apis": "Permission APIs",
|
||||
"compiler_apis": "Compiler APIs",
|
||||
"workers": "Workers"
|
||||
}
|
||||
|
@ -75,7 +76,6 @@
|
|||
"file_server": "File server",
|
||||
"tcp_echo": "TCP echo server",
|
||||
"subprocess": "Creating a subprocess",
|
||||
"permissions": "Inspecting and revoking permissions",
|
||||
"os_signals": "OS Signals",
|
||||
"file_system_events": "File system events",
|
||||
"testing_if_main": "Checking if file is main"
|
||||
|
|
|
@ -18,3 +18,6 @@ os_pipe = "0.9.2"
|
|||
regex = "1.3.9"
|
||||
tempfile = "3.1.0"
|
||||
warp = { version = "0.2.4", features = ["tls"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
pty = "0.2.2"
|
||||
|
|
|
@ -7,6 +7,8 @@ extern crate lazy_static;
|
|||
|
||||
use futures::future::{self, FutureExt};
|
||||
use os_pipe::pipe;
|
||||
#[cfg(unix)]
|
||||
pub use pty;
|
||||
use regex::Regex;
|
||||
use std::env;
|
||||
use std::io::Read;
|
||||
|
@ -767,7 +769,7 @@ impl CheckOutputIntegrationTest {
|
|||
}
|
||||
}
|
||||
|
||||
fn wildcard_match(pattern: &str, s: &str) -> bool {
|
||||
pub fn wildcard_match(pattern: &str, s: &str) -> bool {
|
||||
pattern_match(pattern, s, "[WILDCARD]")
|
||||
}
|
||||
|
||||
|
@ -820,6 +822,39 @@ pub fn pattern_match(pattern: &str, s: &str, wildcard: &str) -> bool {
|
|||
t.1.is_empty()
|
||||
}
|
||||
|
||||
/// Kind of reflects `itest!()`. Note that the pty's output (which also contains
|
||||
/// stdin content) is compared against the content of the `output` path.
|
||||
#[cfg(unix)]
|
||||
pub fn test_pty(args: &str, output_path: &str, input: &[u8]) {
|
||||
use pty::fork::Fork;
|
||||
|
||||
let tests_path = tests_path();
|
||||
let fork = Fork::from_ptmx().unwrap();
|
||||
if let Ok(mut master) = fork.is_parent() {
|
||||
let mut output_actual = String::new();
|
||||
master.write_all(input).unwrap();
|
||||
master.read_to_string(&mut output_actual).unwrap();
|
||||
fork.wait().unwrap();
|
||||
|
||||
let output_expected =
|
||||
std::fs::read_to_string(tests_path.join(output_path)).unwrap();
|
||||
if !wildcard_match(&output_expected, &output_actual) {
|
||||
println!("OUTPUT\n{}\nOUTPUT", output_actual);
|
||||
println!("EXPECTED\n{}\nEXPECTED", output_expected);
|
||||
panic!("pattern match failed");
|
||||
}
|
||||
} else {
|
||||
deno_cmd()
|
||||
.current_dir(tests_path)
|
||||
.env("NO_COLOR", "1")
|
||||
.args(args.split_whitespace())
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wildcard_match() {
|
||||
let fixtures = vec![
|
||||
|
|
Loading…
Reference in a new issue