Firebaseのルールを設定する - UnityでFirebaseを使ったオンラインランキングシステムを作るvol4

この記事はシリーズ物です。
シリーズの記事は以下を参照ください。

www.project-unknown.jp www.project-unknown.jp www.project-unknown.jp

はじめに

この記事はFirebase RealtimeDatabaseのルールについて記載します。
ルールについては、Unity以外でも(iOS/Android Native)利用できる所ですので、可能な限りUnity以外でも適用できるように記載します。

本稿は、Firebaseで実際にTwitter認証を用いてデータを登録し、参照出来るようになったのは良いのですが、今のままでは誰でもデータを書き込みできるようになってしまっています。
性善説がまかり通る世の中であれば、特に気にしなくても良いのですが、そうは問屋が卸さないので、適切な権限を付与する必要があります。

Firebase RealtimeDatabaseは、データに対する書き込み権限や、どんなデータを登録できるのか?を「ルール」と言う概念で設定することが出来ます。

ここで紹介するお話は、初見だとかなり「うっ!?」となるかもしれませんが、実際にやってみると直感的で慣れてくると非常に管理しやすいですので、諦めずに頑張りましょう!

今回は、Firebaseのルールを用いてアクセス制限を注目して記載しています。
ルールの機能として、他には値を登録する際のValidate的な使い方もできますが、今回は取り上げません。

ルールを開く

Firebaseのコンソールを開き、プロジェクトを選択して、「Database>ルール」を選択します。

恐らく、デフォルトは以下のようになっているかと思います。

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

上記は、「Firebase Authenticationで認証したユーザなら、誰でもデータの読み書きが出来る」の設定がされている状態です。

ルールの説明に行く前に

ルールとデータは表裏一体と考えても良いです。
ですので、ルールを設定する際は、常にデータ構造を意識しましょう。
ここで説明するルールは、前回Firebaseのデータを登録する際に設定した以下のデータ構造を前提に説明していきます。

root/
  UnitySample/
    Ranking/
      uid/
        name
        id
        score
        updatedate

ルールを設定する

細かく説明してもわかりにくいと思うので、今回やりたいルールを一気に設定しちゃいます。
今回は、以下のルールを設定したいと思います。

  • ランキングなので、他の人のScoreは見れるようにする
  • ユーザのScoreは、ユーザ本人だけが更新できるものなので、ユーザのデータへの書き込み権限は本人しか持たない

これのルールを設定すると、以下の通りになります。(見ての通り、ルールはjsonデータとして作成します)

{
  "rules": {
    "UnitySample": {
      ".read": "auth !== null",
      "Ranking": {
        "$user_id": {
          ".write": "auth.uid === $user_id"
        } 
      }
    }
  }
}

では、上記に出てきたものを細かく説明していきます。

rules

これは、これからルールを設定していくと言う宣言となります。
このrulesに囲われた範囲が、ルールについての定義を行っていることを指し示します。

UnitySample

データ構造にある、「UnitySample」に対する、ルールをこれから設定していく事を指し示します。
このようにルールでは、データ構造と密接した宣言になります。
上記より、UnitySample/Rankingに対するルール設定の場合は以下のように記載します。

"UnitySample": {
  "Ranking": {
    
  }
}

".read": "auth !== null"

.readは、読み取り設定のプロパティを指します。この場合は、UnitySampleの読み取り設定は、右辺の「auth !== null」が設定されます。

auth(auth変数)には、データにアクセスした際のFirebaseの認証情報が入っています。
authには、以下のプロパティがあります。

  • auth.provider
    • 認証情報のプロバイダを取得できます。 ("twitter", "fasebook"など)
  • auth.uid
    • ユーザを一意に示すIDを取得できます。主に、以下のキャプチャの部分を指します。

f:id:project-unknown:20170830001015p:plain

今回の場合、「auth !== null」ですので、auth情報を持っているユーザ。つまり何かしらの形でFirebaseの認証が終わっているユーザについては読み取り権限が付与されます。

親要素と、子要素で違う権限を付与したらどうなるの?

例えば、以下の場合はどうなるのでしょうか?

"UnitySample": {
  ".read": "auth !== null"
  "Ranking": {
    ".read": "auth === null"
  }
}

上記設定は、UnitySample以下は、auth認証を必要とするとしていますが、Ranking以下は不要と言う設定になっています。
このようなルールがコンフリクトする場合は、浅いレイヤーのルールが優先されます
なので、上記のような場合は、Ranking以下でも「auth !== null」が適用されます。

余談 「==」と「===」について

言語によっては、「===」や「!==」に馴染みのない方もいらっしゃるかもしれないので、補足すると、

  • 「==」は良く見る「等価演算子」
  • 「===」は「厳密等価演算子」

と呼ばれます。
等価演算子は、例えば

int val = 0;

if (val == false) {
    echo "hoge";
} else {
    echo "fuga";
}

上記の例の場合、出力結果は「hoge」になります。
このように等価演算子は型が異なっても評価を行うことが出来るのに対し、厳密等価演算子を用いた場合

int val = 0;

if (val === false) {
    echo "hoge";
} else {
     echo "fuga";
}

上記のケースでは、出力結果は「fuga」になります。
これは、「val」と「false」が型が違うので、評価対象にならずelseに流れるからです。
なので、通常の等価演算子よりも型を気にしてより厳密に評価を行うので厳密等価演算子と呼ばれています。

"$user_id":

この$user_idは、$location変数と呼ばれる変数です。別に「$user_id」じゃなくて「$hoge」でも良い、好きな名前を付けれます。
$location変数の意味ですが、その階層にあるNodeのKeyが入ります。
今回のデータ構造で言うと、「ユーザID」がここに入ります。
「ユーザID」はユーザ毎に異なるIDですので、$location変数を使って、その後の評価に使えるようにしています。

".write": "auth.uid === $user_id"

.writeは、.readを見てきたので薄々感づかれると思いますが、書き込み権限を表し、右辺の条件が設定されます。

右辺の、"auth.uid"は、上述説明の通り、ユーザを一意に表すIDが入っているんでしたね。
また、$user_idはユーザID、auth.uidと同等の値が入ります。

なので、この条件では、"auth.uid"と$user_idが一致する要素の場合、.write権限が付与されるようになります。

ルールをシミュレートする

さて、ルールの設定が出来ました。
次はうまくルールが効いているのかをシミュレートします。

Firebaseには、ルールが正常に設定されているかを検証するシミュレータが備わっています。 (以下キャプチャ参照)

f:id:project-unknown:20170830002155p:plain

ここで、実際に読み書きが出来るのかを確認できます。

読み取りをシミュレート

確認するのは、自分含めて、他のユーザのデータを読み込みが出来るか?です。
まずシミュレーションタイプを「読み取り」にします。

f:id:project-unknown:20170929010510p:plain

次に、ルート「/」且つ認証していない状態でアクセス出来るか確認します。
ここでの期待値は、ルートへのアクセスのルールは設定していないのでエラーになる事を期待します。

ロケーションを「/」に、認証済みのチェックを外して実行を押します。

f:id:project-unknown:20170929011519p:plain

画面の上部に「シミュレートされた read が拒否されました」と表示されれば成功です。

f:id:project-unknown:20170929010806p:plain

次に、認証済みにチェックを入れます。
プロバイダを選択する項目があるので、これまでのシリーズでTwitter認証でやってきましたので、Twitterを選択。

UIDの部分はご自身が登録した、Authenticationをコピペしてください。

f:id:project-unknown:20170830002717p:plain

これで実行しましょう。

f:id:project-unknown:20170929010806p:plain

エラーが起きましたね。これで問題ありません。
先程も記載しましたが、読み取りの設定が入っているのは、認証ユーザ且つ、UnitySample以下に対して読み取り出来るようにしているからです。

では、次に読み取り出来るはずの、UnitySampleのシミュレートをしてみましょう。

ロケーションに「/UnitySample」と入力し、実行ボタンを押します。

f:id:project-unknown:20170929011758p:plain

無事シミュレート成功しました。
念のため、自分のデータが見れるかまで見ておきましょうか、
ロケーションを「/UnitySample/Ranking/{$UID}/score」として、実行ボタンを押します。 ({$UID}の部分は登録したUIDを入力してください。)

f:id:project-unknown:20170929011625p:plain

こちらも問題なく読み取りが出来ることが確認できました。

読み取りは想定通り設定出来ているので、書き込みを見てみます。

書き込みをシミュレート

確認するのは、指定されたユーザのデータへ書き込みが出来るか?です。

まず、シミュレーションタイプを「書き込み」にします。

f:id:project-unknown:20170830002437p:plain

次に、ロケーションを、「UnitySample/Ranking/ユーザID」にします。
(ユーザIDはAuthenticationのUIDを記載してください)

データは、デフォルトである、以下で十分だと思います。

{
  "key": "value"
}

プロバイダは、認証済みにチェックを入れ、読み取り同様Twitterとします。

UIDも、先程と同じく、AuthenticationのUIDを記載します。

f:id:project-unknown:20170830002717p:plain

ここまで設定したら実行ボタンをクリックしてください。

「シミュレートされたwriteが許可されました」と表示されれば成功です。

f:id:project-unknown:20170830002824p:plain

また、ちゃんと他のUIDだと弾かれるか確認します。

プロバイダのUIDを適当な文字列に変更します。

f:id:project-unknown:20170830002951p:plain

この状態で、実行を押すとエラーが発生しています。

f:id:project-unknown:20170830003032p:plain

これで、他のユーザへの書き込みは出来ないことまで確認できました。

さいごに

今回の記事は、ただ読むだけだとイメージが湧きにくいかもしれませんが、
実際にご自身で触りながら読んでいただくと、そこまで難しく無いかと思います(冒頭でも記載しましたが、ルールは直感的に設定できるので)
逆に言うと、これだけの設定でそこそこ強固なアクセス制限等を掛けられるので、是非ものにしましょう!