Cloud Firestoreのセキュリティルールの書き方

## はじめに
firestoreのセキュリティルールの記述方法に関する記事がそんなに多くないので書いてみました
どういう設計にすれば良いとか、どれくらいお金がかかるとか、そもそもfirestoreとはなんぞやみたいなことは書いてませんので、ご了承ください

## 基本

```js
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{user} {
      allow read: if request.auth.uid != null;  //ログインしていれば閲覧を許可
      allow write: if request.auth.uid != null;  //ログインしていれば書込を許可
    }
  }
}
```
コレクション名はキャメルケースにしてください
ケバブケースではセキュリティルールを設定できませんでした

#### ワイルドカードを使用して、配下のドキュメント全てにルールを設定できる

```js
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read: if request.auth.uid != null;
    }
  }
} 
```

#### ワイルドカード使用時の注意
同じドキュメントに対して一つでも許可をする記述があると、他の項目で拒否の設定をしても許可されてしまいます

```js
// NG例
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read;
    }
    match /users/{user} {
      allow read: if request.auth.uid != null;
    }
  }
}
```
上記の場合だと、`{document=**}`の項目で全てのユーザーに対して読込を許可しているため、`{user}`でログインユーザーのみ閲覧可の設定をしても全てのユーザーが閲覧できてしまいます


#### サブコレクションは入れ子にできる

```js
match /databases/{database}/documents {
  match /users/{user} {
    allow read: if request.auth.uid != null;
    match /children/{child} {
      allow read: if request.auth.uid != null;
      allow write: if request.auth.uid != null;
    }
  }
}
```
#### 他のコレクションを参照できる

```js
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth.uid != null;
            write: if request.auth.uid != null;
      match /children/{child} {
        allow read: if request.auth.uid != null;
        allow create: if request.auth.uid != null &&
                      // userドキュメントのemailフィールドと、ログインユーザーのemailが一致すれば許可
                      get(/databases/$(database)/documents/users/$(userId)).data.email == request.auth.token.email && 
                      // usersコレクションにログインユーザーのIDと同じIDのドキュメントが存在すれば許可
                      exists(/databases/$(database)/documents/users/$(request.auth.uid)) == true;
      }              
    }
  }
}
```

`get()`で他コレクションのドキュメントを取得できます
`exists()`はドキュメントが存在すればtrue、存在しなければfalseを返します
`{}`内の値を`$()`で代入することができます
これらを使用して、`ログインしているユーザーのデータしか編集できないようにする`といった設定が可能です

#### 条件を関数化できる


```js
match /databases/{database}/documents {
  function isLogin() {
    return request.auth.uid != null;
  }
  match /users/{user} {
    allow read: if isLogin();
    match /children/{child} {
      allow read: if isLogin();
      allow write: if isLogin();
    }
  }
}
```

####  read/writeはさらに細かく分類できる
- read
 - get
 - list
- write
 - create
 - update
 - delete

## バリデーション

```js
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{user} {
      allow read: if request.auth.uid != null;
      allow create, update: if request.auth.uid != null &&
                            resquest.resource.data.name is string; //データ型を指定
                            resquest.resource.data.name.size() <= 50;  //文字数を指定
                            resquest.resource.data.email.matches('.*@domain[.]com') == true;  //文字列が一致するかをチェック
    }
  }
}
```
`request.resource.data`で送られてくる値を取得することができる

設定可能なデータ型一覧
https://firebase.google.com/docs/reference/rules/rules?hl=ja

#### referenceの場合
firestoreではドキュメントのフィールドに対して、他のドキュメントを参照するリファレンスを設定することができます
![スクリーンショット 2019-08-13 9.52.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324514/c6e5ba1f-613e-f9ec-0ff7-7704268b06f6.png)
この場合のデータ型指定↓

```js
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{user} {
      allow read: if request.auth.uid != null;
      match /children/{child} {
        allow read: if request.auth.uid != null;
        allow create: if request.auth.uid != null &&
                      resquest.resource.data.parent is path; //reference型であれば許可
      }              
    }
  }
}
```
## シミュレータ
firestoreにはセキュリティルールの記述を確認するためのシミュレータが用意されています
ドキュメントのモックを作成し、ルールが設定できているのかを簡単にテストすることができます

シミュレータの使い方はこちら
https://qiita.com/sgr-ksmt/items/a9032557aba9fd2f7843

ただ、このシミュレータは完全ではないようで、他コレクションを参照する`is path` `get()` `exists()`についてはテストすることができません(2019/8/10現在)
それ以外のところでは簡単にルールをテストできるため、とても便利だと思います

## 最後に
読んでくださり、ありがとうございました
アドバイス、ご指摘等あればコメントよろしくお願いします

#### 参考サイト
公式リファレンス
https://firebase.google.com/docs/firestore/security/secure-data?hl=ja#multiple_rules_for_the_same_document

その他
https://medium.com/google-cloud-jp/firestore3-9518331f8748
https://tech-blog.sgr-ksmt.org/2018/12/11/194022/