Firebase のセキュリティルールで面倒な「場所」

記事
IT・テクノロジー

Firebase のセキュリティルールで面倒な「場所」

Firebase のセキュリティルールを設定する上で一番面倒なのが場所です。理由は簡単で、種類が多いからです。「人」は、ユーザー認証(ログイン)で区別できますし、「出来る事」は、基本的には「読み込み」と「書き込み(作成・更新・削除)」です。しかし、「場所」は、使い方にもよりますが、シンプルには全部書き出せません。この記事では Firebase のセキュリティルールで指定する場所について紹介します。


何故「場所」が面倒なのか?

面倒な理由は、「場所」の範囲を特定できないからです。 人の場合は、ログイン名(Firebase の場合基本は E-Mail アドレス)や UID(ユーザー ID)で特定できます。出来ることも、基本は読み込みと書き込みで、全部挙げるのは簡単です。つまり、比較的簡単に特定できて指定可能なので殆どの場合問題になりません。

しかし、アクセスを許される「場所」の場合は、範囲の特定が難しいので面倒になります。

復習を兼ねて人を指定する場合の基本は:

* 特定の E-Mail アドレスか UID(E-Mail や UID が一致する利用者)
* ログインしているかしていないか
出来ることの場合は、

* 読み込みが出来るか
* 書き込みが出来るか(新規作成できるか、データの更新ができるか、削除できるか)
基本的にこれだけです。

ところが場所の場合は、シンプルにいかないケースがあるので面倒ということです。

ブログサービスの場合は簡単!

場所の指定も、ブログサービスの場合は実はシンプルです。 前回の記事で紹介した通り、利用者は、「サイトの運営者(管理者)」と「それ以外の一般利用者」だけです。できることも、一般の利用者はブログの閲覧、つまり読み込みのみです。管理者だけが、書き込みが出来るというルールで十分です。

その場合は、場所は全ての場所という形で指定できるので場所の指定はシンプルです。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
        allow read;
        allow write: if request.quth.uid == "admin_uid";
    }
  }
}
というシンプルなルールで対応可能です。「match /{domument=**}」で全てのドキュメントへのサクセスを許可しています。 書き込みは、「管理者の UID でログインしている人のみ」の許可です。

これは、ブログサービスの場合、利用者の分類と出来ることが完全に分けられているためです。

面倒なケースは?

ではどのようなケースが面倒かというと、場所によってそれぞれの利用者の出来る事が変わってしまう例です。 例えば、メッセージ交換のサービスを例に挙げて考えてみます。

このサービスは、ログインした利用者が、指定した別の利用者にメッセージを送るというサービスです。 メッセージは、登録した利用者間でやり取りできます。

* 送信したメッセージは「送信先(受取人)」と「送信者」のみが見ることができます。
* 「送信先(受取人)」と「送信者」以外の利用者は見る事ができません
* 登録している利用者は、全ての利用者にメッセージを送信可能
* 全ての登録している利用者宛にメッセージを送信可能
このような条件のサービスを作る場合の場所を考えてみると

* 各利用者がメッセージを受信する受信箱をデータベース上に持つ(inbox)
* 各利用者が送信したメッセージの控えを保存する場所をデータベース上に持つ(sent)
のような感じになります。

では、まず「誰」を考えてみます

* ユーザー認証(ログイン)を行なって、各利用者の「E-Mail」と「UID」を特定
* UID で利用者を特定
出来る事は、

* 各利用者の「sent」は所有者のみ「読み込み」と「書き込み」が可能
* 各利用者の「inbox」は所有者のみ「読み込み」「削除」が可能、書き込み(新規作成)は誰でも可能、更新は不可
の様になります。E-Mail を想像して頂ければ良いかと思います。 シンプルにするために、送信後の全ての利用者で更新は禁止にします。

この機能を実現するデータベースの構造を考えてみます。

* メッセージ保存のコレクションを作成(コレクション名は「data」)
* 「データ」の下に各利用者毎のドキュメントを作成(ドキュメント名は「{uid}(各利用者の uid)」)
* メッセージ受け取り用のサブコレクションを作成(コレクション名は「inbox」)
* メッセージの控え用のサブコレクションを作成(コレクション名は「sent」)
* 送付先の「inbox」の下にメッセージを保存
* 送信者の「sent」に控えのメッセージを保存
コレクション(名前:data)
  ドキュメント(名前:{uid})
    コレクション(名前:inbox)
          ドキュメント(名前:{message_id})
          ......
    コレクション(名前:sent)
      ドキュメント(名前:{message_id})
      .....
このようにデータベースを作成すると、利用者一人につき 

* data のコレクションの下のドキュメント(一つ)
* inbox のサブコレクション(一つ)
* sent のサブコネクション(一つ)
* inbox の下のメッセージ(沢山)
* sent の下のメッセージ(沢山)
これに対する出来ることは

   所有者          他の利用者
 inbox read/create/delete    create
 sent read/create/delete     None
の様な形になります。

全部書けば簡単

例えば、利用者が登録される度に各利用者のスペース(場所)を作成して、セキュリティルールを追加するという管理方法ならば、毎回セキュリティルールに各利用者のコレクション・ドキュメントを指定してルールを追加していけば良いことになります。 つまり、一つ一つ場所を特定(ドキュメントのパス)を書いて、その場所の所有者とそれ以外の人と分けてセキュリティルールを決めていけば想定した人のみがアクセスできるセキュリティールールを作ることができます。

しかし、この方法は全ての場所を列挙する必要があるので、利用者が増えるとセキュリティールールを全て「個別」に作成しなければならないので、大変ですし、手間がかかります。プログラムによる自動化(利用者の自動登録)も難しくなる欠点があります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /data/user0/inbox/{message_id} {
        allow create;
        allow create, delete, read: if request.quth.uid == "user0";
    }
    match /data/user0/sent/{message_id} {
        allow create, delete, read: if request.auth.uid == "user0";
    }
    match /data/user1/inbox/{message_id} {
        allow create;
        allow create, delete, read: if request.quth.uid == "user1";
    }
    match /data/user1/sent/{message_id} {
        allow create, delete, read: if request.auth.uid == "user1";
    }
    .........
  }

}
しかし、少しルールを工夫するとシンプルにできます。 コレクション名が「uid」になっているのに着目して、「request.auth.uid」で取得した「uid」と一致すれば「所有者」、一致しなければそれ以外の人という形でルールを書けば、セキュリティルールはシンプルで、自動で利用者の追加も簡単にできます。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /data/{uid}/inbox/{message_id} {
        allow create;
        allow create, delete, read: if request.quth.uid == uid;
    }
    match /data/{uid}/sent/{message_id} {
        allow create, delete, read: if request.auth.uid == uid;
    }
  }
}
の様な感じで記述が可能です。 場所の書き方を、上手くまとめるとシンプルにできます。

まとめ
Firebasen のセキュリティルールを設定する際に「場所」を特定するのは意外に面倒です。 人や出来る事と違って、範囲の特定が難しいためです。

シンプルな場合は、全ての場所(パス名など)を全て書き出す方法を使えば、記述自体はそれほど難しくありませんが、ルールの記述量が増えて、自動化が難しくなるという欠点があります。セキュリティールールでは、「uid」や「email」、データベースのデータの中身も取得できるので、場所の表現を工夫すると、セキュリティルールをシンプルにできます。

セキュリティルールの作成にはコツがあって、慣れが必要ですが、場所と人を上手く結びつける事ができるとシンプルで間違いの少ないルールの設定が可能になります。
サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す