firestore security ruleの知識を棚卸し

ドキュメント

サンプルコード

roleの設計

カスタムクレームをつかう

get()などで発生するコストがかからない

でもfunctions(admin sdk)じゃないと変更できない

チャットアプリのような場合だと、ルームのメンバーの権限をそこそこ変えることがありそうだが、 反映させる場合はidtokenを取得しなおさないといけないから若干手間
https://firebase.google.com/docs/auth/admin/custom-claims?hl=ja#propagate_custom_claims_to_the_client
※クライアントへカスタム クレームを伝播する

ただ下記の様な方法もあるから全てをカスタムクレームで済ませる必要はなさそう
https://firebase.google.com/docs/firestore/solutions/role-based-access?hl=ja

カスタムクレームを使わない

firestoreのどこかに保存する

この場合は、roleを確認するのにget()しないといけないのでコストがかかる

アクセス呼び出しと料金 https://firebase.google.com/docs/firestore/security/rules-conditions?hl=ja#access_calls_and_pricing

案1. usersのフィールドにrolesを持たせる

users/user_abc {
  fcmTokens:[]
  roles : [commenter, editor]
}
  • アプリ側でrolesを使わないような場合は、モデルがアプリと管理画面で別々になる
  • セキュリティールール
    • 'editor' in get(/.../users/uid).data.roles
    • アプリ側で間違ってsetした時エラーにしないといけない。そうしないとフィールドが消える
  • flutter側。例えば管理画面で更新ボタンを表示・非表示する場合。後で記述するパターンと比べるとnullチェックが多め
isEditor() async {
   final snap = await FirebaseFirestore.instance.doc('users/user_abc').get();
   final data = snap.data();
   if (data == null) return false; // ここがnullなことはほとんどないと思うけど念の為
   final List? roles = data['roles'];
   if (roles == null) return false;
   return roles.contains('editor');
}

上の様なコードを下記の様なボタンの出しわけで使う想定

if (user.isEditor())
  ElevatedButton(onPressed: () => db.update, child: Text('更新'));

案2. usersのサブコレクションにrolesを持たせる

collectionGroup()しないならメリットが思いつかない。 collectionGroupでwhere検索する場合はインデックスを作成しないといけない(沢山作ると料金が発生)。 ただ、そんなケースがあるのか?

users/user_abc/roles/role {
  editor: true,
  reader: true
}
  • セキュリティルールget(/.../users/uid/roles/role).data.editor
  • クライアント側のコード
isEditor() async {
  final snap =
      await FirebaseFirestore.instance.doc('users/users_abc/roles/role').get();
  return snap.data() != null && snap.data()!['editor'] == true;
}

案3. usersのサブコレクションにrolesを持たせ、ドキュメントIDはrole名

users/user_abc/roles/editor:{
  isAvailable: true
}
users/user_abc/roles/commenter:{
  isAvailable: true
}

いろいろやってみましたが、メリットなさそう。

案3. rolesコレクションを作って、ドキュメントIDはUID

roles/uid {
  editor: true,
  reader: true
}
  • セキュリティルール get(/.../roles/uid).data.editor
  • クライアント側
isEditor() async {
    final snap = await FirebaseFirestore.instance.doc('roles/user_abc').get();
    return snap.data() != null && snap.data()!['editor'] == true;
  }

なかなか悪くない。usersが巨大化したら分けるのもあり。

その他

最近powershellから起動しているせいかセキュリティールールを更新しても読み込んでくれない時がある。 その場合は、一旦停止してから、再度起動。

rules-unit-testingが2.0.0から大きく変わる

initializeAdminApp()がなくなる。結局内部はadmin sdkなので自分で作りだせはする。

v1系ではまだ存在する

https://github.com/firebase/firebase-js-sdk/blob/3664731934d28fad50d5c302b260a412170375f9/packages/rules-unit-testing/src/api/index.ts#L242