データの安全なクエリ
このページでは、セキュリティ ルールの構造化とセキュリティ ルールの条件の記述のコンセプトを基に、Firestore のセキュリティ ルールとクエリがどのように関係しているかを説明します。作成可能なクエリにセキュリティ ルールがどのように影響するかを詳しく見ていき、セキュリティ ルールと同じ制約をクエリで使用する方法を説明します。また、limit
や orderBy
などのクエリ プロパティに基づいてクエリを許可または拒否するセキュリティ ルールを記述する方法についても説明します。
ルールはフィルタではない
ドキュメントを取得するクエリを記述するとき、セキュリティ ルールはフィルタではないことにご注意ください。セキュリティ ルールは、クエリが完全に動作するか、まったく動作しないかを決定します。時間とリソースを節約するため、Firestore はすべてのドキュメントの実際のフィールドの値ではなく、結果セットの可能性に対してクエリを評価します。クライアントが読み取り権限を持たないドキュメントをクエリが返す可能性がある場合、リクエスト全体が失敗します。
クエリとセキュリティ ルール
以下の例で示すように、セキュリティ ルールの制約に合わせてクエリを記述する必要があります。
auth.uid
に基づくドキュメントの保護とクエリ
次の例は、セキュリティ ルールで保護されたドキュメントを取得するクエリを記述する方法を示しています。story
ドキュメントのコレクションを含むデータベースを考えてみましょう。
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
各ドキュメントには title
フィールドと content
フィールドに加えて、アクセス制御に使用される author
フィールドと published
フィールドが格納されています。ここで示す例では、アプリが Firebase Authentication を使用して、ドキュメントを作成したユーザーの UID を author
フィールドに設定していると想定しています。また、Firebase Authentication はセキュリティ ルールの request.auth
変数に値を設定します。
次のセキュリティ ルールでは、request.auth
変数と resource.data
変数を使用して、各 story
に対する読み取りと書き込みのアクセス権を作成者のみに制限しています。
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Only the authenticated user who authored the document can read or write
allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
あるアプリに、ユーザーが作成した story
ドキュメントのリストを表示するページを含めるとします。次のクエリを使用してこのページのデータを取得できそうに思えますが、セキュリティ ルールと同じ制約が含まれていないため、このクエリは失敗します。
無効: クエリの制約がセキュリティ ルールの制約と一致していない
// This query will fail
db.collection("stories").get()
現在のユーザーがすべての story
ドキュメントの作成者であったとしても、このクエリは失敗します。この動作の理由は、Firestore がセキュリティ ルールを適用するときには、データベース内のドキュメントの実際のプロパティに対してではなく、結果セットの「可能性」に対してクエリを評価するためです。セキュリティ ルールに違反するドキュメントがクエリに含まれる可能性がある場合、クエリは失敗します。
これとは対照的に、次のクエリは成功します。これは、author
フィールドに関してセキュリティ ルールと同じ制約が含まれているためです。
有効: クエリの制約がセキュリティ ルールの制約と一致している
var user = firebase.auth().currentUser;
db.collection("stories").where("author", "==", user.uid).get()
フィールドに基づいてドキュメントを保護し、クエリする
クエリとルールの関係の例をさらに示します。次のセキュリティ ルールでは、stories
コレクションの読み取りアクセス対象を広げ、published
フィールドが true
に設定されている story
ドキュメントをすべてのユーザーが読み取ることができるようにします。
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Anyone can read a published story; only story authors can read unpublished stories
allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
// Only story authors can write
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
公開ページのクエリには、セキュリティ ルールと同じ制約を含める必要があります。
db.collection("stories").where("published", "==", true).get()
クエリの制約 .where("published", "==", true)
によって、すべての結果で resource.data.published
が true
であることが保証されます。したがって、このクエリはセキュリティ ルールに合致し、データを読み取ることができます。
OR
件のクエリ
ルールセットに対して論理 OR
クエリ(or
、in
、または array-contains-any
)を評価する場合、Firestore によって各比較値が個別に評価されます。各比較値はセキュリティ ルールの制約を満たしている必要があります。たとえば、次のルールの場合:
match /mydocuments/{doc} {
allow read: if resource.data.x > 5;
}
無効: 可能性のあるすべてのドキュメントについて x > 5
になることがクエリによって保証されない
// These queries will fail
query(db.collection("mydocuments"),
or(where("x", "==", 1),
where("x", "==", 6)
)
)
query(db.collection("mydocuments"),
where("x", "in", [1, 3, 6, 42, 99])
)
有効: 可能性のあるすべてのドキュメントについて x > 5
になることがクエリによって保証される
query(db.collection("mydocuments"),
or(where("x", "==", 6),
where("x", "==", 42)
)
)
query(db.collection("mydocuments"),
where("x", "in", [6, 42, 99, 105, 200])
)
クエリの制約の評価
セキュリティ ルールは、クエリの制約に基づいてそのクエリを許可または拒否することもできます。request.query
変数には、クエリの limit
、offset
、orderBy
の各プロパティが含まれています。たとえば、取得されるドキュメントの最大数を特定の範囲に制限していないクエリをセキュリティ ルールで拒否できます。
allow list: if request.query.limit <= 10;
次のルールセットは、クエリ内の制約を評価するセキュリティ ルールを記述する方法を示しています。この例では、前出の stories
ルールセットを拡張し、次の変更を加えています。
- このルールセットでは read ルールを
get
ルールとlist
ルールに分割しています。 get
ルールでは、単一のドキュメントの取得を、公開ドキュメントか、そのユーザー自身が作成したドキュメントに制限しています。list
ルールでは、クエリ以外についてはget
ルールと同じ制限が適用されます。また、このルールではクエリの制限がチェックされ、無制限のクエリまたは制限が 10 を超えるクエリは拒否されます。- このルールセットでは、コードの重複を避けるために、
authorOrPublished()
関数を定義しています。
service cloud.firestore {
match /databases/{database}/documents {
match /stories/{storyid} {
// Returns `true` if the requested story is 'published'
// or the user authored the story
function authorOrPublished() {
return resource.data.published == true || request.auth.uid == resource.data.author;
}
// Deny any query not limited to 10 or fewer documents
// Anyone can query published stories
// Authors can query their unpublished stories
allow list: if request.query.limit <= 10 &&
authorOrPublished();
// Anyone can retrieve a published story
// Only a story's author can retrieve an unpublished story
allow get: if authorOrPublished();
// Only a story's author can write to a story
allow write: if request.auth.uid == resource.data.author;
}
}
}
コレクション グループのクエリとセキュリティ ルール
デフォルトでは、クエリは単一コレクションにスコープされ、コレクションからのみ結果を取得します。コレクション グループ クエリを使用すると、同一 ID を持つすべてのコレクションで構成されるコレクション グループから結果を取得できます。ここでは、セキュリティ ルールを使用してコレクション グループ クエリを保護する方法について説明します。
コレクション グループに基づくドキュメントの保護とクエリ
セキュリティ ルールでは、コレクション グループのルールを記述して、コレクション グループのクエリを明示的に許可する必要があります。
rules_version = '2';
がルールセットの最初の行であることを確認します。コレクション グループ クエリには、セキュリティ ルール バージョン 2 の新しい再帰ワイルドカード{name=**}
の動作が必要です。match /{path=**}/[COLLECTION_ID]/{doc}
を使用して、コレクション グループのルールを記述します。
たとえば、posts
サブコレクションを含む forum
ドキュメントに編成されたフォーラムを考えてみましょう。
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
}
このアプリケーションでは、投稿はオーナーが編集でき、認証済みのユーザーが読み取りできます。
service cloud.firestore {
match /databases/{database}/documents {
match /forums/{forumid}/posts/{post} {
// Only authenticated users can read
allow read: if request.auth != null;
// Only the post author can write
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
認証済みのユーザーは、任意の単一のフォーラムの投稿を取得できます。
db.collection("forums/technology/posts").get()
では、現在のユーザーが、すべてのフォーラムにわたる自分の投稿を見ることができるようにする場合はどうでしょうか。コレクション グループ クエリを使用して、すべての posts
コレクションから結果を取得できます。
var user = firebase.auth().currentUser;
db.collectionGroup("posts").where("author", "==", user.uid).get()
セキュリティ ルールでは、posts
コレクション グループの読み取りまたは一覧のルールを記述して、このクエリを許可する必要があります。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Authenticated users can query the posts collection group
// Applies to collection queries, collection group queries, and
// single document retrievals
match /{path=**}/posts/{post} {
allow read: if request.auth != null;
}
match /forums/{forumid}/posts/{postid} {
// Only a post's author can write to a post
allow write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
ただし、これらのルールは、階層に関係なく、ID posts
を持つすべてのコレクションに適用されます。たとえば、これらのルールは、次の posts
コレクションすべてに適用されます。
/posts/{postid}
/forums/{forumid}/posts/{postid}
/forums/{forumid}/subforum/{subforumid}/posts/{postid}
フィールドに基づく安全なコレクション グループ クエリ
単一コレクションのクエリと同様に、コレクション グループ クエリも、セキュリティ ルールで設定されている制約を満たす必要があります。たとえば、上記の stories
の例と同様、各フォーラム投稿に published
フィールドを追加できます。
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
published: false
}
これにより、published
ステータスと投稿 author
に基づく、posts
コレクション グループのルールを記述できます。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns `true` if the requested post is 'published'
// or the user authored the post
function authorOrPublished() {
return resource.data.published == true || request.auth.uid == resource.data.author;
}
match /{path=**}/posts/{post} {
// Anyone can query published posts
// Authors can query their unpublished posts
allow list: if authorOrPublished();
// Anyone can retrieve a published post
// Authors can retrieve an unpublished post
allow get: if authorOrPublished();
}
match /forums/{forumid}/posts/{postid} {
// Only a post's author can write to a post
allow write: if request.auth.uid == resource.data.author;
}
}
}
こうしたルールにより、ウェブ、Apple、Android のクライアントは次のクエリを実行できます。
フォーラムで公開された投稿を誰でも取得できる。
db.collection("forums/technology/posts").where('published', '==', true).get()
すべてのフォーラムで、誰でも投稿者が公開した投稿を取得できる。
db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
投稿者は、すべてのフォーラムで、公開済みおよび未公開の投稿をすべて取得できる。
var user = firebase.auth().currentUser; db.collectionGroup("posts").where("author", "==", user.uid).get()
コレクション グループとドキュメント パスに基づいてドキュメントを保護し、クエリを実行する
場合によっては、ドキュメント パスに基づいてコレクション グループ クエリを制限することもできます。こうした制限を作成するには、フィールドに基づいてドキュメントの保護とクエリを行う場合と同じ手法を使用します。
いくつかの証券取引所と暗号通貨取引所の間で、各ユーザーの取引を追跡するアプリケーションを考えてみましょう。
/users/{userid}/exchange/{exchangeid}/transactions/{transaction}
{
amount: 100,
exchange: 'some_exchange_name',
timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
user: "some_auth_id",
}
user
フィールドに注目してください。transaction
ドキュメントをどのユーザーが所有しているかはドキュメントのパスから確認できますが、次の 2 つのことを行うために、各 transaction
ドキュメントにこの情報を複製します。
ドキュメント パスに特定の
/users/{userid}
が含まれるドキュメントのみを対象とするコレクション グループ クエリを記述します。例:var user = firebase.auth().currentUser; // Return current user's last five transactions across all exchanges db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
transactions
コレクション グループのすべてのクエリに対してこの制限を適用し、別のユーザーのtransaction
ドキュメントを取得できないようにします。
この制限は、Google のセキュリティ ルールに適用され、user
フィールドのデータ検証を含みます。
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{path=**}/transactions/{transaction} {
// Authenticated users can retrieve only their own transactions
allow read: if resource.data.user == request.auth.uid;
}
match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
// Authenticated users can write to their own transactions subcollections
// Writes must populate the user field with the correct auth id
allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
}
}
}
次のステップ
- ロールベースのアクセス制御の詳しい例については、ユーザーとグループのデータアクセスを保護するをご覧ください。
- セキュリティ ルールのリファレンスをご覧ください。