パーミッション API

この API は unstable です。詳細は以下。 unstable な機能

パーミッションは deno コマンドの実行時に CLI から権限を与えられます。 ユーザーコードはそれを実行するために必要なひと揃いのパーミッションを仮定することがよくありますが、 コードの実行中に許可されたパーミッションがそれに合致していることは保証されません。

あるケースではフォールトトレラントなプログラムが実行時にパーミッションシステムと相互作用する仕組みを必要とすることがあります。

パーミッションディスクリプタ

/foo/bar に対する読み取りのパーミッションは、CLI において --allow-read=/foo/bar と表されます。 JS ランタイムにおいてこれは以下のように表されます。

const desc = { name: "read", path: "/foo/bar" };

以下は他の例です。

// グローバルな書き込みパーミッション
const desc1 = { name: "write" };

// `$PWD/foo/bar` に対する書き込みパーミッション
const desc2 = { name: "write", path: "foo/bar" };

// グローバルなネット接続パーミッション
const desc3 = { name: "net" };

// 127.0.0.1:8000 に対するネット接続パーミッション
const desc4 = { name: "net", url: "127.0.0.1:8000" };

// 高分解能時間に対するパーミッション
const desc5 = { name: "hrtime" };

パーミッションのクエリ

ディスクリプタを使うと、あるパーミッションが許可されているかどうかを確認できます。

// 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" }

パーミッションの状態

パーミッションの状態は "granted"、"prompt"、"denied" のいずれかになります。 CLI から許可されたパーミッションのクエリ結果は { state: "granted" } となります。 許可されていないパーミッションのクエリ結果はデフォルトで { state: "prompt" } となります。 { state: "denied" } は明示的に拒否されたパーミッションのために予約されています。 これはパーミッションのリクエストで詳述します。

パーミッションの強さ

パーミッションのクエリの二番目のクエリ結果がどうしてこのようになるのかを直感的に説明すると、 /foo に対する読み取りアクセスが許可されていてfoo/barfoo の中にあるため、foo/bar の読み取りが許可されているということになります。

desc1desc2 より強い とも言えます。これは CLI で許可される他のパーミッションについても同様です。

  1. desc1 のクエリ結果が { state: "granted" } ならば desc2 でも同じ結果になる
  2. desc2 のクエリ結果が { state: "denied" } ならば desc1 でも同じ結果になる

他の例。

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

パーミッションのリクエスト

ユーザーがまだ許可していないパーミッションは CLI プロンプトを通じてリクエストできます。

// 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" }

現在のパーミッション状態が "prompt" のとき、リクエストを許可してよいかどうかを尋ねるプロンプトがユーザーのターミナルに表示されます。 desc1 のリクエストは許可されたため新しいステータスが返却され、CLI で --allow-read=/foo が指定されたのと同じ状態で実行が継続します。 desc2 のリクエストは拒否されたためパーミッション状態は "prompt" から "denied" にダウングレードされます。

現在のパーミッション状態がすでに "granted" か "denied" になっていれば、リクエストはクエリと同じような振る舞いをして単に現在のステータスを返します。 これにより、すでに許可されたパーミッションと以前に拒否されたリクエストはプロンプトを表示せずに続行できます。

パーミッションの取り消し

パーミッションを "granted" から "prompt" にダウングレードできます。

// deno run --unstable --allow-read=/foo main.ts

const desc = { name: "read", path: "/foo" };
console.log(await Deno.permissions.revoke(desc));
// PermissionStatus { state: "prompt" }

けれども、CLI で許可されたパーミッションの 一部 を取り消そうとする場合には何が起きるでしょうか。

// 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" }

取り消されませんでした。

この振る舞いを理解するには、Deno が 明示的に許可されたパーミッションディスクリプタ の集合を内部で保持していると想像してみてください。 CLI で --allow-read=/foo,/bar を指定するとこの集合が以下のように初期化されます。

[
  { name: "read", path: "/foo" },
  { name: "read", path: "/bar" },
];

実行時に { name: "write", path: "/foo" } のリクエストを許可すると、この集合は次のように更新されます。

[
  { name: "read", path: "/foo" },
  { name: "read", path: "/bar" },
  { name: "write", path: "/foo" },
];

Deno のパーミッション取り消しアルゴリズムは、この集合の各要素を見て、引数で与えるパーミッションディスクリプタがその要素よりも強い場合にそれを取り除くという仕組みになっています。そのため desc をもう許可しないことを保証するには、 より強い ディスクリプタを引数に与える必要があります。 明示的に許可されたパーミッションディスクリプタdesc より強い ものであれば何でも構いません。

// 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" }