Na tej stronie, na podstawie informacji z artykułów Tworzenie reguł zabezpieczeń i Tworzenie warunków reguł zabezpieczeń, wyjaśniamy, jak Cloud Firestore Security Rules współdziała z zapytaniami. W tym artykule omawiamy szczegółowo, jak reguły zabezpieczeń wpływają na zapytania, które możesz tworzyć, oraz wyjaśniamy, jak zapewnić, aby zapytania stosowały te same ograniczenia co reguły zabezpieczeń. Na tej stronie znajdziesz też opis sposobu tworzenia reguł zabezpieczeń, które zezwalają na zapytania lub je blokują na podstawie właściwości zapytań, takich jak limit
i orderBy
.
Reguły nie są filtrami
Podczas tworzenia zapytań służących do pobierania dokumentów pamiętaj, że reguły bezpieczeństwa to nie filtry – zapytania działają na zasadzie wszystko albo nic. Aby zaoszczędzić czas i zasoby, Cloud Firestore ocenia zapytanie na podstawie potencjalnego zbioru wyników zamiast rzeczywistych wartości pól wszystkich dokumentów. Jeśli zapytanie może zwrócić dokumenty, do których klient nie ma uprawnień do odczytu, cała prośba kończy się niepowodzeniem.
Zapytania i reguły zabezpieczeń
Jak widać na przykładach poniżej, musisz tworzyć zapytania, które będą pasować do ograniczeń określonych w regułach zabezpieczeń.
Zabezpieczanie dokumentów i wysyłanie zapytań do nich na podstawie auth.uid
Ten przykład pokazuje, jak napisać zapytanie, aby pobrać dokumenty chronione przez regułę zabezpieczeń. Weź pod uwagę bazę danych zawierającą kolekcję dokumentów story
:
/stories/{storyid}
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
Oprócz pól title
i content
każdy dokument zawiera pola author
i published
, które służą do kontroli dostępu. Te przykłady zakładają, że aplikacja używa Uwierzytelniania Firebase do ustawiania pola author
na identyfikator UID użytkownika, który utworzył dokument. Firebase Authentication wypełnia też zmienną request.auth
w regułach zabezpieczeń.
Poniższa reguła zabezpieczeń używa zmiennych request.auth
i resource.data
, aby ograniczyć dostęp do odczytu i zapisu dla każdego elementu story
dla jego autora:
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;
}
}
}
Załóżmy, że Twoja aplikacja zawiera stronę, na której użytkownik widzi listę story
dokumentów, których jest autorem. Możesz oczekiwać, że do wypełnienia tej strony możesz użyć tego zapytania. To zapytanie się nie powiedzie, ponieważ nie zawiera tych samych ograniczeń co reguły zabezpieczeń:
Nieprawidłowe: ograniczenia zapytań nie pasują do ograniczeń reguł bezpieczeństwa
// This query will fail
db.collection("stories").get()
Zapytanie kończy się niepowodzeniem nawet wtedy, gdy bieżący użytkownik jest autorem wszystkich dokumentów story
. Dzieje się tak, ponieważ Cloud Firestore stosuje reguły bezpieczeństwa, oceniając zapytanie na podstawie potencjalnego zbioru wyników, a nie rzeczywistych właściwości dokumentów w bazie danych. Jeśli zapytanie może zawierać dokumenty, które naruszają Twoje reguły zabezpieczeń, nie zostanie ono zrealizowane.
Natomiast to zapytanie zostanie wykonane, ponieważ zawiera ono takie samo ograniczenie pola author
jak reguły zabezpieczeń:
Prawidłowy: ograniczenia zapytania pasują do ograniczeń reguł zabezpieczeń
var user = firebase.auth().currentUser;
db.collection("stories").where("author", "==", user.uid).get()
Bezpieczeństwo i zapytania dotyczące dokumentów na podstawie pola
Aby lepiej zobrazować interakcję zapytań i reguł, reguły zabezpieczeń poniżej rozszerzają uprawnienia odczytu dla kolekcji stories
, aby każdy użytkownik mógł odczytywać dokumenty story
, w których polu published
ustawiono wartość true
.
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;
}
}
}
Zapytanie o opublikowane strony musi zawierać te same ograniczenia co reguły zabezpieczeń:
db.collection("stories").where("published", "==", true).get()
Ograniczenie zapytania .where("published", "==", true)
gwarantuje, że resource.data.published
będzie równe true
w przypadku każdego wyniku. Dlatego to zapytanie spełnia reguły zabezpieczeń i ma uprawnienia do odczytu danych.
Zapytania: OR
Podczas sprawdzania zapytania logicznego OR
(or
, in
lub array-contains-any
) pod kątem zgodności z regułami Cloud Firestore sprawdza każdą wartość porównawczą osobno. Każda wartość porównawcza musi spełniać ograniczenia reguły bezpieczeństwa. Na przykład w przypadku tej reguły:
match /mydocuments/{doc} {
allow read: if resource.data.x > 5;
}
Nieprawidłowe: zapytanie nie gwarantuje, że x > 5
dla wszystkich możliwych dokumentów
// 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])
)
Właściwe: zapytanie gwarantuje, że
x > 5
dla wszystkich potencjalnych dokumentów
query(db.collection("mydocuments"),
or(where("x", "==", 6),
where("x", "==", 42)
)
)
query(db.collection("mydocuments"),
where("x", "in", [6, 42, 99, 105, 200])
)
Ocenianie ograniczeń zapytań
Twoje reguły dotyczące zabezpieczeń mogą też akceptować lub odrzucać zapytania na podstawie ich ograniczeń.
Zmienna request.query
zawiera właściwości zapytania limit
, offset
i orderBy
. Na przykład reguły zabezpieczeń mogą odrzucać żądania, które nie ograniczają maksymalnej liczby dokumentów do określonego zakresu:
allow list: if request.query.limit <= 10;
Poniższy zestaw reguł pokazuje, jak pisać reguły zabezpieczeń, które oceniają ograniczenia nałożone na zapytania. W tym przykładzie rozszerzamy poprzedni zbiór reguł stories
o te zmiany:
- Zestaw reguł dzieli regułę dotyczącą odczytu na reguły dotyczące
get
ilist
. - Reguła
get
ogranicza pobieranie pojedynczych dokumentów do dokumentów publicznych lub dokumentów utworzonych przez użytkownika. - Reguła
list
stosuje te same ograniczenia co regułaget
, ale w przypadku zapytań. Sprawdza też limit zapytań, a następnie odrzuca każde zapytanie bez limitu lub z limitem większym niż 10. - Zestaw reguł definiuje funkcję
authorOrPublished()
, aby uniknąć duplikacji kodu.
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;
}
}
}
Zapytania dotyczące grupy zbiorów i reguły zabezpieczeń
Domyślnie zapytania są ograniczone do jednej kolekcji i wyświetlają wyniki tylko z tej kolekcji. Za pomocą zapytań dotyczących grupy kolekcji możesz pobierać wyniki z grupy kolekcji zawierającej wszystkie kolekcje o tym samym identyfikatorze. W tej sekcji opisujemy, jak zabezpieczyć zapytania dotyczące grupy zbiorów za pomocą reguł bezpieczeństwa.
Bezpieczne przechowywanie dokumentów i wysyłanie zapytań do nich na podstawie grup kolekcji
W regułach zabezpieczeń musisz wyraźnie zezwolić na zapytania dotyczące grupy zbiorów danych, tworząc regułę dla tej grupy:
- Upewnij się, że
rules_version = '2';
jest pierwszym wierszem reguł. Zapytania dotyczące grup kolekcji wymagają nowego rekursywnego działania za pomocą symbolu wieloznacznego{name=**}
w ramach reguł bezpieczeństwa w wersji 2. - Utwórz regułę dla grupy kolekcji za pomocą
match /{path=**}/[COLLECTION_ID]/{doc}
.
Weźmy na przykład forum podzielone na forum
dokumenty zawierające posts
podkolekcji:
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
}
W tej aplikacji wpisy mogą być edytowane przez ich właścicieli i czytane przez uwierzytelnionych użytkowników:
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;
}
}
}
Każdy uwierzytelniony użytkownik może pobrać posty z dowolnego forum:
db.collection("forums/technology/posts").get()
Co jednak, jeśli chcesz wyświetlić bieżącemu użytkownikowi jego posty na wszystkich forach?
Za pomocą zapytania dotyczącego grupy kolekcji możesz pobrać wyniki ze wszystkich kolekcji posts
:
var user = firebase.auth().currentUser;
db.collectionGroup("posts").where("author", "==", user.uid).get()
W swoich regułach zabezpieczeń musisz zezwolić na to zapytanie, tworząc regułę odczytu lub regułę listy dla grupy kolekcji 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;
}
}
}
Pamiętaj jednak, że te reguły będą stosowane do wszystkich kolekcji o identyfikatorze posts
, niezależnie od hierarchii. Te reguły mają zastosowanie do wszystkich tych kolekcji:posts
/posts/{postid}
/forums/{forumid}/posts/{postid}
/forums/{forumid}/subforum/{subforumid}/posts/{postid}
Bezpieczne zapytania dotyczące zbioru grup na podstawie pola
Podobnie jak zapytania dotyczące pojedynczej kolekcji, zapytania dotyczące grupy kolekcji muszą też spełniać ograniczenia określone przez Twoje reguły zabezpieczeń. Możemy na przykład dodać pole published
do każdego wpisu na forum, tak jak w przypadku pola stories
w przykładzie powyżej:
/forums/{forumid}/posts/{postid}
{
author: "some_auth_id",
authorname: "some_username",
content: "I just read a great story.",
published: false
}
Następnie możemy napisać reguły dla grupy kolekcji posts
na podstawie stanu published
i postu author
:
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;
}
}
}
Dzięki tym regułom klienci internetowi, klienci Apple i klienci Androida mogą wykonywać te zapytania:
Każdy może pobrać opublikowane posty na forum:
db.collection("forums/technology/posts").where('published', '==', true).get()
Każdy może pobrać opublikowane przez autora posty na wszystkich forach:
db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
Autorzy mogą pobierać wszystkie opublikowane i nieopublikowane posty ze wszystkich forów:
var user = firebase.auth().currentUser; db.collectionGroup("posts").where("author", "==", user.uid).get()
Bezpieczne przechowywanie dokumentów i wysyłanie zapytań do nich na podstawie grupy kolekcji i ścieżki dokumentu
W niektórych przypadkach możesz ograniczyć zapytania do grupy kolekcji na podstawie ścieżki dokumentu. Aby utworzyć te ograniczenia, możesz użyć tych samych technik zabezpieczania i wyszukiwania dokumentów na podstawie pola.
Weź pod uwagę aplikację, która śledzi transakcje każdego użytkownika na kilku giełdach papierów wartościowych i giełdach kryptowalut:
/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",
}
Zwróć uwagę na pole user
. Chociaż wiemy, który użytkownik jest właścicielem dokumentu transaction
na podstawie jego ścieżki, powielamy te informacje w każdym dokumencie transaction
, ponieważ pozwala nam to na:
Pisać zapytania dotyczące grupy kolekcji, które są ograniczone do dokumentów zawierających określony element
/users/{userid}
na ścieżce dokumentu. Przykład: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)
Zastosuj to ograniczenie do wszystkich zapytań dotyczących grupy kolekcji
transactions
, aby jeden użytkownik nie mógł pobierać dokumentówtransactions
należących do innego użytkownika.transaction
Wprowadzamy to ograniczenie w regułach bezpieczeństwa i włączamy weryfikację danych dla pola 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
}
}
}
Dalsze kroki
- Przykład kontroli dostępu na podstawie ról znajdziesz w artykule Bezpieczeństwo dostępu do danych użytkowników i grup.
- Przeczytaj dokumentację reguł zabezpieczeń.