🧑‍🚒

Firestore 公式ドキュメントを紐解く #1

2021/06/14に公開

Firestore セキュリティールール を公式ドキュメントで学ぶ

Cloud Firestore セキュリティ ルールを使用すると、データベース内のドキュメントおよびコレクションへのアクセスを制御できます。
ルールの構文の柔軟性により、データベース全体へのすべての書き込みから特定のドキュメントに対するオペレーションまで、どのようなものにも一致するルールを作成できます。

開発モードの Default セキュリティールール

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if
          request.time < timestamp.date(2021, 6, 26);
    }
  }
}

ということで、 Dafault のセキュリティールールを適宜変更し、表題を実現できるようドキュメントを紐解いていく

Cloud Firestore セキュリティ ルールを構造化する

サービスとデータベースの宣言

Cloud Firestore セキュリティ ルールは、常に、次の宣言で始まります。

↓ できること
ルールのスコープを Cloud Firestore にできる
(Cloud Firestore セキュリティ ルールと Cloud Storage などの他のプロダクトのルールとの間の競合を防ぐ)

service cloud.firestore {
  match /databases/{database}/documents {
    // ...
  }
}

基本的な読み書きのルール

  • match ドキュメント パスを指定

    • 例1 match /cities/SF 特定のドキュメントを指す
    • 例2 match /cities/{city} ワイルドカードですべてのドキュメントを指す
  • allow データ読み書き許可の詳細を指定

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read: if <condition>;
      allow write: if <condition>;
    }
  }
}

詳細なオペレーション

  • read ルールは get と list に分割可
  • write ルールは create update delete に分割可

セキュリティールールを分けて安全なコードが書ける

service cloud.firestore {
  match /databases/{database}/documents {
    // read は get と list に分割できます。
    match /cities/{city} {
      // 1つのドキュメント読み取りリクエストに適用されます。
      allow get: if <condition>;
      // クエリやコレクションの読み込みリクエストに適用されます。
      allow list: if <condition>;
    }

    // write は、create, update, delet に分割できます。
    match /cities/{city} {
      // 新規作成ドキュメントに適用されます。
      allow create: if <condition>;
      // 既存のドキュメントへの上書きに適用されます。
      allow update: if <condition>;
      // 削除操作に適用されます。
      allow delete: if <condition>;
    }
  }
}

階層データ

Cloud Firestore のデータはドキュメントのコレクションにまとめられる。
各ドキュメントはサブコレクションによって階層を拡張できる。

↓ ドキュメントの中にネストして作るコレクション(下記画像赤アンダーライン部)をサブコレクションと呼ぶと思われる

cities コレクションの各ドキュメントに landmarks サブコレクションが含まれている状況を考えてみましょう。

  • ルールは一致したパスにのみ適用される
    ( 例 cities コレクションで定義されたルールは landmarks サブコレクションには適用されない )

サブコレクションへのアクセスを制御する明示的なルールを記述します。

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read, write: if <condition>;

        // 'landmarks'サブコレクションのルールを明示的に定義する
        match /landmarks/{landmark} {
          allow read, write: if <condition>;
        }
    }
  }
}

↓ 下記ふたつのコードは同じ意味

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      match /landmarks/{landmark} {
        allow read, write: if <condition>;
      }
    }
  }
}
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city}/landmarks/{landmark} {
      allow read, write: if <condition>;
    }
  }
}

再起ワイルドカード

ルールが任意の深い階層に適用されるようにするには、再帰ワイルドカード構文、 { name=** } を使用します。

ワイルドカード変数には一致するパスセグメント全体が含められます。
たとえば、下に示したルールは、 /cities/SF/landmarks/coit_tower に置かれたドキュメントと一致し、document 変数の値は SF/landmarks/coit_tower になります。

service cloud.firestore {
  match /databases/{database}/documents {
    // cities コレクション内のすべてのドキュメント、およびサブコレクション内のすべてのドキュメントを含む。
    match /cities/{document=**} {
      allow read, write: if <condition>;
    }
  }
}

ただし、再帰ワイルドカードの動作は、セキュリティールールのバージョンによって異なります。

セキュリティールールバージョン2 について

(現行でのデフォルトセキュリティールールバージョン)

セキュリティ ルールの先頭に rules_version = '2'; 追記する必要があります。

バージョン 2 では、ワイルドカードを match ステートメント内の任意の場所に配置できます。次に例を示します。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // songs コレクショングループ内の任意のドキュメントを指定できる。
    match /{path=**}/songs/{song} {
      allow read, write: if <condition>;
    }
  }
}

match ステートメントの重複

複数の allow 式がリクエストと一致する場合、いずれかの条件が true と評価されると、アクセスが許可されます。

下の例では、cities コレクションに対するすべての読み取りと書き込みが許可されます。これは、最初のルールが常に false になっても、2 番目のルールが常に true なるためです。

service cloud.firestore {
  match /databases/{database}/documents {
    // cities コレクション内のドキュメントを指定
    match /cities/{city} {
      allow read, write: if false;
    }

    // cities コレクションに含まれるサブコレクション内のドキュメントも指定
    match /cities/{document=**} {
      allow read, write: if true;
    }
  }
}

セキュリティ ルールの上限

下記に関して、初心者(私)が上限に達することはなさそう
関数の再帰的な呼び出し、または循環的な呼び出しが許可されていない、ということは頭の片隅に置いておく

  • リクエストあたりの exists() get() getAfter() 呼び出しの最大数

    • 単一ドキュメントに対するリクエストとクエリ リクエストの場合は 10。
    • 複数のドキュメントに対する読み取り、トランザクション、一括書き込みの場合は 20。
    • 各オペレーションには、前述の上限(10)も適用されます。
    • いずれかの上限を超えると、アクセス拒否のエラーが発生します。
    • キャッシュされた呼び出しは上限数に計上されません。
  • ネストされた match ステートメントの深さの最大数 10

  • ネストされた一連の match ステートメント内で許可されるパスセグメント内の最大パス長 100

  • ネストされた一連の match ステートメント内で許可されるパスキャプチャ変数の最大数 20

  • 関数呼び出しの深さの最大数 20

  • 関数引数の最大数 7

  • 関数あたりの let 変数バインディングの最大数 10

  • 関数の再帰的な呼び出し、または循環的な呼び出しの最大数 許可されていません

  • リクエストあたり評価される式の最大数 1,000

  • ルールセットの最大サイズ 256 KB

セキュリティールールの構造化のチャプターはここまで

Discussion