Firebase Summit のすべての発表内容に目を通し、Firebase を活用してアプリ開発を加速し、自信を持ってアプリを実行できる方法をご確認ください。 詳細

CloudFirestoreのセキュリティルールをテストする

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

アプリを作成しているときに、Cloud Firestore データベースへのアクセスをロックダウンしたい場合があります。ただし、起動する前に、より微妙な Cloud Firestore セキュリティ ルールが必要になります。 Cloud Firestore エミュレータを使用すると、アプリの一般的な機能と動作のプロトタイピングとテストに加えて、Cloud Firestore セキュリティ ルールの動作をチェックする単体テストを作成できます。

クイックスタート

簡単なルールを使用したいくつかの基本的なテスト ケースについては、クイックスタート サンプルを試してください。

Cloud Firestore セキュリティ ルールを理解する

モバイル クライアント ライブラリとウェブ クライアント ライブラリを使用する場合は、サーバーレス認証、認可、データ検証のためにFirebase AuthenticationCloud Firestore セキュリティ ルールを実装します。

Cloud Firestore セキュリティ ルールには、次の 2 つの要素が含まれます。

  1. データベース内のドキュメントを識別するmatchステートメント。
  2. それらのドキュメントへのアクセスを制御するallow式。

Firebase Authentication はユーザーの資格情報を検証し、ユーザーベースおよびロールベースのアクセス システムの基盤を提供します。

Cloud Firestore モバイル/ウェブ クライアント ライブラリからのすべてのデータベース リクエストは、データの読み取りまたは書き込みの前に、セキュリティ ルールに対して評価されます。指定されたドキュメント パスのいずれかへのアクセスがルールによって拒否された場合、要求全体が失敗します。

Cloud Firestore セキュリティ ルールの詳細については、 Cloud Firestore セキュリティ ルールの概要をご覧ください。

エミュレーターをインストールする

Cloud Firestore エミュレータをインストールするには、 Firebase CLIを使用して以下のコマンドを実行します。

firebase setup:emulators:firestore

エミュレーターを実行する

まず、作業ディレクトリで Firebase プロジェクトを初期化します。これは、Firebase CLI を使用する場合の一般的な最初のステップです。

firebase init

次のコマンドを使用してエミュレータを起動します。エミュレーターは、プロセスを強制終了するまで実行されます。

firebase emulators:start --only firestore

多くの場合、エミュレーターを起動してテスト スイートを実行し、テストの実行後にエミュレーターをシャットダウンします。 emulators:execコマンドを使用すると、これを簡単に実行できます。

firebase emulators:exec --only firestore "./my-test-script.sh"

起動すると、エミュレータはデフォルト ポート (8080) で実行しようとします。 firebase.jsonファイルの"emulators"セクションを変更することで、エミュレータ ポートを変更できます。

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

エミュレータを実行する前に

エミュレーターの使用を開始する前に、次の点に注意してください。

  • エミュレーターは最初に、 firebase.jsonファイルのfirestore.rulesフィールドで指定されたルールを読み込みます。 Cloud Firestore セキュリティ ルールを含むローカル ファイルの名前を想定し、それらのルールをすべてのプロジェクトに適用します。ローカル ファイル パスを指定しない場合、または以下で説明するようにloadFirestoreRulesメソッドを使用しない場合、エミュレーターはすべてのプロジェクトをオープン ルールを持つものとして扱います。
  • ほとんどの Firebase SDKはエミュレーターと直接連携しますが、 @firebase/rules-unit-testingライブラリのみがセキュリティ ルールでauthのモックをサポートしているため、単体テストがはるかに簡単になります。さらに、ライブラリは、以下に示すように、すべてのデータを消去するなど、エミュレーター固有の機能をいくつかサポートしています。
  • エミュレーターは、クライアント SDK を介して提供される本番用の Firebase Auth トークンも受け入れ、それに応じてルールを評価します。これにより、統合および手動テストでアプリケーションをエミュレーターに直接接続できます。

ローカル単体テストを実行する

v9 JavaScript SDK を使用してローカル単体テストを実行する

Firebase は、バージョン 9 の JavaScript SDK とバージョン 8 の SDK の両方を備えたセキュリティ ルールの単体テスト ライブラリを配布しています。ライブラリ API は大きく異なります。 v9 テスト ライブラリをお勧めします。これはより合理化されており、エミュレーターに接続するためのセットアップが少なくて済むため、運用リソースの誤った使用を安全に回避できます。下位互換性のために、引き続きv8 テスト ライブラリを利用できます

@firebase/rules-unit-testingモジュールを使用して、ローカルで実行されるエミュレーターとやり取りします。タイムアウトまたはECONNREFUSEDエラーが発生した場合は、エミュレータが実際に実行されていることを再確認してください。

async/await表記を使用できるように、最新バージョンの Node.js を使用することを強くお勧めします。テストする可能性のあるほとんどすべての動作には非同期関数が含まれており、テスト モジュールは Promise ベースのコードで動作するように設計されています。

v9 Rules Unit Testing ライブラリは、常にエミュレーターを認識しており、実稼働リソースに触れることはありません。

v9 モジュラー インポート ステートメントを使用してライブラリをインポートします。例えば:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment,
  RulesTestEnvironment,
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

インポートしたら、単体テストの実装には次の作業が含まれます。

  • initializeTestEnvironmentを呼び出してRulesTestEnvironmentを作成および構成します。
  • RulesTestEnvironment.withSecurityRulesDisabledを一時的にバイパスできる便利なメソッドを使用して、Rules をトリガーせずにテスト データを設定します。
  • RulesTestEnvironment.cleanup()RulesTestEnvironment.clearFirestore() ) などのテスト データと環境をクリーンアップするための呼び出しを使用して、テスト スイートとテストごとの前後フックを設定します。
  • RulesTestEnvironment.authenticatedContextおよびRulesTestEnvironment.unauthenticatedContextを使用して、認証状態を模倣するテスト ケースを実装します。

一般的なメソッドとユーティリティ関数

v9 SDK のエミュレーター固有のテスト メソッドも参照してください。

initializeTestEnvironment() => RulesTestEnvironment

この関数は、ルールの単体テスト用のテスト環境を初期化します。テストのセットアップのために、最初にこの関数を呼び出します。正常に実行するには、エミュレーターが実行されている必要があります。

この関数は、プロジェクト ID とエミュレーター構成設定で構成できるTestEnvironmentConfigを定義するオプションのオブジェクトを受け入れます。

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

このメソッドは、認証された Authentication ユーザーのように動作するRulesTestContextを作成します。返されたコンテキストを介して作成されたリクエストには、モック認証トークンが添付されます。必要に応じて、認証トークン ペイロードのカスタム クレームまたはオーバーライドを定義するオブジェクトを渡します。

返されたテスト コンテキスト オブジェクトをテストで使用して、 initializeTestEnvironmentで構成されたものを含め、構成されたエミュレーター インスタンスにアクセスします。

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", { … });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

このメソッドは、Authentication 経由でログインしていないクライアントのように動作するRulesTestContextを作成します。返されたコンテキストを介して作成されたリクエストには、Firebase Auth トークンが添付されません。

返されたテスト コンテキスト オブジェクトをテストで使用して、 initializeTestEnvironmentで構成されたものを含め、構成されたエミュレーター インスタンスにアクセスします。

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

セキュリティ ルールが無効になっているかのように動作するコンテキストでテスト セットアップ関数を実行します。

このメソッドは、Security-Rules-bypassing コンテキストを取得して promise を返すコールバック関数を受け取ります。プロミスが解決/拒否されると、コンテキストは破棄されます。

RulesTestEnvironment.cleanup()

このメソッドは、テスト環境で作成されたすべてのRulesTestContextsを破棄し、基礎となるリソースをクリーンアップして、クリーンな終了を可能にします。

このメソッドは、エミュレーターの状態を変更しません。テスト間でデータをリセットするには、アプリケーション エミュレータ固有のクリア データ メソッドを使用します。

assertSucceeds(pr: Promise<any>)) => Promise<any>

これはテスト ケースのユーティリティ関数です。

この関数は、エミュレータ操作をラップする提供された Promise がセキュリティ ルール違反なしで解決されることをアサートします。

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

これはテスト ケースのユーティリティ関数です。

この関数は、エミュレータ操作をラップする提供された Promise がセキュリティ ルール違反で拒否されることをアサートします。

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

エミュレータ固有のメソッド

v9 SDK の一般的なテスト メソッドとユーティリティ関数も参照してください。

RulesTestEnvironment.clearFirestore() => Promise<void>

このメソッドは、Firestore エミュレータ用に構成されたprojectIdに属する Firestore データベースのデータを消去します。

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

このメソッドは、このテスト コンテキストの Firestore インスタンスを取得します。返された Firebase JS クライアント SDK インスタンスは、クライアント SDK API (v9 モジュラーまたは v9 互換) で使用できます。

ルールの評価を視覚化する

Cloud Firestore エミュレータを使用すると、Firebase セキュリティ ルールの評価トレースを含め、Emulator Suite UI でクライアント リクエストを視覚化できます。

[ Firestore] > [リクエスト] タブを開いて、各リクエストの詳細な評価シーケンスを表示します。

セキュリティ ルールの評価を示す Firestore Emulator Requests Monitor

テスト レポートの生成

一連のテストを実行した後、各セキュリティ ルールがどのように評価されたかを示すテスト カバレッジ レポートにアクセスできます。

レポートを取得するには、エミュレーターの実行中に、公開されているエンドポイントに対してクエリを実行します。ブラウザ対応バージョンの場合は、次の URL を使用してください。

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

これにより、ルールが式と部分式に分割され、マウスオーバーすると、評価の数や返される値などの詳細が表示されます。このデータの生の JSON バージョンについては、クエリに次の URL を含めます。

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

エミュレータと本番の違い

  1. Cloud Firestore プロジェクトを明示的に作成する必要はありません。エミュレーターは、アクセスされるすべてのインスタンスを自動的に作成します。
  2. Cloud Firestore エミュレータは、通常の Firebase Authentication フローでは機能しません。代わりに、Firebase Test SDK では、 authフィールドを受け取るrules-unit-testingライブラリでinitializeTestApp()メソッドを提供しています。このメソッドを使用して作成された Firebase ハンドルは、指定したエンティティとして正常に認証されたかのように動作します。 nullを渡すと、認証されていないユーザーとして動作します (たとえば、 auth != nullルールは失敗します)。

既知の問題のトラブルシューティング

Cloud Firestore エミュレータを使用すると、次の既知の問題が発生する可能性があります。以下のガイダンスに従って、発生している異常な動作をトラブルシューティングしてください。これらのメモはセキュリティ ルールの単体テスト ライブラリを念頭に置いて書かれていますが、一般的なアプローチはどの Firebase SDK にも適用できます。

テストの動作に一貫性がない

テスト自体に変更を加えなくても、テストがときどき成功したり失敗したりする場合は、テストが適切に順序付けられていることを確認する必要があります。エミュレーターとのほとんどの対話は非同期であるため、すべての非同期コードが適切にシーケンスされていることを再確認してください。 promise をチェーンするか、 await表記を自由に使用することで、シーケンスを修正できます。

特に、次の非同期操作を確認してください。

  • initializeTestEnvironmentなどを使用して、セキュリティ ルールを設定します。
  • db.collection("users").doc("alice").get()などを使用したデータの読み取りと書き込み。
  • assertSucceedsおよびassertFailsを含む操作アサーション。

テストは、エミュレーターを初めてロードしたときにのみ合格します

エミュレーターはステートフルです。書き込まれたすべてのデータはメモリに保存されるため、エミュレータがシャットダウンするたびにデータが失われます。同じプロジェクト ID に対して複数のテストを実行している場合、各テストは後続のテストに影響を与える可能性のあるデータを生成できます。次のいずれかの方法を使用して、この動作をバイパスできます。

  • テストごとに一意のプロジェクト ID を使用します。これを行う場合は、各テストの一部としてinitializeTestEnvironmentを呼び出す必要があることに注意してください。ルールは、デフォルトのプロジェクト ID に対してのみ自動的に読み込まれます。
  • テストを再構築して、以前に書き込まれたデータと相互作用しないようにします (たとえば、テストごとに異なるコレクションを使用します)。
  • テスト中に書き込まれたすべてのデータを削除します。

テストのセットアップは非常に複雑です

テストを設定するときに、Cloud Firestore セキュリティ ルールで実際には許可されていない方法でデータを変更したい場合があります。ルールによってテストのセットアップが複雑になっている場合は、セットアップ手順でRulesTestEnvironment.withSecurityRulesDisabledを使用してみてください。そうすれば、読み取りと書き込みでPERMISSION_DENIEDエラーがトリガーされなくなります。

その後、テストは、 RulesTestEnvironment.authenticatedContextunauthenticatedContextをそれぞれ使用して、認証済みまたは未認証のユーザーとして操作を実行できます。これにより、Cloud Firestore セキュリティ ルールがさまざまなケースを正しく許可/拒否することを検証できます。