LoginSignup
17
10

More than 5 years have passed since last update.

cloud firestore のsecurity rule でハマったところ

Posted at

よくあるサーバー側のプログラムやWAFで行っているデータストアのバリデーションやアクセス制御は、cloud firestoreの場合はsecurity ruleを使って行うことになる。
そのsecurity ruleの編集をする中でいろいろハマったところがあったので、覚書。

const, let, varなどの変数は使えない

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{user} {
      allow read: if request.auth.uid == resource.data.author_id;
      allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.canWrite == true;
    }
  }
}

このように、get(/databases/$(database)/documents/users/$(request.auth.uid)) で取得できるデータを複数使いたい場合(この場合はadminというフィールドとcanWriteというフィールド)、getで取得したデータを変数に格納しておいて使い回せるようにしたい。わざわざ2回同じ情報を取得しにいくのは無駄だし、コードも冗長だ。

が、security ruleでは変数を使うことができない。そのため、上記のようにわざわざ2回取得することになる。

function hasValidAuthority(){
    const user = get(/databases/$(database)/documents/users/$(request.auth.uid))
    user.data.admin == true && user.data.canWrite == true
}
service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches the 'author_id' field
    // of the document
    match /users/{user} {
      allow read: if request.auth.uid == resource.data.author_id;
      allow write: if hasValidAuthority();
    }
  }
}

このようにfunctionの中で使うのもダメ。とにかくsecurity ruleの中では変数が使えない。
公式ドキュメントにも以下のような注意がある

関数には 1 つの return ステートメントのみを含めることができます。追加ロジックを含めることはできません。たとえば、中間変数を作成したり、ループを実行したり、外部サービスを呼び出したりすることはできません。

==, != を使って明示的に条件判断をしないといけない。

function が使えるので security ruleはJavaScriptで書けるのかと思いきや、そうではない。あくまでもsecurity ruleはsecurity ruleなので、JavaScriptではない。

例えば以下のようなrule。getで取得できるadminフィールドの値はbool値が入ってるとする。

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches the 'author_id' field
    // of the document
    match /users/{user} {
      allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin;
    }
  }
}

get(...).data.admin で返ってくる値は truefalse だし、この条件分岐でもいけるのでは…?と思ってしまうが、これはダメ。getで取得した情報の型情報などないのだから、当然といえば当然だし、JavaScriptではないので、boolean以外の値を条件式が勝手にいい感じに評価してくれるわけではない。
なので、以下のように条件を書く必要がある。

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches the 'author_id' field
    // of the document
    match /users/{user} {
      allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }
  }
}

時間によって条件評価したい

request という変数をrule内で使うことができる。その名の通り、firestoreへのリクエスト時に使われた情報が入っている。この中に time というフィールドがある。これはfirestoreへのリクエストが行われた時間が入っている。なので、この値を使えば時間を元にしたアクセス制限を行うことができる。
例えば最終ログイン時刻をfirestoreに入れていて、その最終ログイン時刻の30分以内であればreadができるというruleは以下のように書ける。

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches the 'author_id' field
    // of the document
    match /users/{user} {
      allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.lastLoginTime + duration.value(30, 'm') > request.time;
    }
  }
}

duration.value(30, 'm') で30分の部分を表している。 lastLoginTime はfirestoreのtimestamp型の値とする。timestamp型の値と request.time はそのまま比較することができる。 unix timestampの値をintegerとして lastLoginTime に格納していた場合などは値の変換が必要となる。
(未検証だけど、request.time.toMillis() とであれば比較できると思う)
security ruleで扱える値にもデータ型が存在しているので、以下のドキュメントを参照。
https://firebase.google.com/docs/reference/rules/rules

参考: https://firebase.google.com/docs/reference/rules/rules.timestamp_

17
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
10