diff options
author | Nayeem Rahman <nayeemrmn99@gmail.com> | 2020-08-18 21:29:32 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-08-18 16:29:32 -0400 |
commit | 015fa0bd41ce51afbee4a1413cb90534155c041f (patch) | |
tree | 3220c821156a011225392d2361fdf7272c7be952 /docs/runtime | |
parent | f6e9150b33168ab8c5e48238860e2c3f3bf625f3 (diff) |
refactor: permissions (#7074)
Diffstat (limited to 'docs/runtime')
-rw-r--r-- | docs/runtime/compiler_apis.md | 6 | ||||
-rw-r--r-- | docs/runtime/permission_apis.md | 189 |
2 files changed, 192 insertions, 3 deletions
diff --git a/docs/runtime/compiler_apis.md b/docs/runtime/compiler_apis.md index d9a49a01c..3424c2b5f 100644 --- a/docs/runtime/compiler_apis.md +++ b/docs/runtime/compiler_apis.md @@ -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. diff --git a/docs/runtime/permission_apis.md b/docs/runtime/permission_apis.md new file mode 100644 index 000000000..a76f0c0d4 --- /dev/null +++ b/docs/runtime/permission_apis.md @@ -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" } +``` |