• トップ
  • ブログ一覧
  • テスト駆動で学ぶ Firestore セキュリティルール 【データ検証編 / 前編】
  • テスト駆動で学ぶ Firestore セキュリティルール 【データ検証編 / 前編】

    広告メディア事業部広告メディア事業部
    2020.04.22

    IT技術

    テスト駆動で商品データのルールを実装する

    この記事では、セキュリティルールによるデータ検証について、商品データのルールを例に解説していきます。

    前編・後編の二回に分け、テスト駆動で商品データのルールの実装を進めます。

    条件設定で使用できるデータなどについても解説していたいと思います!

    簡単な流れ

    以下のような流れで解説を進めていきます。

    前編

    1. 商品データコレクションの要件を設定
    2. テスト環境の準備
    3. テストデータ用のクラスを作成
    4. データ追加ルールを実装・解説

    後編

    1. データ更新ルールを実装・解説
    2. データ取得ルールを実装・解説
    3. 全体テストで要件の最終チェック
    4. まとめ

    今回は、前編パートの、「実装目標(要件)の設定からデータ追加ルールの実装まで」を解説していきます。

    実装目標

    商品データコレクション items を想定して、以下の要件を目標に実装を進めていきます。

    商品データ追加の要件

    1. データ追加をリクエストしたユーザが商品管理者と一致しない場合は追加不可

    商品データ更新の要件

    1. データ更新をリクエストしたユーザが商品管理者と一致しない場合は追加不可
    2. 商品管理者の変更不可
    3. ロックされたデータの更新不可
    4. 商品名の変更不可

    商品データ取得の要件

    1. 認証されていないユーザはデータ取得不可
    2. 売り切れ商品のデータ取得不可

    前準備 ~ テスト環境の準備 ~

    初めに、テスト環境を準備します。

    テスト環境のセットアップ

    以下のコマンドを実行して、テスト環境をセットアップします。

    1git clone "https://github.com/rightcode/firestore-security-rules-test_data-verification.git" sandbox
    2
    3cd sandbox
    4git checkout refs/tags/test-environment-only

    上記のリポジトリに、この記事のコードがまとめてあります。

    npm パッケージのインストール

    以下のコマンドを実行して、npm パッケージをインストールしてください。

    1npm install

    Firebase CLI ツールのインストール

    まだ、Firebase CLI ツールをインストールしていない場合は、以下のコマンドを実行してインストールします。

    1npm install -g firebase-tools

    Firestore エミュレータを起動

    以下のコマンドを実行して、Firestore エミュレータを起動してください。

    1firebase setup:emulators:firestore
    2firebase emulators:start --only firestore

    以上で、テスト環境の準備は完了です。

    コードを確認したい場合は、以下のコマンドより、コーディング済みのタグを指定してチェックアウトしてください。

    1// テスト実装済み
    2git checkout refs/tags/test-only
    3
    4// テストとルール実装済み
    5git checkout refs/tags/test-and-rules

    テスト環境のセットアップの解説

    テスト環境のセットアップについて、より詳細な内容は、以下の記事をご参照ください。

    featureImg2020.02.12Firestoreエミュレータ+Jestでセキュリティルールをテストする!Firestore(ファイアストア)とは?「Firestore」は、Google製のスケーラブルなドキュメントデータベ...

    前準備 ~ テストデータクラス ~

    テストを作成する前に、まず、テストデータ用のクラスを作成します。

    tests/TestData.ts ファイルを作成して、以下の内容を記述してください。

    テストデータクラスのコード

    1class TestData {
    2    // コレクションパスの取得
    3    static getCollectionPath() {
    4        return "items"
    5    }
    6
    7    // 商品IDの取得
    8    static getItemId() {
    9        return "XXXXXXXXXX"
    10    }
    11
    12    // 商品タイトルの取得
    13    static getTitle() {
    14        return "吾輩は犬である!"
    15    }
    16
    17    // 商品管理者の取得
    18    static getAdminUser() {
    19        return "fuyutsuki";
    20    }
    21
    22    // 初期データの取得
    23    static getInitialData() {
    24        return {
    25            item_id: this.getItemId(),
    26            title: this.getTitle(),
    27            admin_user: this.getAdminUser(),
    28            price: 1000,
    29            description: "猫じゃないよ。犬だよ。",
    30            locked: false,
    31            sold_out: false
    32        }
    33    }
    34
    35    // 要件に沿った更新データの取得
    36    static getValidUpdateData() {
    37        return {
    38            item_id: this.getItemId(),
    39            title: this.getTitle(),
    40            admin_user: this.getAdminUser(),
    41            price: 12000,
    42            description: "猫が好きです。でも犬はも~っと好きです。",
    43            locked: false,
    44            sold_out: false
    45        };
    46    }
    47}
    48
    49export default TestData;

    テストデータクラスの概要

    getInitialData()

    初期データの取得メソッド

    初期データの追加テストや、データの更新・取得テストの初期データの準備に使用します。

    getValidUpdateData()

    要件にあった更新データの取得メソッド

    データ更新テストに使用します。

    商品データ追加ルールの実装

    テスト作成の前に、データ追加の要件を再確認します。

    1. データ追加をリクエストしたユーザが商品管理者と一致しない場合は追加不可

    上記の要件に沿って、テストを作成します。

    tests/data.add.test.ts ファイルを追加して、以下のようにコードを記述してください。

    テストのコード

    1// Firestoreエミュレータのホストとポートを指定
    2process.env.FIRESTORE_EMULATOR_HOST = "localhost:58080";
    3
    4import {FirestoreTestSupporter} from "firestore-test-supporter";
    5import * as firebase from "@firebase/testing";
    6import path from "path";
    7
    8// テストデータクラスの読み込み
    9import TestData from "./TestData";
    10
    11describe("データ追加テスト", () => {
    12    const supporter = new FirestoreTestSupporter("my-test-project", path.join(__dirname, "firestore.rules"));
    13
    14    // テストデータを変数に設定
    15    const collectionPath = TestData.getCollectionPath();
    16    const item_id = TestData.getItemId();
    17    const initialData = TestData.getInitialData();
    18    const admin_user = TestData.getAdminUser();
    19
    20    beforeEach(async () => {
    21        // セキュリティルールの読み込み
    22        await supporter.loadRules();
    23    });
    24
    25    afterEach(async () => {
    26        // データのクリーンアップ
    27        await supporter.cleanup();
    28    });
    29
    30    test('要件にあったデータの追加に成功', async () => {
    31        const db = supporter.getFirestoreWithAuth(admin_user);
    32        const doc = db.collection(collectionPath).doc(item_id);
    33        await firebase.assertSucceeds(doc.set(initialData))
    34    });
    35
    36    test('データ追加をリクエストしたユーザが商品管理者と一致しない場合は追加不可', async () => {
    37        // 商品管理者と異なるユーザで認証
    38        const db = supporter.getFirestoreWithAuth("other_user");
    39
    40        const doc = db.collection(collectionPath).doc(item_id);
    41        await firebase.assertFails(doc.set(initialData))
    42    });
    43});

    テストの概要

    コードの冒頭

    Firestore エミュレータのホスト指定をし、作成したテストデータクラスの読み込みを行っています。

    デフォルトポートでエミュレータを起動している場合は、ホスト指定は必要ありません。

    ですが、今回は 58080 ポートでエミュレータを起動しているので、ホストを 58080 ポートで指定しています。

    「データ追加をリクエストしたユーザが商品管理者と一致しない場合は追加不可」テスト

    const db = supporter.getFirestoreWithAuth("other_user"); の部分で、商品管理者 admin_user とは異なるユーザ other_user で認証された Firestore クライアントを取得しています。

    ルールの実装

    商品データ追加用のテストが完成したので、テスト駆動でセキュリティルールの実装を進めていきます。

    テスト駆動のスタイルに従い、まずはテストを実施してみて、テストが失敗することを確認します。

    テストをスタート

    以下のコマンドを実行し、テストをスタートしてください。

    1npm run test-watch tests/data.add.test.ts

    テスト結果

    各テストの結果に、以下のようなエラーメッセージが表示されて、テストが失敗します。

    1ENOENT: no such file or directory, open '.../tests/firestore.rules'

    全アクセスを拒否するセキュリティルールを設定

    tests/firestore.rules ファイルがないという事なので、tests/firestore.rules ファイルを追加して、以下のように記述してください。

    1rules_version = '2';
    2service cloud.firestore {
    3  match /databases/{database}/documents {
    4    // 全てのアクセスを拒否
    5    allow read, wreite: if false;
    6  }
    7}

    まずは、全てのアクセスを拒否するとこらからスタートし、上から順にテストを通して行きます

    テスト結果

    テストが自動で再実施され、テスト結果の冒頭に以下のような結果が表示されます。

    1FAIL  tests/data.add.test.ts
    2  データ追加テスト
    3    × 要件にあったデータの追加に成功 (2291ms)
    4データ追加をリクエストしたユーザが商品管理者と一致しない場合は追加不可 (53ms)

    テスト駆動開発では、失敗するテストを順次追加していくのが一般的です。

    しかし、この記事では、解説のわかりやすさのため、テストをまとめて追加しています。

    そのため、一部テストが通ってしまっていますが、とりあえずスルーしてください。

    コレクションへの全アクセスを許可するセキュリティルールを設定

    「要件にあったデータの追加に成功」テストが失敗しているので、とりあえず items コレクションへのアクセスを全て許可してみます。

    以下のようにコードを調整してください。

    1rules_version = '2';
    2service cloud.firestore {
    3  match /databases/{database}/documents {
    4    match /items/{item_id}{
    5      allow read, write: if true;
    6    }
    7  }
    8}

    テスト結果

    テスト結果は、以下の通りになります。

    1 FAIL  tests/data.add.test.ts
    2  データ追加テスト
    3要件にあったデータの追加に成功 (2095ms)
    4    × データ追加をリクエストしたユーザが商品管理者と一致しない場合は追加不可 (58ms)

    「要件にあったデータの追加に成功」テストが成功しました。

    次に、「データ追加をリクエストしたユーザが商品管理者と一致しない場合は追加不可」テストを通します。

    ユーザ情報を許可条件の設定に使用する

    以下のようにルールを調整してください。

    1rules_version = '2';
    2service cloud.firestore {
    3  match /databases/{database}/documents {
    4    match /items/{item_id}{
    5      allow create: if request.auth.uid == request.resource.data.admin_user;
    6    }
    7  }
    8}

    ここで、request.auth フィールドにより、リクエストしたユーザの情報を、許可条件の設定に使用することができます

    uid のほか、email や phone_number などを参照できます。

    また、request.resource.data フィールドにより、追加データの情報を条件に使用することができます

    なお、request.auth のより詳しい内容は、以下を参照してください。

    【参考】
    Interface: Request | Firebase - auth

    テスト結果

    テスト結果は、以下の通りとなります。

    1 PASS  tests/data.add.test.ts
    2  データ追加テスト
    3要件にあったデータの追加に成功 (2095ms)
    4データ追加をリクエストしたユーザが商品管理者と一致しない場合は追加不可 (56ms)

    テストが全て通りましたので、商品データ追加向けのセキュリティルールの実装はこれで完了です。

    次のテストとの競合を避けるために、データ追加用のテストを終了してください。

    後編へつづく!

    以上、「実装目標の設定」から、「データ追加ルールを実装する」ところまで解説してみました。

    後編では、データ更新と取得のルールを、今回と同じように、テスト駆動で実装していきます

    また、データベースにすでにあるデータの利用などについても解説していきます。

    それでは、続きは後編で!

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    featureImg2020.08.04エンジニアの働き方 特集社員としての働き方社員としてのエンジニアの働き方とは?ライトコードのエンジニアはどんな働き方をしてるのか、まとめたいと...

    featureImg2020.07.27IT・コンピューターの歴史特集IT・コンピューターの歴史をまとめていきたいと思います!弊社ブログにある記事のみで構成しているため、まだ「未完成状態」...

    後編の記事はこちら!

    test-dirven-firestore-security-rules-data-verification-2nd2020.04.28テスト駆動で学ぶ Firestore セキュリティルール 【データ検証編 / 後編】テスト駆動で商品データのルールを実装する【後編】この記事では、前編・後編の二回に分けて、Firestore 上の商品デ...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    広告メディア事業部

    広告メディア事業部

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background