Firestoreのルールシミュレーターを使ってみる

Firestoreのルールのシミュレータが実用段階っぽい感じになってきたので、実際にどんな感じで使えるのか書き記してみる。

## 追記
2018/06/20、Firebaseのblogでシミュレータに関してアナウンスがありました。
- [Announcing the Firestore Security Rules Simulator!](https://firebase.googleblog.com/2018/06/announcing-firestore-security-rules.html)

# Firestoreのルールシミュレータ
projectのconsoleから、[Database]（→[Cloud Firestore')→[ルール]を選択し、変更履歴が並んでいるペインの一番下までいくと、「シミュレータ」という項目が表示される。

![](https://qiita-image-store.s3.amazonaws.com/0/27802/26fd4644-3131-6e79-3c03-7f615e97753c.png)

これを押すと、変更履歴の一覧から変わり、シミュレートするためのエリアが出現。

![](https://qiita-image-store.s3.amazonaws.com/0/27802/d6e8a6ce-04a7-79fc-0060-1825e2fcff60.png)


ここでは、read(get/list),write(create,update,delete)それぞれの動作をシミュレートすることができる。

![](https://qiita-image-store.s3.amazonaws.com/0/27802/60551c29-be63-e164-d592-89b9adeca479.png)

また、シミュレートした結果、どの行数が成功した/失敗したかが表示されるようになっている。

![](https://qiita-image-store.s3.amazonaws.com/0/27802/6a637928-47fb-8ecd-43b5-8f5c33c12993.png)![](https://qiita-image-store.s3.amazonaws.com/0/27802/9fad28b9-2ddc-121f-5cb3-fa05e213b64c.png)


## 5月末くらいまでは
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">あれ、Firestoreにrulesのシミュレータきてる？ <a href="https://t.co/rMyXihHHkw">pic.twitter.com/rMyXihHHkw</a></p>&mdash; su- (@sgr_ksmt_dev) <a href="https://twitter.com/sgr_ksmt_dev/status/1001382379146043392?ref_src=twsrc%5Etfw">2018年5月29日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

こんな感じで、「読み取り」「書き込み」「削除」といったオペレーションしかなく、

- get/listの区別が付けられない
- create, updateの区別がつけられない

といった問題(?)がありました。いつの間にかパワーアップしていた。

# 実際に試してみる
実際に簡単なサンプルを交えて、get,createそれぞれやってみる。(list,update,deleteはここでは省略します。)

## **get**

ひとまずこんな感じで `/post/`のコレクションに対するルールを書いてみます。

```
service cloud.firestore {
  match /databases/{database}/documents {
    function isAuthenticated() {
      return request.auth != null;
    }

    match /post/{postID} {
      allow get: if isAuthenticated();
    }
  }
}
```

この場合、認証済みのユーザーであればgetが可能となっている。
シミュレータ側で、次のようにして、実行してみる。

![スクリーンショット 2018-06-19 19.03.43.png](https://qiita-image-store.s3.amazonaws.com/0/27802/4986eece-de1b-f28f-577e-41db0ffadd57.png)

そうすると、認証済みでない状態でシミュレートしたので許可されなかったよとエラーが表示される

![スクリーンショット 2018-06-19 19.03.50.png](https://qiita-image-store.s3.amazonaws.com/0/27802/b406b67a-c2c2-c263-aa8c-3acd8fedf369.png)

次に「認証済み」のスイッチをONにして、再度試してみると、今度は成功する。
![](https://qiita-image-store.s3.amazonaws.com/0/27802/c12c35d6-29e9-e7b4-ba3b-413882a6bcc5.png)
![](https://qiita-image-store.s3.amazonaws.com/0/27802/184fe710-1188-8ff7-78ca-5d44a3a4eaf1.png)

:tada: 

### 認証状態もカスタマイズできる
認証済みにした場合は、どのプロバイダで認証しているのか、FirebaseのUIDや名前といったものも変更が可能。

![](https://qiita-image-store.s3.amazonaws.com/0/27802/4b361d29-f821-83ae-e546-e068d020f0a3.png)![](https://qiita-image-store.s3.amazonaws.com/0/27802/cea8dea2-bf2d-0538-e8b8-98188bdba9c3.png)

ちなみに、これら入力したあとに、認証情報（のペイロード）がどうなっているかは確認可能。

![](https://qiita-image-store.s3.amazonaws.com/0/27802/24d24134-7303-7378-5c16-6681598aa175.png)

## get(実際のDBのデータを用いてやってみる)
先程は`foobar` と適当なIDを使ってやってみましたが、これだと実際に読み出す時にデータの詳細な検証ができないので、
実際にDBに存在するドキュメントを用いてテストしてみる。
まず、こんな感じで`/post/`以下にドキュメントを作る(IDは自動生成されたもので問題ないです、スクショの例と合わせる必要はなし)

![](https://qiita-image-store.s3.amazonaws.com/0/27802/e13915f2-4280-23d9-d044-e24adaf859e7.png)
![](https://qiita-image-store.s3.amazonaws.com/0/27802/b4cda011-574b-1b3a-7dcb-421d16abfcb1.png)

その後、ルールを少し編集して`visibility=='public'`が成立するpostならgetできるようにする。

```
service cloud.firestore {
  match /databases/{database}/documents {
    function isAuthenticated() {
      return request.auth != null;
    }

    match /post/{postID} {
      allow get: if isAuthenticated() && resource.data.visibility == 'public';
    }
  }
}
```


これで実際に試してみると
`AF8yUrAqKjHiRzOYli96`でシミュレートした場合は成功し、
`2uYM4teiXw2IN2zCjg0e`でシミュレートした場合は失敗する。

### 要望的なこと
シミュレートする場合に、実際のドキュメントを指定することになるのだが、
createのルールのシミュレートと同様に架空のドキュメントを準備できて、それを読み込むシミュレートができるともう少し捗りそうだなと :thinking: 

## **create**
今度はcreateを試してみる。
`/post/`のコレクション以下に、以下の制約をもたせるようにルールを組む

- createするときは`title`, `body`, `publishedAt` を持つ
- titleはstring型で1字以上、50字以下
- bodyはstring型で1字以上、2000字以下
- publishedAtはtimestamp型である

```
service cloud.firestore {
  match /databases/{database}/documents {
    function isAuthenticated() {
      return request.auth != null;
    }

    function validateString(text, min, max) {
      return text is string && min <= text.size() && text.size() <= max;
    }

    match /post/{postID} {
      allow get: if isAuthenticated() && resource.data.visibility == 'public';
      allow create: if isAuthenticated()
                    && request.resource.data.keys().hasAll(['title', 'body', 'publishedAt'])
                    && validateString(request.resource.data.title, 1, 50)
                    && validateString(request.resource.data.body, 1, 2000)
                    && request.resource.data.publishedAt is timestamp;
    }
  }
}
```

このように定義したら、シミュレータで、`create`に切り替えて、「ドキュメントを作成」を押して、次のように書き込みたいドキュメントを作成する。

![](https://qiita-image-store.s3.amazonaws.com/0/27802/f4ecc65b-8199-e456-9d7c-a166e9da3d81.png)![](https://qiita-image-store.s3.amazonaws.com/0/27802/572455e4-08ad-1094-8625-c57d2dce4801.png)

ドキュメント作成ができたら、「実行」を押すと、title、body,publishedAtが存在していて、validationも通るデータであればシミュレートの結果は成功となる。
もしルールに沿わないものがあれば、シミュレートの結果は失敗となる（titileがない、規定の文字数でない、等)

## まだできないこと
まだできないこととしては

- listの場合のquery指定までチェックできない(`request.query.limit < 10` みたいなのが確認できない)
- 異なるコレクションのドキュメントを同時に保存した場合のシミュレートができない（バッジ処理、トランザクション処理）
    - なので、`getAfter()`関数を使ったルールのシミュレートができない

が挙げられる。

## まとめ
- シミュレータが登場していた
- 実際のドキュメントパスを指定した上でシミュレートすることもできる
- まだ一部できないこともある
- 認証情報も設定できる
- 機能としてもまだBeta段階なので今後も機能追加されたり改善されたり変更される可能性がある


### 感想
いつ来るか、、と待ち望んでいた機能が実用段階になりつつあって嬉しかったのでさくっと調べてみた。
だが、まだまだできないこともあるし、シミュレートを常にここでやり続けるわけにもいかないので、自分たちでオンラインテストなり組んで、セキュリティルールのテストを組めると良いなと思って模索している。